Line data Source code
1 : /*
2 : * Copyright (C) 2014-2017 Red Hat, Inc.
3 : *
4 : * This library is free software; you can redistribute it and/or
5 : * modify it under the terms of the GNU Lesser General Public
6 : * License as published by the Free Software Foundation; either
7 : * version 2.1 of the License, or (at your option) any later version.
8 : *
9 : * This library is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 : * Lesser General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU Lesser General Public
15 : * License along with this library; if not, see <http://www.gnu.org/licenses/>.
16 : *
17 : * Author: Vratislav Podzimek <vpodzime@redhat.com>
18 : */
19 :
20 : #include <glib.h>
21 : #include <string.h>
22 : #include <unistd.h>
23 : #include <sys/swap.h>
24 : #include <fcntl.h>
25 : #include <blkid.h>
26 : #include <uuid.h>
27 : #include <blockdev/utils.h>
28 :
29 : #include "swap.h"
30 : #include "check_deps.h"
31 :
32 : #define MKSWAP_MIN_VERSION "2.23.2"
33 :
34 : /**
35 : * SECTION: swap
36 : * @short_description: plugin for operations with swap space
37 : * @title: Swap
38 : * @include: swap.h
39 : *
40 : * A plugin for operations with swap space.
41 : */
42 :
43 : /**
44 : * bd_swap_error_quark: (skip)
45 : */
46 0 : GQuark bd_swap_error_quark (void)
47 : {
48 0 : return g_quark_from_static_string ("g-bd-swap-error-quark");
49 : }
50 :
51 :
52 : static volatile guint avail_deps = 0;
53 : static GMutex deps_check_lock;
54 :
55 : #define DEPS_MKSWAP 0
56 : #define DEPS_MKSWAP_MASK (1 << DEPS_MKSWAP)
57 : #define DEPS_SWAPLABEL 1
58 : #define DEPS_SWAPLABEL_MASK (1 << DEPS_SWAPLABEL)
59 : #define DEPS_LAST 2
60 :
61 : static const UtilDep deps[DEPS_LAST] = {
62 : {"mkswap", MKSWAP_MIN_VERSION, NULL, "mkswap from util-linux ([\\d\\.]+)"},
63 : {"swaplabel", NULL, NULL, NULL},
64 : };
65 :
66 :
67 : /**
68 : * bd_swap_init:
69 : *
70 : * Initializes the plugin. **This function is called automatically by the
71 : * library's initialization functions.**
72 : *
73 : */
74 20 : gboolean bd_swap_init (void) {
75 : /* nothing to do here */
76 20 : return TRUE;
77 : };
78 :
79 : /**
80 : * bd_swap_close:
81 : *
82 : * Cleans up after the plugin. **This function is called automatically by the
83 : * library's functions that unload it.**
84 : *
85 : */
86 20 : void bd_swap_close (void) {
87 : /* nothing to do here */
88 20 : }
89 :
90 :
91 : /**
92 : * bd_swap_is_tech_avail:
93 : * @tech: the queried tech
94 : * @mode: a bit mask of queried modes of operation (#BDSwapTechMode) for @tech
95 : * @error: (out) (optional): place to store error (details about why the @tech-@mode combination is not available)
96 : *
97 : * Returns: whether the @tech-@mode combination is available -- supported by the
98 : * plugin implementation and having all the runtime dependencies available
99 : */
100 11 : gboolean bd_swap_is_tech_avail (BDSwapTech tech G_GNUC_UNUSED, guint64 mode, GError **error) {
101 11 : guint32 requires = 0;
102 11 : if (mode & BD_SWAP_TECH_MODE_CREATE)
103 6 : requires |= DEPS_MKSWAP_MASK;
104 11 : if (mode & BD_SWAP_TECH_MODE_SET_LABEL)
105 7 : requires |= DEPS_SWAPLABEL_MASK;
106 11 : if (mode & BD_SWAP_TECH_MODE_SET_UUID)
107 7 : requires |= DEPS_SWAPLABEL_MASK;
108 :
109 11 : return check_deps (&avail_deps, requires, deps, DEPS_LAST, &deps_check_lock, error);
110 : }
111 :
112 : /**
113 : * bd_swap_mkswap:
114 : * @device: a device to create swap space on
115 : * @label: (nullable): a label for the swap space device
116 : * @uuid: (nullable): UUID for the swap space device
117 : * @extra: (nullable) (array zero-terminated=1): extra options for the creation (right now
118 : * passed to the 'mkswap' utility)
119 : * @error: (out) (optional): place to store error (if any)
120 : *
121 : * Returns: whether the swap space was successfully created or not
122 : *
123 : * Tech category: %BD_SWAP_TECH_SWAP-%BD_SWAP_TECH_MODE_CREATE
124 : */
125 5 : gboolean bd_swap_mkswap (const gchar *device, const gchar *label, const gchar *uuid, const BDExtraArg **extra, GError **error) {
126 5 : guint8 next_arg = 2;
127 :
128 5 : if (!check_deps (&avail_deps, DEPS_MKSWAP_MASK, deps, DEPS_LAST, &deps_check_lock, error))
129 1 : return FALSE;
130 :
131 : /* We use -f to force since mkswap tends to refuse creation on lvs with
132 : a message about erasing bootbits sectors on whole disks. Bah. */
133 4 : const gchar *argv[8] = {"mkswap", "-f", NULL, NULL, NULL, NULL, NULL, NULL};
134 :
135 4 : if (label) {
136 1 : argv[next_arg] = "-L";
137 1 : next_arg++;
138 1 : argv[next_arg] = label;
139 1 : next_arg++;
140 : }
141 :
142 4 : if (uuid) {
143 1 : argv[next_arg] = "-U";
144 1 : next_arg++;
145 1 : argv[next_arg] = uuid;
146 1 : next_arg++;
147 : }
148 :
149 4 : argv[next_arg] = device;
150 :
151 4 : return bd_utils_exec_and_report_error (argv, extra, error);
152 : }
153 :
154 : /**
155 : * bd_swap_swapon:
156 : * @device: swap device to activate
157 : * @priority: priority of the activated device or -1 to use the default
158 : * @error: (out) (optional): place to store error (if any)
159 : *
160 : * Returns: whether the swap device was successfully activated or not
161 : *
162 : * Tech category: %BD_SWAP_TECH_SWAP-%BD_SWAP_TECH_MODE_ACTIVATE_DEACTIVATE
163 : */
164 8 : gboolean bd_swap_swapon (const gchar *device, gint priority, GError **error) {
165 8 : blkid_probe probe = NULL;
166 8 : gint fd = 0;
167 8 : gint status = 0;
168 8 : guint n_try = 0;
169 8 : const gchar *value = NULL;
170 8 : gint64 status_len = 0;
171 8 : gint64 swap_pagesize = 0;
172 8 : gint64 sys_pagesize = 0;
173 8 : gint flags = 0;
174 8 : gint ret = 0;
175 8 : guint64 progress_id = 0;
176 8 : gchar *msg = NULL;
177 8 : GError *l_error = NULL;
178 :
179 8 : msg = g_strdup_printf ("Started 'swapon %s'", device);
180 8 : progress_id = bd_utils_report_started (msg);
181 8 : g_free (msg);
182 :
183 8 : bd_utils_report_progress (progress_id, 0, "Analysing the swap device");
184 : /* check the device if it is an activatable swap */
185 8 : probe = blkid_new_probe ();
186 8 : if (!probe) {
187 0 : g_set_error (&l_error, BD_SWAP_ERROR, BD_SWAP_ERROR_UNKNOWN_STATE,
188 : "Failed to create a new probe");
189 0 : bd_utils_report_finished (progress_id, l_error->message);
190 0 : g_propagate_error (error, l_error);
191 0 : return FALSE;
192 : }
193 :
194 8 : fd = open (device, O_RDONLY|O_CLOEXEC);
195 8 : if (fd == -1) {
196 3 : g_set_error (&l_error, BD_SWAP_ERROR, BD_SWAP_ERROR_UNKNOWN_STATE,
197 : "Failed to open the device '%s'", device);
198 3 : bd_utils_report_finished (progress_id, l_error->message);
199 3 : g_propagate_error (error, l_error);
200 3 : blkid_free_probe (probe);
201 3 : return FALSE;
202 : }
203 :
204 : /* we may need to try multiple times with some delays in case the device is
205 : busy at the very moment */
206 10 : for (n_try=5, status=-1; (status != 0) && (n_try > 0); n_try--) {
207 5 : status = blkid_probe_set_device (probe, fd, 0, 0);
208 5 : if (status != 0)
209 0 : g_usleep (100 * 1000); /* microseconds */
210 : }
211 5 : if (status != 0) {
212 0 : g_set_error (&l_error, BD_SWAP_ERROR, BD_SWAP_ERROR_UNKNOWN_STATE,
213 : "Failed to create a probe for the device '%s'", device);
214 0 : bd_utils_report_finished (progress_id, l_error->message);
215 0 : g_propagate_error (error, l_error);
216 0 : blkid_free_probe (probe);
217 0 : close (fd);
218 0 : return FALSE;
219 : }
220 :
221 5 : blkid_probe_enable_superblocks (probe, 1);
222 5 : blkid_probe_set_superblocks_flags (probe, BLKID_SUBLKS_TYPE | BLKID_SUBLKS_MAGIC);
223 :
224 : /* we may need to try multiple times with some delays in case the device is
225 : busy at the very moment */
226 10 : for (n_try=5, status=-1; !(status == 0 || status == 1) && (n_try > 0); n_try--) {
227 5 : status = blkid_do_safeprobe (probe);
228 5 : if (status < 0)
229 0 : g_usleep (100 * 1000); /* microseconds */
230 : }
231 5 : if (status < 0) {
232 : /* -1 or -2 = error during probing*/
233 0 : g_set_error (&l_error, BD_SWAP_ERROR, BD_SWAP_ERROR_UNKNOWN_STATE,
234 : "Failed to probe the device '%s'", device);
235 0 : bd_utils_report_finished (progress_id, l_error->message);
236 0 : g_propagate_error (error, l_error);
237 0 : blkid_free_probe (probe);
238 0 : close (fd);
239 0 : return FALSE;
240 5 : } else if (status == 1) {
241 : /* 1 = nothing detected */
242 1 : g_set_error (&l_error, BD_SWAP_ERROR, BD_SWAP_ERROR_UNKNOWN_STATE,
243 : "No superblock detected on the device '%s'", device);
244 1 : bd_utils_report_finished (progress_id, l_error->message);
245 1 : g_propagate_error (error, l_error);
246 1 : blkid_free_probe (probe);
247 1 : close (fd);
248 1 : return FALSE;
249 : }
250 :
251 4 : status = blkid_probe_lookup_value (probe, "TYPE", &value, NULL);
252 4 : if (status != 0) {
253 0 : g_set_error (&l_error, BD_SWAP_ERROR, BD_SWAP_ERROR_UNKNOWN_STATE,
254 : "Failed to get format type for the device '%s'", device);
255 0 : bd_utils_report_finished (progress_id, l_error->message);
256 0 : g_propagate_error (error, l_error);
257 0 : blkid_free_probe (probe);
258 0 : close (fd);
259 0 : return FALSE;
260 : }
261 :
262 4 : if (g_strcmp0 (value, "swap") != 0) {
263 0 : g_set_error (&l_error, BD_SWAP_ERROR, BD_SWAP_ERROR_UNKNOWN_STATE,
264 : "Device '%s' is not formatted as swap", device);
265 0 : bd_utils_report_finished (progress_id, l_error->message);
266 0 : g_propagate_error (error, l_error);
267 0 : blkid_free_probe (probe);
268 0 : close (fd);
269 0 : return FALSE;
270 : }
271 :
272 4 : status = blkid_probe_lookup_value (probe, "SBMAGIC", &value, NULL);
273 4 : if (status != 0) {
274 0 : g_set_error (&l_error, BD_SWAP_ERROR, BD_SWAP_ERROR_UNKNOWN_STATE,
275 : "Failed to get swap status on the device '%s'", device);
276 0 : bd_utils_report_finished (progress_id, l_error->message);
277 0 : g_propagate_error (error, l_error);
278 0 : blkid_free_probe (probe);
279 0 : close (fd);
280 0 : return FALSE;
281 : }
282 :
283 4 : if (g_strcmp0 (value, "SWAP-SPACE") == 0) {
284 0 : g_set_error (&l_error, BD_SWAP_ERROR, BD_SWAP_ERROR_ACTIVATE_OLD,
285 : "Old swap format, cannot activate.");
286 0 : bd_utils_report_finished (progress_id, l_error->message);
287 0 : g_propagate_error (error, l_error);
288 0 : blkid_free_probe (probe);
289 0 : close (fd);
290 0 : return FALSE;
291 4 : } else if (g_strcmp0 (value, "S1SUSPEND") == 0 || g_strcmp0 (value, "S2SUSPEND") == 0) {
292 0 : g_set_error (&l_error, BD_SWAP_ERROR, BD_SWAP_ERROR_ACTIVATE_SUSPEND,
293 : "Suspended system on the swap device, cannot activate.");
294 0 : bd_utils_report_finished (progress_id, l_error->message);
295 0 : g_propagate_error (error, l_error);
296 0 : blkid_free_probe (probe);
297 0 : close (fd);
298 0 : return FALSE;
299 4 : } else if (g_strcmp0 (value, "SWAPSPACE2") != 0) {
300 0 : g_set_error (&l_error, BD_SWAP_ERROR, BD_SWAP_ERROR_ACTIVATE_UNKNOWN,
301 : "Unknown swap space format, cannot activate.");
302 0 : bd_utils_report_finished (progress_id, l_error->message);
303 0 : g_propagate_error (error, l_error);
304 0 : blkid_free_probe (probe);
305 0 : close (fd);
306 0 : return FALSE;
307 : }
308 :
309 4 : status_len = (gint64) strlen (value);
310 :
311 4 : status = blkid_probe_lookup_value (probe, "SBMAGIC_OFFSET", &value, NULL);
312 4 : if (status != 0 || !value) {
313 0 : g_set_error (&l_error, BD_SWAP_ERROR, BD_SWAP_ERROR_ACTIVATE_PAGESIZE,
314 : "Failed to get swap status on the device '%s'", device);
315 0 : bd_utils_report_finished (progress_id, l_error->message);
316 0 : g_propagate_error (error, l_error);
317 0 : blkid_free_probe (probe);
318 0 : close (fd);
319 0 : return FALSE;
320 : }
321 :
322 4 : swap_pagesize = status_len + g_ascii_strtoll (value, (char **)NULL, 10);
323 :
324 4 : blkid_free_probe (probe);
325 4 : close (fd);
326 :
327 4 : sys_pagesize = getpagesize ();
328 :
329 4 : if (swap_pagesize != sys_pagesize) {
330 1 : g_set_error (&l_error, BD_SWAP_ERROR, BD_SWAP_ERROR_ACTIVATE_PAGESIZE,
331 : "Swap format pagesize (%"G_GINT64_FORMAT") and system pagesize (%"G_GINT64_FORMAT") don't match",
332 : swap_pagesize, sys_pagesize);
333 1 : bd_utils_report_finished (progress_id, l_error->message);
334 1 : g_propagate_error (error, l_error);
335 1 : return FALSE;
336 : }
337 :
338 3 : bd_utils_report_progress (progress_id, 10, "Swap device analysed, enabling");
339 3 : if (priority >= 0) {
340 0 : flags = SWAP_FLAG_PREFER;
341 0 : flags |= (priority << SWAP_FLAG_PRIO_SHIFT) & SWAP_FLAG_PRIO_MASK;
342 : }
343 :
344 3 : ret = swapon (device, flags);
345 3 : if (ret != 0) {
346 1 : g_set_error (&l_error, BD_SWAP_ERROR, BD_SWAP_ERROR_ACTIVATE,
347 : "Failed to activate swap on %s: %m", device);
348 1 : bd_utils_report_finished (progress_id, l_error->message);
349 1 : g_propagate_error (error, l_error);
350 : }
351 :
352 3 : bd_utils_report_finished (progress_id, "Completed");
353 3 : return ret == 0;
354 : }
355 :
356 : /**
357 : * bd_swap_swapoff:
358 : * @device: swap device to deactivate
359 : * @error: (out) (optional): place to store error (if any)
360 : *
361 : * Returns: whether the swap device was successfully deactivated or not
362 : *
363 : * Tech category: %BD_SWAP_TECH_SWAP-%BD_SWAP_TECH_MODE_ACTIVATE_DEACTIVATE
364 : */
365 10 : gboolean bd_swap_swapoff (const gchar *device, GError **error) {
366 10 : gint ret = 0;
367 10 : guint64 progress_id = 0;
368 10 : gchar *msg = NULL;
369 10 : GError *l_error = NULL;
370 :
371 10 : msg = g_strdup_printf ("Started 'swapoff %s'", device);
372 10 : progress_id = bd_utils_report_started (msg);
373 10 : g_free (msg);
374 :
375 10 : ret = swapoff (device);
376 10 : if (ret != 0) {
377 8 : g_set_error (&l_error, BD_SWAP_ERROR, BD_SWAP_ERROR_ACTIVATE,
378 : "Failed to deactivate swap on %s: %m", device);
379 8 : bd_utils_report_finished (progress_id, l_error->message);
380 8 : g_propagate_error (error, l_error);
381 : }
382 :
383 10 : bd_utils_report_finished (progress_id, "Completed");
384 10 : return ret == 0;
385 : }
386 :
387 : /**
388 : * bd_swap_swapstatus:
389 : * @device: swap device to get status of
390 : * @error: (out) (optional): place to store error (if any)
391 : *
392 : * Returns: %TRUE if the swap device is active, %FALSE if not active or failed
393 : * to determine (@error) is set not a non-NULL value in such case)
394 : *
395 : * Tech category: %BD_SWAP_TECH_SWAP-%BD_SWAP_TECH_MODE_QUERY
396 : */
397 7 : gboolean bd_swap_swapstatus (const gchar *device, GError **error) {
398 7 : gchar *file_content = NULL;
399 7 : gchar *real_device = NULL;
400 : gsize length;
401 : gchar *next_line;
402 : gboolean success;
403 :
404 7 : success = g_file_get_contents ("/proc/swaps", &file_content, &length, error);
405 7 : if (!success) {
406 : /* error is already populated */
407 0 : return FALSE;
408 : }
409 :
410 : /* get the real device node for device-mapper devices since the ones
411 : with meaningful names are just dev_paths */
412 7 : if (g_str_has_prefix (device, "/dev/mapper/") || g_str_has_prefix (device, "/dev/md/")) {
413 3 : real_device = bd_utils_resolve_device (device, NULL);
414 3 : if (!real_device) {
415 : /* the device doesn't exist and thus is not an active swap */
416 0 : g_free (file_content);
417 0 : return FALSE;
418 : }
419 : }
420 :
421 7 : if (g_str_has_prefix (file_content, real_device ? real_device : device)) {
422 0 : g_free (real_device);
423 0 : g_free (file_content);
424 0 : return TRUE;
425 : }
426 :
427 7 : next_line = (strchr (file_content, '\n') + 1);
428 14 : while (next_line && ((gsize)(next_line - file_content) < length)) {
429 9 : if (g_str_has_prefix (next_line, real_device ? real_device : device)) {
430 2 : g_free (real_device);
431 2 : g_free (file_content);
432 2 : return TRUE;
433 : }
434 :
435 7 : next_line = (strchr (next_line, '\n') + 1);
436 : }
437 :
438 5 : g_free (real_device);
439 5 : g_free (file_content);
440 5 : return FALSE;
441 : }
442 :
443 : /**
444 : * bd_swap_check_label:
445 : * @label: label to check
446 : * @error: (out) (optional): place to store error
447 : *
448 : * Returns: whether @label is a valid label for swap or not
449 : * (reason is provided in @error)
450 : *
451 : * Tech category: always available
452 : */
453 1 : gboolean bd_swap_check_label (const gchar *label, GError **error) {
454 1 : if (strlen (label) > 16) {
455 1 : g_set_error (error, BD_SWAP_ERROR, BD_SWAP_ERROR_LABEL_INVALID,
456 : "Label for swap must be at most 16 characters long.");
457 1 : return FALSE;
458 : }
459 :
460 0 : return TRUE;
461 : }
462 :
463 : /**
464 : * bd_swap_set_label:
465 : * @device: a device to set label on
466 : * @label: label that will be set
467 : * @error: (out) (optional): place to store error (if any)
468 : *
469 : * Returns: whether the label was successfully set or not
470 : *
471 : * Tech category: %BD_SWAP_TECH_SWAP-%BD_SWAP_TECH_MODE_SET_LABEL
472 : */
473 1 : gboolean bd_swap_set_label (const gchar *device, const gchar *label, GError **error) {
474 1 : const gchar *argv[5] = {"swaplabel", "-L", label, device, NULL};
475 :
476 1 : if (!check_deps (&avail_deps, DEPS_SWAPLABEL_MASK, deps, DEPS_LAST, &deps_check_lock, error))
477 0 : return FALSE;
478 :
479 1 : return bd_utils_exec_and_report_error (argv, NULL, error);
480 : }
481 :
482 : /**
483 : * bd_swap_check_uuid:
484 : * @uuid: UUID to check
485 : * @error: (out) (optional): place to store error
486 : *
487 : * Returns: whether @uuid is a valid UUID for swap or not
488 : * (reason is provided in @error)
489 : *
490 : * Tech category: always available
491 : */
492 1 : gboolean bd_swap_check_uuid (const gchar *uuid, GError **error) {
493 1 : g_autofree gchar *lowercase = NULL;
494 1 : gint ret = 0;
495 : uuid_t uu;
496 :
497 1 : if (!g_str_is_ascii (uuid)) {
498 0 : g_set_error (error, BD_SWAP_ERROR, BD_SWAP_ERROR_UUID_INVALID,
499 : "Provided UUID is not a valid RFC-4122 UUID.");
500 0 : return FALSE;
501 : }
502 :
503 1 : lowercase = g_ascii_strdown (uuid, -1);
504 1 : ret = uuid_parse (lowercase, uu);
505 1 : if (ret < 0){
506 1 : g_set_error (error, BD_SWAP_ERROR, BD_SWAP_ERROR_UUID_INVALID,
507 : "Provided UUID is not a valid RFC-4122 UUID.");
508 1 : return FALSE;
509 : }
510 :
511 0 : return TRUE;
512 : }
513 :
514 : /**
515 : * bd_swap_set_uuid:
516 : * @device: a device to set UUID on
517 : * @uuid: UUID that will be set
518 : * @error: (out) (optional): place to store error (if any)
519 : *
520 : * Returns: whether the UUID was successfully set or not
521 : *
522 : * Tech category: %BD_SWAP_TECH_SWAP-%BD_SWAP_TECH_MODE_SET_UUID
523 : */
524 1 : gboolean bd_swap_set_uuid (const gchar *device, const gchar *uuid, GError **error) {
525 1 : const gchar *argv[5] = {"swaplabel", "-U", uuid, device, NULL};
526 :
527 1 : if (!check_deps (&avail_deps, DEPS_SWAPLABEL_MASK, deps, DEPS_LAST, &deps_check_lock, error))
528 0 : return FALSE;
529 :
530 1 : return bd_utils_exec_and_report_error (argv, NULL, error);
531 : }
|