/* * virnodesuspend.c: Support for suspending a node (host machine) * * Copyright (C) 2014 Red Hat, Inc. * Copyright (C) 2011 Srivatsa S. Bhat * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . * */ #include #include "virnodesuspend.h" #include "virsystemd.h" #include "vircommand.h" #include "virthread.h" #include "datatypes.h" #include "viralloc.h" #include "virfile.h" #include "virlog.h" #include "virerror.h" #define VIR_FROM_THIS VIR_FROM_NONE VIR_LOG_INIT("util.nodesuspend"); #define SUSPEND_DELAY 10 /* in seconds */ /* Give sufficient time for performing the suspend operation on the host */ #define MIN_TIME_REQ_FOR_SUSPEND 60 /* in seconds */ /* * Bitmask to hold the Power Management features supported by the host, * such as Suspend-to-RAM, Suspend-to-Disk, Hybrid-Suspend etc. */ static unsigned int nodeSuspendTargetMask; static bool nodeSuspendTargetMaskInit; static virMutex virNodeSuspendMutex = VIR_MUTEX_INITIALIZER; static bool aboutToSuspend; /** * virNodeSuspendSetNodeWakeup: * @alarmTime: time in seconds from now, at which the RTC alarm has to be set. * * Set up the RTC alarm to the specified time. * Return 0 on success, -1 on failure. */ static int virNodeSuspendSetNodeWakeup(unsigned long long alarmTime) { g_autoptr(virCommand) setAlarmCmd = NULL; if (alarmTime < MIN_TIME_REQ_FOR_SUSPEND) { virReportError(VIR_ERR_INVALID_ARG, _("Suspend duration is too short, must be at least %1$u seconds"), MIN_TIME_REQ_FOR_SUSPEND); return -1; } setAlarmCmd = virCommandNewArgList("rtcwake", "-m", "no", "-s", NULL); virCommandAddArgFormat(setAlarmCmd, "%lld", alarmTime); return virCommandRun(setAlarmCmd, NULL); } /** * virNodeSuspendHelper: * @cmdString: pointer to the command string this thread has to execute. * * Actually perform the suspend operation by invoking the command. * Give a short delay before executing the command so as to give a chance * to virNodeSuspend() to return the status to the caller. * If we don't give this delay, that function will not be able to return * the status, since the suspend operation would have begun and hence no * data can be sent through the connection to the caller. However, with * this delay added, the status return is best-effort only. */ static void virNodeSuspendHelper(void *cmdString) { g_autoptr(virCommand) suspendCmd = virCommandNew((const char *)cmdString); /* * Delay for sometime so that the function virNodeSuspend() * can return the status to the caller. */ sleep(SUSPEND_DELAY); if (virCommandRun(suspendCmd, NULL) < 0) VIR_WARN("Failed to suspend the host"); /* * Now that we have resumed from suspend or the suspend failed, * reset 'aboutToSuspend' flag. */ VIR_WITH_MUTEX_LOCK_GUARD(&virNodeSuspendMutex) { aboutToSuspend = false; } } /** * virNodeSuspend: * @conn: pointer to the hypervisor connection * @target: the state to which the host must be suspended to - * VIR_NODE_SUSPEND_TARGET_MEM (Suspend-to-RAM), * VIR_NODE_SUSPEND_TARGET_DISK (Suspend-to-Disk), * VIR_NODE_SUSPEND_TARGET_HYBRID (Hybrid-Suspend) * @duration: the time duration in seconds, for which the host must be * suspended * @flags: any flag values that might need to be passed; currently unused. * * Suspend the node (host machine) for the given duration of time in the * specified state (such as Mem, Disk or Hybrid-Suspend). Attempt to resume * the node after the time duration is complete. * * First, an RTC alarm is set appropriately to wake up the node from its * sleep state. Then the actual node suspend operation is carried out * asynchronously in another thread, after a short time delay so as to * give enough time for this function to return status to its caller * through the connection. However this returning of status is best-effort * only, and should generally happen due to the short delay but might be * missed if the machine suspends first. * * Returns 0 in case the node is going to be suspended after a short delay, * -1 if suspending the node is not supported, or if a previous suspend * operation is still in progress. */ int virNodeSuspend(unsigned int target, unsigned long long duration, unsigned int flags) { static virThread thread; const char *cmdString = NULL; unsigned int supported; VIR_LOCK_GUARD lock = { NULL }; virCheckFlags(0, -1); if (virNodeSuspendGetTargetMask(&supported) < 0) return -1; /* * Ensure that we are the only ones trying to suspend. * Fail if somebody has already initiated a suspend. */ lock = virLockGuardLock(&virNodeSuspendMutex); if (aboutToSuspend) { /* A suspend operation is already in progress */ virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("Suspend operation already in progress")); return -1; } /* Check if the host supports the requested suspend target */ switch (target) { case VIR_NODE_SUSPEND_TARGET_MEM: if (!(supported & (1 << VIR_NODE_SUSPEND_TARGET_MEM))) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Suspend-to-RAM")); return -1; } cmdString = "pm-suspend"; break; case VIR_NODE_SUSPEND_TARGET_DISK: if (!(supported & (1 << VIR_NODE_SUSPEND_TARGET_DISK))) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Suspend-to-Disk")); return -1; } cmdString = "pm-hibernate"; break; case VIR_NODE_SUSPEND_TARGET_HYBRID: if (!(supported & (1 << VIR_NODE_SUSPEND_TARGET_HYBRID))) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Hybrid-Suspend")); return -1; } cmdString = "pm-suspend-hybrid"; break; default: virReportError(VIR_ERR_INVALID_ARG, "%s", _("Invalid suspend target")); return -1; } /* Just set the RTC alarm. Don't suspend yet. */ if (virNodeSuspendSetNodeWakeup(duration) < 0) return -1; if (virThreadCreateFull(&thread, false, virNodeSuspendHelper, "node-suspend", false, (void *)cmdString) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Failed to create thread to suspend the host")); return -1; } aboutToSuspend = true; return 0; } #ifdef WITH_PM_UTILS static int virNodeSuspendSupportsTargetPMUtils(unsigned int target, bool *supported) { g_autoptr(virCommand) cmd = NULL; g_autofree char *binary = NULL; int status; *supported = false; binary = virFindFileInPath("pm-is-supported"); if (!binary) return -2; switch (target) { case VIR_NODE_SUSPEND_TARGET_MEM: cmd = virCommandNewArgList(binary, "--suspend", NULL); break; case VIR_NODE_SUSPEND_TARGET_DISK: cmd = virCommandNewArgList(binary, "--hibernate", NULL); break; case VIR_NODE_SUSPEND_TARGET_HYBRID: cmd = virCommandNewArgList(binary, "--suspend-hybrid", NULL); break; default: return -1; } if (virCommandRun(cmd, &status) < 0) return -1; /* * Check return code of command == 0 for success * (i.e., the PM capability is supported) */ *supported = (status == 0); return 0; } #else /* ! WITH_PM_UTILS */ static int virNodeSuspendSupportsTargetPMUtils(unsigned int target G_GNUC_UNUSED, bool *supported G_GNUC_UNUSED) { return -2; } #endif /* ! WITH_PM_UTILS */ static int virNodeSuspendSupportsTargetSystemd(unsigned int target, bool *supported) { int ret = -1; *supported = false; switch (target) { case VIR_NODE_SUSPEND_TARGET_MEM: ret = virSystemdCanSuspend(supported); break; case VIR_NODE_SUSPEND_TARGET_DISK: ret = virSystemdCanHibernate(supported); break; case VIR_NODE_SUSPEND_TARGET_HYBRID: ret = virSystemdCanHybridSleep(supported); break; default: return ret; } return ret; } /** * virNodeSuspendSupportsTarget: * @target: The power management target to check whether it is supported * by the host. Values could be: * VIR_NODE_SUSPEND_TARGET_MEM * VIR_NODE_SUSPEND_TARGET_DISK * VIR_NODE_SUSPEND_TARGET_HYBRID * @supported: set to true if supported, false otherwise * * Run the script 'pm-is-supported' (from the pm-utils package) * to find out if @target is supported by the host. * * Returns 0 if the query was successful, -1 on failure. */ static int virNodeSuspendSupportsTarget(unsigned int target, bool *supported) { int ret; ret = virNodeSuspendSupportsTargetSystemd(target, supported); /* If just unavailable, try other options */ if (ret == -2) ret = virNodeSuspendSupportsTargetPMUtils(target, supported); /* If still unavailable, then report unsupported */ if (ret == -2) { *supported = false; ret = 0; } return ret; } /** * virNodeSuspendGetTargetMask: * * Get the Power Management Capabilities that the host system supports, * such as Suspend-to-RAM (S3), Suspend-to-Disk (S4) and Hybrid-Suspend * (a combination of S3 and S4). * * @bitmask: Pointer to the bitmask which will be set appropriately to * indicate all the supported host power management targets. * * Returns 0 if the query was successful, -1 on failure. */ int virNodeSuspendGetTargetMask(unsigned int *bitmask) { VIR_LOCK_GUARD lock = virLockGuardLock(&virNodeSuspendMutex); *bitmask = 0; /* Get the power management capabilities supported by the host */ if (!nodeSuspendTargetMaskInit) { bool supported; nodeSuspendTargetMask = 0; /* Check support for Suspend-to-RAM (S3) */ if (virNodeSuspendSupportsTarget(VIR_NODE_SUSPEND_TARGET_MEM, &supported) < 0) return -1; if (supported) nodeSuspendTargetMask |= (1 << VIR_NODE_SUSPEND_TARGET_MEM); /* Check support for Suspend-to-Disk (S4) */ if (virNodeSuspendSupportsTarget(VIR_NODE_SUSPEND_TARGET_DISK, &supported) < 0) return -1; if (supported) nodeSuspendTargetMask |= (1 << VIR_NODE_SUSPEND_TARGET_DISK); /* Check support for Hybrid-Suspend */ if (virNodeSuspendSupportsTarget(VIR_NODE_SUSPEND_TARGET_HYBRID, &supported) < 0) return -1; if (supported) nodeSuspendTargetMask |= (1 << VIR_NODE_SUSPEND_TARGET_HYBRID); nodeSuspendTargetMaskInit = true; } *bitmask = nodeSuspendTargetMask; return 0; }