/**
 * File:
 *   Nfs.ycp
 *
 * Module:
 *   Configuration of nfs
 *
 * Summary:
 *   NFS client configuration data, I/O functions.
 *
 * Authors:
 *   Jan Holesovsky <kendy@suse.cz>
 *   Dan Vesely <dan@suse.cz>
 *   Martin Vidner <mvidner@suse.cz>
 *
 * $Id: Nfs.ycp 18949 2004-08-30 22:14:23Z nashif $
 */

{
    module "Nfs";

    textdomain "nfs";

    import "Report";
    import "Service";
    import "Summary";
    import "SuSEFirewall";
    import "Progress";
    import "Wizard";

    include "nfs/routines.ycp";


    /* default value of settings modified */
    global boolean modified = false;


    /**
     * Function sets internal variable, which indicates, that any
     * settings were modified, to "true"
     */
    global define void SetModified () {
        modified = true;
    }

    /**
     * Functions which returns if the settings were modified
     * @return boolean  settings were modified
     */
    global define boolean GetModified () {
        return modified;
    }

    /**
     * Required packages
     */
    global list required_packages = ["portmap"];

    /**
     * eg.: [ $["spec": "moon:/cheese", file: "/mooncheese", "mntopts": "defaults"], ...]
     */
    global list<map<string,any> > nfs_entries = [];

    /**
     * Read only, intended for checking mount-point uniqueness.
     */
    global list<map> non_nfs_entries = [];

    // list of created directories
    list<string> created_dirs = [];

    /**
     * Set module data
     * @param settings module settings
     * @return void
     */
    global define void Set(list<map> settings) ``{
	nfs_entries = maplist(map entry, settings, ``{
	    return($[
		     "spec":entry["server_path"]:"",
		     "file":entry["mount_point"]:"",
		     "mntops":entry["nfs_options"]:""
		   ]);
	});
	return;
    }

    /**
     * Get all NFS configuration from a map.
     * When called by nfs_auto (preparing autoinstallation data)
     * the map may be empty.
     * @param settings	a map with a single key: nfs_entries
     * @return	success
     */
    global define boolean Import (list<map> settings) ``{

	boolean missing = false;
	foreach(map s,settings,``{
	    foreach (string k, ["server_path", "mount_point", "nfs_options"], ``{
		if (! haskey (s, k))
		{
		    y2error ("Missing at Import: '%1'.", k);
		    missing = true;
		}
	    });
	});
	if (missing)
	{
	    return false;
	}

	Set(settings);
	return true;
    }

    /**
     * Dump the NFS settings to a map, for autoinstallation use.
     * @return a list of nfs entries.
     */
    global define list Export () ``{
	list entries = maplist(map entry, nfs_entries, ``{
		return($[
			"server_path":entry["spec"]:"",
			"mount_point":entry["file"]:"",
			"nfs_options":entry["mntops"]:""
			]);
	});
	return  entries;
    }

    /* ------------------------------------------------------------ */
    // Space escaping.
    // This should be done by the agent, but any-agent sucks.

    /**
     * Escape spaces " " -> "\\040".
     * @param s a string or nil
     * @return escaped string or nil
     */
    define string EscapeSpaces1 (string s) ``{
	return s == nil ? nil : mergestring (splitstring (s, " "), "\\040");
    }

    /**
     * Escape spaces " " -> "\\040" in all values of all entries
     * @param entries a list of maps, such as nfs_entries
     * @return escaped entries
     */
    define list<map<string,any> > EscapeSpaces (list<map<string,any> > entries) ``{
	return maplist (map<string,any> entry, entries, ``(
			    mapmap (string key, any value, entry, ``(
					$[ key :
					  (is (value, string)?
					  EscapeSpaces1 ((string) value): value)]
					))
			    ));
    }

    /**
     * (like awk gsub, but return the result, not number of replacements)
     * replaces from back!
     */
    define string gsub (string regex, string replacement, string s) ``{
	string temp = nil;
	while (true)
	{
	    // argh, regexpsub logs an error if it cannot sub
	    if (! regexpmatch (s, ".*" + regex + ".*"))
	    {
		break;
	    }
	    temp = regexpsub (s, "(.*)"+regex+"(.*)", "\\1"+replacement+"\\2");
	    if (temp == nil)
	    {
		break;
	    }
	    s = temp;
	}
	return s;
    }

    /**
     * Un-escape spaces "\\040" -> " "
     * @param s string or nil
     * @return escaped string or nil
     */
    define string UnescapeSpaces1 (string s) ``{
	// escaped space, \040, is /\\040/
	// which is "\\\\040"
	return s == nil ? nil : gsub ("\\\\040", " ", s);
    }

    /**
     * Un-escape spaces "\\040" -> " " in all values of all entries
     * @param entries a list of maps, such as nfs_entries
     * @return escaped entries
     */
    define list<map<string,any> > UnescapeSpaces (list<map<string,any> > entries) ``{
	return maplist (map<string,any> entry, entries, ``(
			    mapmap (string key, any value, entry, ``(
					$[ key:
					  (is (value, string)?
					  UnescapeSpaces1 ((string) value): value)]
					))
			    ));
    }

    /* ------------------------------------------------------------ */

    /**
     * Reads NFS settings from the SCR (.etc.fstab)
     * @return true on success
     */
    global define boolean Read () ``{
	list<map<string,any> > fstab = (list<map<string,any> >) SCR::Read (.etc.fstab);
	fstab = UnescapeSpaces (fstab);

	// For simplicity, this leaves also the unused fileds in the maps.
	nfs_entries = filter (map<string,any> entry, fstab, ``{
	    return (entry["vfstype"]:"" == "nfs");
	});
	non_nfs_entries = filter (map entry, fstab, ``{
	    return (entry["vfstype"]:"" != "nfs");
	});

	SuSEFirewall::Read ();

	return true;
    }

    /**
     * Writes the NFS client configuration without
     * starting/stopping the service.
     * Autoinstallation uses this and then calls SuSEconfig only once
     * and starts the services together.
     * (No parameters because it is too short to abort)
     * @return true on success
     */
    global define boolean WriteOnly () ``{
	// Merge with non-nfs entries from fstab:
	list<map<string,any> > fstab = (list<map<string,any> >) SCR::Read (.etc.fstab);
	// unescape deferred for optimization
	fstab = filter (map<string,any> entry, fstab, ``{
	    return (entry["vfstype"]:"" != "nfs");
	});
	fstab = UnescapeSpaces (fstab);
	foreach (map<string,any> entry, nfs_entries, ``{
	    fstab = add (fstab, (map<string,any>)union (entry, $["vfstype": "nfs", "freq": 0, "passno": 0]));

	    // create mount points
	    string file = entry["file"]:"";
	    if (!(boolean) SCR::Execute (.target.mkdir, file))
	    {
		// error popup message
		Report::Warning (sformat (_("Unable to create directory '%1'."), file));
	    }
	});


	SCR::Execute (.target.bash, "/bin/cp $ORIG $BACKUP", $["ORIG" : "/etc/fstab", "BACKUP" : "/etc/fstab.YaST2.save"]);

	fstab = EscapeSpaces (fstab);
	if (!SCR::Write(.etc.fstab, fstab))
	{
	    // error popup message
	    Report::Error (_("Unable to write to /etc/fstab.
No  changes will be made to the
the NFS client configuration.\n"));
	    return false;
	}

/*	if (size (nfs_entries) == 0)
	{
	    // Service::Adjust ("nfs", "disable"); // what if autofs needs it?
	}*/

	if (size (nfs_entries) != 0)
	{
	    Service::Enable("portmap");
	    Service::Enable("nfs");
	    // #36737: it just runs sm_notify at boot time
	    // replaces rpc.statd
	    Service::Enable("nfsboot");
	}

	SuSEFirewall::WriteOnly ();

	return true;
    }

    /**
     * Writes the NFS client configuration and starts/stops the service.
     * (No parameters because it is too short to abort)
     * @return true on success
     */
    global define boolean Write () ``{
	if (WriteOnly ())
	{
	    // dialog label
	    Progress::New (_("Writing NFS Configuration..."), " ", 2, [
	    // progress stage label
	    _("Stop services"),
	    // progress stage label
	    _("Start services"),
	   ], [
	    // progress step label
	    _("Stopping services..."),
	    // progress step label
	    _("Starting services..."),
	    // final progress step label
	    _("Finished") ],
	    "" );

	    // help text
	    Wizard::RestoreHelp(_("Writing NFS client settings"));

	    Progress::NextStage ();

	    Service::Stop("nfs");

	    Progress::NextStage ();
	    if (size (nfs_entries) > 0)
	    {
		if (Service::Status ("portmap") != 0)
		{
		    // portmap must not be started if it is running already (see bug # 9999)
		    Service::Start("portmap");
		}

		Service::Start("nfs");
		if (Service::Status("nfs") != 0)
		{
		    // error popup message
		    Report::Error (_("Unable to mount the NFS entries from /etc/fstab."));
		    return false;
		}
	    }

	    SuSEFirewall::ActivateConfiguration ();

	    Progress::NextStage ();
	    return true;
	}
	else
	{
	    return false;
	}
    }

    /**
     * Summary()
     * @return Html formatted configuration summary
     */
    global define string Summary () ``{
	string summary = "";
	string nc = Summary::NotConfigured ();
	// summary header
	summary = Summary::AddHeader(summary, _("NFS entries"));
	integer entries = size(nfs_entries);
	y2milestone("Entries: %1", nfs_entries);
	string configured = sformat("%1 entries configured.", entries);
	summary = Summary::AddLine(summary, (entries>0) ? configured :  nc);
	return summary;
    }


    /**
     * Mount NFS directory
     * @param server remote server name
     * @param share name of the exported directory
     * @param mpoint mount point (can be empty or nil, in this case it will be mounted in temporary directory)
     * @param options mount options - e.g. "ro,hard,intr", see man nfs
     * @return string directory where volume was mounted or nil if mount failed
     */

    global define string Mount(string server, string share, string mpoint, string options) ``{

	if (size(server) == 0 || size(share) == 0)
	{
	    return nil;
	}

	// check if options are valid
	if (size(options) > 0)
	{
	    if (check_options(options) != "")
	    {
		y2warning("invalid mount options: %1", options);
		return nil;
	    }
	}

	// mount to temporary directory if mpoint is nil
	if (mpoint == nil)
	{
	    string tmpdir = (string) SCR::Read(.target.tmpdir);

	    if (tmpdir == nil || tmpdir == "")
	    {
		y2security("Warning: using /tmp directory!");
		tmpdir = "/tmp";
	    }

	    mpoint = tmpdir + "/nfs" + sformat("%1", size(created_dirs));	// use num to allow parallel mounts
	}

	// check mount point
	if (CheckPath(mpoint) == false)
	{
	    // mount point is not valid
	    y2warning("invalid mount point: %1", mpoint);
	    return nil;
	}

	// check whether portmapper is installed
	if (IsPortmapInstalled() == false)
	{
	    y2warning("portmap is not installed");
	    return nil;
	}

	// start portmapper if it isn't running
	if (Service::Status("portmap") != 0)
	{
	    if (Service::Start("portmap") == false)
	    {
		y2warning("portmap cannot be started");
		return nil;
	    }
	}

	// create mount point if it doesn't exist
	if (SCR::Read(.target.dir, mpoint) == nil)
	{
	    if (!(boolean) SCR::Execute(.target.mkdir, mpoint))
	    {
		y2warning("cannot create mount point %1", mpoint);
		return nil;
	    }

	    // remember name of created directory
	    created_dirs = add(created_dirs, mpoint);
	}

	// build mount command
	string command = sformat("/bin/mount %1 %2:%3 %4", (size(options) > 0) ? "-o " + options : "", server, share, mpoint);

	// execute mount command
	return (SCR::Execute(.target.bash, command) == 0) ? mpoint : nil;
    }

    /**
     * Unmount NFS directory from the system
     * @param mpoint NFS mount point to unmount
     * @return boolean true on success
     */
    global define boolean Unmount(string mpoint) ``{
	if (size(mpoint) == 0)
	{
	    return false;
	}

	// unmount directory if it's NFS mountpoint
	list<map<string,any> > mounts = (list<map<string,any> >) SCR::Read(.proc.mounts);
	boolean found = false;

	foreach(map<string, any> m, mounts,
	    ``{
		string type = (string) (m["vfstype"]:nil);
		string file = (string) (m["file"]:nil);

		if (type == "nfs" && file == mpoint)
		{
		    found = true;
		}
	    }
	);

	if (found)
	{
	    string command = sformat("/bin/umount %1", mpoint);

	    if (SCR::Execute(.target.bash, command) != 0)
	    {
		return false;
	    }
	}
	else
	{
	    y2warning("%1 is not NFS mount point", mpoint);
	    return false;
	}

	// if the directory was created by Mount call and it is empty then remove it
	if (contains(created_dirs, mpoint) && SCR::Read(.target.dir, mpoint) == [])
	{
	    string command = sformat("/bin/rmdir %1", mpoint);

	    if (SCR::Execute(.target.bash, command) != 0)
	    {
		return false;
	    }

	    // remove directory from list
	    created_dirs = filter(string d, created_dirs, ``(d != mpoint));
	}

	return true;
    }

	/**
	* Return required packages for auto-installation
    * @return map of packages to be installed and to be removed
    */
    global define map AutoPackages() ``{
	     return ($["install": required_packages, "remove": []]);

    }




}
