/**
 *  File:
 *    OSRPtbl.ycp
 *
 *  Module:
 *    YaST OS Repair. Automatic error detection & repair tool for Linux.
 *
 *  Summary:
 *    YaST OS Repair. Automatic error detection & repair tool for Linux.
 *
 *  Author:
 *    Johannes Buchhold <jbuch@suse.de>
 *
 * $Id: OSRPtbl.ycp 20503 2004-12-06 14:25:38Z jsuchome $
 */
{
  module "OSRPtbl";

  import "OSRExecute";
  import "OSRFloppy";
  import "OSRPopup";
  import "OSRCommon";

  import "Storage";
  import "StorageDevices";
  import "Mode";
  import "Report";

  import "Label";
  import "Popup";

  include "partitioning/partition_defines.ycp";

  textdomain "repair";


  string s_ignore_label		= _("Ignore");
  string s_recover_label	= _("Recover");


  define string floppy_help_text ();
  define string gpart_help_text ();
  define string original_help_text ();

  define symbol restore_from_floppy ();
  define symbol restore_original();
  define symbol expert_restore_with_gpart ();
  global define symbol RestoreWithGpart ();

  /**
   * All repair methods
   */
  map repair_methods = $[
	 `floppy : $[
	    "header" : _("&Copy Saved Partition Table from Floppy"),
	    "help"   : floppy_help_text,
	    "method" : restore_from_floppy
	 ],
	 `gpart : $[
	    "header"  : _("Have &gpart Suggest Partition Table"),
	    "help"    : gpart_help_text,
	    "method"  : RestoreWithGpart,
	    "default" : true
	 ],
	 `gpart_e : $[
	    "header"  : _("Have g&part Suggest Partition Table (Expert Mode)"),
	    "help"    : gpart_help_text,
	    "method"  : expert_restore_with_gpart,
	    "default" : true
	 ],
	 `original : $[
	    "header" :  _("&Restore Original Partition Table"),
	    "help"   : original_help_text,
	    "method" : restore_original,
	    "enabled" : false
	 ]
  ];

  /**
   * The harddisk name. e.g.: /dev/hda
   */
  string target = "";

  /**
   * The path of the harddisk (for fdisk calls). e.g.: .dev.hda.
   */
  path	 target_scrpath = .;


  /**
   * At the first call of the OSRPtbl module the constructor saves all
   * mbr's to the YaST"2.tmp Directory. The names of the randomly
   * created mbr files are stored in the map backup_dev_filenames.
   */
  map backup_dev_filenames  = $[];


  /**
   * backup_mbr_status is true, if all mbr's are successfully stored
   * to the tmp directory.
   */
  boolean backup_mbr_status = false;


  /**
   * Expert options for gpart
   */
  string gpart_expert_options = "";
  

  global define	void Reset()``{
      target 			= "";
      target_scrpath 		= .;
      gpart_expert_options 	= "";
  }

  global define void OSRPtbl()``{

    if (! StorageDevices::FloppyPresent )
    {
	// filter floppy repair methode
	y2milestone("filter floppy repair methode. No floppy found");
	repair_methods = filter (symbol r, map<any,any> d, (map<symbol,map<any,any> >)repair_methods, ``(
	    r != `floppy ));
    }
  }

  /**
   * Help text for the floppy repair method.
   */
  define string floppy_help_text() ``{
      return sformat(_("
<p><h3>No partition table found for hard disk %1.</h3></BR>

Repair will search for a valid partition table of disk %1
on the inserted floppy disk. If YaST OS Repair cannot find
one, it will start the program gpart to guess the lost
partition table. You can decide if the information gpart
finds is valid for your system.</p>
"),target ) +

_("<p>gpart may be of some help when the primary partition table
has been lost or destroyed, but it can, under no circumstances,
replace proper disk and partition table backups. To save the
master boot record (MBR) including the primary partition
table to the file /tmp/hda.mbr, type

<pre>
$ dd if=/dev/hda of=/tmp/hda.mbr bs=512 count=1
</pre>

replace /dev/hda with the block device name of the disk
in question. This should be done for all disks in the system.
To restore the primary partition table without overwriting
the MBR, type

<pre>
$ dd if=/tmp/hda.mbr of=/dev/hda bs=1 count=64 skip=446 seek=446
</pre></p>
") +

_("<p>Warning: Make sure all parameters are typed as shown
and the disk device is correct. Failing to do so may
result in severe corruption of the file system. The saved file
should be stored in a safe place like a floppy disk.</p>
") +

_("<p>From the manual page of gpart:<br>
\"It should be stressed that gpart does a very heuristic
job, never believe its output without any plausability
checks. It can be easily right in its guesswork but it can
also be terribly wrong. You have been warned.\"</p>");

  };

  /**
   * Help for gpart
   */
  define string gpart_help_text()``{
      return sformat(_("
<P><h3>No partition table found for hard disk %1.</h3><BR>

Repair will start gpart to guess the primary partition
table of disk %1.</p>
"),  target) +

_("<p>Gpart may be of some help when the primary partition table
was lost or destroyed, but it can, under no circumstances,
replace proper disk and partition table backups. To save the
master boot record (MBR) including the primary partition
table to the file /tmp/hda.mbr, type

<pre>
$ dd if=/dev/hda of=/tmp/hda.mbr bs=512 count=1
</pre>

replacing /dev/hda with the block device name of the disk
in question. This should be done for all disks in the system.
To restore the primary partition table without overwriting
the MBR, type

<pre>
$ dd if=/tmp/hda.mbr of=/dev/hda bs=1 count=64 skip=446 seek=446
</pre></p>
") +

_("<p>Warning: Make sure all parameters are typed as shown
and the disk device is correct. Failing to do so may
result in severe corruption of the file system. The saved file
should be stored in a safe place like a floppy disk.</p>
") +

_("<p>From the manual page of gpart:<br>
It should be stressed that gpart does a very heuristic
job, never believe its output without any plausability
checks. It can be easily right in its guesswork but it can
also be terribly wrong. You have been warned.</p>");  

  }


  /**
   * Help text for restoring orginal mbr's
   */
  define string original_help_text()``{
      return sformat(_("
Before starting any scan or repair sequence,
YaST creates a backup of the original
partition table of the device %1.
"), target );
    
  }

  /**
   * Save all mbr's to tmp directory and saves the path names
   * in the map backup_dev_filenames.
   */
  define void SavePtbl()``{
    y2milestone("Saving all mbr's to tmp directory");

    // save all current ptbl's to tmp directory
    list<string> devlist = (list<string>)
	Storage::GetMainDevFilter("only_disk", "key_list" );

    if (devlist != nil && devlist != [] ) {
	  integer ok = 0;
	  foreach(string dev, devlist, ``{

	    // build mbr backup string
	    string randompart = sformat("%1/%2_%3.mbr",
		WFM::Read(.local.tmpdir,"" ),
		mergestring(splitstring(dev,"/"),"_") ,
		random(100000));

	      if (OSRExecute::Command(.local.bash, sformat("/bin/dd if=%1 of=%2 count=1 bs=512",dev,randompart ))){
		  ok =ok+1;
		  backup_dev_filenames[dev] = randompart;
	      }
	  });

	  if (ok == size(devlist))  backup_mbr_status = true;
      }
      else {
	  y2error("Emtpy devive list. No harddisk found.");
      }
  }

  /**
   * Check reparired mbr.
   */
  define boolean CheckRestoredPtbl(string target , path target_scrpath ) ``{
      y2milestone("checking restored ptl");

      // read the partition table for checking if it is o.k.
      string partition_path	  = ".disk" + target_scrpath + ".partitions";
      // no TYPE
      list   partition_table   = (list) SCR::Read(topath(partition_path));

      if ((partition_table != nil) && (partition_table != []))
      {
	  y2milestone("restoring was successful. Reread Storage");
	  // o.k., partition table successfully reverted.
	  Storage::ReReadTargetMap();
	  // popup message
	  Report::Message(_("
Partition table successfully repaired.
"));
	  return true;
      }
      else {
	  // popup message
	  Report::Message(_("
Partition table was not successfully repaired.
"));
      }
      return false;
  }

  /**
   * Build the file name for reading partition list from floppy.
   */
  define string build_file_name( string key )``{
      return sformat("%2/%1",mergestring(splitstring(key, "/"),"_") , OSRFloppy::mount_point);
  }

  /**
   * Need mounted floppy
   */
  define list<map> read_part_list_from_floppy(string target)``{
    return (list<map>) WFM::Read(.local.ycp, build_file_name(target)+".ycp");
  }

  /**
   * Show
   */
  define list<map> FoundPtblDialog (list<map> partitions, string dev, string repair_tool, boolean whole_mbr )``{

     y2milestone("Found partitions for recovering %1", partitions );

      string label_status = _("Status");
      string label_name   = _("Name");
      string label_fsid   = _("Fsid");
      string label_start  = _("Start");
      string label_end    = _("End");
      string label_size   = _("Size");
      string label_whole  = _("Restore the Entire Master Boot Record");

      list<term> items = [];
      foreach (map n,partitions, {

	if (n["region",1]:0 != 1)
	{
	    map mdev	= Storage::GetMainDev (dev);
	    items = add (items, `item (`id(sformat("%1", n["nr"]:1)),
			s_ignore_label,
			dev + n["nr"]:1,
			n["fsid"]:131,
			sformat ("%1", n["region",0]:0),
			sformat ("%1", n["region",1]:0 + n["region",0]:0 - 1),
			ByteToHumanString (n["region",1]:0 * mdev["cyl_size"]:0)
	    ));
	}
      });

      // help_text  first sentence.
      string help_text = _("<P>The table in the center of the dialog shows the partitions
that could be recovered.</P>
") +

	  OSRPopup::build_label_description( OSRPopup::ignore_label,  sformat(_("Change the status of a partition to %1."), s_ignore_label )) +
	  OSRPopup::build_label_description( OSRPopup::recover_label, sformat(_("Change the status of a partition to %1."), s_recover_label)) +

	  OSRPopup::build_label_description( OSRPopup::repair_label, sformat(_("If you have changed the status of all partitions for recovering
to %1, press Repair to execute the recovery process.
"), s_recover_label)) +
 
	  // help_text description for table column
	  OSRPopup::build_label_description( label_status, sformat(_("The status of a listed partition
can be switched between %1 and %2. YaST only
recovers partitions with the status
%2.
"), s_ignore_label, s_recover_label )) +

	  OSRPopup::build_label_description( label_name, _("Name of the partition that could be
recovered.
")) +

	  OSRPopup::build_label_description( label_fsid, _("The file system ID of the partition.
For example, 131 is a normal Linux partition and  6, 11, 12, and 14 are fat and fat32 partitions.
")) +

	  OSRPopup::build_label_description( label_start, _("The start cylinder of the partition.")) +

	  OSRPopup::build_label_description( label_end,   _("End cylinder of the partition.")) +

	  OSRPopup::build_label_description( label_size,  _("Size of the partition."));

      term whole_term = `Empty();
      if (whole_mbr )
      {
	  whole_term = `Left(`CheckBox(`id(`whole_mbr), `opt(`notify), label_whole ,false));
	  OSRPopup::build_label_description( label_whole, _("Select this option to
recover the entire master boot record with all partitions.
"));
      }


      // dialog caption
      OSRPopup::OpenSuggestDialog(_("Recover Lost Partitions"),
				  // dialog text
				  sformat(_("
YaST found the following partitions on device %1.
To recover a partition, select the entry
in the table and press \"Recover\".
"), dev),
				  help_text,
				  `VBox(`Table(`id(`table), `header(label_status, label_name, label_fsid, label_start, label_end, label_size ), items ),
					`Left(`HBox(
						    `PushButton(`id(`ignore),  OSRPopup::ignore_label  ),
						    `PushButton(`id(`recover), OSRPopup::recover_label )
						    )),
					whole_term
					),
				  40);
    UI::SetFocus(`id(`table));

    any ret          = `ok;
    list<map>   recover = [];
    repeat
    {
	ret = UI::UserInput();

	string current_item = (string)UI::QueryWidget(`id(`table),`CurrentItem);
	term it = (term) UI::QueryWidget (`id(`table), `Item (current_item));
	string current_value = it[1]:"";

	map current_part = find (map part, partitions, ``(
	    sformat("%1", part["nr"]:"") == current_item ));

	if (current_part == nil || current_part == $[] )
	    y2error(" partition not found");

	if (ret == `ignore )
	{
	    if (current_part["type"]:`unknown == `extended )
	    {
		foreach (map part,
		    filter (map p, partitions,``(p["type"]:`unknown==`logical)),
		``{
		    UI::ChangeWidget(`id(`table),
			`Item(sformat("%1", part["nr"]:""), 0), s_ignore_label);
		});
	    }
	    UI::ChangeWidget(`id(`table),`Item(current_item, 0),s_ignore_label);
	}
	if (ret == `recover )
	{
	    if (current_part["type"]:`unknown == `logical )
	    {
		map extended = find (map part, partitions, ``(
		    part["type"]:`unknown == `extended ));
		UI::ChangeWidget(`id(`table),
		    `Item(sformat("%1", extended["nr"]:""), 0),s_recover_label);
	    }
	    UI::ChangeWidget(`id(`table),`Item(current_item,0),s_recover_label);
	}
	if (ret == `whole_mbr )
	{
	    UI::ChangeWidget(`id(`table), `Enabled,
		!(boolean) UI::QueryWidget(`id(`whole_mbr), `Value ));
	}

	if ( ret == `ok )
	{
	    recover = filter (map n, partitions, {
		term it	= (term) UI::QueryWidget (
		    `id(`table), `Item(sformat("%1", n["nr"]:1)));
		return (it[1]:"" == s_recover_label);
	    });

	    y2milestone( "all selected items %1", recover );

	    if (whole_mbr )
	    {
		if ((boolean)UI::QueryWidget(`id(`whole_mbr ), `Value ))
		    recover = [ $["whole_mbr":$[]] ];
	    }
	}
    } until( ret == `ok || ret == `cancel );

    UI::CloseDialog();
    return recover;
  }


  /**
   * Call fdisk
   */
  define boolean fdisk_call(path target_scrpath, list<map> torecover )``{

      map args = $[ "parted" : false,
		    "label"  : ""  ];

      UI::OpenDialog(`VBox(`VSpacing(1),
			   `Label(_("Recovering partition table. Please wait...")),
			   `VSpacing(1)));

      boolean result = true;

      if (! Mode::test () )
      {
      /////////////////////////////////////////////////////
	  result = SCR::Write(.disk + target_scrpath + .partitions,
			      torecover, args );
      /////////////////////////////////////////////////////
	  repair_methods[ `original,  "enabled"] = true;

	  Storage::ReReadTargetMap();
      }
      else {
	  y2milestone("fdisk call not executed. Mode::test () = true !!!");
      }

      UI::CloseDialog();

      return result;
  };

 

  
  /**
   * Restore lost mbr with a backup from floppy.
   */
  define symbol restore_from_floppy( )``{

      string error_message_start = sformat(_("No primary partition table was found on hard disk %1."), target );
      string error_message       = sformat(_("

If you have a floppy with some
up-to-date system information from a former rescue
session, insert it in your drive and press Repair.

Press Skip and repair manually if you have a
backup of the partition table of disk %1.

Press Help to learn how to
make a backup of a partition table manually
and how to restore it.
"), target);

   
      // this can't be repaired without a floppy drive, so the user must not be asked again
      // to repair via floppy
      if (! OSRCommon::RequireBoolean("has_floppy"))
      {
	  // display that no floppy drive was detected
	  Report::Message(_("
YaST cannot detect a floppy drive
in your system. A backup copy of the
primary partition table could not be
read.
"));
	  return `error;
      }	
      else {
	  if (! OSRPopup::Repair(_("Error detected."), error_message_start + error_message, floppy_help_text())) return `cancel;
	  
	  if (OSRFloppy::Open(true, 2))
	  {
	      OSRFloppy::NextStep(_("Searching partition table..."));
	      list<map> found_partitions = read_part_list_from_floppy(target);
	      list   to_recover          = [];
		  
	      if (found_partitions != nil && found_partitions != [] ) {
		  to_recover = FoundPtblDialog(found_partitions, target, "Floppy", true );
		  
		  boolean revert = false;
		  
		  if (size(to_recover ) == 0 ) {
		      Report::Warning(_("
Could not find a valid partition table.
Insert another floppy disk and
try again or have gpart suggest a
partition table.
"));
		      OSRFloppy::Close();
		      return `cancel;
		  }
		  else if (haskey( to_recover[0]:$[], "whole_mbr")) {

		      y2milestone("recover the whole mbr");

		      if (WFM::Read(.local.size, build_file_name(target)+".mbr" ) == 512 )
		      {		
			  OSRFloppy::NextStep(_("Restoring old partition table..."));		      
		      
			  // copy the backup of the primary partition table to the target device
			  revert = OSRExecute::Command(.local.bash,
						       sformat("/bin/dd if=%1.mbr of=%2 bs=1 count=512", build_file_name(target ),target ));
			  
			  // call block device ioctls: reread the partition table (important after having
			  // changed the partition table!)
			  OSRExecute::Command(.target.bash, sformat("/sbin/blockdev --rereadpt %1", target));
		      }
		      else {
			  Report::Error(_("
Could not find a valid master boot
record on floppy disk. Insert
another floppy disk and try again or
have gpart suggest the partition table.
"));
			  OSRFloppy::Close();
			  return `error;
		      }
		  }

		  OSRFloppy::Close();

		  if (! haskey( to_recover[0]:$[], "whole_mbr") ) {
		      list<map> new_to_recover = [];
		      foreach(map<string,any> p, (list<map<string,any> >)to_recover, ``{
			  p["create"] = true;
			  new_to_recover = add(new_to_recover, p);
		      });
		      revert = fdisk_call( target_scrpath, new_to_recover );
		  }
		  
		  if (revert )
		  {
		      if (CheckRestoredPtbl(target, target_scrpath ) ) {
			  return `ok; 
		      }
		  }

		  // error message
		  Report::Error(_("
Restoring the partition table
was not successful.
Insert another floppy disk
and try again or have gpart
suggest the partition table.
"));
		  return `error;
	      }
	  }
	  else
	  {
	      // error message
	      Report::Error (_("
Either no floppy disk is inserted in
the drive or the inserted floppy is not
correctly formatted. Insert
another floppy disk and try again or
have gpart suggest the partition table.
"));
	  }

	  OSRFloppy::Close();
      }
      return `cancel;
  }

  /**
   * Cat the fsid from a line.
   */
  define integer gpart_cat_fsid(string line)``{
    string sub = substring( line, find(line, ":"), find(line,"(")-find(line,":") );
    string allow =  "0123456789";
    sub = filterchars( sub, allow);
    return tointeger(sub);  
  }

  /**
   * Cat the geometry from a line.
   */
  define map gpart_cat_geometry( string line)``{
    string allow = "0123456789/";

    string sub1  = substring( line, find(line, "d"), size(line));
    list<string> split   = splitstring(sub1, "-" );
    string start = filterchars(split[0]:"",  allow);
    string end   = filterchars(split[1]:"", allow);
    list<string> s = splitstring( start, "/");
    list<string> e = splitstring( end, "/");

    map ret = $[ "start": $[ "c" : tointeger( s[0]:"0"),
			  "h" : tointeger( s[1]:"0"),
			  "s" : tointeger( s[2]:"0") ],
		 "end"  : $[ "c" : tointeger( e[0]:"0"),
			     "h" : tointeger(    e[1]:"0"),
			    "s" : tointeger(    e[2]:"0") ]];

    y2milestone("retmap gemetry %1", ret);
    return ret;
};


  /**
   * Dialog with expert gpart options.
   */
  define symbol expert_restore_with_gpart() {


    // checkbox label
    string full_label		= _("Full Scan");
    // button label
    string start_label		= _("Start Detection");

    map maindev			= Storage::GetMainDev (target);
    integer cyl_count		= maindev["cyl_count"]:0;
    gpart_expert_options	= "";

    map symbol_2_option = $[
	`extended		: "-E",
	`skip_errors		: "-e",
	`full_scan		: "-f"
    ];

    // help text label
    string help_text = _("<P>
<B>Help for Expert Mode</B>
</P>
") +
	// help text (restorng partition)
	OSRPopup::build_label_description (full_label, _("
<P>When a possible partition is found,
gpart normally skips all sectors this entry seems
to occupy and continues the scan from the end of
the last possible partition. The disk scan can take
quite a while if this option is given. Be patient.</P>
"));

      UI::OpenDialog( `HBox(
			    `HWeight(2, `RichText(`id(`help),  help_text )),
			    `HWeight(4,
				     `HBox(
					    `HSpacing(2),
					    `VBox(
						    // headline label
						  `Heading(_("Select the Scan Options for gpart:")),
						  `VSpacing(3),
						  // checkbox label
						  `Left(`CheckBox(`id(`extended),    _("Do Not Try To &Identify Extended Partition Tables"))),
						  // checkbox label
						  `Left(`CheckBox(`id(`skip_errors), _("Do Not &Skip Disk Read Errors"))),
						  `Left(`CheckBox(`id(`full_scan),  full_label  )),
						  `VSpacing(4),
						  `HBox(
							`PushButton(`id(`ok), start_label ),
							`PushButton(`id(`cancel), Label::CancelButton())
							)
						  ),
					    `HSpacing(2)
					    )
				     )
		      ));

    UI::SetFocus(`id(`extended));

    symbol ret     = `ok;
    repeat
    {
	ret = (symbol) UI::UserInput();

	if (ret == `ok ) {

	    //build expert options
	    foreach (symbol key, string option, (map<symbol,string>)symbol_2_option, ``{
		gpart_expert_options = gpart_expert_options +
		    ((boolean)UI::QueryWidget(`id(key), `Value)
			? " " + option + " " : "");
	    });

	    y2milestone("gpart options %1", gpart_expert_options );
	}
    } until( ret == `ok || ret == `cancel );
    UI::CloseDialog();

    if (ret == `ok ) ret = RestoreWithGpart ();

    gpart_expert_options = "";
    return  ret;
  }


  define map compare_found_with_existing (  list<map> found_partitions,
					    list<map> existing_partitions,
					    string target) ``{
      map ret		    = $[];

      foreach(map paritition, found_partitions, ``{

	  map matching = find(map e_part, existing_partitions, ``(e_part["region"]:[] == paritition["region"]:[0]));

	  if (matching != nil && matching != $[] )
	  {
	      ret["match"] = add(ret["match"]:[], paritition );
	      y2milestone("found paritition %1 matchs with exiting paritition %2", paritition["device"]:"", matching["device"]:"");
	  }
	  else
	  {
	      integer found_start = paritition["region",0]:0;
	      integer found_end   = paritition["region",0]:0 + paritition["region", 1]:0  -1;
	      foreach(map e_part,  existing_partitions, ``{
		  integer existing_start = e_part["region", 0]:0;
		  integer existing_end   = e_part["region", 0]:0 + e_part["region", 1]:0 -1;

		  if ((existing_start >= found_start && existing_start <= found_end) ||
		       (existing_end   >= found_start && existing_end   <= found_end) ||
		       (existing_start >= found_start && existing_end   <= found_end) ||
		       (existing_start <= found_start && existing_end   >= found_end)
		       )
		  {
		      paritition["overlap"] = add(paritition["overlap"]:[], e_part );
		      y2milestone("found paritition %1 overlaps with exiting paritition %2", paritition["device"]:"", e_part["device"]:"");
		  }  
	      });
	      if (paritition["overlap"]:[] != [] )
	      {
		  ret["overlap"] = add(ret["overlap"]:[], paritition);

	      }
	      else
	      {
		  ret["free"]    = add(ret["free"]:[], paritition );
		  y2milestone("found free paritition space for %1", paritition["device"]:"");
	      }
	  }
      });
      if (size(ret) > 0 )
      {
	  ret["existing"] = existing_partitions;
      }
      return ret;
  }

  /**
   * return partition information (to be shown to user)
   */
  define string partition_info (map part, string dev) {

      map maindev	= Storage::GetMainDev (dev);
      return
	// partition info 1/3, %1,%2 are numbers
	sformat (_("cylinders %1-%2, "),
	    part["region", 0]:0, part["region",0]:0 + part["region",1]:0 -1) +
	// partition info 2/3, %1 is partition ID (number)
	sformat(_("ID %1, "),  part["fsid"]:131) +
	// partition info 3/3, %1 is partition size (number)
	sformat (_("size %1"), ByteToHumanString (
	    part["region",1]:0 * maindev["cyl_size"]:0));
    }

  /**
   * Compare an existing partition table with the gpart output
   */
  define list<map> ComparePtblDialog (map compared, string target,
				      string repair_tool) ``{

    term part_items		= `VBox();
    list<list> part_list	= [];
    list<map> found_free_parts	= compared["free"]:[];
    list<map> overlap_parts	= compared["overlap"]:[];

    //number to text
    map number2text = $[
	// label: partition number
	1 : _("First Found Partition"),
	2 : _("Second Found Partition"),
	3 : _("Third Found Partition"),
	4 : _("Fourth Found Partition")
    ];

    foreach (map free_part, found_free_parts, ``{
	integer nr	= free_part["nr"]:0;
	part_list	= add (part_list, [
	    nr,
	    `Left (
		`CheckBox(`id(free_part["device"]:""),
		    sformat (_("&%1: %2 - %3"), nr, number2text[nr]:"",
			partition_info (free_part, target)),
		    true
		)
	    )
	]);
    });

    foreach (map overlap_part, overlap_parts, ``{
	integer nr = overlap_part["nr"]:0;
	list<string> del_text_parts = maplist (map e_part,
	    overlap_part["overlap"]:[], ``{
	    return sformat (
		// label: %1 is device name, %2,%3 numbers
		_("Partition %1 must be deleted to recover cylinders %2-%3."),
		e_part["device"]:"",
		overlap_part["region",0]:0,
		overlap_part["region",0]:0 + overlap_part["region",1]:0 - 1);
	});

	part_list = add (part_list, [
	    nr,
	    `Left (`VBox (
		`Left (
		    `CheckBox (`id(overlap_part["device"]:""),
			sformat(_("&%1: %2 - %3"), nr, number2text[nr]:"",
			    partition_info (overlap_part, target)),
			false)
		),
		`VSpacing(0.5),
		`Left (
		    // heading
		    `Heading (_("Warning: Conflicts with existing partitions"))
		),
		`Left (
		    `Label (mergestring (del_text_parts, "\n"))
		)
	    ))
	]);
    });

    part_list = sort (list l, list q, part_list, ``( l[0]:0 < q[0]:0 ));
    foreach (list part, part_list , ``{
	part_items = add (part_items, part[1]:`Empty());
	part_items = add (part_items, `VSpacing(2));
    });

    // gpart found only existing partitions.
    if (size( found_free_parts ) == 0 && size( overlap_parts ) == 0 )
    {
	// popup message
	Report::Message(_("No lost partition found."));
	return [];
    }

    // help text of partition recovery 1/2
    string help_text = _("
<p>
Read the following points accurately
and select only the partition that should
really be recovered.
</P>
") +

    // help text of partition recovery 2/2
    _("<P><B>Use caution.</B><BR>Sometimes an existing partition must
be deleted to recover another partition.</P>
") +

    OSRPopup::build_label_description (OSRPopup::repair_label,
	// button description
	_("Start the Recovery Process"));

    string message = "";
    // popup headline
    OSRPopup::OpenSuggestDialog (_("Recover Previously Existing Partitions"),
	message,
	help_text,
	`HBox (`HSpacing(1), part_items, `HSpacing(1)),
	(size(found_free_parts) + size(overlap_parts)) * 15
    );

    any ret          = `ok;
    list<map>   recover = [];
    repeat
    {
	ret = UI::UserInput();

	if ( ret == `ok )
	{
	    foreach (map free_part, found_free_parts, ``{
		if ((boolean)
		    UI::QueryWidget(`id(free_part["device"]:""), `Value))
		{
		    recover = add (recover, free_part );
		}
	    });
	    foreach (map overlap_part, overlap_parts, ``{
		if ((boolean)
		    UI::QueryWidget(`id(overlap_part["device"]:""), `Value))
		{
		    // warning popup, %1 is device name, %2 list of partitions
		    if (Popup::ContinueCancel (sformat(_("
Recovering the selected partition %1 is only
possible by deleting the following partitions:
%2
Press Continue to delete the partitions
and recover %1."),
			overlap_part["device"]:"",
			mergestring (
			    maplist (map o_part, overlap_part["overlap"]:[],
				``(o_part["device"]:"")),
			    " "
			)
			)))
		    {
			foreach (map delpart, overlap_part["overlap"]:[], ``{
			    delpart["delete"] = true;
			    delpart = filter (string key, any d,
				(map<string,any>) delpart,``(key != "overlap"));
			    recover = (list<map>) union (recover, [delpart]);
			});
			overlap_part = filter (string key, any d,
			    (map<string,any>)overlap_part,``(key != "overlap"));
			recover = add (recover, overlap_part );
		    }
		    else //popup canceled
		    {
			UI::ChangeWidget (`id(overlap_part["device"]:""),
			    `Value, false);
			recover = [];
			ret = `again;
		    }
		}
	    });
	    list<map> existing_partitions	= compared["existing"]:[];
	    list used_nrs			= maplist (
		map ep,existing_partitions,``(ep["nr"]:0));
	    list delete_nrs			= maplist (
		map dpp, filter (map dp, recover, ``(dp["delete"]:false)),
		    ``(dpp["nr"]:0)
	    );

	    used_nrs	= filter (integer nr, (list<integer>) used_nrs, ``(
                !contains(delete_nrs, nr)));

	    list<integer> primary_pos  = [1,2,3,4];
	    list<integer> free_primary = filter (integer nr, primary_pos, ``(
		!contains (used_nrs, nr)));

	    // check partition nr's
	    foreach(map part, recover , {
		if (part["delete"]:false != true && ret != `again )
		{
		    // partition nr is used by a other partition.
		    if (contains (used_nrs, part["nr"]:0))
		    {
			if (size (free_primary) > 0)
			{
			    y2milestone("move partition nr %1 to a free primary position", part["nr"]:0);
			    part["nr"] = free_primary[0]:0;
			    free_primary = remove (free_primary, 0);
			}
			else
			{
			    y2error("no free primary slot found. locical search not supported");
			    // error popup, %1 is paritition number, %2 info
			    Report::Error(sformat(_("
Recovering the partition %1 (%2) is not
possible. The partition number conflicts
with an existing partition. Solving the
conflict is not possible.
"), part["nr"]:0, partition_info (part, target)));

			    UI::ChangeWidget(`id(part["device"]:""), `Value, false);
			    ret = `again;
			}
		    }
		}
	    });
	}
    } until ( ret == `ok || ret == `cancel );

    UI::CloseDialog();
    return recover;
  }


  /**
   * Restoring a partition tables with gpart.
   */
  global define symbol RestoreWithGpart () ``{

    /////////////////////////////////////////////////////////////////////////
    //
    //  Repair partition table with gpart
    //
    /////////////////////////////////////////////////////////////////////////
    list<map> found_partitions	= [];
    string gpart_file           = (string) SCR::Read(.target.tmpdir) + "/gpart";
    WFM::Execute (.local.bash, sformat("if /usr/bin/test -e %1; then /bin/rm %1; else /bin/echo %1 not exist; fi;", gpart_file));

    string gpart_command = "/usr/bin/gpart "+ gpart_expert_options +" "+ target;

    term contents  = `HBox(`HSpacing(0.5),
	`VBox (
	    `Left (`HBox (
		// headline of partition scan popup
		`HWeight (30, `Heading(_("Search for Lost Partitions"))),
		`HWeight (30, `HSpacing(4))
	    )),
	    `VSpacing(1),
	    // addtional info for partition scan
	    `Left (`Label (_("This can take a lot of time depending on the
system performance and the hard disk size.")
	    )),
	    // label text (device name follows)
	    `Left (`Label(_("Hard Disk: ") + target)),
	    `VSpacing(0.5),
	    `VSpacing(1),
	    // button label
	    `HBox (
		`PushButton(`id(`start) , _("&Start")),
		`PushButton (`id(`cancel), Label::CancelButton())
	    )
	),
	`HSpacing(0.5)
    );

    UI::OpenDialog (contents);

    if (UI::UserInput () != `start )
    {
	UI::CloseDialog();
        return `cancel;
    }

    UI::ChangeWidget(`id(`start), `Enabled, false);
    UI::ChangeWidget(`id(`cancel), `Enabled, false);
    // FIXME action cannot be cancelled! -> use background or callback
    // progress doesn't have sence!

    UI::BusyCursor();
    if (OSRExecute::CommandProgress (.target.bash_output, gpart_command, gpart_file))
    {
	string file = (string) WFM::Read(.local.string, gpart_file );
	// display the results in a window
	string partitions_out = substring (file,
	    find (file, "\nPrimary partition(1)"),
	    (size(file) - find (file, "\nPrimary partition(1)"))
	);
	string partition1 = substring(partitions_out,
	    find(partitions_out, "\nPrimary partition(1)"),
	    (find(partitions_out, "\nPrimary partition(2)") -
	     find(partitions_out, "\nPrimary partition(1)"))
	);

	string partition2 = substring (partitions_out,
	    find (partitions_out, "\nPrimary partition(2)"),
	    (find(partitions_out, "\nPrimary partition(3)") -
	     find(partitions_out, "\nPrimary partition(2)"))
	);

	string partition3 = substring (partitions_out,
	    find(partitions_out, "\nPrimary partition(3)"),
	    (find(partitions_out, "\nPrimary partition(4)") -
	     find(partitions_out, "\nPrimary partition(3)"))
	);

	string partition4 = substring(partitions_out,
	    find(partitions_out, "\nPrimary partition(4)"),
	    (size(partitions_out)-find(partitions_out,"\nPrimary partition(4)"))
	);

	list gpart_partitions = [partition1, partition2, partition3,partition4];

	integer index  = 0;
	while ((index >= 0) && (index < size(gpart_partitions)))
	{
	    map partition	= $[];
	    string p		= gpart_partitions[index]:"";
	    list<string> p_list = splitstring(p, "\n");

	    foreach (string line, p_list, ``{
	        if (issubstring(line, "type: "))
	        {
	            partition["fsid"] = gpart_cat_fsid(line );
	        }
	        else if (issubstring( line, "chs:"))
	        {
		    map geometry = gpart_cat_geometry( line );
	            integer s    = geometry["start","c"]:0;
	            integer l    = geometry["end","c"]:0 - s + 1;
	            partition["region"] = [s,l];
		}
	        partition["nr"]		= index +1;
	        partition["type"]	= `primary;
	        partition["create"]	= true;
	        partition["device"] = Storage::GetDeviceName(target, index + 1);
	    });

	    index = index + 1;

	    if (partition["region",1]:0 != 0 && partition["fsid"]:0 != 0)
	    {
		found_partitions = add(found_partitions, partition );
	    }
	}
    }

    UI::CloseDialog();
    UI::NormalCursor();

    if (size (found_partitions) == 0)
    {
	// error popup, %1 is device
	Report::Error(sformat(_("
YaST did not find any valid partitions
for partition table %1. If there were
some partitions on the disk, you can try
to restore them manually. To do
that, enter the cylinder values
in fdisk or the partitioning tool.
"), target));
	return `error;
    }
    else
    {
	map compared		=  compare_found_with_existing (
	    found_partitions,
	    (list<map>) Storage::GetMainDevParam (target, "partitions", []),
	    target);
	list<map> torecover	= ComparePtblDialog (compared, target, "Gpart");

	if (size (torecover) < 1 || Mode::test ())
	    return `cancel;

	if (fdisk_call (target_scrpath, torecover))
	{
	    if (CheckRestoredPtbl (target, target_scrpath))
	    {
		return `ok;
	    }
	}
	else
	{
	    // error popup message
	    Report::Error(_("Partition table was not successfully repaired."));
	    return `error;
	}
    }
    return `error;
  }

  /**
   * Restore saved mbr.
   */
  define boolean write_original(list<string> to_reset, boolean whole_mbr )``{
      if (Mode::test () ) return true;

      y2milestone("write orginal master boot record");

      integer ok = 0 ;      
      foreach(string dev, to_reset , ``{

	  if ( backup_dev_filenames[dev]:"" != "" ) {
	      string command = sformat("/bin/dd if=%1 of=%2 ", backup_dev_filenames[dev]:"", dev );

	      if (whole_mbr ) command = command + "count=1 bs=512";
	      else command = command + "bs=1 count=64 skip=446 seek=446";

	      if (OSRExecute::Command(.local.bash,command)) {
		  ok = ok + 1;
		  Report::Message(sformat(_("Writing the backup of %1 was successful."), dev));
	      }
	      else {
		  Report::Message(sformat(_("Writing the backup of %1 was not successful."), dev));
	      }
	      repair_methods[ `original,  "enabled"] = true;
	  }
	  else {
	      y2error("No entry in backup map found");
	  }
	  
      });
      return ( ok ==  size(to_reset));
  }
  

  /**
   * Restore saved mbr.
   */
  define symbol restore_original()``{
      
      string header   = sformat(_("Restore Original Partition Table %1"), target);
      string ok_label = mergestring(splitstring(Label::OKButton(), "&"),"");

      string message  = original_help_text() +  sformat(_("
If you press %1, YaST will replace the
partition table of %2 with the backup.
"), ok_label, target );
      
      if (Popup::YesNoHeadline(header, message ))
      {   
	  if (! Mode::test () )
	  {
	      if (write_original([target], true )) return `ok;
	      else return `error;
	  }
	  else {
	      return `ok;
	  }
      }
      return `cancel;
  }

  /**
   * Write partition info of given disk to floppy (needs mounted floppy)
   */
  global define boolean WritePartitionsInfo2Floppy (string key, list parts) {

      if (Mode::test () ) return true;

      boolean write_error	= false;
      string file		= build_file_name (key);

      // set the dd-command
      string command = sformat("dd if=%1 of=%2.mbr bs=512 count=1", key, file);

      // write mbr to floppy
      OSRExecute::Command (.local.bash, command);

      // write partition list to floppy
      WFM::Write (.local.ycp, file+".ycp", parts);

      if (!(WFM::Read(.local.size, file + ".mbr") == 512  &&
	    WFM::Read(.local.size, file + ".ycp") != 0 ))
      {
	  write_error = true;
      }

      return (!write_error);
  }

  
  /**
   * Main Dialog for repairing deleted/not valid partition tables.
   *
   * @parm t_target the target e.g.: /dev/hda
   * @parm t_target_scrpath the souce path of the target .dev.hda
   * 
   */
  global define symbol MainPtblRepairDialog(string t_target, path t_target_scrpath ) ``{

      //Save all mbr's to tmp directory.
      SavePtbl();
      
      target 	     = t_target;
      target_scrpath = t_target_scrpath;
      
      term r_options = `VBox(`Left(`Label(_("Select a repair method or skip repairing."))),`VSpacing(0.5));
      
      foreach(symbol ro, map<string,any> rd, (map<symbol,map<string,any> >)repair_methods, ``{
	  term r_button = `RadioButton(`id( ro ));
	  if (! rd["enabled"]:true ) r_button = add( r_button , `opt(`disabled));
	  r_button      = add( r_button ,   rd["header"]:"");
	  r_button      = add( r_button,    rd["default"]:false );
	  r_options = add( r_options , `Left( r_button ));
      });

      string error_text = sformat(_("
No partition table was found for hard disk %1.
If at least one partition should exist for %1,
select one of the following repair methods.  If
no partition ever existed on this hard disk,
skip the repair.

"), target);


      if (! OSRPopup::OpenMainRepairDialog( sformat(_("Repair partition table %1."),target ), error_text, r_options ))
      {
	  y2error("could not open main dialog");
	  return `error;
      }
      symbol ret     = `ok;
      repeat {
	ret             = (symbol) UI::UserInput();
	symbol selected = (symbol) UI::QueryWidget(`id(`rb), `CurrentButton);

	if (ret == `help )
	{
	    string () help_f =
		repair_methods[selected,"help"]:OSRCommon::EmptyString;
	    Popup::LongText (OSRPopup::help_headline,
		`RichText ( help_f () ), 50, 20);
	}
	else if (ret == `ok )
	{
	    UI::CloseDialog();
	    symbol () f =
		repair_methods [selected,"method"]:OSRCommon::SymbolError;
	    ret = f ();

	    if (ret != `ok )
	    {
		if (! OSRPopup::OpenMainRepairDialog (
			// dialog headline
			sformat(_("Repair partition table %1."), target ),
			error_text, r_options ))
		{
		    y2error("could not open main dialog");
		    return `error;
		}
		ret = `again;
	    }
	}
      } until( ret == `cancel || ret == `abort || ret == `ok );
      if (ret != `ok )  UI::CloseDialog();
      return ret;
  }

  global define void SetTarget(string ttarget, path ttarget_scrpath )``{
      target = ttarget;
      target_scrpath = ttarget_scrpath;
  }

}//EOF
