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