1 /*
2  * virnodesuspend.c: Support for suspending a node (host machine)
3  *
4  * Copyright (C) 2014 Red Hat, Inc.
5  * Copyright (C) 2011 Srivatsa S. Bhat <srivatsa.bhat@linux.vnet.ibm.com>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library.  If not, see
19  * <http://www.gnu.org/licenses/>.
20  *
21  */
22 
23 #include <config.h>
24 #include "virnodesuspend.h"
25 
26 #include "virsystemd.h"
27 #include "vircommand.h"
28 #include "virthread.h"
29 #include "datatypes.h"
30 
31 #include "viralloc.h"
32 #include "virfile.h"
33 #include "virlog.h"
34 #include "virerror.h"
35 
36 #define VIR_FROM_THIS VIR_FROM_NONE
37 
38 VIR_LOG_INIT("util.nodesuspend");
39 
40 #define SUSPEND_DELAY 10 /* in seconds */
41 
42 /* Give sufficient time for performing the suspend operation on the host */
43 #define MIN_TIME_REQ_FOR_SUSPEND 60 /* in seconds */
44 
45 /*
46  * Bitmask to hold the Power Management features supported by the host,
47  * such as Suspend-to-RAM, Suspend-to-Disk, Hybrid-Suspend etc.
48  */
49 static unsigned int nodeSuspendTargetMask;
50 static bool nodeSuspendTargetMaskInit;
51 
52 static virMutex virNodeSuspendMutex = VIR_MUTEX_INITIALIZER;
53 
54 static bool aboutToSuspend;
55 
virNodeSuspendLock(void)56 static void virNodeSuspendLock(void)
57 {
58     virMutexLock(&virNodeSuspendMutex);
59 }
60 
virNodeSuspendUnlock(void)61 static void virNodeSuspendUnlock(void)
62 {
63     virMutexUnlock(&virNodeSuspendMutex);
64 }
65 
66 
67 /**
68  * virNodeSuspendSetNodeWakeup:
69  * @alarmTime: time in seconds from now, at which the RTC alarm has to be set.
70  *
71  * Set up the RTC alarm to the specified time.
72  * Return 0 on success, -1 on failure.
73  */
virNodeSuspendSetNodeWakeup(unsigned long long alarmTime)74 static int virNodeSuspendSetNodeWakeup(unsigned long long alarmTime)
75 {
76     virCommand *setAlarmCmd;
77     int ret = -1;
78 
79     if (alarmTime < MIN_TIME_REQ_FOR_SUSPEND) {
80         virReportError(VIR_ERR_INVALID_ARG,
81                        _("Suspend duration is too short, must be at least %u seconds"),
82                        MIN_TIME_REQ_FOR_SUSPEND);
83         return -1;
84     }
85 
86     setAlarmCmd = virCommandNewArgList("rtcwake", "-m", "no", "-s", NULL);
87     virCommandAddArgFormat(setAlarmCmd, "%lld", alarmTime);
88 
89     if (virCommandRun(setAlarmCmd, NULL) < 0)
90         goto cleanup;
91 
92     ret = 0;
93 
94  cleanup:
95     virCommandFree(setAlarmCmd);
96     return ret;
97 }
98 
99 /**
100  * virNodeSuspendHelper:
101  * @cmdString: pointer to the command string this thread has to execute.
102  *
103  * Actually perform the suspend operation by invoking the command.
104  * Give a short delay before executing the command so as to give a chance
105  * to virNodeSuspend() to return the status to the caller.
106  * If we don't give this delay, that function will not be able to return
107  * the status, since the suspend operation would have begun and hence no
108  * data can be sent through the connection to the caller. However, with
109  * this delay added, the status return is best-effort only.
110  */
virNodeSuspendHelper(void * cmdString)111 static void virNodeSuspendHelper(void *cmdString)
112 {
113     virCommand *suspendCmd = virCommandNew((const char *)cmdString);
114 
115     /*
116      * Delay for sometime so that the function virNodeSuspend()
117      * can return the status to the caller.
118      */
119     sleep(SUSPEND_DELAY);
120     if (virCommandRun(suspendCmd, NULL) < 0)
121         VIR_WARN("Failed to suspend the host");
122 
123     virCommandFree(suspendCmd);
124 
125     /*
126      * Now that we have resumed from suspend or the suspend failed,
127      * reset 'aboutToSuspend' flag.
128      */
129     virNodeSuspendLock();
130     aboutToSuspend = false;
131     virNodeSuspendUnlock();
132 }
133 
134 /**
135  * virNodeSuspend:
136  * @conn: pointer to the hypervisor connection
137  * @target: the state to which the host must be suspended to -
138  *         VIR_NODE_SUSPEND_TARGET_MEM       (Suspend-to-RAM),
139  *         VIR_NODE_SUSPEND_TARGET_DISK      (Suspend-to-Disk),
140  *         VIR_NODE_SUSPEND_TARGET_HYBRID    (Hybrid-Suspend)
141  * @duration: the time duration in seconds, for which the host must be
142  *            suspended
143  * @flags: any flag values that might need to be passed; currently unused.
144  *
145  * Suspend the node (host machine) for the given duration of time in the
146  * specified state (such as Mem, Disk or Hybrid-Suspend). Attempt to resume
147  * the node after the time duration is complete.
148  *
149  * First, an RTC alarm is set appropriately to wake up the node from its
150  * sleep state. Then the actual node suspend operation is carried out
151  * asynchronously in another thread, after a short time delay so as to
152  * give enough time for this function to return status to its caller
153  * through the connection. However this returning of status is best-effort
154  * only, and should generally happen due to the short delay but might be
155  * missed if the machine suspends first.
156  *
157  * Returns 0 in case the node is going to be suspended after a short delay,
158  * -1 if suspending the node is not supported, or if a previous suspend
159  * operation is still in progress.
160  */
virNodeSuspend(unsigned int target,unsigned long long duration,unsigned int flags)161 int virNodeSuspend(unsigned int target,
162                    unsigned long long duration,
163                    unsigned int flags)
164 {
165     static virThread thread;
166     const char *cmdString = NULL;
167     int ret = -1;
168     unsigned int supported;
169 
170     virCheckFlags(0, -1);
171 
172     if (virNodeSuspendGetTargetMask(&supported) < 0)
173         return -1;
174 
175     /*
176      * Ensure that we are the only ones trying to suspend.
177      * Fail if somebody has already initiated a suspend.
178      */
179     virNodeSuspendLock();
180 
181     if (aboutToSuspend) {
182         /* A suspend operation is already in progress */
183         virReportError(VIR_ERR_OPERATION_INVALID, "%s",
184                        _("Suspend operation already in progress"));
185         goto cleanup;
186     }
187 
188     /* Check if the host supports the requested suspend target */
189     switch (target) {
190     case VIR_NODE_SUSPEND_TARGET_MEM:
191         if (!(supported & (1 << VIR_NODE_SUSPEND_TARGET_MEM))) {
192             virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Suspend-to-RAM"));
193             goto cleanup;
194         }
195         cmdString = "pm-suspend";
196         break;
197 
198     case VIR_NODE_SUSPEND_TARGET_DISK:
199         if (!(supported & (1 << VIR_NODE_SUSPEND_TARGET_DISK))) {
200             virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Suspend-to-Disk"));
201             goto cleanup;
202         }
203         cmdString = "pm-hibernate";
204         break;
205 
206     case VIR_NODE_SUSPEND_TARGET_HYBRID:
207         if (!(supported & (1 << VIR_NODE_SUSPEND_TARGET_HYBRID))) {
208             virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Hybrid-Suspend"));
209             goto cleanup;
210         }
211         cmdString = "pm-suspend-hybrid";
212         break;
213 
214     default:
215         virReportError(VIR_ERR_INVALID_ARG, "%s", _("Invalid suspend target"));
216         goto cleanup;
217     }
218 
219     /* Just set the RTC alarm. Don't suspend yet. */
220     if (virNodeSuspendSetNodeWakeup(duration) < 0)
221         goto cleanup;
222 
223     if (virThreadCreateFull(&thread, false,
224                             virNodeSuspendHelper,
225                             "node-suspend",
226                             false,
227                             (void *)cmdString) < 0) {
228         virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
229                        _("Failed to create thread to suspend the host"));
230         goto cleanup;
231     }
232 
233     aboutToSuspend = true;
234     ret = 0;
235  cleanup:
236     virNodeSuspendUnlock();
237     return ret;
238 }
239 
240 #ifdef WITH_PM_UTILS
241 static int
virNodeSuspendSupportsTargetPMUtils(unsigned int target,bool * supported)242 virNodeSuspendSupportsTargetPMUtils(unsigned int target, bool *supported)
243 {
244     g_autoptr(virCommand) cmd = NULL;
245     g_autofree char *binary = NULL;
246     int status;
247 
248     *supported = false;
249 
250     binary = virFindFileInPath("pm-is-supported");
251     if (!binary)
252         return -2;
253 
254     switch (target) {
255     case VIR_NODE_SUSPEND_TARGET_MEM:
256         cmd = virCommandNewArgList(binary, "--suspend", NULL);
257         break;
258     case VIR_NODE_SUSPEND_TARGET_DISK:
259         cmd = virCommandNewArgList(binary, "--hibernate", NULL);
260         break;
261     case VIR_NODE_SUSPEND_TARGET_HYBRID:
262         cmd = virCommandNewArgList(binary, "--suspend-hybrid", NULL);
263         break;
264     default:
265         return -1;
266     }
267 
268     if (virCommandRun(cmd, &status) < 0)
269         return -1;
270 
271    /*
272     * Check return code of command == 0 for success
273     * (i.e., the PM capability is supported)
274     */
275     *supported = (status == 0);
276 
277     return 0;
278 }
279 #else /* ! WITH_PM_UTILS */
280 static int
virNodeSuspendSupportsTargetPMUtils(unsigned int target G_GNUC_UNUSED,bool * supported G_GNUC_UNUSED)281 virNodeSuspendSupportsTargetPMUtils(unsigned int target G_GNUC_UNUSED,
282                                     bool *supported G_GNUC_UNUSED)
283 {
284     return -2;
285 }
286 #endif /* ! WITH_PM_UTILS */
287 
288 static int
virNodeSuspendSupportsTargetSystemd(unsigned int target,bool * supported)289 virNodeSuspendSupportsTargetSystemd(unsigned int target, bool *supported)
290 {
291     int ret = -1;
292 
293     *supported = false;
294 
295     switch (target) {
296     case VIR_NODE_SUSPEND_TARGET_MEM:
297         ret = virSystemdCanSuspend(supported);
298         break;
299     case VIR_NODE_SUSPEND_TARGET_DISK:
300         ret = virSystemdCanHibernate(supported);
301         break;
302     case VIR_NODE_SUSPEND_TARGET_HYBRID:
303         ret = virSystemdCanHybridSleep(supported);
304         break;
305     default:
306         return ret;
307     }
308 
309     return ret;
310 }
311 
312 /**
313  * virNodeSuspendSupportsTarget:
314  * @target: The power management target to check whether it is supported
315  *           by the host. Values could be:
316  *           VIR_NODE_SUSPEND_TARGET_MEM
317  *           VIR_NODE_SUSPEND_TARGET_DISK
318  *           VIR_NODE_SUSPEND_TARGET_HYBRID
319  * @supported: set to true if supported, false otherwise
320  *
321  * Run the script 'pm-is-supported' (from the pm-utils package)
322  * to find out if @target is supported by the host.
323  *
324  * Returns 0 if the query was successful, -1 on failure.
325  */
326 static int
virNodeSuspendSupportsTarget(unsigned int target,bool * supported)327 virNodeSuspendSupportsTarget(unsigned int target, bool *supported)
328 {
329     int ret;
330 
331     ret = virNodeSuspendSupportsTargetSystemd(target, supported);
332 
333     /* If just unavailable, try other options */
334     if (ret == -2)
335         ret = virNodeSuspendSupportsTargetPMUtils(target, supported);
336 
337     /* If still unavailable, then report unsupported */
338     if (ret == -2) {
339         *supported = false;
340         ret = 0;
341     }
342 
343     return ret;
344 }
345 
346 /**
347  * virNodeSuspendGetTargetMask:
348  *
349  * Get the Power Management Capabilities that the host system supports,
350  * such as Suspend-to-RAM (S3), Suspend-to-Disk (S4) and Hybrid-Suspend
351  * (a combination of S3 and S4).
352  *
353  * @bitmask: Pointer to the bitmask which will be set appropriately to
354  *           indicate all the supported host power management targets.
355  *
356  * Returns 0 if the query was successful, -1 on failure.
357  */
358 int
virNodeSuspendGetTargetMask(unsigned int * bitmask)359 virNodeSuspendGetTargetMask(unsigned int *bitmask)
360 {
361     int ret = -1;
362 
363     *bitmask = 0;
364 
365     virNodeSuspendLock();
366     /* Get the power management capabilities supported by the host */
367     if (!nodeSuspendTargetMaskInit) {
368         bool supported;
369         nodeSuspendTargetMask = 0;
370 
371         /* Check support for Suspend-to-RAM (S3) */
372         if (virNodeSuspendSupportsTarget(VIR_NODE_SUSPEND_TARGET_MEM, &supported) < 0)
373             goto cleanup;
374         if (supported)
375             nodeSuspendTargetMask |= (1 << VIR_NODE_SUSPEND_TARGET_MEM);
376 
377         /* Check support for Suspend-to-Disk (S4) */
378         if (virNodeSuspendSupportsTarget(VIR_NODE_SUSPEND_TARGET_DISK, &supported) < 0)
379             goto cleanup;
380         if (supported)
381             nodeSuspendTargetMask |= (1 << VIR_NODE_SUSPEND_TARGET_DISK);
382 
383         /* Check support for Hybrid-Suspend */
384         if (virNodeSuspendSupportsTarget(VIR_NODE_SUSPEND_TARGET_HYBRID, &supported) < 0)
385             goto cleanup;
386         if (supported)
387             nodeSuspendTargetMask |= (1 << VIR_NODE_SUSPEND_TARGET_HYBRID);
388 
389         nodeSuspendTargetMaskInit = true;
390     }
391 
392     *bitmask = nodeSuspendTargetMask;
393     ret = 0;
394  cleanup:
395     virNodeSuspendUnlock();
396     return ret;
397 }
398