/**
 *  File:
 *    OSRFsck.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: OSRFsck.ycp 20509 2004-12-07 15:38:00Z jsuchome $
 */
{
  module "OSRFsck";

  import "FileSystems";
  import "Storage";
  import "Partitions";
  import "Mode";
  import "Popup";
  import "Stage";

  import "OSRExecute";
  import "OSRLogFile";
  import "OSRModuleLoading";
  import "OSRPopup";
  import "OSRCommon";

  textdomain "repair";

  /**
   * The last return value from a check_... call
   */
  integer last_check_return		= 0;

  /**
   * Options for a repair call. Not Supported at the moment.
   */
  string  fsck_options			= "";

  /**
   * The not loaded filesystem an lvm/raid modules.
   */
  global list<string> not_installed_modules = [];

  /**
   * The loaded modules.
   */
  global list<string> fs_kernel_modules = [];

  /**
   * A list of all possible filesystem id's for fsck...
   */
  global list fsck_fsid			= [];

  /**
   * A list of all possible types for a fsck ...
   */
  global list<symbol> fsck_type		= [`sw_raid, `lvm, `logical, `primary];

  /**
   * The partition name witch is currently in work ( checked - repaired)
   */
  string check_partition		= "";

  // prototypes of check/repair functions
  define boolean is_ext2fs();
  define boolean is_reiserfs();
  define boolean is_xfs();
  define boolean is_jfs();
  define boolean is_fat();
  define boolean is_ntfs();

  define boolean repair_ext2fs();
  define boolean repair_reiserfs();
  define boolean repair_xfs();
  define boolean repair_jfs();

  define boolean check_ext2fs();
  define boolean check_reiserfs();
  define boolean check_xfs();
  define boolean check_jfs();

  /**
   * The find/check/repair define for all supported filesystems.
   */
  map fsck_map = $[
		    `ext2 : $[
				"find"		: is_ext2fs,
				"repair"	: repair_ext2fs,
				"check"		: check_ext2fs
		    ],
		    `ext3 :  $[
				"find"		: is_ext2fs,
				"repair"	: repair_ext2fs,
				"check"		: check_ext2fs
		    ],
		    `reiser: $[
				"find"		: is_reiserfs,
				"repair"	: repair_reiserfs,
				"check"		: check_reiserfs
		    ],
		    `xfs : $[
				"find"		: is_xfs,
				"repair"	: repair_xfs,
				"check"		: check_xfs
		    ],
		    `jfs : $[
				"find"		: is_jfs,
				"repair"	: repair_jfs,
				"check"		: check_jfs
		    ],
		    // "Do not touch non-linux partitions" (#25002)
		    // -> no check/repair functions for other filesystems
		    `fat16 : $[
				"find"		: is_fat
		    ],
		    `fat32 : $[
				"find"		: is_fat
		    ],
		    `ntfs : $[
				"find"		: is_ntfs
		    ]
  ];


  string file_name = "";

  global define void Reset()``{
      file_name			= "";
      last_check_return		= 0;
      fsck_options		= "";
      not_installed_modules	= [];
      fs_kernel_modules		= [];
      check_partition		= "";
  }

  /**
   * Constructor.
   * Fill the list fsck_fsid with the date from module Partition.
   */
  global define void OSRFsck()``{

    fsck_fsid = union (Partitions::fsid_wintypes, Partitions::fsid_dostypes );
    fsck_fsid = union (fsck_fsid, Partitions::fsid_ntfstypes );
    fsck_fsid = union (fsck_fsid,
	[ Partitions::fsid_native, Partitions::fsid_lvm  ]);
  }


  /**
   * Load all needed fileystem and lvm/raid modules.
   */
  global define boolean LoadAllFsModules()``{

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

      not_installed_modules = [];
      fs_kernel_modules     = [];

      list all_supportet_fs = maplist (symbol fs, map details,
	    (map<symbol,map<any,any> >)FileSystems::GetAllFileSystems(false, false ), ``(fs) );

      foreach (symbol fs, (list<symbol>) all_supportet_fs , ``{
	  fs_kernel_modules = (list<string>) union (
	    fs_kernel_modules, FileSystems::GetNeededModules (fs));
      });
      fs_kernel_modules = (list<string>) union (
	fs_kernel_modules , [ "xor", "raid0", "raid1", "raid5"]);

      // iterate all listed kernel modules
      foreach(string fs_module, fs_kernel_modules, ``{
	  if (! OSRModuleLoading::Load( fs_module, "","","",false, true))
	      not_installed_modules = add (not_installed_modules, fs_module);
      });

      return size( not_installed_modules ) == 0;
  }




  /**
   * Return a list witch contains all partitions names that can be
   * used for a fsck.
   */
  global define list<map> PossiblePartitions()``{

      list<map> partitions =	[];

      foreach(string dev, map target, (map<string,map<string,any> >)Storage::GetTargetMap(), ``{
	  foreach(map p, target["partitions"]:[],``{

	      if ( p["delete"]:false == false &&
		  ( contains ( fsck_fsid, p["fsid"]:0 ) &&
		    contains ( fsck_type, p["type"]:`unknown ) ) )
	      {
		  p["maindev"] = dev;
		  partitions = add(partitions, p );
	      }
	  });
      });

      y2milestone("all partitions %1", partitions);
      return partitions;
  }


  define boolean is_unknown()``{
      y2warning ("unkonwn file system");
      return false;
  }

  /**
   *  Checks if the specified check_partition is of filesystem-type ext2fs.
   *  @return boolean True if the check_partition is ext2fs.
   */
  define boolean is_ext2fs() ``{

    boolean isext2fs = OSRExecute::Command (
	.local.bash, "/sbin/dumpe2fs -h " + check_partition);

    if (isext2fs )
	y2milestone("partition %1 is ext2fs", check_partition);
    else
	y2milestone("partition %1 is NOT ext2fs", check_partition);

    return isext2fs;
  };

  /**
   *  Checks if the specified partition is of filesystem-type reiserfs.
   *  @return boolean True if the partition is reiserfs.
   */
  define boolean is_reiserfs() ``{

    boolean isreiserfs = OSRExecute::Command (
	.local.bash, "/sbin/debugreiserfs " + check_partition);

    if ( isreiserfs )
    {
	y2milestone("partition %1 is reiserfs", check_partition);
    }
    else
    {
	y2milestone("partition %1 is NOT reiserfs", check_partition);
    }
    return isreiserfs;
  };


  /**
   *  Checks if the specified partition is of filesystem-type xfs.
   *  @return boolean True if the partition is xfs.
   */
  define boolean is_xfs()``{

    boolean isxfs = OSRExecute::CommandOutput (
	.local.bash, "/usr/sbin/xfs_admin -l -u " + check_partition);
    // failed
    //xfs_admin: unexpected XFS SB magic number 0xdd0a05a6
    //bad sb magic # 0xdd0a05a6 in AG 0
    //failed to read label in AG 0
    //bad sb magic # 0xdd0a05a6 in AG 0
    //failed to read UUID from AG 0

    // success
    //label = ""
    //uuid = ad743076-12b8-452b-86c0-2e62918479ae
    if (issubstring (OSRExecute::stdout, "label") &&
	issubstring (OSRExecute::stdout, "uuid" ))
    {
	y2milestone("partition %1 is xfs", check_partition);
	isxfs = true;
    }
    else
    {
	y2milestone("partition %1 is NOT xfs", check_partition);
	isxfs = false;
    }
    return isxfs;
  };

  /**
   *  Checks if the specified partition is of filesystem-type jfs.
   *  @return boolean True if the partition is jfs.
   */
  define boolean is_jfs()``{

    string command = sformat ("if /usr/bin/test -f %1; then /bin/rm %1; fi",
	OSRLogFile::GetTmpDir() +"/jfslog.dmp" );

    // delete the old progress_file, if it exists
    if (WFM::Execute(.local.bash, command) != 0)
    {
	y2error("The file %1 could not be deleted.",
	    OSRLogFile::GetTmpDir() +"/jfslog.dmp" );
    }

    command		= sformat("cd %1; /sbin/jfs_logdump %2",
	OSRLogFile::GetTmpDir(), check_partition );
    boolean isjfs	= OSRExecute::CommandOutput (.local.bash, command );

    // check_jfs is 0 !! -> read jfslog.dmp
    string jfslog	= (string) WFM::Read (.local.string,
	OSRLogFile::GetTmpDir()+"/jfslog.dmp" );

    y2milestone("jfslog.dmp %1", jfslog);

    if (jfslog == nil || issubstring( jfslog, "?????????????????" ))
    {
	isjfs = false;
    }
    return isjfs;
  };


  /**
   *  Checks if the specified partition is of filesystem-type fat/vfat.
   *  @return boolean True if the partition is fat/vfat.
   */
  define boolean is_fat()``{

    string command	= sformat("/bin/guessfstype %1", check_partition );
    boolean isfat	= OSRExecute::CommandOutput (.local.bash, command );

    if ( issubstring( OSRExecute::stdout, "fat" ))
    {
	isfat = true;
    }
    else
    {
	isfat = false;
    }
    return isfat;
  };

  /**
   *  Checks if the specified partition is of filesystem-type ntfs
   *  @return boolean True if the partition is ntfs
   */
  define boolean is_ntfs()  ``{

    string command	= sformat("/bin/guessfstype %1", check_partition );
    boolean isntfs	= false;
    OSRExecute::CommandOutput (.local.bash, command );

    if (issubstring (OSRExecute::stdout, "ntfs" ))
	isntfs = true;
    return isntfs;
  };

  /**
   *  Checks the specified partition for consistency with "/sbin/e2fsck".
   *  @return boolean
   *    The exit code returned by e2fsck is the sum of the following conditions:
   *      0 - No errors,
   *      1 - File system errors corrected,
   *      2 - File system errors corrected, system should be rebooted if file system was mounted,
   *      4 - File system errors left uncorrected,
   *      8 - Operational error,
   *      16 - Usage or syntax error,
   *      128 - Shared library error.
   */
  define boolean check_ext2fs() ``{

    string command	= sformat("/sbin/fsck.ext2 -n -f %1", check_partition );
    boolean checkext2fs = OSRExecute::Command (.local.bash,command);
    last_check_return	= OSRExecute::result;

    if (checkext2fs)
    {
	y2milestone("partition %1 is o.k., /sbin/e2fsck reports no errors",
	    check_partition);
    }
    else
    {
	y2error("/sbin/e2fsck reports an error: %1, command line: %2",
	    checkext2fs, command);
    }
    return checkext2fs;
  };

  /**
   *  Checks the specified partition for consistency with "/sbin/reiserfsck".
   */
  define boolean check_reiserfs() ``{

    string command = sformat("/bin/echo Yes | /sbin/reiserfsck --check %1",
	check_partition);

    // don't use OSRExecute .. because OSRExecute use .target.bash_output and
    // this break the whole Yast2 -> output is to much !! e.g. 14 MB
    last_check_return		= (integer) WFM::Execute(.local.bash, command);
    boolean checkreiserfs	= last_check_return == 0 ;

    if (checkreiserfs )
    {
	y2milestone("partition %1 is o.k., /sbin/reiserfsck reports no errors",
	    check_partition);
    }
    else
    {
	y2error("/sbin/reiserfsck reports an error: %1, command line: %2",
	    checkreiserfs, command);
    }
    return checkreiserfs;
  };

  /**
   * Checks the specified partition for consistency with "/sbin/xfs_db -c check".
   */
  define boolean check_xfs()``{

    string command = sformat("/usr/sbin/xfs_db -c check %1", check_partition);
    boolean checkxfs	= OSRExecute::Command(.local.bash, command);
    last_check_return   = OSRExecute::result;

    if (checkxfs)
    {
	y2milestone("partition %1 is o.k., xfs_check reports no errors",
	    check_partition);
    }
    else
    {
	y2milestone("xfs_check reports an error for partition %1",
	    check_partition );
    }
    return checkxfs;
  };

  /**
   * Checks the specified partition for consistency with "/sbin/fsck.jfs -n ".
   */
  define boolean check_jfs()``{

    string command	= sformat("/sbin/fsck.jfs -n %1", check_partition);
    boolean checkjfs	= OSRExecute::Command(.local.bash, command);
    last_check_return	= OSRExecute::result;

    if (checkjfs)
    {
	y2milestone ("partition %1 is o.k., jfs_check reports no errors",
	    check_partition);
    }
    else
    {
	y2milestone("jfs_check reports an error for partition %1",
	    check_partition );
    }
    return checkjfs;
  };

/**
 * checks for filesystem type
 * (Repair module should not touch non-linux partitions - bug #25002)
 */
global define boolean IsNonLinuxFileSystem (symbol fs) ``{

    return contains ([`ntfs, `fat16, `fat32], fs);

}

/**
  * Test the filesystem of one partition.
  * @param the partition map
  */
global define boolean Check(map part ) ``{
    if(Mode::test () ) return true;

    symbol used_fs = part["detected_fs"]:`unknown;
    check_partition = Storage::GetDeviceName(part["maindev"]:"",part["nr"]:nil);

    y2milestone("Check the file system of the partition %1", check_partition );
    // find fs

    boolean () find_fs = fsck_map [used_fs, "find"]:is_unknown;
    if (!find_fs ())
    {
	// ignore LVM partitions
	if (part["fsid"]:0 == Partitions::fsid_lvm)
	{
	    y2milestone("LVM group: no checks");
	    return true;
	}

	y2error("used_fs is not ok or unknown. Searching for new filesystem");

	used_fs            = `unknown;
	symbol new_used_fs = `unknown;

	foreach (symbol fs, map fs_map, (map<symbol,map<string,any> >)fsck_map, ``{
	    if ( fs != used_fs ) {
		find_fs = fs_map["find"]:OSRCommon::False;
		if (find_fs ())
		{
		    new_used_fs = fs;
		}
	    }
	});
	if ( new_used_fs != `unknown )
	    used_fs = new_used_fs;
	else
	{
	    y2error("no file systen found for %1", check_partition );
	}
    }
    else
    {
	y2milestone("used_fs is ok. Executing file system check !!");
    }
    Storage::SetSubDevParam (
	Storage::GetSubDevKey (part["maindev"]:"", part["nr"]:nil),
	"detected_fs", used_fs );

    if ( used_fs != `unknown && used_fs != nil )
    {
	// ----------- start check now
	if (IsNonLinuxFileSystem (used_fs))
	{
	    y2milestone("non-linux filesystem: no checks");
	    return true;
	}
	boolean () f_check = fsck_map[used_fs, "check"]:OSRCommon::False;
	if (f_check ())
	{
	    y2milestone("file system check successful executed");
	    return true;
	}
    }
    else
    {
	y2milestone("no file system found");
	return nil;
    }
    y2error("file system check was not successful");
    return false;
  }



  /**
   * The main repair dialog.
   * @param t_check_partition the partition e.g.: /dev/hda1
   * @param used_fs the filesystem symbol
   * @return `ok,`error,`cancel
   */
  global define symbol Repair(string t_check_partition, symbol used_fs ) ``{

      // not used at the moment.
      fsck_options   = "";

      file_name      = OSRLogFile::GetTmpDir()+ "/" + "fsck_repair";

      // makes global use possible ( from another define as Check);
      check_partition = t_check_partition;

      // error message, %1 is filesystem type, %2 partition
      string error_message = sformat(_("
The %1 file system of the partition %2 is corrupted.
To repair the file system, press \"Repair\".

Press \"Skip\" to avoid repairing.
"), FileSystems::GetName( used_fs, "unkonwn"), check_partition );

      // error message
      string help_text         = _("
<p>An inconsistent file system occurs when
a partition is not correctly unmounted.
This could happen during a system crash.</p>
") +

      // error message
_("<p>There is a repair method for each file
system. For example, if the file system
of the damaged partition is ext2 or
ext3, use fsck.ext2 (e2fsck) with the
corresponding options to repair the
file system. The following lines list
the repair tools of the different Linux
file systems.</p>
") +

_("<p><pre>
ext2 and ext2: fsck.ext2 or e2fsck
reiserfs:      reiserfsck
jfs:           fsck.jfs
xfs:           xfs_repair
msdos (fat):   fsck.msdos</pre></p>");

    // do not write about "closing YaST" when Mode::init?
    if (!Stage::initial ())
	help_text = help_text +
_("<p>If you know what kind of file system
you have and how to use the corresponding
tool, close YaST and repair the
damaged file system on your own. Otherwise,
close this help dialog and press 
\"Repair\" for automatic repair.</p>
");

      while( true )
      {
	  if (! OSRPopup::Repair(_("Error detected."), error_message, help_text) ) return `cancel;

	  UI::OpenDialog(`VBox(`VSpacing(1),
			       `Label(_("Repairing file system. Please wait...")),
			       `VSpacing(1)));

	  // if file exist
	  if( WFM::Execute(.local.bash, "/usr/bin/test -f " + file_name ) == 0 )
	  {
	      WFM::Execute(.local.bash, sformat ("/bin/rm %1", file_name) );
	  }

	  boolean ret = true;
	  if( !Mode::test () )
	  {
	      boolean () f_rep = fsck_map [used_fs, "repair"]:OSRCommon::False;
	      ret = f_rep ();
	  }
	  UI::CloseDialog();

	  string headline = "";

	  if ( ret )
	  {
	      headline = sformat(_("
Repairing %1 file system of
partition %2 was successful."),
		FileSystems::GetName(used_fs, "unkonwn"), check_partition );
	  }
	  else
	  {
	      headline = sformat(_("
Repairing %1 file system of partition
%2 was not successful."),
		FileSystems::GetName(used_fs, "unkonwn"), check_partition );
	  }

	  // if file exist
	  if( WFM::Execute(.local.bash, "/usr/bin/test -f " + file_name ) == 0 )
	  {
	      Popup::ShowFile( headline, file_name);
	  }

	  if ( ret ) return `ok;
      }
  }

  /**
   * Repair ext2/ext3 filesystem.
   * The exit code returned by e2fsck is the sum of the following conditions:
   *      0 - No errors,
   *      1 - File system errors corrected,
   *      2 - File system errors corrected, system should be rebooted if file system was mounted,
   *      4 - File system errors left uncorrected,
   *      8 - Operational error,
   *      16 - Usage or syntax error,
   *      128 - Shared library error.
   */
  define boolean repair_ext2fs()``{

    if (last_check_return == 4 )
    {
	y2milestone("repair ext2/ext3 filesystem on partition %1",
	    check_partition);

	string command		= sformat("/sbin/fsck.ext2 -p -f %1 %2",
	    fsck_options, check_partition );
	boolean repairext2fs	= OSRExecute::CommandProgress (
	    .local.bash,command ,file_name);

	// TODO what is if fsck.ext2 requires y y y y for del indo XX ...
	if (OSRExecute::result == 0 ||
	    OSRExecute::result == 1 ||
	    OSRExecute::result == 2 )
	{
	    return true;
	}
    }
    y2milestone("ext2 filesystem not successful repaired.");
    return false;
  }

  /**
   * Repair reiserfs filesystem.
   */
  define boolean repair_reiserfs()``{

    string command		= "";
    integer repairreiserfs	= 0;

    if ( last_check_return == 1 )
    {
	command = sformat("/bin/echo Yes | /sbin/reiserfsck --fix-fixable %1",
	    check_partition);
	repairreiserfs = (integer) WFM::Execute (.local.bash, command);
    }
    else if ( last_check_return == 2 )
    {
	// popup headline
	if ( Popup::YesNoHeadline(_("Reiserfs Fatal Corruptions"),
	// popup text (yes/no)
_("
Checking the file system (reiserfs) reports
fatal corruptions. Only a rebuild of the reiserfs
file system tree (reiserfsck --rebuild-tree)
could solve the problem.

To rebuild your reiserfs tree,
press \"Yes\". Otherwise press
\"No\" and repair the file system manually.
")))
	{
	    command =
		sformat("/bin/echo Yes | /sbin/reiserfsck --rebuild-tree %1",
		check_partition);
	    repairreiserfs = (integer) WFM::Execute (.local.bash,command);
	}
	else
	{
	    return false;
	}
    }
    if (repairreiserfs == 0 ||
	repairreiserfs == 1 ||
	repairreiserfs == 2 )
    {
	return true;
    }
    return false;
  }

  /**
   * Repair xfs filesystem.
   */
  define boolean repair_xfs()``{

    string command	= sformat("/sbin/xfs_repair %1", check_partition);
    return OSRExecute::CommandProgress (.local.bash,command, file_name);
  }

  /**
   * Repair jfs filesystem.
   */
  define boolean repair_jfs()``{

    string command	= sformat("/sbin/fsck.jfs -p %1", check_partition);
    boolean repairjfs	= OSRExecute::CommandProgress (
	.local.bash, command, file_name);

    if (OSRExecute::result == 0 ||
	OSRExecute::result == 1 ||
	OSRExecute::result == 2 )
    {
	return true;
    }
    y2milestone("jfs filesystem repair was not successful");
    return false;
  }

}//EOF
