Line data Source code
1 : /*
2 : * Copyright (C) 2014 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 <blockdev/utils.h>
24 : #include <bs_size.h>
25 :
26 : #include "btrfs.h"
27 : #include "check_deps.h"
28 :
29 : #define BTRFS_MIN_VERSION "3.18.2"
30 :
31 : /**
32 : * SECTION: btrfs
33 : * @short_description: plugin for operations with BTRFS devices
34 : * @title: BTRFS
35 : * @include: btrfs.h
36 : *
37 : * A plugin for operations with btrfs devices.
38 : */
39 :
40 : /**
41 : * bd_btrfs_error_quark: (skip)
42 : */
43 0 : GQuark bd_btrfs_error_quark (void)
44 : {
45 0 : return g_quark_from_static_string ("g-bd-btrfs-error-quark");
46 : }
47 :
48 0 : BDBtrfsDeviceInfo* bd_btrfs_device_info_copy (BDBtrfsDeviceInfo *info) {
49 0 : if (info == NULL)
50 0 : return NULL;
51 :
52 0 : BDBtrfsDeviceInfo *new_info = g_new0 (BDBtrfsDeviceInfo, 1);
53 :
54 0 : new_info->id = info->id;
55 0 : new_info->path = g_strdup (info->path);
56 0 : new_info->size = info->size;
57 0 : new_info->used = info->used;
58 :
59 0 : return new_info;
60 : }
61 :
62 0 : void bd_btrfs_device_info_free (BDBtrfsDeviceInfo *info) {
63 0 : if (info == NULL)
64 0 : return;
65 :
66 0 : g_free (info->path);
67 0 : g_free (info);
68 : }
69 :
70 0 : BDBtrfsSubvolumeInfo* bd_btrfs_subvolume_info_copy (BDBtrfsSubvolumeInfo *info) {
71 0 : if (info == NULL)
72 0 : return NULL;
73 :
74 0 : BDBtrfsSubvolumeInfo *new_info = g_new0 (BDBtrfsSubvolumeInfo, 1);
75 :
76 0 : new_info->id = info->id;
77 0 : new_info->parent_id = info->parent_id;
78 0 : new_info->path = g_strdup (info->path);
79 :
80 0 : return new_info;
81 : }
82 :
83 0 : void bd_btrfs_subvolume_info_free (BDBtrfsSubvolumeInfo *info) {
84 0 : if (info == NULL)
85 0 : return;
86 :
87 0 : g_free (info->path);
88 0 : g_free (info);
89 : }
90 :
91 0 : BDBtrfsFilesystemInfo* bd_btrfs_filesystem_info_copy (BDBtrfsFilesystemInfo *info) {
92 0 : if (info == NULL)
93 0 : return NULL;
94 :
95 0 : BDBtrfsFilesystemInfo *new_info = g_new0 (BDBtrfsFilesystemInfo, 1);
96 :
97 0 : new_info->label = g_strdup (info->label);
98 0 : new_info->uuid = g_strdup (info->uuid);
99 0 : new_info->num_devices = info->num_devices;
100 0 : new_info->used = info->used;
101 :
102 0 : return new_info;
103 : }
104 :
105 0 : void bd_btrfs_filesystem_info_free (BDBtrfsFilesystemInfo *info) {
106 0 : if (info == NULL)
107 0 : return;
108 :
109 0 : g_free (info->label);
110 0 : g_free (info->uuid);
111 0 : g_free (info);
112 : }
113 :
114 : static volatile guint avail_deps = 0;
115 : static volatile guint avail_module_deps = 0;
116 : static GMutex deps_check_lock;
117 :
118 : #define DEPS_BTRFS 0
119 : #define DEPS_BTRFS_MASK (1 << DEPS_BTRFS)
120 : #define DEPS_LAST 1
121 :
122 : static const UtilDep deps[DEPS_LAST] = {
123 : {"btrfs", BTRFS_MIN_VERSION, NULL, "[Bb]trfs.* v([\\d\\.]+)"},
124 : };
125 :
126 : #define MODULE_DEPS_BTRFS 0
127 : #define MODULE_DEPS_BTRFS_MASK (1 << MODULE_DEPS_BTRFS)
128 : #define MODULE_DEPS_LAST 1
129 :
130 : static const gchar*const module_deps[MODULE_DEPS_LAST] = { "btrfs" };
131 :
132 :
133 : /**
134 : * bd_btrfs_init:
135 : *
136 : * Initializes the plugin. **This function is called automatically by the
137 : * library's initialization functions.**
138 : *
139 : */
140 24 : gboolean bd_btrfs_init (void) {
141 : /* nothing to do here */
142 24 : return TRUE;
143 : };
144 :
145 : /**
146 : * bd_btrfs_close:
147 : *
148 : * Cleans up after the plugin. **This function is called automatically by the
149 : * library's functions that unload it.**
150 : *
151 : */
152 24 : void bd_btrfs_close (void) {
153 : /* nothing to do here */
154 24 : }
155 :
156 :
157 :
158 : /**
159 : * bd_btrfs_is_tech_avail:
160 : * @tech: the queried tech
161 : * @mode: a bit mask of queried modes of operation for @tech
162 : * @error: (out) (optional): place to store error (details about why the @tech-@mode combination is not available)
163 : *
164 : * Returns: whether the @tech-@mode combination is available -- supported by the
165 : * plugin implementation and having all the runtime dependencies available
166 : */
167 4 : gboolean bd_btrfs_is_tech_avail (BDBtrfsTech tech G_GNUC_UNUSED, guint64 mode G_GNUC_UNUSED, GError **error) {
168 : /* all tech-mode combinations are supported by this implementation of the plugin */
169 6 : return check_deps (&avail_deps, DEPS_BTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error) &&
170 2 : check_module_deps (&avail_module_deps, MODULE_DEPS_BTRFS_MASK, module_deps, MODULE_DEPS_LAST, &deps_check_lock, error);
171 : }
172 :
173 18 : static BDBtrfsDeviceInfo* get_device_info_from_match (GMatchInfo *match_info) {
174 18 : BDBtrfsDeviceInfo *ret = g_new(BDBtrfsDeviceInfo, 1);
175 18 : gchar *item = NULL;
176 18 : BSSize size = NULL;
177 18 : BSError *error = NULL;
178 :
179 18 : item = g_match_info_fetch_named (match_info, "id");
180 18 : ret->id = g_ascii_strtoull (item, NULL, 0);
181 18 : g_free (item);
182 :
183 18 : ret->path = g_match_info_fetch_named (match_info, "path");
184 :
185 18 : item = g_match_info_fetch_named (match_info, "size");
186 18 : if (item) {
187 18 : size = bs_size_new_from_str (item, &error);
188 18 : if (size) {
189 18 : ret->size = bs_size_get_bytes (size, NULL, &error);
190 18 : bs_size_free (size);
191 : }
192 18 : if (error)
193 0 : bd_utils_log_format (BD_UTILS_LOG_WARNING, "%s", error->msg);
194 18 : bs_clear_error (&error);
195 18 : g_free (item);
196 : }
197 :
198 18 : item = g_match_info_fetch_named (match_info, "used");
199 18 : if (item) {
200 18 : size = bs_size_new_from_str (item, &error);
201 18 : if (size) {
202 18 : ret->used = bs_size_get_bytes (size, NULL, &error);
203 18 : bs_size_free (size);
204 : }
205 18 : if (error)
206 0 : bd_utils_log_format (BD_UTILS_LOG_WARNING, "%s", error->msg);
207 18 : bs_clear_error (&error);
208 18 : g_free (item);
209 : }
210 :
211 18 : return ret;
212 : }
213 :
214 145 : static BDBtrfsSubvolumeInfo* get_subvolume_info_from_match (GMatchInfo *match_info) {
215 145 : BDBtrfsSubvolumeInfo *ret = g_new(BDBtrfsSubvolumeInfo, 1);
216 145 : gchar *item = NULL;
217 :
218 145 : item = g_match_info_fetch_named (match_info, "id");
219 145 : ret->id = g_ascii_strtoull (item, NULL, 0);
220 145 : g_free (item);
221 :
222 145 : item = g_match_info_fetch_named (match_info, "parent_id");
223 145 : ret->parent_id = g_ascii_strtoull (item, NULL, 0);
224 145 : g_free (item);
225 :
226 145 : ret->path = g_match_info_fetch_named (match_info, "path");
227 :
228 145 : return ret;
229 : }
230 :
231 3 : static BDBtrfsFilesystemInfo* get_filesystem_info_from_match (GMatchInfo *match_info) {
232 3 : BDBtrfsFilesystemInfo *ret = g_new(BDBtrfsFilesystemInfo, 1);
233 3 : gchar *item = NULL;
234 3 : BSSize size = NULL;
235 3 : BSError *error = NULL;
236 :
237 3 : ret->label = g_match_info_fetch_named (match_info, "label");
238 3 : ret->uuid = g_match_info_fetch_named (match_info, "uuid");
239 :
240 3 : item = g_match_info_fetch_named (match_info, "num_devices");
241 3 : ret->num_devices = g_ascii_strtoull (item, NULL, 0);
242 3 : g_free (item);
243 :
244 3 : item = g_match_info_fetch_named (match_info, "used");
245 3 : if (item) {
246 3 : size = bs_size_new_from_str (item, &error);
247 3 : if (size) {
248 3 : ret->used = bs_size_get_bytes (size, NULL, &error);
249 3 : bs_size_free (size);
250 : }
251 3 : if (error)
252 0 : bd_utils_log_format (BD_UTILS_LOG_WARNING, "%s", error->msg);
253 3 : bs_clear_error (&error);
254 3 : g_free (item);
255 : }
256 :
257 3 : return ret;
258 : }
259 :
260 : /**
261 : * bd_btrfs_create_volume:
262 : * @devices: (array zero-terminated=1): list of devices to create btrfs volume from
263 : * @label: (nullable): label for the volume
264 : * @data_level: (nullable): RAID level for the data or %NULL to use the default
265 : * @md_level: (nullable): RAID level for the metadata or %NULL to use the default
266 : * @extra: (nullable) (array zero-terminated=1): extra options for the volume creation (right now
267 : * passed to the 'mkfs.btrfs' utility)
268 : * @error: (out) (optional): place to store error (if any)
269 : *
270 : * Returns: whether the new btrfs volume was created from @devices or not
271 : *
272 : * See mkfs.btrfs(8) for details about @data_level, @md_level and btrfs in general.
273 : *
274 : * Tech category: %BD_BTRFS_TECH_MULTI_DEV-%BD_BTRFS_TECH_MODE_CREATE
275 : */
276 34 : gboolean bd_btrfs_create_volume (const gchar **devices, const gchar *label, const gchar *data_level, const gchar *md_level, const BDExtraArg **extra, GError **error) {
277 34 : const gchar **device_p = NULL;
278 34 : guint8 num_args = 0;
279 34 : const gchar **argv = NULL;
280 34 : guint8 next_arg = 1;
281 34 : gboolean success = FALSE;
282 :
283 68 : if (!check_deps (&avail_deps, DEPS_BTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error) ||
284 34 : !check_module_deps (&avail_module_deps, MODULE_DEPS_BTRFS_MASK, module_deps, MODULE_DEPS_LAST, &deps_check_lock, error))
285 0 : return FALSE;
286 :
287 34 : if (!devices || (g_strv_length ((gchar **) devices) < 1)) {
288 2 : g_set_error (error, BD_BTRFS_ERROR, BD_BTRFS_ERROR_DEVICE, "No devices given");
289 2 : return FALSE;
290 : }
291 :
292 69 : for (device_p = devices; *device_p != NULL; device_p++) {
293 39 : if (access (*device_p, F_OK) != 0) {
294 2 : g_set_error (error, BD_BTRFS_ERROR, BD_BTRFS_ERROR_DEVICE, "Device %s does not exist", *device_p);
295 2 : return FALSE;
296 : }
297 37 : num_args++;
298 : }
299 :
300 30 : if (label)
301 16 : num_args += 2;
302 30 : if (data_level)
303 4 : num_args += 2;
304 30 : if (md_level)
305 4 : num_args += 2;
306 :
307 30 : argv = g_new0 (const gchar*, num_args + 2);
308 30 : argv[0] = "mkfs.btrfs";
309 30 : if (label) {
310 16 : argv[next_arg] = "--label";
311 16 : next_arg++;
312 16 : argv[next_arg] = label;
313 16 : next_arg++;
314 : }
315 30 : if (data_level) {
316 4 : argv[next_arg] = "--data";
317 4 : next_arg++;
318 4 : argv[next_arg] = data_level;
319 4 : next_arg++;
320 : }
321 30 : if (md_level) {
322 4 : argv[next_arg] = "--metadata";
323 4 : next_arg++;
324 4 : argv[next_arg] = md_level;
325 4 : next_arg++;
326 : }
327 :
328 67 : for (device_p = devices; next_arg <= num_args; device_p++, next_arg++)
329 37 : argv[next_arg] = *device_p;
330 30 : argv[next_arg] = NULL;
331 :
332 30 : success = bd_utils_exec_and_report_error (argv, extra, error);
333 30 : g_free (argv);
334 30 : return success;
335 : }
336 :
337 : /**
338 : * bd_btrfs_add_device:
339 : * @mountpoint: mountpoint of the btrfs volume to add new device to
340 : * @device: a device to add to the btrfs volume
341 : * @extra: (nullable) (array zero-terminated=1): extra options for the addition (right now
342 : * passed to the 'btrfs' utility)
343 : * @error: (out) (optional): place to store error (if any)
344 : *
345 : * Returns: whether the @device was successfully added to the @mountpoint btrfs volume or not
346 : *
347 : * Tech category: %BD_BTRFS_TECH_MULTI_DEV-%BD_BTRFS_TECH_MODE_MODIFY
348 : */
349 1 : gboolean bd_btrfs_add_device (const gchar *mountpoint, const gchar *device, const BDExtraArg **extra, GError **error) {
350 1 : const gchar *argv[6] = {"btrfs", "device", "add", device, mountpoint, NULL};
351 2 : if (!check_deps (&avail_deps, DEPS_BTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error) ||
352 1 : !check_module_deps (&avail_module_deps, MODULE_DEPS_BTRFS_MASK, module_deps, MODULE_DEPS_LAST, &deps_check_lock, error))
353 0 : return FALSE;
354 :
355 1 : return bd_utils_exec_and_report_error (argv, extra, error);
356 : }
357 :
358 : /**
359 : * bd_btrfs_remove_device:
360 : * @mountpoint: mountpoint of the btrfs volume to remove device from
361 : * @device: a device to remove from the btrfs volume
362 : * @extra: (nullable) (array zero-terminated=1): extra options for the removal (right now
363 : * passed to the 'btrfs' utility)
364 : * @error: (out) (optional): place to store error (if any)
365 : *
366 : * Returns: whether the @device was successfully removed from the @mountpoint btrfs volume or not
367 : *
368 : * Tech category: %BD_BTRFS_TECH_MULTI_DEV-%BD_BTRFS_TECH_MODE_MODIFY
369 : */
370 1 : gboolean bd_btrfs_remove_device (const gchar *mountpoint, const gchar *device, const BDExtraArg **extra, GError **error) {
371 1 : const gchar *argv[6] = {"btrfs", "device", "delete", device, mountpoint, NULL};
372 2 : if (!check_deps (&avail_deps, DEPS_BTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error) ||
373 1 : !check_module_deps (&avail_module_deps, MODULE_DEPS_BTRFS_MASK, module_deps, MODULE_DEPS_LAST, &deps_check_lock, error))
374 0 : return FALSE;
375 :
376 1 : return bd_utils_exec_and_report_error (argv, extra, error);
377 : }
378 :
379 : /**
380 : * bd_btrfs_create_subvolume:
381 : * @mountpoint: mountpoint of the btrfs volume to create subvolume under
382 : * @name: name of the subvolume
383 : * @extra: (nullable) (array zero-terminated=1): extra options for the subvolume creation (right now
384 : * passed to the 'btrfs' utility)
385 : * @error: (out) (optional): place to store error (if any)
386 : *
387 : * Returns: whether the @mountpoint/@name subvolume was successfully created or not
388 : *
389 : * Tech category: %BD_BTRFS_TECH_SUBVOL-%BD_BTRFS_TECH_MODE_CREATE
390 : */
391 11 : gboolean bd_btrfs_create_subvolume (const gchar *mountpoint, const gchar *name, const BDExtraArg **extra, GError **error) {
392 11 : gchar *path = NULL;
393 11 : gboolean success = FALSE;
394 11 : const gchar *argv[5] = {"btrfs", "subvol", "create", NULL, NULL};
395 :
396 22 : if (!check_deps (&avail_deps, DEPS_BTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error) ||
397 11 : !check_module_deps (&avail_module_deps, MODULE_DEPS_BTRFS_MASK, module_deps, MODULE_DEPS_LAST, &deps_check_lock, error))
398 0 : return FALSE;
399 :
400 11 : if (g_str_has_suffix (mountpoint, "/"))
401 0 : path = g_strdup_printf ("%s%s", mountpoint, name);
402 : else
403 11 : path = g_strdup_printf ("%s/%s", mountpoint, name);
404 11 : argv[3] = path;
405 :
406 11 : success = bd_utils_exec_and_report_error (argv, extra, error);
407 11 : g_free (path);
408 :
409 11 : return success;
410 : }
411 :
412 : /**
413 : * bd_btrfs_delete_subvolume:
414 : * @mountpoint: mountpoint of the btrfs volume to delete subvolume from
415 : * @name: name of the subvolume
416 : * @extra: (nullable) (array zero-terminated=1): extra options for the subvolume deletion (right now
417 : * passed to the 'btrfs' utility)
418 : * @error: (out) (optional): place to store error (if any)
419 : *
420 : * Returns: whether the @mountpoint/@name subvolume was successfully deleted or not
421 : *
422 : * Tech category: %BD_BTRFS_TECH_SUBVOL-%BD_BTRFS_TECH_MODE_DELETE
423 : */
424 2 : gboolean bd_btrfs_delete_subvolume (const gchar *mountpoint, const gchar *name, const BDExtraArg **extra, GError **error) {
425 2 : gchar *path = NULL;
426 2 : gboolean success = FALSE;
427 2 : const gchar *argv[5] = {"btrfs", "subvol", "delete", NULL, NULL};
428 :
429 4 : if (!check_deps (&avail_deps, DEPS_BTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error) ||
430 2 : !check_module_deps (&avail_module_deps, MODULE_DEPS_BTRFS_MASK, module_deps, MODULE_DEPS_LAST, &deps_check_lock, error))
431 0 : return FALSE;
432 :
433 2 : if (g_str_has_suffix (mountpoint, "/"))
434 0 : path = g_strdup_printf ("%s%s", mountpoint, name);
435 : else
436 2 : path = g_strdup_printf ("%s/%s", mountpoint, name);
437 2 : argv[3] = path;
438 :
439 2 : success = bd_utils_exec_and_report_error (argv, extra, error);
440 2 : g_free (path);
441 :
442 2 : return success;
443 : }
444 :
445 : /**
446 : * bd_btrfs_get_default_subvolume_id:
447 : * @mountpoint: mountpoint of the volume to get the default subvolume ID of
448 : * @error: (out) (optional): place to store error (if any)
449 : *
450 : * Returns: ID of the @mountpoint volume's default subvolume. If 0,
451 : * @error) may be set to indicate error
452 : *
453 : * Tech category: %BD_BTRFS_TECH_SUBVOL-%BD_BTRFS_TECH_MODE_QUERY
454 : */
455 5 : guint64 bd_btrfs_get_default_subvolume_id (const gchar *mountpoint, GError **error) {
456 5 : GRegex *regex = NULL;
457 5 : GMatchInfo *match_info = NULL;
458 5 : gboolean success = FALSE;
459 5 : gchar *output = NULL;
460 5 : gchar *match = NULL;
461 5 : guint64 ret = 0;
462 5 : const gchar *argv[5] = {"btrfs", "subvol", "get-default", mountpoint, NULL};
463 :
464 10 : if (!check_deps (&avail_deps, DEPS_BTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error) ||
465 5 : !check_module_deps (&avail_module_deps, MODULE_DEPS_BTRFS_MASK, module_deps, MODULE_DEPS_LAST, &deps_check_lock, error))
466 0 : return 0;
467 :
468 5 : regex = g_regex_new ("ID (\\d+) .*", 0, 0, error);
469 5 : if (!regex) {
470 0 : bd_utils_log_format (BD_UTILS_LOG_WARNING, "Failed to create new GRegex");
471 : /* error is already populated */
472 0 : return 0;
473 : }
474 :
475 5 : success = bd_utils_exec_and_capture_output (argv, NULL, &output, error);
476 5 : if (!success) {
477 1 : g_regex_unref (regex);
478 1 : return 0;
479 : }
480 :
481 4 : success = g_regex_match (regex, output, 0, &match_info);
482 4 : if (!success) {
483 0 : g_set_error (error, BD_BTRFS_ERROR, BD_BTRFS_ERROR_PARSE, "Failed to parse subvolume's ID");
484 0 : g_regex_unref (regex);
485 0 : g_match_info_free (match_info);
486 0 : g_free (output);
487 0 : return 0;
488 : }
489 :
490 4 : match = g_match_info_fetch (match_info, 1);
491 4 : ret = g_ascii_strtoull (match, NULL, 0);
492 :
493 4 : g_free (match);
494 4 : g_match_info_free (match_info);
495 4 : g_regex_unref (regex);
496 4 : g_free (output);
497 :
498 4 : return ret;
499 : }
500 :
501 : /**
502 : * bd_btrfs_set_default_subvolume:
503 : * @mountpoint: mountpoint of the volume to set the default subvolume ID of
504 : * @subvol_id: ID of the subvolume to be set as the default subvolume
505 : * @extra: (nullable) (array zero-terminated=1): extra options for the setting (right now
506 : * passed to the 'btrfs' utility)
507 : * @error: (out) (optional): place to store error (if any)
508 : *
509 : * Returns: whether the @mountpoint volume's default subvolume was correctly set
510 : * to @subvol_id or not
511 : *
512 : * Tech category: %BD_BTRFS_TECH_SUBVOL-%BD_BTRFS_TECH_MODE_MODIFY
513 : */
514 2 : gboolean bd_btrfs_set_default_subvolume (const gchar *mountpoint, guint64 subvol_id, const BDExtraArg **extra, GError **error) {
515 2 : const gchar *argv[6] = {"btrfs", "subvol", "set-default", NULL, mountpoint, NULL};
516 2 : gboolean ret = FALSE;
517 :
518 4 : if (!check_deps (&avail_deps, DEPS_BTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error) ||
519 2 : !check_module_deps (&avail_module_deps, MODULE_DEPS_BTRFS_MASK, module_deps, MODULE_DEPS_LAST, &deps_check_lock, error))
520 0 : return FALSE;
521 :
522 2 : argv[3] = g_strdup_printf ("%"G_GUINT64_FORMAT, subvol_id);
523 2 : ret = bd_utils_exec_and_report_error (argv, extra, error);
524 2 : g_free ((gchar *) argv[3]);
525 :
526 2 : return ret;
527 : }
528 :
529 : /**
530 : * bd_btrfs_create_snapshot:
531 : * @source: path to source subvolume
532 : * @dest: path to new snapshot volume
533 : * @ro: whether the snapshot should be read-only
534 : * @extra: (nullable) (array zero-terminated=1): extra options for the snapshot creation (right now
535 : * passed to the 'btrfs' utility)
536 : * @error: (out) (optional): place to store error (if any)
537 : *
538 : * Returns: whether the @dest snapshot of @source was successfully created or not
539 : *
540 : * Tech category: %BD_BTRFS_TECH_SNAPSHOT-%BD_BTRFS_TECH_MODE_CREATE
541 : */
542 2 : gboolean bd_btrfs_create_snapshot (const gchar *source, const gchar *dest, gboolean ro, const BDExtraArg **extra, GError **error) {
543 2 : const gchar *argv[7] = {"btrfs", "subvol", "snapshot", NULL, NULL, NULL, NULL};
544 2 : guint next_arg = 3;
545 :
546 4 : if (!check_deps (&avail_deps, DEPS_BTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error) ||
547 2 : !check_module_deps (&avail_module_deps, MODULE_DEPS_BTRFS_MASK, module_deps, MODULE_DEPS_LAST, &deps_check_lock, error))
548 0 : return FALSE;
549 :
550 2 : if (ro) {
551 1 : argv[next_arg] = "-r";
552 1 : next_arg++;
553 : }
554 2 : argv[next_arg] = source;
555 2 : next_arg++;
556 2 : argv[next_arg] = dest;
557 :
558 2 : return bd_utils_exec_and_report_error (argv, extra, error);
559 : }
560 :
561 : /**
562 : * bd_btrfs_list_devices:
563 : * @device: a device that is part of the queried btrfs volume
564 : * @error: (out) (optional): place to store error (if any)
565 : *
566 : * Returns: (array zero-terminated=1): information about the devices that are part of the btrfs volume
567 : * containing @device or %NULL in case of error
568 : *
569 : * Tech category: %BD_BTRFS_TECH_MULTI_DEV-%BD_BTRFS_TECH_MODE_QUERY
570 : */
571 12 : BDBtrfsDeviceInfo** bd_btrfs_list_devices (const gchar *device, GError **error) {
572 12 : const gchar *argv[5] = {"btrfs", "filesystem", "show", device, NULL};
573 12 : gchar *output = NULL;
574 12 : gboolean success = FALSE;
575 12 : gchar **lines = NULL;
576 12 : gchar **line_p = NULL;
577 12 : gchar const * const pattern = "devid[ \\t]+(?P<id>\\d+)[ \\t]+" \
578 : "size[ \\t]+(?P<size>\\S+)[ \\t]+" \
579 : "used[ \\t]+(?P<used>\\S+)[ \\t]+" \
580 : "path[ \\t]+(?P<path>\\S+)\n";
581 12 : GRegex *regex = NULL;
582 12 : GMatchInfo *match_info = NULL;
583 : GPtrArray *dev_infos;
584 :
585 24 : if (!check_deps (&avail_deps, DEPS_BTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error) ||
586 12 : !check_module_deps (&avail_module_deps, MODULE_DEPS_BTRFS_MASK, module_deps, MODULE_DEPS_LAST, &deps_check_lock, error))
587 0 : return NULL;
588 :
589 12 : regex = g_regex_new (pattern, G_REGEX_EXTENDED, 0, error);
590 12 : if (!regex) {
591 0 : bd_utils_log_format (BD_UTILS_LOG_WARNING, "Failed to create new GRegex");
592 : /* error is already populated */
593 0 : return NULL;
594 : }
595 :
596 12 : success = bd_utils_exec_and_capture_output (argv, NULL, &output, error);
597 12 : if (!success) {
598 0 : g_regex_unref (regex);
599 : /* error is already populated from the previous call */
600 0 : return NULL;
601 : }
602 :
603 12 : lines = g_strsplit (output, "\n", 0);
604 12 : g_free (output);
605 :
606 12 : dev_infos = g_ptr_array_new ();
607 66 : for (line_p = lines; *line_p; line_p++) {
608 54 : success = g_regex_match (regex, *line_p, 0, &match_info);
609 54 : if (!success) {
610 36 : g_match_info_free (match_info);
611 36 : continue;
612 : }
613 :
614 18 : g_ptr_array_add (dev_infos, get_device_info_from_match (match_info));
615 18 : g_match_info_free (match_info);
616 : }
617 :
618 12 : g_strfreev (lines);
619 12 : g_regex_unref (regex);
620 :
621 12 : if (dev_infos->len == 0) {
622 0 : g_set_error (error, BD_BTRFS_ERROR, BD_BTRFS_ERROR_PARSE, "Failed to parse information about devices");
623 0 : g_ptr_array_free (dev_infos, TRUE);
624 0 : return NULL;
625 : }
626 :
627 12 : g_ptr_array_add (dev_infos, NULL);
628 12 : return (BDBtrfsDeviceInfo **) g_ptr_array_free (dev_infos, FALSE);
629 : }
630 :
631 : /**
632 : * bd_btrfs_list_subvolumes:
633 : * @mountpoint: a mountpoint of the queried btrfs volume
634 : * @snapshots_only: whether to list only snapshot subvolumes or not
635 : * @error: (out) (optional): place to store error (if any)
636 : *
637 : * Returns: (array zero-terminated=1): information about the subvolumes that are part of the btrfs volume
638 : * mounted at @mountpoint or %NULL in case of error
639 : *
640 : * The subvolumes are sorted in a way that no child subvolume appears in the
641 : * list before its parent (sub)volume.
642 : *
643 : * Tech category: %BD_BTRFS_TECH_SUBVOL-%BD_BTRFS_TECH_MODE_QUERY
644 : */
645 14 : BDBtrfsSubvolumeInfo** bd_btrfs_list_subvolumes (const gchar *mountpoint, gboolean snapshots_only, GError **error) {
646 14 : const gchar *argv[8] = {"btrfs", "subvol", "list", "-a", "-p", NULL, NULL, NULL};
647 14 : gchar *output = NULL;
648 14 : gboolean success = FALSE;
649 14 : gchar **lines = NULL;
650 14 : gchar **line_p = NULL;
651 14 : gchar const * const pattern = "ID\\s+(?P<id>\\d+)\\s+gen\\s+\\d+\\s+(cgen\\s+\\d+\\s+)?" \
652 : "parent\\s+(?P<parent_id>\\d+)\\s+top\\s+level\\s+\\d+\\s+" \
653 : "(otime\\s+(\\d{4}-\\d{2}-\\d{2}\\s+\\d\\d:\\d\\d:\\d\\d|-)\\s+)?"\
654 : "path\\s+(<FS_TREE>/)?(?P<path>.+)";
655 14 : GRegex *regex = NULL;
656 14 : GMatchInfo *match_info = NULL;
657 14 : guint64 i = 0;
658 14 : guint64 y = 0;
659 14 : guint64 next_sorted_idx = 0;
660 : GPtrArray *subvol_infos;
661 14 : BDBtrfsSubvolumeInfo* item = NULL;
662 14 : BDBtrfsSubvolumeInfo* swap_item = NULL;
663 14 : BDBtrfsSubvolumeInfo** ret = NULL;
664 14 : GError *l_error = NULL;
665 :
666 28 : if (!check_deps (&avail_deps, DEPS_BTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error) ||
667 14 : !check_module_deps (&avail_module_deps, MODULE_DEPS_BTRFS_MASK, module_deps, MODULE_DEPS_LAST, &deps_check_lock, error))
668 0 : return NULL;
669 :
670 14 : if (snapshots_only) {
671 4 : argv[5] = "-s";
672 4 : argv[6] = mountpoint;
673 : } else
674 10 : argv[5] = mountpoint;
675 :
676 14 : regex = g_regex_new (pattern, G_REGEX_EXTENDED, 0, error);
677 14 : if (!regex) {
678 0 : bd_utils_log_format (BD_UTILS_LOG_WARNING, "Failed to create new GRegex");
679 : /* error is already populated */
680 0 : return NULL;
681 : }
682 :
683 14 : success = bd_utils_exec_and_capture_output (argv, NULL, &output, &l_error);
684 14 : if (!success) {
685 4 : g_regex_unref (regex);
686 4 : if (g_error_matches (l_error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_NOOUT)) {
687 : /* no output -> no subvolumes */
688 4 : g_clear_error (&l_error);
689 4 : return g_new0 (BDBtrfsSubvolumeInfo*, 1);
690 : } else {
691 0 : g_propagate_error (error, l_error);
692 0 : return NULL;
693 : }
694 : }
695 :
696 10 : lines = g_strsplit (output, "\n", 0);
697 10 : g_free (output);
698 :
699 10 : subvol_infos = g_ptr_array_new ();
700 165 : for (line_p = lines; *line_p; line_p++) {
701 155 : success = g_regex_match (regex, *line_p, 0, &match_info);
702 155 : if (!success) {
703 10 : g_match_info_free (match_info);
704 10 : continue;
705 : }
706 :
707 145 : g_ptr_array_add (subvol_infos, get_subvolume_info_from_match (match_info));
708 145 : g_match_info_free (match_info);
709 : }
710 :
711 10 : g_strfreev (lines);
712 10 : g_regex_unref (regex);
713 :
714 10 : if (subvol_infos->len == 0) {
715 0 : g_set_error (error, BD_BTRFS_ERROR, BD_BTRFS_ERROR_PARSE, "Failed to parse information about subvolumes");
716 0 : g_ptr_array_free (subvol_infos, TRUE);
717 0 : return NULL;
718 : }
719 :
720 : /* now we know how much space to allocate for the result (subvols + NULL) */
721 10 : ret = g_new0 (BDBtrfsSubvolumeInfo*, subvol_infos->len + 1);
722 :
723 : /* we need to sort the subvolumes in a way that no child subvolume appears
724 : in the list before its parent (sub)volume */
725 :
726 : /* let's start by moving all top-level (sub)volumes to the beginning */
727 155 : for (i=0; i < subvol_infos->len; i++) {
728 145 : item = (BDBtrfsSubvolumeInfo*) g_ptr_array_index (subvol_infos, i);
729 145 : if (item->parent_id == BD_BTRFS_MAIN_VOLUME_ID)
730 : /* top-level (sub)volume */
731 13 : ret[next_sorted_idx++] = item;
732 : }
733 : /* top-level (sub)volumes are now processed */
734 23 : for (i=0; i < next_sorted_idx; i++)
735 13 : g_ptr_array_remove_fast (subvol_infos, ret[i]);
736 :
737 : /* now sort the rest in a way that we search for an already sorted parent or sibling */
738 142 : for (i=0; i < subvol_infos->len; i++) {
739 132 : item = (BDBtrfsSubvolumeInfo*) g_ptr_array_index (subvol_infos, i);
740 132 : ret[next_sorted_idx] = item;
741 : /* move the item towards beginning of the array checking if some parent
742 : or sibling has been already processed/sorted before or we reached the
743 : top-level (sub)volumes */
744 383 : for (y=next_sorted_idx; (y > 0 && (ret[y-1]->id != item->parent_id) && (ret[y-1]->parent_id != item->parent_id) && (ret[y-1]->parent_id != BD_BTRFS_MAIN_VOLUME_ID)); y--) {
745 251 : swap_item = ret[y-1];
746 251 : ret[y-1] = ret[y];
747 251 : ret[y] = swap_item;
748 : }
749 132 : next_sorted_idx++;
750 : }
751 10 : ret[next_sorted_idx] = NULL;
752 :
753 : /* now just free the pointer array */
754 10 : g_ptr_array_free (subvol_infos, TRUE);
755 :
756 10 : return ret;
757 : }
758 :
759 : /**
760 : * bd_btrfs_filesystem_info:
761 : * @device: a device that is part of the queried btrfs volume
762 : * @error: (out) (optional): place to store error (if any)
763 : *
764 : * Returns: information about the @device's volume's filesystem or %NULL in case of error
765 : *
766 : * Tech category: %BD_BTRFS_TECH_FS-%BD_BTRFS_TECH_MODE_QUERY
767 : */
768 3 : BDBtrfsFilesystemInfo* bd_btrfs_filesystem_info (const gchar *device, GError **error) {
769 3 : const gchar *argv[5] = {"btrfs", "filesystem", "show", device, NULL};
770 3 : gchar *output = NULL;
771 3 : gboolean success = FALSE;
772 3 : gchar const * const pattern = "Label:\\s+(none|'(?P<label>.+)')\\s+" \
773 : "uuid:\\s+(?P<uuid>\\S+)\\s+" \
774 : "Total\\sdevices\\s+(?P<num_devices>\\d+)\\s+" \
775 : "FS\\sbytes\\sused\\s+(?P<used>\\S+)";
776 3 : GRegex *regex = NULL;
777 3 : GMatchInfo *match_info = NULL;
778 3 : BDBtrfsFilesystemInfo *ret = NULL;
779 :
780 6 : if (!check_deps (&avail_deps, DEPS_BTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error) ||
781 3 : !check_module_deps (&avail_module_deps, MODULE_DEPS_BTRFS_MASK, module_deps, MODULE_DEPS_LAST, &deps_check_lock, error))
782 0 : return NULL;
783 :
784 3 : regex = g_regex_new (pattern, G_REGEX_EXTENDED, 0, error);
785 3 : if (!regex) {
786 0 : bd_utils_log_format (BD_UTILS_LOG_WARNING, "Failed to create new GRegex");
787 : /* error is already populated */
788 0 : return NULL;
789 : }
790 :
791 3 : success = bd_utils_exec_and_capture_output (argv, NULL, &output, error);
792 3 : if (!success) {
793 : /* error is already populated from the call above or just empty
794 : output */
795 0 : g_regex_unref (regex);
796 0 : return NULL;
797 : }
798 :
799 3 : success = g_regex_match (regex, output, 0, &match_info);
800 3 : if (!success) {
801 0 : g_regex_unref (regex);
802 0 : g_match_info_free (match_info);
803 0 : g_free (output);
804 0 : return NULL;
805 : }
806 :
807 3 : ret = get_filesystem_info_from_match (match_info);
808 3 : g_match_info_free (match_info);
809 3 : g_regex_unref (regex);
810 :
811 3 : g_free (output);
812 :
813 3 : return ret;
814 : }
815 :
816 : /**
817 : * bd_btrfs_mkfs:
818 : * @devices: (array zero-terminated=1): list of devices to create btrfs volume from
819 : * @label: (nullable): label for the volume
820 : * @data_level: (nullable): RAID level for the data or %NULL to use the default
821 : * @md_level: (nullable): RAID level for the metadata or %NULL to use the default
822 : * @extra: (nullable) (array zero-terminated=1): extra options for the volume creation (right now
823 : * passed to the 'btrfs' utility)
824 : * @error: (out) (optional): place to store error (if any)
825 : *
826 : * Returns: whether the new btrfs volume was created from @devices or not
827 : *
828 : * See mkfs.btrfs(8) for details about @data_level, @md_level and btrfs in general.
829 : *
830 : * Tech category: %BD_BTRFS_TECH_FS-%BD_BTRFS_TECH_MODE_CREATE
831 : */
832 7 : gboolean bd_btrfs_mkfs (const gchar **devices, const gchar *label, const gchar *data_level, const gchar *md_level, const BDExtraArg **extra, GError **error) {
833 7 : return bd_btrfs_create_volume (devices, label, data_level, md_level, extra, error);
834 : }
835 :
836 : /**
837 : * bd_btrfs_resize:
838 : * @mountpoint: a mountpoint of the to be resized btrfs filesystem
839 : * @size: requested new size
840 : * @extra: (nullable) (array zero-terminated=1): extra options for the volume resize (right now
841 : * passed to the 'btrfs' utility)
842 : * @error: (out) (optional): place to store error (if any)
843 : *
844 : * Returns: whether the @mountpoint filesystem was successfully resized to @size
845 : * or not
846 : *
847 : * Tech category: %BD_BTRFS_TECH_FS-%BD_BTRFS_TECH_MODE_MODIFY
848 : */
849 1 : gboolean bd_btrfs_resize (const gchar *mountpoint, guint64 size, const BDExtraArg **extra, GError **error) {
850 1 : const gchar *argv[6] = {"btrfs", "filesystem", "resize", NULL, mountpoint, NULL};
851 1 : gboolean ret = FALSE;
852 :
853 2 : if (!check_deps (&avail_deps, DEPS_BTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error) ||
854 1 : !check_module_deps (&avail_module_deps, MODULE_DEPS_BTRFS_MASK, module_deps, MODULE_DEPS_LAST, &deps_check_lock, error))
855 0 : return FALSE;
856 :
857 1 : argv[3] = g_strdup_printf ("%"G_GUINT64_FORMAT, size);
858 1 : ret = bd_utils_exec_and_report_error (argv, extra, error);
859 1 : g_free ((gchar *) argv[3]);
860 :
861 1 : return ret;
862 : }
863 :
864 : /**
865 : * bd_btrfs_check:
866 : * @device: a device that is part of the checked btrfs volume
867 : * @extra: (nullable) (array zero-terminated=1): extra options for the check (right now
868 : * passed to the 'btrfs' utility)
869 : * @error: (out) (optional): place to store error (if any)
870 : *
871 : * Returns: whether the filesystem was successfully checked or not
872 : *
873 : * Tech category: %BD_BTRFS_TECH_FS-%BD_BTRFS_TECH_MODE_QUERY
874 : */
875 1 : gboolean bd_btrfs_check (const gchar *device, const BDExtraArg **extra, GError **error) {
876 1 : const gchar *argv[4] = {"btrfs", "check", device, NULL};
877 :
878 2 : if (!check_deps (&avail_deps, DEPS_BTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error) ||
879 1 : !check_module_deps (&avail_module_deps, MODULE_DEPS_BTRFS_MASK, module_deps, MODULE_DEPS_LAST, &deps_check_lock, error))
880 0 : return FALSE;
881 :
882 1 : return bd_utils_exec_and_report_error (argv, extra, error);
883 : }
884 :
885 : /**
886 : * bd_btrfs_repair:
887 : * @device: a device that is part of the to be repaired btrfs volume
888 : * @extra: (nullable) (array zero-terminated=1): extra options for the repair (right now
889 : * passed to the 'btrfs' utility)
890 : * @error: (out) (optional): place to store error (if any)
891 : *
892 : * Returns: whether the filesystem was successfully checked and repaired or not
893 : *
894 : * Tech category: %BD_BTRFS_TECH_FS-%BD_BTRFS_TECH_MODE_MODIFY
895 : */
896 1 : gboolean bd_btrfs_repair (const gchar *device, const BDExtraArg **extra, GError **error) {
897 1 : const gchar *argv[5] = {"btrfs", "check", "--repair", device, NULL};
898 :
899 2 : if (!check_deps (&avail_deps, DEPS_BTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error) ||
900 1 : !check_module_deps (&avail_module_deps, MODULE_DEPS_BTRFS_MASK, module_deps, MODULE_DEPS_LAST, &deps_check_lock, error))
901 0 : return FALSE;
902 :
903 1 : return bd_utils_exec_and_report_error (argv, extra, error);
904 : }
905 :
906 : /**
907 : * bd_btrfs_change_label:
908 : * @mountpoint: a mountpoint of the btrfs filesystem to change label of
909 : * @label: new label for the filesystem
910 : * @extra: (nullable) (array zero-terminated=1): extra options for the volume creation (right now
911 : * passed to the 'btrfs' utility)
912 : * @error: (out) (optional): place to store error (if any)
913 : *
914 : * Returns: whether the label of the @mountpoint filesystem was successfully set
915 : * to @label or not
916 : *
917 : * Tech category: %BD_BTRFS_TECH_FS-%BD_BTRFS_TECH_MODE_MODIFY
918 : */
919 1 : gboolean bd_btrfs_change_label (const gchar *mountpoint, const gchar *label, GError **error) {
920 1 : const gchar *argv[6] = {"btrfs", "filesystem", "label", mountpoint, label, NULL};
921 :
922 2 : if (!check_deps (&avail_deps, DEPS_BTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error) ||
923 1 : !check_module_deps (&avail_module_deps, MODULE_DEPS_BTRFS_MASK, module_deps, MODULE_DEPS_LAST, &deps_check_lock, error))
924 0 : return FALSE;
925 :
926 1 : return bd_utils_exec_and_report_error (argv, NULL, error);
927 : }
|