/**
 * File:
 *	StorageDevices.ycp
 *
 * Module:
 *	StorageDevices
 *
 * Depends:
 *	StorageControllers
 *
 * Summary:
 *	This module does all storage device related stuff:
 *	- hard disk drives
 *	- removable drives (ZIP)
 *	- floppy devices
 *
 * $Id: StorageDevices.ycp 22732 2005-03-22 11:02:22Z fehr $
 *
 * Author:
 *	Klaus Kaempf <kkaempf@suse.de> (initial)
 */
{
    module "StorageDevices";

    import "Mode";
    import "Stage";
    import "String";
    import "HwStatus";
    import "Partitions";
    import "Installation";
    import "Kernel";
    import "AsciiFile";

    import "Arch";
    import "Report";

    textdomain "storage";



    boolean disks_valid = false;

    /**
     * @return map of $["device": $[..target..], ...] for each ZIP drive
     *
     */
    global map<string,any> ZipDrives = $[];


    /**
     * @return list of maps: all kinds of removable media, esp. ZIP drives
     * @see: FloppyDevice
     * @see: FloppyReady
     */
    global list<map> FloppyDrives = [];


    /**
     * @return true if floppy drive present
     * @see: FloppyDevice
     * @see: FloppyReady
     */
    global boolean FloppyPresent = false;


    /**
     * @return Device name of floppy, empty string if no floppy present
     * @see: FloppyPresent
     * @see: FloppyReady
     */
    global string FloppyDevice = "";	// set in constructor


    //---------------------------------------------------------------

    /**
     * list of cd-rom drives
     */
    global list<map> cddrives = [];

    global list<string> TypeNames = [ "cdrom", "dvd", "cdrecorder", "dvdrecorder", 
				      "dvdram" ];

    // storage for localProbe, see Probe()
    map targetMap = $[];

    //---------------------------------------------------------------


//---------------------------------------------------------------
// CD Recorders and CD-ROM links
list idecdrws = [];		// list of IDE CD-RW drivers for ide-scsi
list scsicds = [];		// list of SCSI CDs

global define integer TypeNameIndex( string cdtype )
    ``{
    integer idx = 0;
    integer n = 0;
    foreach( string t, StorageDevices::TypeNames,
	``{
	if( substring( cdtype, 0, size(t)) == t )
	    {
	    idx = n;
	    }
	n = n + 1;
	});
    return( idx );
    }




  /**
   *	
      #include "storage/routines.ycp";
   */
global define string GetDeviceName( string disk, any partition ) 
    ``{
      string ret = disk;

      if( is( partition, integer ) )
	  {
  	  if( find( disk, "/dev/cciss/" )==0 ||
  	      find( disk, "/dev/ida/" )==0 ||
  	      find( disk, "/dev/ataraid/" )==0 ||
	      find( disk, "/dev/etherd/" )==0 ||
  	      find( disk, "/dev/rd/" )==0 )
  	      {
  	      ret = ret + "p";
  	      }
  	  ret = ret + sformat( "%1", partition );
	  }
      else if( size( (string)partition )>0 )
          {
  	  ret = ret + "/" + (string)partition;
	  }
      return( ret );
  };

global define list<map> AddNormalLinknames( list<map> cddrives )
    ``{
    map linknum = $[ "cdrom"       : 0, 
                     "cdrecorder"  : 0, 
		     "dvd"         : 0, 
		     "dvdrecorder" : 0,
		     "dvdram"      : 0 ];

    cddrives = maplist( map e, cddrives,
	``{
	// first, determine the drive type and make a linkname guess
	string cddevice = e["dev_name"]:"";

	string linkname = "cdrom";
	if( e["dvdram"]:false )
	    {
	    linkname = "dvdram";
	    }
	else if( e["dvdr"]:false )
	    {
	    linkname = "dvdrecorder";
	    }
	else if( e["cdr"]:false || e["cdrw"]:false )
	    {
	    linkname = "cdrecorder";
	    }
	else if( e["dvd"]:false )
	    {
	    linkname = "dvd";
	    }

	// now check the number (for /dev/cdrom, /dev/cdrom2, ...)
	integer number = linknum[linkname]:0;
	linknum[linkname] = number + 1;

	string devname = "/dev/" + linkname;

	if( number > 0)
	    devname = sformat( "%1%2", devname, number+1 );

	e["linkname"] = devname;
	return( e );
	});
    y2milestone( "AddNormalLinknames linknum %1", linknum );
    return( cddrives );
    }

global define list<map> AddAlternateLinks( list<map> cddrives )
    ``{
    list<string> llist = [];
    foreach( map cd, cddrives, 
	``{
	llist = (list<string>)union( llist, cd["udev_links"]:[] );
	});
    y2milestone( "AddAlternateLinks llist %1", llist );

    if( !contains( llist, "cdrom" ))
	{
	cddrives[0,"udev_links"] = add( cddrives[0,"udev_links"]:[], "cdrom" );
	y2milestone( "AddAlternateLinks cdrom %1", cddrives[0]:$[] );
	}
    integer i = 0;
    if( !contains( llist, "dvd" ))
	{
	while( i<size(cddrives) && !cddrives[i,"dvd"]:false )
	    {
	    i = i + 1;
	    }
	if( i<size(cddrives) )
	    {
	    cddrives[i,"udev_links"] = add( cddrives[i,"udev_links"]:[], "dvd" );
	    y2milestone( "AddAlternateLinks dvd %1", cddrives[i]:$[] );
	    }
	}
    if( !contains( llist, "cdrecorder" ))
	{
	i = 0;
	while( i<size(cddrives) && !cddrives[i,"cdrw"]:false )
	    {
	    i = i + 1;
	    }
	if( i<size(cddrives) )
	    {
	    cddrives[i,"udev_links"] = add( cddrives[i,"udev_links"]:[], "cdrecorder" );
	    y2milestone( "AddAlternateLinks cdrecorder %1", cddrives[i]:$[] );
	    }
	}
    y2milestone( "AddAlternateLinks ret %1", cddrives );
    return( cddrives );
    }

/**
  * ProbeCDROMs()
  *
  * Initialize cddrives
  *
  */
global define boolean ProbeCDROMs()
    ``{
    if (size (cddrives) == 0)
	{
	if (Stage::initial () || Stage::cont ())
	    {
	    cddrives = (list<map>) SCR::Read (.probe.cdrom);
	    // write out data for hardware status check
	    foreach (map drive, cddrives,
		``{
		HwStatus::Set (drive["unique_key"]:"", `yes);
		});
	    }
	else
	    {
	    cddrives = [];
	    foreach(map e, (list<map>) SCR::Read (.probe.cdrom.manual),
		``{
		map conf = (map) SCR::Read(.probe.status, e["unique_key"]:"");
		y2milestone( "ProbeCDROMs conf:%1", conf );
		y2milestone( "ProbeCDROMs cd:%1", e );
		if( conf["available"]:`no != `no )
		    {
		    cddrives = add( cddrives, e );
		    }
		});

	    if ((cddrives == nil) || (size (cddrives) == 0))
		{
		cddrives = [ $["dev_name":"/dev/cdrom"] ];
		}
	    }

	// sort out idecdrws and scsicds
	foreach (map e, cddrives,
	    ``{
	    if( (e["cdr"]:false || e["cdrw"]:false || e["dvdr"]:false ) &&
		e["bus"]:"" == "IDE" )
		{
		y2milestone ("IDE CD-RW %1", idecdrws );
		}
	    if( e["bus"]:"" == "SCSI" )
		{
		scsicds = add (scsicds, e["dev_name"]:"");
		}
	    });

	string boot_device = (string) SCR::Read (.etc.install_inf.Cdrom);
	if (boot_device == nil) boot_device = "";

	if( boot_device != "" )
	    {
	    y2milestone( "ProbeCDROMs cddrives:%1", cddrives );
	    y2milestone( "ProbeCDROMs boot_device:%1", boot_device );

	    if( find( boot_device, "/dev/" ) != 0 )
		boot_device = "/dev/" + boot_device;
	    list tmp = filter( map e, cddrives, ``(e["dev_name"]:""==boot_device));
	    if( size(tmp)>0 )
		{
		cddrives = filter( map e, cddrives, ``(e["dev_name"]:""!=boot_device));
		cddrives = (list<map>)merge( tmp, cddrives );
		}

	    y2milestone( "ProbeCDROMs cddrives:%1", cddrives );
	    }

	y2milestone( "ProbeCDROMs cddrives:%1", cddrives );
	cddrives = AddNormalLinknames( cddrives );
	cddrives = maplist( map drive, cddrives,
	    ``{
	    drive["udev_links"] = [ substring(drive["linkname"]:"",5) ];
	    return( drive );
	    });
	cddrives = AddAlternateLinks( cddrives );
	}
    y2milestone ("ProbeCDROMs (%1)", cddrives);
    return (size (cddrives) > 0);
    }

global define map GetCdromEntry( string device )
    ``{
    map ret = $[];
    ret = find(map e, cddrives, ``(e["dev_orig"]:(e["dev_name"]:"") == device));
    if( ret == nil )
	{
	ret = $[];
	}
    y2milestone( "ret %1", ret );
    return( ret );
    }

/*
 * FloppyReady ()
 * @return floppy media status
 * determines if a media is present.
 * @see: FloppyPresent
 * @see: FloppyDevice
 */

global define boolean FloppyReady ()
    ``{
    if( Stage::initial () )
	{
	y2milestone( "before .probe.floppy" );
	FloppyDrives = (list<map>) SCR::Read(.probe.floppy);
	y2milestone( "after .probe.floppy" );

	if( !FloppyPresent )	// only once !
	    {
	    // write out data for hardware status check
	    foreach (map drive, FloppyDrives,
		``{
		HwStatus::Set (drive["unique_key"]:"", `yes);
		});
	    }
	}
    else
	{
	FloppyDrives = (list<map>) SCR::Read(.probe.floppy.manual);
	}

    map floppy_data = FloppyDrives[0]:$[];
    FloppyDevice = floppy_data["dev_name"]:"";
    if( (FloppyDevice != "") || Mode::test ())
	{
	FloppyPresent = true;
	}
    y2milestone( "FloppyDrives %1", FloppyDrives );
    return( size(floppy_data)>0 && !haskey(floppy_data,"notready") );
    }


// loop over floppy drives to find IDE ZIPs
// return map of $[ "device" : $[target], ...]

define map findZIPs ()
    ``{
    map zips = $[];
    foreach (map disk, FloppyDrives,
	``{
	if (disk["zip"]:false)
	    {
	    map target = $[];
	    string dname = "";

	    string ddevice = disk["dev_name"]:"?";
	    string dinfo = disk["vendor"]:"";

	    target["vendor"] = dinfo;
	    if (dinfo != "") dname = dname + dinfo + "-";

	    dinfo = disk["device"]:"";
	    target["model"] = dinfo;

	    if (dinfo != "") dname = dname + dinfo;
	    target["name"] = dname;
	    target["partitions"] = [];

	    zips[ddevice] = target;
	    }
	});
    y2milestone( "zips %1", zips );
    return zips;
    }


/*
 * Fake probing for storage devices in test or demo mode -
 * read ready-made target maps from file.
 *
 * @return	map	TargetMap
 */

global define map fakeProbe ()
    ``{
    string fake_map_file = ( Mode::test () ? "demo_target_map.ycp" : "test_target_map.ycp" );
    
    y2milestone( "%1 mode - using fake target map from %2",
                 Mode::test () ? "Demo" : "Test", fake_map_file );

    map target_map = (map) SCR::Read( .target.yast2, fake_map_file );

    y2debug( "Fake target map: %1", target_map );

    return target_map;
    } // fakeProbe()


/*
 * check partitions for given disk (as path, i.e. '.sda')
 * returns list as specified in README.target_partition
 */
global define map Check4partitions( string diskdev, map disk, path diskpath )
    ``{
    list partitions_desc = (list) SCR::Read( .disk+diskpath+.partitions,
				   Partitions::UseParted() );
    if( is(partitions_desc[0]:(any)"", string ) )
	{
	if( size(partitions_desc[0]:"")>0 )
	    {
	    disk["label"] = partitions_desc[0]:"";
	    }
	else
	    {
	    disk["label"] = Partitions::DefaultPartLabel();
	    }
	}
    else
	{
	disk["label"] = Partitions::DefaultPartLabel();
	}
    disk["max_primary"] = Partitions::MaxPrimary( disk["label"]:"" );
    disk["max_logical"] = Partitions::MaxLogical( diskdev );
    y2milestone( "disk %1 label %2 max_primary:%3 max_logical:%4", 
                 diskdev, disk["label"]:"", disk["max_primary"]:0,
		 disk["max_logical"]:0 );

    // the list contains only maps now
    list<map> partitions = (list<map>)remove( partitions_desc, 0 );
    partitions = maplist(map part, partitions,
	``{
	    if( part["fsid"]:0 > 0 && (part["fsid"]:0<256||disk["label"]:""=="gpt"))
		{
		part["fstype"] = Partitions::FsIdToString( part["fsid"]:0 );
		}
	    return( part );
	});
    partitions = maplist(map p, partitions,
	``{
	    p["device"] = GetDeviceName( diskdev, p["nr"]:(any)0 );
	    return( p );
	});
    disk["partitions"] = partitions;
    return disk;
    };

global define map CheckPartedValid( string ddevice, map disk, 
				    list<map> partitions, list<map> ppart )
    ``{
    map<integer,integer> plist1 = $[];
    map<integer,integer> plist2 = $[];
    boolean ret = true;
    y2milestone( "CheckPartedValid target %1", disk );
    string regex = "^" + substring( ddevice, 5 )+"[p]*[0-9]+";
    y2milestone( "CheckPartedValid ddevice %1 regex %2", ddevice, regex );
    list<map> ps = filter( map p, ppart, ``(regexpmatch( p["name"]:"", regex) ));
    y2milestone( "CheckPartedValid ps %1", ps );
    integer cyl_blocks = disk["cyl_size"]:1000000 / 1024;
    list bsd_part = [ Partitions::fsid_freebsd, Partitions::fsid_openbsd, Partitions::fsid_beos, Partitions::fsid_solaris ];
    boolean openbsd = find(map e,partitions, 
                            ``(contains(bsd_part,e["fsid"]:0)))!=nil;
    y2milestone( "CheckPartedValid openbsd %1", openbsd );
    foreach(map p, ps,
	``{
	integer pnr = tointeger(deletechars(substring(p["name"]:"",size(ddevice)-5),"p"));
	plist1[pnr] = p["size"]:0/cyl_blocks;
	});
    foreach(map p, partitions,
	``{
	plist2[p["nr"]:0] = p["region",1]:0;
	});
    if( find( ddevice, "/dev/sd" )==0 )
	{
	if( size(filter( map p, partitions, ``(p["nr"]:0>15)))>0 &&
	    Stage::initial () && !Mode::autoinst () )
	    {
	    partitions = filter( map p, partitions, ``(p["nr"]:0<=15));
	    plist1 = filter( integer idx, integer val, plist1, ``(idx<=15));
	    plist2 = filter( integer idx, integer val, plist2, ``(idx<=15));
	    string txt = sformat( 
_("Your disk %1 contains more than fifteen partitions.
You will not be able to access partitions numbered above fifteen.
"), ddevice );
	    Report::Error( txt );
	    }
	}
    map ext = find(map p, partitions, ``(p["type"]:`unknown==`extended));
    if( ext != nil )
	{
	plist1 = filter(integer k, integer e, plist1, ``(k!=ext["nr"]:0));
	plist2 = filter(integer k, integer e, plist2, ``(k!=ext["nr"]:0));
	}
    y2milestone( "CheckPartedValid plist1 %1", plist1 );
    y2milestone( "CheckPartedValid plist2 %1", plist2 );
    ret = size(plist1) >= size(plist2);
    if( ret )
	{
	foreach(integer k, integer e, plist1, 
	    ``{
	    if( !haskey( plist2, k ))
		{
		ret = ret && openbsd;
		}
	    else
		{
		integer diff = e - plist2[k]:0;
		if( diff < 0 ) diff = -diff;
		if( diff <= 2 ) diff = 0;
		ret = ret && diff <= plist2[k]:0/100;
		y2milestone( "CheckPartedValid k:%1 diff:%2 ret:%3", k, diff, 
		             ret );
		}
	    });
	foreach(integer k, integer e, plist2, 
	    ``{
	    if( !haskey( plist1, k ))
		{
		ret = false;
		}
	    else
		{
		integer diff = e - plist1[k]:0;
		if( diff < 0 ) diff = -diff;
		if( diff <= 2 ) diff = 0;
		ret = ret && diff <= plist2[k]:0/100;
		y2milestone( "CheckPartedValid k:%1 diff:%2 ret:%3", k, diff,  
		             ret );
		}
	    });
	}
    if( !ret )
	{
	list new_p = [];
	integer reg_start=0;
	y2milestone( "CheckPartedValid ps=%1", ps );
	foreach(map p, ps,
	    ``{
	    map part = $[];
	    string ts = substring(p["name"]:"",size(ddevice)-5);
	    integer pos = findfirstof(ts, "0123456789");
	    if( pos > 0 )
		{
		ts = substring( ts, pos );
		}
	    integer pnr = tointeger(ts);
	    part["nr"] = pnr;
	    part["region"] = [ reg_start, p["size"]:0/cyl_blocks ];
	    reg_start = reg_start + part["region",1]:0;
	    part["device"] =  GetDeviceName( ddevice, pnr );
	    if( part["region",1]:0==0 )
		{
		part["type"] = `extended;
		part["fsid"] = Partitions::fsid_extended_win;
		}
	    else
		{
		part["type"] = pnr<5 ? `primary : `logical;
		part["fsid"] = Partitions::fsid_native;
		}
	    map pp = find(map e, partitions, ``(e["nr"]:0==pnr));
	    if( pp != nil )
		{
		y2milestone( "entry in parted list %1", pp );
		part["fsid"] = pp["fsid"]:0;
		part["type"] = pp["type"]:`primary;
		}
	    part["fstype"] = Partitions::FsIdToString( part["fsid"]:0 );
	    new_p = add( new_p, part );
	    });
	partitions = (list<map>)new_p;
	y2milestone( "CheckPartedValid new p %1", partitions );
	}
    y2milestone( "CheckPartedValid disk %1 ret:%2", ddevice, ret );
    return( $["ret":ret, "partitions":partitions ] );
    };


global define list GetS390Partitions( string ddev, map target, list<map> ppart )
    ``{
    list<map> partitions = [];
    y2milestone( "target %1", target );
    string regex = substring( ddev, 5 )+"[0-9]+";
    y2milestone( "ddev %1 regex %2", ddev, regex );
    list<map> ps = filter(map p, ppart, ``(regexpmatch( p["name"]:"", regex) ));
    y2milestone( "ps %1", ps );
    integer cyl_blocks = target["cyl_size"]:1000000 / 1024;
    integer start = 0;
    foreach(map p, ps,
	``{
	map part = $[];
	part["nr"] = tointeger(substring(p["name"]:"",size(ddev)-5));
	part["region"] = [ start, p["size"]:0/cyl_blocks ];
	start = start + p["size"]:0/cyl_blocks;
	part["fsid"] = Partitions::fsid_native;
	part["type"] = `s390;
	part["fstype"] = "S390 DASD";
	y2milestone( "part %1", part );
	partitions = add( partitions, part );
	});

    if( size(ps)==0 )
	{
	map part = $[];
	part["nr"] = 1;
	part["create"] = true;
	part["region"] = [ 0, target["cyl_count"]:0 ];
	part["fsid"] = Partitions::fsid_native;
	part["type"] = `s390;
	part["fstype"] = "S390 DASD";
	partitions = [ part ];
	}
    partitions = maplist(map p, partitions,
	``{
	p["device"] = GetDeviceName( ddev, p["nr"]:0 );
	return( p );
	});
    y2milestone( "part %1", partitions );
    return( partitions );
    }

/*
 * Probe for storage devices attached to storage controllers
 * Should be called after StorageControllers::Initialize
 * @return	map	TargetMap
 */

define map localProbe ()
    ``{
    map targets = $[];
    list<map> ppart = [];

    if( Mode::test () )
	{
	return fakeProbe();
	}
    ppart = (list<map>) SCR::Read(.proc.partitions);
    y2milestone( "localProbe: ppart %1", ppart );

    // do the probing
    list<map> all_disks = (list<map>) SCR::Read(.probe.disk);

    y2milestone( "localProbe: disks probed");
    y2milestone( "localProbe: all_disks %1", all_disks );

    if( size(all_disks)==0 )
	{
	// somehow, we couldn't find any harddisks for installation.
	// This is a fatal error, we can't do anything about it
	return targets;
	}

    // loop over all_disks, constructing targets map

    integer ide_count = 0;
    integer scsi_count = 0;
    integer raid_count = 0;
    integer other_count = 0;
    string fake_raid = "";
    map target = $[];

    foreach (map disk, filter(map e,all_disks,``(size(e["dev_name"]:"")>0)),
	``{
	target = $[];
	y2milestone( "localProbe: disk %1", disk );

	boolean notready = disk["notready"]:false;
	boolean is_zip = (notready || disk["zip"]:false);

	if( disk["softraiddisk"]:false )
	    {
	    target["softraiddisk"] = true;
	    if( size(fake_raid)>0 )
		{
		fake_raid = fake_raid + " ";
		}
	    fake_raid = fake_raid + disk["dev_name"]:"";
	    y2milestone( "localProbe: fake_raid %1", fake_raid );
	    }

	y2milestone( "localProbe: is_zip %1 notready:%2 sofraid %3", 
	             is_zip, notready, disk["softraiddisk"]:false );

	string bus = disk["bus"]:"?";
	string dname = "";
	integer i = 0;

	// write out data for hardware status check
	HwStatus::Set (disk["unique_key"]:"", `yes);

	target["unique"] = disk["unique_key"]:"";

	// ------------------------------------------------------
	// check bus, count disks per bus

	if (bus == "IDE")
	    {
	    ide_count = ide_count + 1;
	    i = ide_count;
	    }
	else if (bus == "SCSI")
	    {
	    scsi_count = scsi_count + 1;
	    i = scsi_count;
	    }
	else if (bus == "RAID")
	    {
	    raid_count = raid_count + 1;
	    i = raid_count;
	    }
	else
	    {
	    other_count = other_count + 1;
	    i = other_count;
	    }

	target["bus"] = bus;

	// ------------------------------------------------------
	// construct disk name for user

	dname = ""+i+". ";
	if( bus != "None" )
	    {
	    dname = dname + bus;
	    }
	else
	    {
	    dname = dname + "Disk";
	    }
	dname = dname + ", ";

	// needed also later as key
	string ddevice = disk["dev_name"]:"?";

	// remember bios id code if set
	string bios_id = disk["bios_id"]:"";
	if (bios_id != "")
	    {
	    target["bios_id"] = bios_id;
	    }

	// - - - - - - - - - - - - - - - - - - - -
	// split /dev/... -> [ "", "dev", "hda" ]

	list listpath = splitstring (ddevice, "/");

	if (size (listpath) < 3)
	    {
	    y2error("too few / in %1", ddevice);
	    }

	if (listpath[1]:"" != "dev")
	    {
	    y2error("not /dev in %1", ddevice);
	    }

	// get first path after /dev

	path diskpath = .;
	diskpath = diskpath + listpath[2]:"";

	// might be RAID /dev/ida/xy -> [ "", "dev", "ida", "xy" ]

	if (size (listpath) > 3)
	    {
	    list raidpathes = [ "rd", "ida", "cciss", "ataraid" ];
	    if (contains (raidpathes, listpath[2]:""))
		{
		target["raid"] = true;
		}
	    }

	integer pathidx = 3;
	while (pathidx < size (listpath))
	    {
	    diskpath = diskpath + listpath[pathidx]:"";
	    pathidx = pathidx + 1;
	    }

	path fullpath = .disk + diskpath;

	y2milestone("localProbe: disk_size: %1", ddevice);
	y2milestone("localProbe: diskpath: %1 fullpath: %2", diskpath, fullpath );

	// call fdisk agent to get size information

	integer cyl_count = 0;
	integer cyl_size = 0;

	if( find( ddevice, "/dev/dasd" )!=0 )
	    {
	    // call fdisk agent to get size information
	    if (!is_zip && !notready )
		{
		cyl_count = (integer) SCR::Read(fullpath+.disk_size);
		cyl_size= (integer) SCR::Read(fullpath+.bytes_per_unit);
		}

	    target["cyl_count"] = cyl_count;
	    target["cyl_size"] = cyl_size;

	    y2milestone( "localProbe: diskinfo: cyl_count %1 cyl_size %2", 
	                 cyl_count, cyl_size );
	    }
	else
	    {
	    // on s390 look at libhd-info for diskinfo
	    // "resource":$["disk_log_geo":
	    //                  [$["cylinders":2226, "heads":15, "sectors":12]
	    //              "size":[$["unit":"sectors", "x":400680, "y":4096]]

	    map     size_map   = disk["resource", "size", 0]:$[];
	    integer disk_size  = size_map["x"]:0 * size_map["y"]:0;

	    cyl_count  = disk["resource", "disk_log_geo", 0, "cylinders"]:1;
	    cyl_size   = disk_size / cyl_count;

	    target["bios_id"] = disk["sysfs_bus_id"]:"";
	    target["cyl_count"] = cyl_count;
	    target["cyl_size"] = cyl_size;
	    target["tracks"] = disk["resource", "disk_log_geo", 0, "heads"]:1;


	    y2milestone( "localProbe: diskinfo sizemap %1 disksize %2 cyl_count %3 cyl_size %4 tracks %5", 
	                 size_map, disk_size, cyl_count, cyl_size, 
			 target["tracks"]:0 );
	    }

	// ------------------------------------------------------
	// construct full target name

	dname = dname + Partitions::ByteToHumanString(cyl_count * cyl_size) + ", " + ddevice + ", ";

	string dinfo = disk["vendor"]:"";
//	target["vendor"]:dinfo;

	if (dinfo != "")
	    {
	    dname = dname + dinfo + "-";
	    }
	dinfo = disk["device"]:"";
	target["model"] = dinfo;

	if (dinfo != "")
	    {
	    dname = dname + dinfo;
	    }

	target["name"] = dname;

	// ----------------------------------------------------------
	// Partitions

	list partitions = [];
	if( !is_zip )
	    {
	    if( find( ddevice, "/dev/dasd" )!=0 && !notready )
		{
		target = Check4partitions (ddevice, target, diskpath);
		partitions = target["partitions"]:[];
		y2milestone( "localProbe: old p %1", partitions );
		map r = CheckPartedValid( ddevice, target, (list<map>)partitions, ppart );
		if( !r["ret"]:true )
		    {
		    partitions = r["partitions"]:[];
		    y2milestone( "localProbe: new p %1", partitions );
		    target["readonly"] = !disk["softraiddisk"]:false &&
		                         target["label"]:"" != "aix";
		    if( Stage::initial () && !Mode::autoinst () && 
		        target["readonly"]:false )
			{
			Report::Error( Partitions::RdonlyText(ddevice,false) );
			}
		    }
		if( target["label"]:"" == "aix" )
		    {
		    target["label"] = Partitions::DefaultPartLabel();
		    target["max_primary"] = 
			Partitions::MaxPrimary( target["label"]:"" );
		    partitions = [];
		    }
		}
	    else if( find( ddevice, "/dev/dasd" )==0 )
		{
		if( notready && Stage::initial () )
		    {
		    target["dasdfmt"] = true;
		    }
		target["label"] = "dasd";
		target["max_primary"] = 
		    Partitions::MaxPrimary( target["label"]:"" );
		partitions = GetS390Partitions( ddevice, target, ppart );
		}
	    }

	target["scrpath"] = diskpath;
	target["partitions"] = partitions;

	// add constructed target map to list of all targets

	if( (!notready||find(ddevice,"/dev/dasd")==0) && (size (target) > 0))
	    {
	    if (is_zip)
		ZipDrives[ddevice] = target;
	    else
		targets[ddevice] = target;
	    }
	y2milestone( "localProbe: disk %1 tg: %2", ddevice, 
	             targets[ddevice]:$[] );
	}); // foreach (disk)

    SCR::UnmountAgent(.disk);

    if( size(fake_raid)>0 )
	{
	string txt = sformat( _("
WARNING: This system has at least one hard disk with a RAID configuration
presented by the BIOS as RAID that is in fact a software RAID. The
following disks were detected as part of such a RAID:

    %1

The Linux kernel 2.4 supported some of these systems (like Promise FastTrack and
HighPoint RocketRaid), but the Linux kernel 2.6 does not support them at all.

If you install onto these disks, your RAID configuration and any data
on the RAID will be lost. Refer to http://portal.suse.com to learn how to
migrate to a Linux software RAID."), fake_raid );
	Report::Error( txt );
	}

    ZipDrives = (map<string,any>)union (ZipDrives, findZIPs ());

    foreach(string k, any e, ZipDrives, 
	``{
	FloppyDrives = filter(map f, FloppyDrives, ``(f["dev_name"]:""!=k));
	});
    y2milestone( "localProbe: FloppyDrives %1", FloppyDrives );
    y2milestone( "localProbe: ZipDrives %1", ZipDrives );

    return targets;
    } // localProbe()

/*
 * Probe ()
 * probe for target devices, return map
 * used like proposal-api
 *
 * @param boolean force_reset
 */
global define map Probe (boolean force_reset)
    ``{
    y2milestone( "force_reset:%1 disks_valid:%2", force_reset, disks_valid );
    if( force_reset )
	targetMap = $[];

    if( targetMap==$[] && disks_valid )
	{
	targetMap = localProbe();
	ProbeCDROMs();
	}
    return targetMap;
    }


/*
 * symlink_cdrom
 *
 * make proper symlink for cd-rom/dvd/cdrecorder drive
 *
 * @param	map	cdinfo	result of .probe.cdrom
 */

// count the links for "pseudo" devices

define void symlink_cdrom( map cdinfo )
    ``{
    y2milestone( "device %1 link %2", cdinfo["dev_name"]:"",
                 cdinfo["linkname"]:"" );
    if( size(cdinfo["linkname"]:"")>0 && size(cdinfo["dev_name"]:"")>0 )
	{
	string link = cdinfo["linkname"]:"";
	string device = cdinfo["dev_name"]:"";
	
	if( Installation::scr_destdir != "/" )
	    {
	    link = Installation::scr_destdir + link;
	    }
	y2milestone( "pathname %1 links to %2", link, device );
	SCR::Execute (.target.symlink, substring(device,5), link);
	}
    return;
    }

define string UdevBusEntry( string bus )
    ``{
    return( sformat( "BUS=\"%1\",", bus ));
    }

define string UdevPlaceEntry( string place )
    ``{
    return( sformat( "ID=\"%1\",", place ));
    }

global define string UdevSylinkEntry( list<string> links )
    ``{
    return( sformat( "SYMLINK=\"%1\"", mergestring( links, " ") ));
    }

global define string UdevCdromPath()
    ``{
    string pathname = "/etc/udev/rules.d/20-cdrom.rules";
    if( Installation::scr_destdir != "/" )
	{
	pathname = Installation::scr_destdir + pathname;
	}
    return( pathname );
    }

global define map<integer,map> GetSysfsCdrom()
    ``{
    map<integer,map> file = $[];
    AsciiFile::SetComment( file, "^[ \t]*#" );
    AsciiFile::SetDelimiter( file, " " );
    AsciiFile::ReadFile( file, UdevCdromPath() );
    return( file );
    }

global define string MakeUdevRulesLine( map drive )
    ``{
    y2milestone( "MakeUdevRulesLine %1", drive );
    string line = sformat( "%1 %2 SYSFS{removable}=\"1\", %3",
                           UdevBusEntry(drive["bus_hwcfg"]:""), 
			   UdevPlaceEntry(drive["sysfs_bus_id"]:""),
			   UdevSylinkEntry(drive["udev_links"]:[]) );
    y2milestone( "MakeUdevRulesLine ret %1", line );
    return( line );
    }

global define list<integer> FindUdevRulesLine( map sysfs, string bus, string id )
    ``{
    list<integer> a = AsciiFile::FindLineField( sysfs, 0, UdevBusEntry(bus) );
    list<integer> b = AsciiFile::FindLineField( sysfs, 1, UdevPlaceEntry(id) );
    a = filter( integer i, a, ``(contains( b, i )));
    y2milestone( "FindUdevRulesLine bus %1 place %2 idx %3", bus, id, a );
    return( a );
    }

/*
 * Symlink (/mnt)/dev/cdromX to all real cdrom devices (/dev/sr0, etc.)
 * create type dependant links (i.e. /dev/dvd for a DVD drive
 * but make sure that at least /dev/cdrom exists.
 */

global define void MakeCDLinks ()
    ``{
    if( ProbeCDROMs() )
	{
	map udev_file = GetSysfsCdrom();
	if( AsciiFile::NumLines( udev_file )==0 )
	    {
	    AsciiFile::AppendLine( udev_file, [ "# cdrom links generated by YaST2" ] );
	    AsciiFile::AppendLine( udev_file, [ "# " ] );

	    foreach( map drive, cddrives, 
		``{ AsciiFile::AppendLine( udev_file, [ MakeUdevRulesLine(drive) ]); });
	    y2milestone( "MakeCDLinks %1", cddrives );
	    string dir = substring( UdevCdromPath(), 0, findlastof( UdevCdromPath(), "/" ) );
	    y2milestone( "MakeCDLinks dir %1", dir );
	    if( SCR::Read( .target.size, dir )<=0 ) 
		SCR::Execute( .target.mkdir, dir );
	    AsciiFile::RewriteFile( udev_file, UdevCdromPath() );
	    }
	}
    return;
    }

/*
 * Initialize
 */

global define void FullProbe ()
    ``{
    FloppyReady();		// probe floppy
    ProbeCDROMs();		// probe CDs
    }

global define void InitDone()
    ``{
    disks_valid = true;
    y2milestone( "called disks_valid %1", disks_valid );
    }

/*
 * Constructor
 */

global define void StorageDevices ()
    ``{
    if( !Stage::initial () && !Mode::config () )
	{
	FullProbe ();
	disks_valid = true;
	}
    return;
    }
}
