/**
 * Module: 		auto_part_functions.ycp
 *
 * Authors: 		Andreas Schwab (schwab@suse.de)
 *			Klaus Kämpf (kkaempf@suse.de)
 *
 * Purpose: 		This module define functions of general use
 *			to the automatic partitioner
 *
 * $Id: auto_part_functions.ycp 21785 2005-02-21 15:51:26Z fehr $
 *
 * needs global variables:
 *
 * integer bytes_per_unit
 * integer first_logical_nr
 * integer max_partitions
 *
 * defined functions:
    global define size_of_region (list region) ``{
    global define size_to_units (integer bytes) ``{
    global define start_of_region (list region) ``{
    global define end_of_region (list region) ``{
    global define num_primary (list partitions) ``{
    global define contiguous_selection (list selection) ``{
    global define contains_extended (list partitions) ``{
    global define extended_region (list partitions) ``{
    global define calculate_selected_region (list selection, any extended_region) ``{
    global define can_create_logical (list partitions) ``{
    global define bootable (list region) ``{
    global define can_resize( list partitions ) ``{
    global define renumber_logical_partitions (list partitions) ``{
    global define unused_extended_region (list partitions) ``{
    global define unused_logical_region (list partitions) ``{
    global define compute_max_partitions (map target) ``{
    global define compute_max_primary (map target)
 *
 */
{
    import "Arch";
    import "Partitions";

    textdomain "storage";

    include "partitioning/partition_defines.ycp";

    // --------------------------------------------------------------
    // helper functions

    // Return the size of a disk region in bytes

define integer size_of_region (list<integer> region, 
                                          integer bytes_per_unit) ``{
	return region[1]:0 * bytes_per_unit;
    };

    // Return the number of units that holds at least BYTES

define integer size_to_units( integer bytes, 
                                         integer bytes_per_unit) ``{
	return (bytes + bytes_per_unit - 1) / bytes_per_unit;
    };

    // Return the start of the region.

define integer start_of_region (list<integer> region) ``{
	return region[0]:0;
    };

    // Return the end of the region, ie. start of the next region

define integer end_of_region (list<integer> region) ``{
	return region[0]:0 + region[1]:0;
    };

    // Return the number of primary partitions

define integer num_primary (list<map> partitions) ``{
	integer n = 0;

	foreach (map pentry, partitions, ``{
	    if (pentry["type"]:`unknown == `primary)
		n = n + 1;
	});

	return n;
    };

    // The maximum partition number the kernel can handle for the target
    // disk, count from 0 on, return the maximal allowed value for
    // a partition number

define integer compute_max_partitions (map disk) 
	``{
	integer ret = disk["max_primary"]:4 - 1;
	if( disk["label"]:""=="msdos" )
	    {
	    if( disk["bus"]:"" == "IDE" )
		{
		ret = 63;
		}
	    else
		{
		ret = 15;
		}
	    }
	return( ret );
	};

    // return the maximal number of primary partitions

define integer compute_max_primary (map disk) ``{
	return disk["max_primary"]:4;
    };

    // Return whether the list of selected partitions is contiguous.

define boolean contiguous_selection (list<map> selection) ``{
	boolean ret = true;
	symbol  last_type = nil;
	list<integer>    last_region = nil;

	foreach (map pentry, selection, ``{
	    if (!pentry["create"]:false) {
		list<integer> region = pentry["region"]:[];
		symbol type = pentry["type"]:`unknown;
		if ((last_region != nil)
		    && (end_of_region (last_region) != start_of_region (region)))
		{
		    ret = false;
		}
		last_region = region;
		last_type = type;
	    }
	});
	return ret;
    }; // contiguous_selection


    // Return true if an extended partition exists

define boolean contains_extended (list<map> partitions) ``{
	boolean ret = find( map p, partitions, 
	                    ``(p["type"]:`unknown==`extended && 
			       !p["delete"]:false)) != nil;
	return( ret );
    };

define boolean ignored_partition( map disk, map part )
    ``{
    // skip #3 on AlphaBSD and SparcBSD
    boolean ret = disk["label"]:""=="bsd" || disk["label"]:""=="sun";
    if( ret )
	{
	ret = part["nr"]:0 == 3;
	}
    return( ret );
    };

    // Return the region of the extended partition

define list<integer> extended_region (list<map> partitions) ``{
	list<integer> ret = [ 0, 0 ];
	foreach (map pentry, partitions, ``{
	if (pentry["type"]:`unknown == `extended)
	    ret = (list<integer>) (pentry["region"]:[]);
	});
	return ret;
    };


    // Return the region for the selected partitions.  If EXTENDED_REGION
    // is not nil it is used to clip the region so that it is contained
    // entirely within it.

define list<integer> calculate_selected_region( list selection, 
                                                list<integer> ext_region ) 
    ``{
    integer start = -1;
    integer end = 0;
    if( size(selection) > 0) 
	{
	start = start_of_region( selection[0,"region"]:[] );
	end = end_of_region( selection[size(selection)-1,"region"]:[] );
	}
    if (start != -1) 
	{
	if (size (ext_region)>0) 
	    {
	    // Use only the part that is contained in the extended
	    // partition
	    if (start >= end_of_region (ext_region))
		return [ 0, 0 ];
	    if (start < start_of_region (ext_region))
		start = start_of_region (ext_region);
	    if (end > end_of_region (ext_region))
		end = end_of_region (ext_region);
	    }
	return [ start, end - start ];
	}
    return [ 0, 0 ];
    }; // selected_region


    // Check whether three logical partitions can be created without
    // running past the kernel limit for the number of partitions

define boolean can_create_logical( list<map> partitions )
    ``{
    list logicals = filter( map pentry, partitions,
			    ``(pentry["type"]:`unknown == `logical));
    integer num_logical = size (logicals);
    return ((first_logical_nr + num_logical + 3) <= (max_partitions + 1));
    };

    // Check whether the boot partition is located entrely below the bootable
    // cylinder limit

define boolean bootable(list<integer> region, integer bps) 
    ``{
    return( start_of_region(region) + size_to_units(size_of_boot,bps) < 
		Partitions::BootCyl() );
    };


    // Check if the given partition is a FAT partition
    // Input:  partition map to be checked (from targets)
    // Return: true if partition is FAT, false otherwise
    //
define boolean is_fat_partition( map partition ) ``{
	return ( partition["fsid"]:-1 == 0x06 ||	// FAT16
		 partition["fsid"]:-1 == 0x0b ||	// Win95 FAT32 CHS
		 partition["fsid"]:-1 == 0x0c ||	// Win95 FAT32 LBA
		 partition["fsid"]:-1 == 0x0e );	// Win95 FAT16 LBA	
    }


    // Check if the given partition is a NTFS partition
    // Input:  partition map to be checked (from targets)
    // Return: true if partition is NTFS, false otherwise
    //
define boolean is_ntfs_partition( map partition ) ``{
	return ( partition["fsid"]:-1 == 0x07 || 	// HPFS/NTFS
		 partition["fsid"]:-1 == 0x86 || 	// NTFS-Datenträger
		 partition["fsid"]:-1 == 0x87 );	// NTFS-Datenträger
    }

    
    // Get the partition map with the highest minor number out of the given partition list
    // Input:  List of partition maps.
    // Return: Partition map if found or $[] if not.
    //
define map get_last_used_partition( list<map> partitions ) ``{
	map last_partition = $[];
	integer minor = -1;

	y2milestone( "get_last_used_partition p:%1", partitions );

	foreach( map partition, partitions, 
	    ``{
	    if ( partition["nr"]:-1 > minor )
		{
		minor = partition["nr"]:-1;
		last_partition = partition;
		}
	    });
	y2milestone( "get_last_used_partition ret %1", last_partition );

	return last_partition;
    }

    
    // Check whether the partition list has a resizable partition as the highest partition.
    // Input:  List of partition maps
    // Return: resizeable partition map or $[] if none found
    //
define map can_resize( list<map> partitions ) ``{
	map last_used_partition = $[];
	
	if ( !Arch::i386 () )
	{
	    y2warning( "Wrong architecture - can't resize partitions" );
	    return $[];	// for now
	}

	// Filter out empty space that might exist behind valid partitions.
	// This space would be there as a pseudo partition of type `free.
	//
	list<map> partitions_local =
	    filter( map pentry, partitions, ``( pentry["type"]:`dummy != `free ));

	last_used_partition = get_last_used_partition( partitions_local );
	
	if ( last_used_partition == $[] ) return $[];	// no last resizeable partition found
	    
	// Check for supported filesystem types.
        //	
	if ( is_fat_partition( last_used_partition ) )
	    return last_used_partition;
	else
	    return $[];
    }

    
    // Check if the given file does exist.
    // Input:  File to be checked incl. path as string, e.g. "/usr/lib/YaST2/clients/installation.ycp"
    //         This may also point to a directory.
    // Return: true if found, false if not.
    //
define boolean file_exist( string file_path ) ``{
	  boolean file_found = (integer)SCR::Read(.target.size, file_path) >= 0;
	  y2milestone( "file %1 found %2 ret:%3", file_path, file_found,
	               SCR::Read(.target.size, file_path) );
	  return file_found;
    }

    
// Check whether the partition list has a resizable partition as the highest 
// partition.
// Input:  map of data containing the disk
// Return: true if there is NT on the system, otherwise false
//
define integer check_win_nt_system( map disk ) 
    ``{
    boolean is_nt = false;
    boolean go_on = true;
    integer local_ret = 0;
    boolean local_err = false;
    list<map> partitions = [];
    list<map> partitions_local = [];
    string fat_partition = "";
    
    partitions = disk["partitions"]:[];
    if ( ! is_nt && ! local_err && go_on )
	{
	// First check if there are any NTFS partitions on the system
	//
	partitions_local = filter( map pentry, partitions, 
	                           ``( is_ntfs_partition( pentry ) ) );

	if ( size( partitions_local ) != 0 ) is_nt = true;	// is NT system
	}

    if( !is_nt && !local_err && go_on )
	{
	// Then look for specific files on all FAT partitions
	//
	partitions_local = filter( map pentry, partitions, 
	                           ``( is_fat_partition( pentry ) ) );
	
	if ( size( partitions_local ) == 0 ) go_on = false;	// not an NT system
	}

    // If there are FAT partitions mount them and check specific files
    //
    if( ! is_nt && ! local_err && go_on )
	{
	foreach( map pentry, partitions_local, 
	    ``{
	    // Now we are looping over all partitions for the current device.
	    // get some special values from the partition entry.

	    if( ! is_nt && ! local_err && go_on )
		{
		// build devicename.
		fat_partition = pentry["device"]:"";

		// mount the partition
		local_ret = 
		    (integer)SCR::Execute( .target.bash, 
					   "/bin/mount "+fat_partition+" /mnt" );

		if ( local_ret != 0 )
		    {
		    y2error( "FAT partition <%1> could not be mounted. Canceled", fat_partition );
		    local_err = true;
		    }
		}

	    if( ! is_nt && ! local_err && go_on )
		{
		if ( file_exist( "/mnt/winnt/system32/ntoskrnl.exe" ) ||
		     file_exist( "/mnt/winnt/system32/dllcache" ) )
		    {
		    y2error( "Current Windows device <%1> is NT or 2000. Canceled", fat_partition );
		    is_nt = true;
		    }
		}

	    // unmount the partition if was mounted
	    if ( !local_err ) SCR::Execute(.target.bash, "/bin/umount " + fat_partition );
	    });   // loop over all partitions
	}

    if ( local_err ) return 2;
    if ( is_nt )     return 1;
    else 	     return 0;
    }; // End of check_win_nt_system()


    // --------------------------------------------------------------
    // Renumber the logical partitions if some of them are deleted
    // The returned list is sorted by partition number instead of region

define list<map<string, any> > renumber_logical_partitions (list<map<string, any> > partitions) ``{
	partitions = sort (map<string, any> a, map<string, any> b, partitions, ``(a["nr"]:0 < b["nr"]:0));
	integer logicals_deleted = 0;
	return maplist (map<string, any> pentry, partitions, ``{
	    if (pentry["type"]:`unknown == `logical) {
		if (pentry["delete"]:false)
		    logicals_deleted = logicals_deleted + 1;
		else
		    pentry = add (pentry, "nr",
			    pentry["nr"]:0 - logicals_deleted);
	    }
	    return pentry;
	});
    };

    // Find unused space at the end of the extended partition
define list<integer> unused_extended_region (list<map> partitions) ``{
	list<integer> extended = extended_region (partitions);
	list logicals = filter (map pentry, partitions, ``(pentry["type"]:`unknown == `logical));
	integer end_of_logicals = 0;

	if (size (logicals) > 0)
	    end_of_logicals = end_of_region(logicals[size(logicals)-1,"region"]:[]);
	else
	    end_of_logicals = start_of_region (extended);

	if (end_of_logicals < end_of_region (extended))
	    return [ end_of_logicals, end_of_region (extended) - end_of_logicals ];
	return [0, 0];
    };


    // Find unused space at the start of the extended partition
define list<integer> unused_logical_region (list<map> partitions) ``{
	list<integer> extended = extended_region (partitions);
	list logicals = filter (map pentry, partitions, ``(pentry["type"]:`unknown == `logical));
	integer start_of_logicals = 0;

	if (size (logicals) > 0)
	    start_of_logicals = start_of_region (logicals[0,"region"]:[]);
	else
	    start_of_logicals = start_of_region (extended);

	if (start_of_logicals > start_of_region (extended))
	    return [ start_of_region(extended), start_of_region (extended) - start_of_logicals ];
	return [0, 0];
    };

define integer calculate_needed_swap ()
    ``{
    return 1024*1024*Partitions::SwapSizeMb( 0 );
    };

define boolean need_boot_partititon( list<integer> region )
    ``{
    return( Partitions::NeedBoot() || 
            (start_of_region(region)<Partitions::BootCyl() && 
	     end_of_region(region)>=Partitions::BootCyl()));
    }

}
