/* **********************************************************
 * Copyright 2005-2006 VMware, Inc.  All rights reserved. 
 * **********************************************************/

/*
 * utils.js --
 *
 *     Generic utilities for VCB wrapper scripts.
 */

/*
 * Major/minor version of the VCB wrapper scripts. The major version gets 
 * bumped up whenever there is an incompatible interface change.
 */
vcbMajor = 2;
vcbMinor = 2;

// Default configuration options (if not specified in any config file)
DEFAULT_BACKUPROOT = "C:\\mnt";
DEFAULT_PORT = "443"; // Default SOAP port
DEFAULT_SNAPSHOT_POLICY = "automatic";
DEFAULT_VM_LOOKUP_METHOD = "ipaddr";
DEFAULT_PREEXISTING_VCB_SNAPSHOT = "fail";
DEFAULT_PREEXISTING_MOUNTPOINT = "fail";
DEFAULT_MAX_RETRIES = 0;
DEFAULT_BACKOFF_TIME = 10;
DEFAULT_TRANSPORT_MODE = "san";
DEFAULT_LOGLEVEL = 3;

// Snapshot name as assigned by "vcbMounter"
VCB_SNAPSHOT_NAME="_VCB-BACKUP_";

// Generic VCB error codes
vcbErrOk = 0;

// These two errors correspond to return codes from our
// external programs
vcbErrCommandFailed = 1;
vcbErrInvalidCommandSyntax = 5;

vcbErrVersionMismatch = 10;
vcbErrProgramNotFound = 11;
vcbErrMisconfigured = 12;
vcbErrUnspecified = 13;
vcbErrUnknownBackupType = 14;
vcbErrInvalidPath = 15;
vcbErrVmNotFound = 16;
vcbErrSnapDeleteFailed = 17;

vcbErrUnknownVMNameError = 32;

// Use this as a basis for defining product-specific error codes.
vcbErrProductSpecific = 64;

/*
 * Backup Types
 */
vcbBackupTypeUnknown = "unknown";
vcbBackupTypeFile    = "file";   // mount guest FSes, walk files
vcbBackupTypeFullVM  = "fullvm"; // back up entire VM


/*
 * Global variables. - If your backup software of choice offers
 * other means of logging, override the default with something
 * else that provides a "Write" method.
 */
var StdOut = WScript.StdOut;
var StdErr = WScript.StdErr;
var StdIn  = WScript.StdIn;

/*
 * Snapshot policy variables. 
 */
var Util_CreateSnapshot = true;
var Util_DeleteSnapshot = true;

// Toggle printing of debugging messages
var Util_DebugOn = true;

// Postfix for the full VM Mount path
Util_FullVMPostfix = "fullVM";

// Path to the VCB installation directory
Util_VcbPath = "";


/*
 *-----------------------------------------------------------------------------
 *
 * Util_Debug --
 *    Print "msg" to StdOut if the global Variable "Util_DebugOn" is set 
 *    to "true".
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Prints debug message to StdOut.
 *
 *-----------------------------------------------------------------------------
 */
function Util_Debug(msg)
{
   if (Util_DebugOn) {
      StdOut.Write(msg);
   }
}



/*
 *-----------------------------------------------------------------------------
 *
 * Util_CheckVersion --
 *    Assure that the backup framework version is compatible with the version
 *    expected by the calling integration module. Major versions have to match
 *    exactly, while  the minor version of the backup framework must be equal
 *    or greater than what the integration module requires.
 *
 * Results:
 *    Throws a vcbErrVersionMismatch exception if there is a version mismatch.
 *
 * Side effects:
 *    None.
 *
 *-----------------------------------------------------------------------------
 */
function Util_CheckVersion(name, major, minor)
{
   if (major != vcbMajor) {
      throw Error (vcbErrVersionMismatch, name + " requires VCB Integration " +
		   "Framework, major version " + major + " but version " +
		   vcbMajor + "." + vcbMinor + " is installed.\n");
   }
   if (vcbMinor < minor) {
      throw Error (vcbErrVersionMismatch, name + " requires VCB Integration " +
		  "Framework Version " + major + "." + minor + " or newer, " +
		  "but version " + vcbMajor + "." + vcbMinor + 
		  " is installed.\n");
   }
}



/*
 *-----------------------------------------------------------------------------
 *
 * Util_ErrorMessage --
 *    Based on the Error object "err" and the Util_VMBackupInfo object "VMInfo"
 *    print a reasonable error message to StdErr. Handles all the vcbErr* 
 *    values defined in this file.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Prints error message to StdErr.
 *
 *-----------------------------------------------------------------------------
 */
function Util_ErrorMessage(err, VMInfo)
{
   var msg;

   switch (err.number & 0xffff) {
   case vcbErrCommandFailed:
      msg = "External command failed. See error above.";
      break;
   case vcbErrInvalidCommandSyntax:
      msg = "An external command was called with an invalid syntax. " +
	 "This is an internal error in the JScript backup framework.";
      break;
   case vcbErrVersionMismatch:
      msg = "Version mismatch: " + err.description;
      break;
   case vcbErrMisconfigured:
      msg = "The VCB framework is misconfigured: " + err.description;
      break;
   case vcbErrVmNotFound:
      msg = "The virtual machine identified by " + VMInfo.name +
	 " is unknown.";
      break;
   case vcbErrSnapDeleteFailed:
      msg = "Virtual machine had pre-existing backup snapshot that " +
	 "could not be deleted.";
      break;
   case vcbErrUnknownVMNameError:
      msg = err.description;
      break;
   case vcbErrProgramNotFound:
      msg = "Could not execute an external program. Error message: " +
	 err.description;
      break;
   case vcbErrUnknownBackupType:
      msg = "Backup type " + VMInfo.type + " is unknown.";
      break;
   case vcbErrInvalidPath:
      msg = "Invalid VCB path: " + err.description + " (Did you move " +
            "the VCB install directory to another location?)" ;
      break;
   case vcbErrUnspecified:
      msg = "Unspecified error: " + err.description;
      break;
   default:
      msg = "Unknown error: " + err.description;
      break;
   }
   StdErr.Write(msg + "\n");
   StdErr.Write("Exit Code: " + err.number + "\n");
}



/*
 *-----------------------------------------------------------------------------
 *
 * Util_InitializeConfigVars --
 *    Make sure all default config options are defined. Use default options
 *    (if applicable) for unspecified values.
 * 
 * Results:
 *    Throws a  "vcbErrMisconfigured" exception if a mandatory config option
 *    (for which no fallback defaults are provided) is unset. Throws 
 *    "vcbErrInvalidPath" if "vcbPath" points to a non-existing directory.
 *
 * Side effects:
 *    Changes unset config variables that have defaults to their defaults.
 *    Prints a warning to StdErr for each variable this is done for.
 *
 *-----------------------------------------------------------------------------
 */
function Util_InitializeConfigVars(vcbPath)
{
   var Fso;
   
   Fso = new ActiveXObject("Scripting.FileSystemObject");
   if (!Fso.FolderExists(vcbPath)) {
      throw Error(vcbErrInvalidPath, vcbPath);
   }
   Util_VcbPath = vcbPath;

   if ((typeof(vcbPath) == "undefined") || (null == vcbPath) ||
       ("" == "vcbPath")) {
      StdOut.Write("Warning: Path to installation directory is unset.\n");
   }

   if ((typeof(BACKUPROOT) == "undefined") ||
       (null == BACKUPROOT)) {
      BACKUPROOT = DEFAULT_BACKUPROOT;
      StdOut.Write("BACKUPROOT is not set, defaulting to " +
		   DEFAULT_BACKUPROOT + "\n");
   }

   if ((typeof(LOGLEVEL) == "undefined") || (LOGLEVEL == "") ||
       (null == LOGLEVEL)) {
      LOGLEVEL = DEFAULT_LOGLEVEL;
   } else if (isNaN(parseInt(LOGLEVEL)) || (LOGLEVEL < 0) || (LOGLEVEL > 6)) {
	 throw Error(vcbErrMisconfigured, "LOGLEVEL has to be set to a number " +
		     "in the range from 0 (no logging) - 6 (most logging).");
   }

   if ((typeof(USERNAME) == "undefined") ||
       (null == USERNAME) || ("" == USERNAME)) {
      throw Error(vcbErrMisconfigured, "USERNAME variable is not set in " +
		  "confguration file.\n" +
		  "Please edit ../config/config.js\n");
   }
   if ((typeof(PASSWORD) == "undefined") ||
       (null == PASSWORD)) {
      throw Error(vcbErrMisconfigured, "PASSWORD variable is not set in " +
		  "confguration file.\n" +
		  "Please edit ../config/config.js\n");
   }

   if ((typeof(HOST) == "undefined") ||
       (null == HOST) || ("" == HOST)) {
      throw Error(vcbErrMisconfigured, "HOST variable is not set in " +
		  "confguration file.\n" +
		  "Please edit ../config/config.js\n");
   }
   if ((typeof(PORT) == "undefined") ||
       (null == PORT) || ("" == PORT)) {
      PORT = DEFAULT_PORT;
      StdOut.Write("PORT is not set, defaulting to " +
		   DEFAULT_PORT + "\n");
   }

   if ((typeof(TRANSPORT_MODE) == "undefined") ||
       (null == TRANSPORT_MODE) || ("" == TRANSPORT_MODE)) {
      TRANSPORT_MODE = DEFAULT_TRANSPORT_MODE;
   } else if (!(("san" == TRANSPORT_MODE) || ("nbd" == TRANSPORT_MODE) ||
                ("nbdssl" == TRANSPORT_MODE)) ) {
      StdErr.Write("Illegal value for config option TRANSPORT_MODE: " +
		   TRANSPORT_MODE + ".\nValid options are \"san\", " +
		   "\"nbd\" and \"nbdssl\".\n");
      throw Error(vcbErrMisconfigured);
   }

   if ((typeof(SNAPSHOT_POLICY) == "undefined") ||
       (null == SNAPSHOT_POLICY) || ("" == SNAPSHOT_POLICY)) {
      SNAPSHOT_POLICY = DEFAULT_SNAPSHOT_POLICY;
   } else if ("automatic" == SNAPSHOT_POLICY) {
      Util_CreateSnapshot = true;
      Util_DeleteSnapshot = true;
   } else if ("manual" == SNAPSHOT_POLICY) {
      Util_CreateSnapshot = false;
      Util_DeleteSnapshot = false;
   } else if ("createonly" == SNAPSHOT_POLICY) {
      Util_CreateSnapshot = true;
      Util_DeleteSnapshot = true;
   } else if ("deleteonly" == SNAPSHOT_POLICY) {
      Util_CreateSnapshot = false;
      Util_DeleteSnapshot = true;
   } else {
      StdErr.Write("Illegal value for config option SNAPSHOT_POLICY: "+
		   SNAPSHOT_POLICY + ".\nValid options are \"automatic\", " +
		   "\"manual\", \"createonly\", \"deleteonly\"\n");
      throw Error(vcbErrMisconfigured);
   }

   if ((typeof(VM_LOOKUP_METHOD) == "undefined") ||
       (null == VM_LOOKUP_METHOD) || ("" == VM_LOOKUP_METHOD)) {
      VM_LOOKUP_METHOD = DEFAULT_VM_LOOKUP_METHOD;
   } else if (("ipaddr" != VM_LOOKUP_METHOD) &&
	      ("name" != VM_LOOKUP_METHOD)) {
      StdErr.Write("Illegal value for config option VM_LOOKUP_METHOD: " +
		   VM_LOOKUP_METHOD + ".\nValid options are \"ipaddr\" " +
		   "and \"name\"\n");
      throw Error(vcbErrMisconfigured);
   }

   if ((typeof(PREEXISTING_VCB_SNAPSHOT) == "undefined") ||
       (null == PREEXISTING_VCB_SNAPSHOT) ||
       ("" == PREEXISTING_VCB_SNAPSHOT)) {
      PREEXISTING_VCB_SNAPSHOT = DEFAULT_PREEXISTING_VCB_SNAPSHOT;
   } else if (("fail" != PREEXISTING_VCB_SNAPSHOT) &&
	      ("delete" != PREEXISTING_VCB_SNAPSHOT)) {
      StdErr.Write("Illegal value for config option " +
		   "PREEXISTING_VCB_SNAPSHOT: " + PREEXISTING_VCB_SNAPSHOT +
		   ".\nValid options are \"fail\" and " +
		   "\"delete\"\n");
      throw Error(vcbErrMisconfigured);
   }

   if ((typeof(PREEXISTING_MOUNTPOINT) == "undefined") ||
       (null == PREEXISTING_MOUNTPOINT) ||
       ("" == PREEXISTING_MOUNTPOINT)) {
      PREEXISTING_MOUNTPOINT = DEFAULT_PREEXISTING_MOUNTPOINT;
   } else if (("fail" != PREEXISTING_MOUNTPOINT) &&
	      ("delete" != PREEXISTING_MOUNTPOINT)) {
      StdErr.Write("Illegal value for config option " +
		   "PREEXISTING_MOUNTPOINT: " + PREEXISTING_MOUNTPOINT +
		   ".\nValid options are \"fail\" and " +
		   "\"delete\"\n");
      throw Error(vcbErrMisconfigured);
   }

   if ((typeof(MAX_RETRIES) == "undefined") ||
       (null == MAX_RETRIES) ||
       ("" == MAX_RETRIES)) {
      MAX_RETRIES = DEFAULT_MAX_RETRIES;
   }

   if ((typeof(BACKOFF_TIME) == "undefined") ||
       (null == BACKOFF_TIME) ||
       ("" == BACKOFF_TIME)) {
      BACKOFF_TIME = DEFAULT_BACKOFF_TIME;
   }

}



/*
 *-----------------------------------------------------------------------------
 *
 * UtilP_OutputAggregator_AppendOutput --
 *    "Private member function" of "UtilP_OutputAggregator". 
 *    Prints the strings passed in by "stdout" and "stderr" to StdOut and
 *    StdErr respectively. - Input arguments can be null, in which case nothing
 *    gets printed to the respective output channel.
 * 
 * Results:
 *    None.
 *
 * Side effects:
 *    Output to StdOut and/or StdErr
 *
 *-----------------------------------------------------------------------------
 */
function UtilP_OutputAggregator_AppendOutput(stdout, stderr)
{
   if (stdout != null) {
      StdOut.Write(stdout);
   }
   if (stderr != null) {
      StdErr.Write(stderr);
   }
}



/*
 *-----------------------------------------------------------------------------
 *
 * UtilP_OutputAggregator --
 *    "Private class" to provide an output aggregator suitable for implementing
 *    "Util_Run". - This output aggregator prints incoming data to StdErr and
 *    StdOut respectively.
 * 
 * Results:
 *    Constructor (an UtilP_OutputAggregator object).
 *
 * Side effects:
 *    None
 *
 *-----------------------------------------------------------------------------
 */
function UtilP_OutputAggregator()
{
   this.AddOutput = UtilP_OutputAggregator_AppendOutput;
   this.exitCode = -1;
}



/*
 *-----------------------------------------------------------------------------
 *
 * UtilP_CmdExec --
 *    Private helper function for Util_Run and Util_RunAndCapture. Runs the
 *    command specified in "cmd" and forwards the output and exit code to
 *    "aggregator". -- "aggregator" must have a member function called 
 *    "AddOutput" which takes two strings (stdout and stderr), to accept the
 *    output coming form "cmd" when it gets executed. Also, "aggregator" must
 *    have an integer property called "exitCode", which will be set to the
 *    exit code returned by "cmd".
 * 
 * Results:
 *    Might throw a vcbErrProgramNotFound exception. Results from the external
 *    command will be stored in "aggregator".
 *
 * Side effects:
 *    Executes external command.
 *
 *-----------------------------------------------------------------------------
 */
function UtilP_CmdExec(cmd, aggregator)
{
   try { 
      var shell = new ActiveXObject("WScript.Shell");
      var task;

      task = shell.Exec(cmd);
      while (0 == task.Status) {
	 aggregator.AddOutput(task.StdOut.ReadAll(), task.StdErr.ReadAll());
	 WScript.Sleep(100);
      }
      
      // Make sure to pick up the last pieces of log
      aggregator.AddOutput(task.StdOut.ReadAll(), task.StdErr.ReadAll());
      aggregator.exitCode = task.ExitCode;
      task = null;
   } catch (e) {
      StdErr.Write(e.description);
      throw Error(vcbErrProgramNotFound, cmd);
   }
}



/*
 *-----------------------------------------------------------------------------
 *
 * UtilP_CmdResult_AppendOutput --
 *    "Private" AppendOutput method of Util_CmdResult object. Used by 
 *    UtilP_CmdExec to pass in the output (stdout/stderr) from the external
 *    command being run.
 * 
 * Results:
 *    None.
 *
 * Side effects:
 *    None.
 *
 *-----------------------------------------------------------------------------
 */
function UtilP_CmdResult_AppendOutput(stdout, stderr)
{
   if (stdout != null) {
      this.stdOut += stdout;
   }
   
   if (stderr != null) {
      this.stdErr += stderr;
   }
}



/*
 *-----------------------------------------------------------------------------
 *
 * Util_CmdResult --
 *    Constructor for an "Util_CmdResult" object. 
 *    This is the data type that is being returned by "Util_RunAndCapture".
 *    
 *    This object represents the result of an external program that has been
 *    executed. It contains the programs output to stderr and stdout as well as
 *    the program's exit code.
 * 
 * Results:
 *    A new Util_CmdResult object.
 *
 * Side effects:
 *    None.
 *
 *-----------------------------------------------------------------------------
 */
function Util_CmdResult()
{
   this.AddOutput = UtilP_CmdResult_AppendOutput; // private
   this.exitCode = -1; // exit code returned by the external command
   this.stdOut = "";   // the output to stdout generated by the external command
   this.stdErr = "";   // the output to stderr generated by the external command
}



/*
 *-----------------------------------------------------------------------------
 *
 * Util_Run --
 *    Runs an external command line (as specified by "cmd") and returns the
 *    program's exit code.
 * 
 * Results:
 *    The exit code as returned by the external command. Throws an exception if
 *    the program could not be run (command not found, for example).
 *
 * Side effects:
 *    Whatever the external command invoked does.
 *
 *-----------------------------------------------------------------------------
 */
function Util_Run(cmd)
{
   var aggregator = new UtilP_OutputAggregator();

   UtilP_CmdExec(cmd, aggregator);
   return aggregator.exitCode;
}



/*
 *-----------------------------------------------------------------------------
 *
 * Util_RunAndCapture --
 *    Similar to "Util_Run": While "Util_Run" only returns a program's exit
 *    code, this function returns an "Util_CmdResult" object allowing the
 *    caller to examine the output of the external command invoked as well.
 *    This is the data type that is being returned by "Util_RunAndCapture".
 * 
 * Results:
 *    A new "Util_CmdResult" object representing the output generated by the
 *    external command. Throws an exception if the program could not be run 
 *    (command not found, for example).
 *
 * Side effects:
 *    Whatever the external command does.
 *
 *-----------------------------------------------------------------------------
 */
function Util_RunAndCapture(cmd)
{
   var aggregator = new Util_CmdResult();
   
   UtilP_CmdExec(cmd, aggregator);
   return aggregator;
}



/*
 *-----------------------------------------------------------------------------
 *
 * Util_Split --
 *    Breaks up the string "str" into an array of strings. "delim" specifies
 *    the character used as a split point. (This function is similar to
 *    the "split" function found in the popuar Unix tool "awk".
 * 
 *    This function is being used instead of string.split(), because
 *    string.split() seems to be having weird issues when the splitting
 *    character is a newline.
 *
 * Results:
 *    Returns an array of strings as a result of the "split" operation.
 *
 * Side effects:
 *    None.
 *
 *-----------------------------------------------------------------------------
 */
function Util_Split(str, delim)
{
   var result = new Array();
   var end, i;
   
   i = 0;
   while(0 < str.length) {
      end = str.search(delim);
      if (-1 == end) {
	 // Deal with last line not being terminated by "delim"
	 end = str.length;
      }
      result[i++] = str.substr(0, end);
      str = str.substr(end+1);
   }
   
   return result;
}



/*
 *-----------------------------------------------------------------------------
 *
 * Util_SplitLines --
 *    Breaks up the string "str" into multiple lines. Can deal with
 *    both DOS and Unix line ending conventions.
 *
 * Results:
 *    Returns an array of strings as a result of the "split" operation.
 *
 * Side effects:
 *    None.
 *
 *-----------------------------------------------------------------------------
 */
function Util_SplitLines(str)
{
   return Util_Split(str.replace(/\r/g, ""), "\n");
}





/*
 *-----------------------------------------------------------------------------
 *
 * Util_VMBackupInfo --
 *    Create a Util_VMBackupInfo object. This object describes all the vital
 *    properties of a VM backup: "vmName" specifies the user friendly VM name
 *    "vmPaths" specifies the array of paths within the VM mount directory to 
 *    be backed up (Example: C/Windows/System32). 
 *    "type" is the kind of backup that should be performed. Valid kinds
 *    are listed in this file: (see vcbBackupTypeXXX constants)
 *
 *    This constructor also obtains the internal VMId for the VM in question.
 * 
 * Results:
 *    Constructor for a "Util_VMBackupInfo" object. If there is no VM matching
 *    the user friendly name of the VM (as specified in "vmName"), or if there
 *    is more than one VM, the corresponding exceptions are thrown.
 *
 * Side effects:
 *    None.
 *
 *-----------------------------------------------------------------------------
 */
function Util_VMBackupInfo(vmName, type, vmPaths)
{
   var VM;

   this.name = vmName;               // User friendly VM name
   this.paths = vmPaths;             // Path to be backed up
   this.ssId = "dontcare";           // Snapshot ID - keep for backwards 
                                     // compatibility

   // Name of directory to be backed up on the proxy
   // Should not be used directly by consumers of the interface.
   // Consumers of the interface should use "Util_VMBackupInfo.GetBackupRoot
   this.proxyPath = BACKUPROOT + "\\" + vmName;
   this.GetBackupRoot = Util_VMBackupInfo.GetBackupRoot;

   /*
    * Store the backup type. Based on the backup type, we
    * use different ways to make the data available on the
    * proxy and to remove it again.
    *
    * Typically, one would do this with inheritance, but this
    * is not an option with JScript...
    */
   this.backupType = type;
   switch (type) {
   case vcbBackupTypeFullVM:
      this.proxyPath += "-" + Util_FullVMPostfix;
      break;
   case vcbBackupTypeFile:
      break;
   default:
      throw(vcbErrUnknownBackupType);
      break;
   }
}



/*
 *-----------------------------------------------------------------------------
 *
 * Util_GetVMBackupInfo --
 *    Create a Util_VMBackupInfo object by calling the Util_VMBackupInfo
 *    constructor. -- There seems to be a  bug with catching exceptions
 *    that get thrown in constructors, so this wrapper works around this by
 *    obtaining the internal VMId for the VM in question outside the
 *    constructor.
 * 
 * Results:
 *    A new Util_VMBackupInfo object.
 *
 * Side effects:
 *    None.
 *
 *-----------------------------------------------------------------------------
 */
function
Util_GetVMBackupInfo(vmName, type, vmPaths)
{
   return new Util_VMBackupInfo(vmName, type, vmPaths)
}



/*
 *-----------------------------------------------------------------------------
 *
 * Util_VMBackupInfo.GetBackupRoot --
 *    Return the directory containing all the files to be backed up in
 *    this job for this VM. - The backup root directory name depends on 
 *    the VM name and the flavor of backup to be performed.
 * 
 * Results:
 *    Path local on the proxy or throws an exception if an unknown 
 *    backup type is encountered.
 *
 * Side effects:
 *    None.
 *
 *-----------------------------------------------------------------------------
 */
function Util_VMBackupInfo.GetBackupRoot()
{
   var result;

   switch (this.backupType) {
   case vcbBackupTypeFile:
      result = this.proxyPath + "\\letters";
      break;
   case vcbBackupTypeFullVM:
      result = this.proxyPath;
      break;
   default:
      throw (vcbErrUnknownBackupType);
   }

   return result;
}



/*
 *-----------------------------------------------------------------------------
 *
 * Util_Prompt --
 *    Ask the user to answer "question" (by printing it ot stdout) and read
 *    the user's reply from stdin. - If "fallback" is non-null or a non-empty
 *    string, this will be the default value if the user just hits enter.
 *
 *    If no default is specified, the user is prompted until he enters a
 *    non-empty reply.
 * 
 * Results:
 *    Returns the user's response (or the fallback value).
 *
 * Side effects:
 *    I/O to stdin and stdout.
 *
 *-----------------------------------------------------------------------------
 */
function Util_Prompt(question, fallback)
{
  var result;

  if (null == fallback) {
    fallback = "";
  }

  do {
     if ("" != fallback) {
	StdOut.Write(question + "\n["+fallback+"] ? ");
     } else {
	StdOut.Write(question + "? ");
     }
     result = StdIn.ReadLine();
     if ("" == result) {
	result = fallback;
     }
  } while ("" == result);
  return result;
}



/*
 *-----------------------------------------------------------------------------
 *
 * Util_YesNo --
 *    Ask the user to answer a yes/no "question" (by printing it ot stdout).
 *    "fallback" is the default answer if the user just presses Enter.
 *    "fallback" can be "true", "false" or "null", in which case the user
 *    is prompted until he enters a valid answer.
 *
 *    Valid answers are any strings starting with either of the letters
 *    "yYnN".
 * 
 * Results:
 *    true or false, depending on user input and fallback value.
 *
 * Side effects:
 *    I/O to stdin and stdout.
 *
 *-----------------------------------------------------------------------------
 */
function Util_YesNo(question, fallback)
{
  var result;
  var done;

  do {
    StdOut.Write(question + "\n");
    switch (fallback) {
    case null:
       StdOut.Write("[y/n]? ");
       break;
    case true:
       StdOut.Write("[Y/n]? ");
       break;
    case false:
       StdOut.Write("[y/N]? ");
       break;
    default:
       StdErr.Write("Scripting error\n");
       break;
    }
    
    result = StdIn.ReadLine();
    if ((0 == result.length) && (null != fallback)) {
       done = true;
       result = fallback;
    } else {
       switch(result.charAt(0)) {
       case "y":
       case "Y":
	  result = true;
	  done = true;
	  break;
       case "n":
       case "N":
	  result = false;
	  done = true;
	  break;
       default:
	  done = false;
	  break;
       }
    }
  } while (!done);
  
  return result;
}



/*
 *-----------------------------------------------------------------------------
 *
 * Util_HostName --
 *    Obtain the host's name by calling the external "hostname" command.
 *
 * Results:
 *    Returns the result of "hostname" on success or an empty string on
 *    failure.
 *
 * Side effects:
 *    Runs external command. Prints warning to stderr if the host name could
 *    not be determined.
 *
 *-----------------------------------------------------------------------------
 */
function Util_HostName()
{
   var retval = "";

   try {
      var result = Util_RunAndCapture("hostname");
      if (0 == result.exitCode) {
	 retval = Util_SplitLines(result.stdOut)[0];
      }
   } catch (e) {
      StdErr.Write("Warning: Could not determine host name.\n" + 
		   e.description);
   }
   return retval;
}



/*
 *-----------------------------------------------------------------------------
 *
 * Util_PrintTimestamp --
 *    routine to print time stamp to StdOut.
 *
 * Results:
 *    writes time stamp to std out
 *
 * Side effects:
 *    None.
 *
 *-----------------------------------------------------------------------------
 */
function Util_PrintTimestamp()
{
    var ddate;
    var string;

    ddate = new Date();
    string = "";
    string += (ddate.getMonth()+1);
    string += ddate.getDate();
    string += ddate.getFullYear();
    string += ddate.getHours();
    string += ddate.getMinutes();
    string += ddate.getSeconds();
    StdOut.Write(string);
    return;
}
