+ hyper.PowercycleNode(hvparams=hvparams)
+
+
+def _VerifyRestrictedCmdName(cmd):
+ """Verifies a restricted command name.
+
+ @type cmd: string
+ @param cmd: Command name
+ @rtype: tuple; (boolean, string or None)
+ @return: The tuple's first element is the status; if C{False}, the second
+ element is an error message string, otherwise it's C{None}
+
+ """
+ if not cmd.strip():
+ return (False, "Missing command name")
+
+ if os.path.basename(cmd) != cmd:
+ return (False, "Invalid command name")
+
+ if not constants.EXT_PLUGIN_MASK.match(cmd):
+ return (False, "Command name contains forbidden characters")
+
+ return (True, None)
+
+
+def _CommonRestrictedCmdCheck(path, owner):
+ """Common checks for restricted command file system directories and files.
+
+ @type path: string
+ @param path: Path to check
+ @param owner: C{None} or tuple containing UID and GID
+ @rtype: tuple; (boolean, string or C{os.stat} result)
+ @return: The tuple's first element is the status; if C{False}, the second
+ element is an error message string, otherwise it's the result of C{os.stat}
+
+ """
+ if owner is None:
+ # Default to root as owner
+ owner = (0, 0)
+
+ try:
+ st = os.stat(path)
+ except EnvironmentError, err:
+ return (False, "Can't stat(2) '%s': %s" % (path, err))
+
+ if stat.S_IMODE(st.st_mode) & (~_RCMD_MAX_MODE):
+ return (False, "Permissions on '%s' are too permissive" % path)
+
+ if (st.st_uid, st.st_gid) != owner:
+ (owner_uid, owner_gid) = owner
+ return (False, "'%s' is not owned by %s:%s" % (path, owner_uid, owner_gid))
+
+ return (True, st)
+
+
+def _VerifyRestrictedCmdDirectory(path, _owner=None):
+ """Verifies restricted command directory.
+
+ @type path: string
+ @param path: Path to check
+ @rtype: tuple; (boolean, string or None)
+ @return: The tuple's first element is the status; if C{False}, the second
+ element is an error message string, otherwise it's C{None}
+
+ """
+ (status, value) = _CommonRestrictedCmdCheck(path, _owner)
+
+ if not status:
+ return (False, value)
+
+ if not stat.S_ISDIR(value.st_mode):
+ return (False, "Path '%s' is not a directory" % path)
+
+ return (True, None)
+
+
+def _VerifyRestrictedCmd(path, cmd, _owner=None):
+ """Verifies a whole restricted command and returns its executable filename.
+
+ @type path: string
+ @param path: Directory containing restricted commands
+ @type cmd: string
+ @param cmd: Command name
+ @rtype: tuple; (boolean, string)
+ @return: The tuple's first element is the status; if C{False}, the second
+ element is an error message string, otherwise the second element is the
+ absolute path to the executable
+
+ """
+ executable = utils.PathJoin(path, cmd)
+
+ (status, msg) = _CommonRestrictedCmdCheck(executable, _owner)
+
+ if not status:
+ return (False, msg)
+
+ if not utils.IsExecutable(executable):
+ return (False, "access(2) thinks '%s' can't be executed" % executable)
+
+ return (True, executable)
+
+
+def _PrepareRestrictedCmd(path, cmd,
+ _verify_dir=_VerifyRestrictedCmdDirectory,
+ _verify_name=_VerifyRestrictedCmdName,
+ _verify_cmd=_VerifyRestrictedCmd):
+ """Performs a number of tests on a restricted command.
+
+ @type path: string
+ @param path: Directory containing restricted commands
+ @type cmd: string
+ @param cmd: Command name
+ @return: Same as L{_VerifyRestrictedCmd}
+
+ """
+ # Verify the directory first
+ (status, msg) = _verify_dir(path)
+ if status:
+ # Check command if everything was alright
+ (status, msg) = _verify_name(cmd)
+
+ if not status:
+ return (False, msg)
+
+ # Check actual executable
+ return _verify_cmd(path, cmd)
+
+
+def RunRestrictedCmd(cmd,
+ _lock_timeout=_RCMD_LOCK_TIMEOUT,
+ _lock_file=pathutils.RESTRICTED_COMMANDS_LOCK_FILE,
+ _path=pathutils.RESTRICTED_COMMANDS_DIR,
+ _sleep_fn=time.sleep,
+ _prepare_fn=_PrepareRestrictedCmd,
+ _runcmd_fn=utils.RunCmd,
+ _enabled=constants.ENABLE_RESTRICTED_COMMANDS):
+ """Executes a restricted command after performing strict tests.
+
+ @type cmd: string
+ @param cmd: Command name
+ @rtype: string
+ @return: Command output
+ @raise RPCFail: In case of an error
+
+ """
+ logging.info("Preparing to run restricted command '%s'", cmd)
+
+ if not _enabled:
+ _Fail("Restricted commands disabled at configure time")
+
+ lock = None
+ try:
+ cmdresult = None
+ try:
+ lock = utils.FileLock.Open(_lock_file)
+ lock.Exclusive(blocking=True, timeout=_lock_timeout)
+
+ (status, value) = _prepare_fn(_path, cmd)
+
+ if status:
+ cmdresult = _runcmd_fn([value], env={}, reset_env=True,
+ postfork_fn=lambda _: lock.Unlock())
+ else:
+ logging.error(value)
+ except Exception: # pylint: disable=W0703
+ # Keep original error in log
+ logging.exception("Caught exception")
+
+ if cmdresult is None:
+ logging.info("Sleeping for %0.1f seconds before returning",
+ _RCMD_INVALID_DELAY)
+ _sleep_fn(_RCMD_INVALID_DELAY)
+
+ # Do not include original error message in returned error
+ _Fail("Executing command '%s' failed" % cmd)
+ elif cmdresult.failed or cmdresult.fail_reason:
+ _Fail("Restricted command '%s' failed: %s; output: %s",
+ cmd, cmdresult.fail_reason, cmdresult.output)
+ else:
+ return cmdresult.output
+ finally:
+ if lock is not None:
+ # Release lock at last
+ lock.Close()
+ lock = None
+
+
+def SetWatcherPause(until, _filename=pathutils.WATCHER_PAUSEFILE):
+ """Creates or removes the watcher pause file.
+
+ @type until: None or number
+ @param until: Unix timestamp saying until when the watcher shouldn't run
+
+ """
+ if until is None:
+ logging.info("Received request to no longer pause watcher")
+ utils.RemoveFile(_filename)
+ else:
+ logging.info("Received request to pause watcher until %s", until)
+
+ if not ht.TNumber(until):
+ _Fail("Duration must be numeric")
+
+ utils.WriteFile(_filename, data="%d\n" % (until, ), mode=0644)