Line data Source code
1 : /*
2 : * Copyright (C) 2020 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: Vojtech Trefny <vtrefny@redhat.com>
18 : */
19 :
20 : #include <blockdev/utils.h>
21 : #include <check_deps.h>
22 : #include <stdio.h>
23 :
24 : #include "btrfs.h"
25 : #include "fs.h"
26 : #include "common.h"
27 :
28 : static volatile guint avail_deps = 0;
29 : static GMutex deps_check_lock;
30 :
31 : #define DEPS_MKFSBTRFS 0
32 : #define DEPS_MKFSBTRFS_MASK (1 << DEPS_MKFSBTRFS)
33 : #define DEPS_BTRFSCK 1
34 : #define DEPS_BTRFSCK_MASK (1 << DEPS_BTRFSCK)
35 : #define DEPS_BTRFS 2
36 : #define DEPS_BTRFS_MASK (1 << DEPS_BTRFS)
37 : #define DEPS_BTRFSTUNE 3
38 : #define DEPS_BTRFSTUNE_MASK (1 << DEPS_BTRFSTUNE)
39 :
40 : #define DEPS_LAST 4
41 :
42 : static const UtilDep deps[DEPS_LAST] = {
43 : {"mkfs.btrfs", NULL, NULL, NULL},
44 : {"btrfsck", NULL, NULL, NULL},
45 : {"btrfs", NULL, NULL, NULL},
46 : {"btrfstune", NULL, NULL, NULL},
47 : };
48 :
49 : static guint32 fs_mode_util[BD_FS_MODE_LAST+1] = {
50 : DEPS_MKFSBTRFS_MASK, /* mkfs */
51 : 0, /* wipe */
52 : DEPS_BTRFSCK_MASK, /* check */
53 : DEPS_BTRFSCK_MASK, /* repair */
54 : DEPS_BTRFS_MASK, /* set-label */
55 : DEPS_BTRFS_MASK, /* query */
56 : DEPS_BTRFS_MASK, /* resize */
57 : DEPS_BTRFSTUNE_MASK, /* set-uuid */
58 : };
59 :
60 :
61 : /**
62 : * bd_fs_btrfs_is_tech_avail:
63 : * @tech: the queried tech
64 : * @mode: a bit mask of queried modes of operation (#BDFSTechMode) for @tech
65 : * @error: (out) (optional): place to store error (details about why the @tech-@mode combination is not available)
66 : *
67 : * Returns: whether the @tech-@mode combination is available -- supported by the
68 : * plugin implementation and having all the runtime dependencies available
69 : */
70 : G_GNUC_INTERNAL gboolean
71 92 : bd_fs_btrfs_is_tech_avail (BDFSTech tech G_GNUC_UNUSED, guint64 mode, GError **error) {
72 92 : guint32 required = 0;
73 92 : guint i = 0;
74 :
75 828 : for (i = 0; i <= BD_FS_MODE_LAST; i++)
76 736 : if (mode & (1 << i))
77 460 : required |= fs_mode_util[i];
78 :
79 92 : return check_deps (&avail_deps, required, deps, DEPS_LAST, &deps_check_lock, error);
80 : }
81 :
82 : /**
83 : * bd_fs_btrfs_info_copy: (skip)
84 : *
85 : * Creates a new copy of @data.
86 : */
87 0 : BDFSBtrfsInfo* bd_fs_btrfs_info_copy (BDFSBtrfsInfo *data) {
88 0 : if (data == NULL)
89 0 : return NULL;
90 :
91 0 : BDFSBtrfsInfo *ret = g_new0 (BDFSBtrfsInfo, 1);
92 :
93 0 : ret->label = g_strdup (data->label);
94 0 : ret->uuid = g_strdup (data->uuid);
95 0 : ret->size = data->size;
96 0 : ret->free_space = data->free_space;
97 :
98 0 : return ret;
99 : }
100 :
101 : /**
102 : * bd_fs_btrfs_info_free: (skip)
103 : *
104 : * Frees @data.
105 : */
106 0 : void bd_fs_btrfs_info_free (BDFSBtrfsInfo *data) {
107 0 : if (data == NULL)
108 0 : return;
109 :
110 0 : g_free (data->label);
111 0 : g_free (data->uuid);
112 0 : g_free (data);
113 : }
114 :
115 : G_GNUC_INTERNAL BDExtraArg **
116 6 : bd_fs_btrfs_mkfs_options (BDFSMkfsOptions *options, const BDExtraArg **extra) {
117 6 : GPtrArray *options_array = g_ptr_array_new ();
118 6 : const BDExtraArg **extra_p = NULL;
119 :
120 6 : if (options->label && g_strcmp0 (options->label, "") != 0)
121 1 : g_ptr_array_add (options_array, bd_extra_arg_new ("-L", options->label));
122 :
123 6 : if (options->uuid && g_strcmp0 (options->uuid, "") != 0)
124 1 : g_ptr_array_add (options_array, bd_extra_arg_new ("-U", options->uuid));
125 :
126 6 : if (options->no_discard)
127 0 : g_ptr_array_add (options_array, bd_extra_arg_new ("-K", ""));
128 :
129 6 : if (options->force)
130 2 : g_ptr_array_add (options_array, bd_extra_arg_new ("-f", ""));
131 :
132 6 : if (extra) {
133 0 : for (extra_p = extra; *extra_p; extra_p++)
134 0 : g_ptr_array_add (options_array, bd_extra_arg_copy ((BDExtraArg *) *extra_p));
135 : }
136 :
137 6 : g_ptr_array_add (options_array, NULL);
138 :
139 6 : return (BDExtraArg **) g_ptr_array_free (options_array, FALSE);
140 : }
141 :
142 : /**
143 : * bd_fs_btrfs_mkfs:
144 : * @device: the device to create a new btrfs fs on
145 : * @extra: (nullable) (array zero-terminated=1): extra options for the creation (right now
146 : * passed to the 'mkfs.btrfs' utility)
147 : * @error: (out) (optional): place to store error (if any)
148 : *
149 : * Returns: whether a new btrfs fs was successfully created on @device or not
150 : *
151 : * Tech category: %BD_FS_TECH_BTRFS-%BD_FS_TECH_MODE_MKFS
152 : *
153 : */
154 22 : gboolean bd_fs_btrfs_mkfs (const gchar *device, const BDExtraArg **extra, GError **error) {
155 22 : const gchar *args[3] = {"mkfs.btrfs", device, NULL};
156 :
157 22 : if (!check_deps (&avail_deps, DEPS_MKFSBTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error))
158 0 : return FALSE;
159 :
160 22 : return bd_utils_exec_and_report_error (args, extra, error);
161 : }
162 :
163 : /**
164 : * bd_fs_btrfs_check:
165 : * @device: the device containing the file system to check
166 : * @extra: (nullable) (array zero-terminated=1): extra options for the check (right now
167 : * passed to the 'btrfsck' utility)
168 : * @error: (out) (optional): place to store error (if any)
169 : *
170 : * Returns: whether the filesystem was successfully checked or not
171 : *
172 : * Tech category: %BD_FS_TECH_BTRFS-%BD_FS_TECH_MODE_CHECK
173 : */
174 3 : gboolean bd_fs_btrfs_check (const gchar *device, const BDExtraArg **extra, GError **error) {
175 3 : const gchar *argv[4] = {"btrfsck", device, NULL};
176 :
177 3 : if (!check_deps (&avail_deps, DEPS_BTRFSCK_MASK, deps, DEPS_LAST, &deps_check_lock, error))
178 0 : return FALSE;
179 :
180 3 : return bd_utils_exec_and_report_error (argv, extra, error);
181 : }
182 :
183 : /**
184 : * bd_fs_btrfs_repair:
185 : * @device: the device containing the file system to repair
186 : * @extra: (nullable) (array zero-terminated=1): extra options for the repair (right now
187 : * passed to the 'btrfsck' utility)
188 : * @error: (out) (optional): place to store error (if any)
189 : *
190 : * Returns: whether the filesystem was successfully checked and repaired or not
191 : *
192 : * Tech category: %BD_FS_TECH_BTRFS-%BD_FS_TECH_MODE_REPAIR
193 : */
194 3 : gboolean bd_fs_btrfs_repair (const gchar *device, const BDExtraArg **extra, GError **error) {
195 3 : const gchar *argv[5] = {"btrfsck", "--repair", device, NULL};
196 :
197 3 : if (!check_deps (&avail_deps, DEPS_BTRFSCK_MASK, deps, DEPS_LAST, &deps_check_lock, error))
198 0 : return FALSE;
199 :
200 3 : return bd_utils_exec_and_report_error (argv, extra, error);
201 : }
202 :
203 : /**
204 : * bd_fs_btrfs_set_label:
205 : * @mpoint: the mount point of the file system to set label for
206 : * @label: label to set
207 : * @error: (out) (optional): place to store error (if any)
208 : *
209 : * Returns: whether the label of btrfs file system on the @mpoint was
210 : * successfully set or not
211 : *
212 : * Note: This function is intended to be used for btrfs filesystem on a single device,
213 : * for more complicated setups use the btrfs plugin instead.
214 : *
215 : * Tech category: %BD_FS_TECH_BTRFS-%BD_FS_TECH_MODE_SET_LABEL
216 : */
217 5 : gboolean bd_fs_btrfs_set_label (const gchar *mpoint, const gchar *label, GError **error) {
218 5 : const gchar *argv[6] = {"btrfs", "filesystem", "label", mpoint, label, NULL};
219 :
220 5 : if (!check_deps (&avail_deps, DEPS_BTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error))
221 0 : return FALSE;
222 :
223 5 : return bd_utils_exec_and_report_error (argv, NULL, error);
224 : }
225 :
226 : /**
227 : * bd_fs_btrfs_check_label:
228 : * @label: label to check
229 : * @error: (out) (optional): place to store error
230 : *
231 : * Returns: whether @label is a valid label for the btrfs file system or not
232 : * (reason is provided in @error)
233 : *
234 : * Note: This function is intended to be used for btrfs filesystem on a single device,
235 : * for more complicated setups use the btrfs plugin instead.
236 : *
237 : * Tech category: always available
238 : */
239 5 : gboolean bd_fs_btrfs_check_label (const gchar *label, GError **error) {
240 5 : if (strlen (label) > 256) {
241 2 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_LABEL_INVALID,
242 : "Label for btrfs filesystem must be at most 256 characters long.");
243 2 : return FALSE;
244 : }
245 :
246 3 : if (strchr (label, '\n') != NULL) {
247 1 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_LABEL_INVALID,
248 : "Label for btrfs filesystem cannot contain new lines.");
249 1 : return FALSE;
250 : }
251 :
252 2 : return TRUE;
253 : }
254 :
255 : /**
256 : * bd_fs_btrfs_set_uuid:
257 : * @device: the device containing the file system to set the UUID (serial number) for
258 : * @uuid: (nullable): UUID to set or %NULL to generate a new one
259 : * @error: (out) (optional): place to store error (if any)
260 : *
261 : * Returns: whether the UUID of the btrfs file system on the @device was
262 : * successfully set or not
263 : *
264 : * Note: This function is intended to be used for btrfs filesystem on a single device,
265 : * for more complicated setups use the btrfs plugin instead.
266 : *
267 : * Tech category: %BD_FS_TECH_BTRFS-%BD_FS_TECH_MODE_SET_UUID
268 : */
269 4 : gboolean bd_fs_btrfs_set_uuid (const gchar *device, const gchar *uuid, GError **error) {
270 4 : const gchar *args[5] = {"btrfstune", NULL, NULL, NULL, NULL};
271 :
272 4 : if (!check_deps (&avail_deps, DEPS_BTRFSTUNE_MASK, deps, DEPS_LAST, &deps_check_lock, error))
273 0 : return FALSE;
274 :
275 4 : if (!uuid) {
276 2 : args[1] = "-u";
277 2 : args[2] = device;
278 : } else {
279 2 : args[1] = "-U";
280 2 : args[2] = uuid;
281 2 : args[3] = device;
282 : }
283 :
284 4 : return bd_utils_exec_with_input (args, "y\n", NULL, error);
285 : }
286 :
287 : /**
288 : * bd_fs_btrfs_check_uuid:
289 : * @uuid: UUID to check
290 : * @error: (out) (optional): place to store error
291 : *
292 : * Returns: whether @uuid is a valid UUID for the btrfs file system or not
293 : * (reason is provided in @error)
294 : *
295 : * Note: This function is intended to be used for btrfs filesystem on a single device,
296 : * for more complicated setups use the btrfs plugin instead.
297 : *
298 : * Tech category: always available
299 : */
300 3 : gboolean bd_fs_btrfs_check_uuid (const gchar *uuid, GError **error) {
301 3 : return check_uuid (uuid, error);
302 : }
303 :
304 : /**
305 : * bd_fs_btrfs_get_info:
306 : * @mpoint: a mountpoint of the btrfs filesystem to get information about
307 : * @error: (out) (optional): place to store error (if any)
308 : *
309 : * Returns: (transfer full): information about the file system on @device or
310 : * %NULL in case of error
311 : *
312 : * Note: This function WON'T WORK for multi device btrfs filesystems,
313 : * for more complicated setups use the btrfs plugin instead.
314 : *
315 : * Tech category: %BD_FS_TECH_BTRFS-%BD_FS_TECH_MODE_QUERY
316 : */
317 34 : BDFSBtrfsInfo* bd_fs_btrfs_get_info (const gchar *mpoint, GError **error) {
318 34 : const gchar *argv[6] = {"btrfs", "filesystem", "show", "--raw", mpoint, NULL};
319 34 : g_autofree gchar *output = NULL;
320 34 : gboolean success = FALSE;
321 34 : gchar const * const pattern = "Label:\\s+(none|'(?P<label>.+)')\\s+" \
322 : "uuid:\\s+(?P<uuid>\\S+)\\s+" \
323 : "Total\\sdevices\\s+(?P<num_devices>\\d+)\\s+" \
324 : "FS\\sbytes\\sused\\s+(?P<used>\\S+)\\s+" \
325 : "devid\\s+1\\s+size\\s+(?P<size>\\S+)\\s+\\S+";
326 34 : GRegex *regex = NULL;
327 34 : GMatchInfo *match_info = NULL;
328 34 : BDFSBtrfsInfo *ret = NULL;
329 34 : g_autofree gchar *item = NULL;
330 34 : guint64 num_devices = 0;
331 34 : guint64 min_size = 0;
332 34 : gint scanned = 0;
333 :
334 34 : if (!check_deps (&avail_deps, DEPS_BTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error))
335 0 : return NULL;
336 :
337 34 : regex = g_regex_new (pattern, G_REGEX_EXTENDED, 0, error);
338 34 : if (!regex) {
339 0 : bd_utils_log_format (BD_UTILS_LOG_WARNING, "Failed to create new GRegex");
340 : /* error is already populated */
341 0 : return NULL;
342 : }
343 :
344 34 : success = bd_utils_exec_and_capture_output (argv, NULL, &output, error);
345 34 : if (!success) {
346 : /* error is already populated from the call above or just empty
347 : output */
348 0 : g_regex_unref (regex);
349 0 : return NULL;
350 : }
351 :
352 34 : success = g_regex_match (regex, output, 0, &match_info);
353 34 : if (!success) {
354 0 : g_regex_unref (regex);
355 0 : g_match_info_free (match_info);
356 0 : return NULL;
357 : }
358 :
359 34 : ret = g_new (BDFSBtrfsInfo, 1);
360 :
361 34 : ret->label = g_match_info_fetch_named (match_info, "label");
362 34 : ret->uuid = g_match_info_fetch_named (match_info, "uuid");
363 :
364 34 : item = g_match_info_fetch_named (match_info, "num_devices");
365 34 : num_devices = g_ascii_strtoull (item, NULL, 0);
366 34 : if (num_devices != 1) {
367 2 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_FAIL,
368 : "Btrfs filesystem mounted on %s spans multiple devices (%"G_GUINT64_FORMAT")." \
369 : "Filesystem plugin is not suitable for multidevice Btrfs volumes, please use " \
370 : "Btrfs plugin instead.", mpoint, num_devices);
371 2 : g_match_info_free (match_info);
372 2 : g_regex_unref (regex);
373 2 : bd_fs_btrfs_info_free (ret);
374 2 : return NULL;
375 : }
376 :
377 32 : item = g_match_info_fetch_named (match_info, "size");
378 32 : ret->size = g_ascii_strtoull (item, NULL, 0);
379 :
380 32 : g_match_info_free (match_info);
381 32 : g_regex_unref (regex);
382 :
383 32 : argv[1] = "inspect-internal";
384 32 : argv[2] = "min-dev-size";
385 32 : argv[3] = mpoint;
386 32 : argv[4] = NULL;
387 :
388 32 : success = bd_utils_exec_and_capture_output (argv, NULL, &output, error);
389 32 : if (!success) {
390 : /* error is already populated from the call above or just empty
391 : output */
392 0 : bd_fs_btrfs_info_free (ret);
393 0 : return NULL;
394 : }
395 :
396 : /* 114032640 bytes (108.75MiB) */
397 32 : scanned = sscanf (output, " %" G_GUINT64_FORMAT " bytes", &min_size);
398 32 : if (scanned != 1) {
399 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_PARSE,
400 : "Failed to parse btrfs filesystem min size.");
401 0 : bd_fs_btrfs_info_free (ret);
402 0 : return NULL;
403 : }
404 :
405 32 : ret->free_space = ret->size - min_size;
406 :
407 32 : return ret;
408 : }
409 :
410 : /**
411 : * bd_fs_btrfs_resize:
412 : * @mpoint: a mountpoint of the to be resized btrfs filesystem
413 : * @new_size: requested new size
414 : * @extra: (nullable) (array zero-terminated=1): extra options for the volume resize (right now
415 : * passed to the 'btrfs' utility)
416 : * @error: (out) (optional): place to store error (if any)
417 : *
418 : * Returns: whether the @mpoint filesystem was successfully resized to @new_size
419 : * or not
420 : *
421 : * Note: This function WON'T WORK for multi device btrfs filesystems,
422 : * for more complicated setups use the btrfs plugin instead.
423 : *
424 : * Tech category: %BD_BTRFS_TECH_FS-%BD_BTRFS_TECH_MODE_MODIFY
425 : */
426 9 : gboolean bd_fs_btrfs_resize (const gchar *mpoint, guint64 new_size, const BDExtraArg **extra, GError **error) {
427 9 : const gchar *argv[6] = {"btrfs", "filesystem", "resize", NULL, mpoint, NULL};
428 9 : gboolean ret = FALSE;
429 9 : BDFSBtrfsInfo *info = NULL;
430 :
431 9 : if (!check_deps (&avail_deps, DEPS_BTRFS_MASK, deps, DEPS_LAST, &deps_check_lock, error))
432 0 : return FALSE;
433 :
434 : /* we don't want to allow resizing multidevice btrfs volumes and get_info
435 : returns error for these so just try to get the info here */
436 9 : info = bd_fs_btrfs_get_info (mpoint, error);
437 9 : if (!info)
438 1 : return FALSE;
439 8 : bd_fs_btrfs_info_free (info);
440 :
441 8 : if (new_size == 0)
442 3 : argv[3] = g_strdup ("max");
443 : else
444 5 : argv[3] = g_strdup_printf ("%"G_GUINT64_FORMAT, new_size);
445 :
446 8 : ret = bd_utils_exec_and_report_error (argv, extra, error);
447 8 : g_free ((gchar *) argv[3]);
448 :
449 8 : return ret;
450 : }
|