Line data Source code
1 : /*
2 : * Copyright (C) 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 <blockdev/utils.h>
21 : #include <check_deps.h>
22 : #include <string.h>
23 : #include <ctype.h>
24 :
25 : #include "xfs.h"
26 : #include "fs.h"
27 : #include "common.h"
28 :
29 : static volatile guint avail_deps = 0;
30 : static GMutex deps_check_lock;
31 :
32 : #define DEPS_MKFSXFS 0
33 : #define DEPS_MKFSXFS_MASK (1 << DEPS_MKFSXFS)
34 : #define DEPS_XFS_DB 1
35 : #define DEPS_XFS_DB_MASK (1 << DEPS_XFS_DB)
36 : #define DEPS_XFS_REPAIR 2
37 : #define DEPS_XFS_REPAIR_MASK (1 << DEPS_XFS_REPAIR)
38 : #define DEPS_XFS_ADMIN 3
39 : #define DEPS_XFS_ADMIN_MASK (1 << DEPS_XFS_ADMIN)
40 : #define DEPS_XFS_GROWFS 4
41 : #define DEPS_XFS_GROWFS_MASK (1 << DEPS_XFS_GROWFS)
42 :
43 : #define DEPS_LAST 5
44 :
45 : static const UtilDep deps[DEPS_LAST] = {
46 : {"mkfs.xfs", NULL, NULL, NULL},
47 : {"xfs_db", NULL, NULL, NULL},
48 : {"xfs_repair", NULL, NULL, NULL},
49 : {"xfs_admin", NULL, NULL, NULL},
50 : {"xfs_growfs", NULL, NULL, NULL},
51 : };
52 :
53 : static guint32 fs_mode_util[BD_FS_MODE_LAST+1] = {
54 : DEPS_MKFSXFS_MASK, /* mkfs */
55 : 0, /* wipe */
56 : DEPS_XFS_DB_MASK, /* check */
57 : DEPS_XFS_REPAIR_MASK, /* repair */
58 : DEPS_XFS_ADMIN_MASK, /* set-label */
59 : DEPS_XFS_ADMIN_MASK, /* query */
60 : DEPS_XFS_GROWFS_MASK, /* resize */
61 : DEPS_XFS_ADMIN_MASK /* set-uuid */
62 : };
63 :
64 :
65 : #ifdef __clang__
66 : #define ZERO_INIT {}
67 : #else
68 : #define ZERO_INIT {0}
69 : #endif
70 :
71 : /**
72 : * bd_fs_xfs_is_tech_avail:
73 : * @tech: the queried tech
74 : * @mode: a bit mask of queried modes of operation (#BDFSTechMode) for @tech
75 : * @error: (out) (optional): place to store error (details about why the @tech-@mode combination is not available)
76 : *
77 : * Returns: whether the @tech-@mode combination is available -- supported by the
78 : * plugin implementation and having all the runtime dependencies available
79 : */
80 : G_GNUC_INTERNAL gboolean
81 8 : bd_fs_xfs_is_tech_avail (BDFSTech tech G_GNUC_UNUSED, guint64 mode, GError **error) {
82 8 : guint32 required = 0;
83 8 : guint i = 0;
84 72 : for (i = 0; i <= BD_FS_MODE_LAST; i++)
85 64 : if (mode & (1 << i))
86 14 : required |= fs_mode_util[i];
87 :
88 8 : return check_deps (&avail_deps, required, deps, DEPS_LAST, &deps_check_lock, error);
89 : }
90 :
91 : /**
92 : * bd_fs_xfs_info_copy: (skip)
93 : *
94 : * Creates a new copy of @data.
95 : */
96 0 : BDFSXfsInfo* bd_fs_xfs_info_copy (BDFSXfsInfo *data) {
97 0 : if (data == NULL)
98 0 : return NULL;
99 :
100 0 : BDFSXfsInfo *ret = g_new0 (BDFSXfsInfo, 1);
101 :
102 0 : ret->label = g_strdup (data->label);
103 0 : ret->uuid = g_strdup (data->uuid);
104 0 : ret->block_size = data->block_size;
105 0 : ret->block_count = data->block_count;
106 :
107 0 : return ret;
108 : }
109 :
110 : /**
111 : * bd_fs_xfs_info_free: (skip)
112 : *
113 : * Frees @data.
114 : */
115 0 : void bd_fs_xfs_info_free (BDFSXfsInfo *data) {
116 0 : if (data == NULL)
117 0 : return;
118 :
119 0 : g_free (data->label);
120 0 : g_free (data->uuid);
121 0 : g_free (data);
122 : }
123 :
124 : G_GNUC_INTERNAL BDExtraArg **
125 8 : bd_fs_xfs_mkfs_options (BDFSMkfsOptions *options, const BDExtraArg **extra) {
126 8 : GPtrArray *options_array = g_ptr_array_new ();
127 8 : const BDExtraArg **extra_p = NULL;
128 8 : gchar *uuid_option = NULL;
129 :
130 8 : if (options->label && g_strcmp0 (options->label, "") != 0)
131 1 : g_ptr_array_add (options_array, bd_extra_arg_new ("-L", options->label));
132 :
133 8 : if (options->uuid && g_strcmp0 (options->uuid, "") != 0) {
134 1 : uuid_option = g_strdup_printf ("uuid=%s", options->uuid);
135 1 : g_ptr_array_add (options_array, bd_extra_arg_new ("-m", uuid_option));
136 1 : g_free (uuid_option);
137 : }
138 :
139 8 : if (options->dry_run)
140 2 : g_ptr_array_add (options_array, bd_extra_arg_new ("-N", ""));
141 :
142 8 : if (options->no_discard)
143 0 : g_ptr_array_add (options_array, bd_extra_arg_new ("-K", ""));
144 :
145 8 : if (options->force)
146 2 : g_ptr_array_add (options_array, bd_extra_arg_new ("-f", ""));
147 :
148 8 : if (extra) {
149 0 : for (extra_p = extra; *extra_p; extra_p++)
150 0 : g_ptr_array_add (options_array, bd_extra_arg_copy ((BDExtraArg *) *extra_p));
151 : }
152 :
153 8 : g_ptr_array_add (options_array, NULL);
154 :
155 8 : return (BDExtraArg **) g_ptr_array_free (options_array, FALSE);
156 : }
157 :
158 :
159 : /**
160 : * bd_fs_xfs_mkfs:
161 : * @device: the device to create a new xfs fs on
162 : * @extra: (nullable) (array zero-terminated=1): extra options for the creation (right now
163 : * passed to the 'mkfs.xfs' utility)
164 : * @error: (out) (optional): place to store error (if any)
165 : *
166 : * Returns: whether a new xfs fs was successfully created on @device or not
167 : *
168 : * Tech category: %BD_FS_TECH_XFS-%BD_FS_TECH_MODE_MKFS
169 : */
170 24 : gboolean bd_fs_xfs_mkfs (const gchar *device, const BDExtraArg **extra, GError **error) {
171 24 : const gchar *args[3] = {"mkfs.xfs", device, NULL};
172 :
173 24 : if (!check_deps (&avail_deps, DEPS_MKFSXFS_MASK, deps, DEPS_LAST, &deps_check_lock, error))
174 0 : return FALSE;
175 :
176 24 : return bd_utils_exec_and_report_error (args, extra, error);
177 : }
178 :
179 : /**
180 : * bd_fs_xfs_check:
181 : * @device: the device containing the file system to check
182 : * @extra: (nullable) (array zero-terminated=1): extra options for the repair (right now
183 : * passed to the 'xfs_repair' utility)
184 : * @error: (out) (optional): place to store error (if any)
185 : *
186 : * Returns: whether an xfs file system on the @device is clean or not
187 : *
188 : * Note: If the file system is mounted RW, it will always be reported as not
189 : * clean!
190 : *
191 : * Tech category: %BD_FS_TECH_XFS-%BD_FS_TECH_MODE_CHECK
192 : */
193 5 : gboolean bd_fs_xfs_check (const gchar *device, const BDExtraArg **extra, GError **error) {
194 5 : const gchar *args[4] = {"xfs_repair", "-n", device, NULL};
195 5 : gboolean ret = FALSE;
196 5 : GError *l_error = NULL;
197 :
198 5 : if (!check_deps (&avail_deps, DEPS_XFS_REPAIR_MASK, deps, DEPS_LAST, &deps_check_lock, error))
199 0 : return FALSE;
200 :
201 5 : ret = bd_utils_exec_and_report_error (args, extra, &l_error);
202 5 : if (!ret) {
203 0 : if (l_error && g_error_matches (l_error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED)) {
204 : /* non-zero exit status -> the fs is not clean, but not an error */
205 : /* TODO: should we check that the device exists and contains an XFS FS beforehand? */
206 0 : g_clear_error (&l_error);
207 : } else
208 0 : g_propagate_error (error, l_error);
209 : }
210 5 : return ret;
211 : }
212 :
213 : /**
214 : * bd_fs_xfs_repair:
215 : * @device: the device containing the file system to repair
216 : * @extra: (nullable) (array zero-terminated=1): extra options for the repair (right now
217 : * passed to the 'xfs_repair' utility)
218 : * @error: (out) (optional): place to store error (if any)
219 : *
220 : * Returns: whether an xfs file system on the @device was successfully repaired
221 : * (if needed) or not (error is set in that case)
222 : *
223 : * Tech category: %BD_FS_TECH_XFS-%BD_FS_TECH_MODE_REPAIR
224 : */
225 5 : gboolean bd_fs_xfs_repair (const gchar *device, const BDExtraArg **extra, GError **error) {
226 5 : const gchar *args[3] = {"xfs_repair", device, NULL};
227 :
228 5 : if (!check_deps (&avail_deps, DEPS_XFS_REPAIR_MASK, deps, DEPS_LAST, &deps_check_lock, error))
229 0 : return FALSE;
230 :
231 5 : return bd_utils_exec_and_report_error (args, extra, error);
232 : }
233 :
234 : /**
235 : * bd_fs_xfs_set_label:
236 : * @device: the device containing the file system to set label for
237 : * @label: label to set
238 : * @error: (out) (optional): place to store error (if any)
239 : *
240 : * Returns: whether the label of xfs file system on the @device was
241 : * successfully set or not
242 : *
243 : * Tech category: %BD_FS_TECH_XFS-%BD_FS_TECH_MODE_SET_LABEL
244 : */
245 5 : gboolean bd_fs_xfs_set_label (const gchar *device, const gchar *label, GError **error) {
246 5 : const gchar *args[5] = {"xfs_admin", "-L", label, device, NULL};
247 5 : if (!label || (strncmp (label, "", 1) == 0))
248 1 : args[2] = "--";
249 :
250 5 : if (!check_deps (&avail_deps, DEPS_XFS_ADMIN_MASK, deps, DEPS_LAST, &deps_check_lock, error))
251 0 : return FALSE;
252 :
253 5 : return bd_utils_exec_and_report_error (args, NULL, error);
254 : }
255 :
256 : /**
257 : * bd_fs_xfs_check_label:
258 : * @label: label to check
259 : * @error: (out) (optional): place to store error
260 : *
261 : * Returns: whether @label is a valid label for the xfs file system or not
262 : * (reason is provided in @error)
263 : *
264 : * Tech category: always available
265 : */
266 5 : gboolean bd_fs_xfs_check_label (const gchar *label, GError **error) {
267 5 : size_t len = 0;
268 :
269 5 : len = strlen (label);
270 5 : if (len > 12) {
271 2 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_LABEL_INVALID,
272 : "Label for XFS filesystem must be at most 12 characters long.");
273 2 : return FALSE;
274 : }
275 :
276 3 : if (strchr (label, ' ') != NULL) {
277 1 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_LABEL_INVALID,
278 : "Label for XFS filesystem cannot contain spaces.");
279 1 : return FALSE;
280 : }
281 :
282 2 : return TRUE;
283 : }
284 :
285 : /**
286 : * bd_fs_xfs_set_uuid:
287 : * @device: the device containing the file system to set uuid for
288 : * @uuid: (nullable): UUID to set %NULL to generate a new one
289 : * UUID can also be one of "nil" and "generate" to clear or
290 : * generate a new UUID
291 : * @error: (out) (optional): place to store error (if any)
292 : *
293 : * Returns: whether the UUID of xfs file system on the @device was
294 : * successfully set or not
295 : *
296 : * Tech category: %BD_FS_TECH_XFS-%BD_FS_TECH_MODE_SET_UUID
297 : */
298 6 : gboolean bd_fs_xfs_set_uuid (const gchar *device, const gchar *uuid, GError **error) {
299 6 : const gchar *args[5] = {"xfs_admin", "-U", uuid, device, NULL};
300 6 : if (!uuid)
301 2 : args[2] = "generate";
302 :
303 6 : if (!check_deps (&avail_deps, DEPS_XFS_ADMIN_MASK, deps, DEPS_LAST, &deps_check_lock, error))
304 0 : return FALSE;
305 :
306 6 : return bd_utils_exec_and_report_error (args, NULL, error);
307 : }
308 :
309 : /**
310 : * bd_fs_xfs_check_uuid:
311 : * @uuid: UUID to check
312 : * @error: (out) (optional): place to store error
313 : *
314 : * Returns: whether @uuid is a valid UUID for the xfs file system or not
315 : * (reason is provided in @error)
316 : *
317 : * Tech category: always available
318 : */
319 4 : gboolean bd_fs_xfs_check_uuid (const gchar *uuid, GError **error) {
320 4 : return check_uuid (uuid, error);
321 : }
322 :
323 : /**
324 : * bd_fs_xfs_get_info:
325 : * @device: the device containing the file system to get info for
326 : * @error: (out) (optional): place to store error (if any)
327 : *
328 : * Returns: (transfer full): information about the file system on @device or
329 : * %NULL in case of error
330 : *
331 : * Tech category: %BD_FS_TECH_XFS-%BD_FS_TECH_MODE_QUERY
332 : */
333 41 : BDFSXfsInfo* bd_fs_xfs_get_info (const gchar *device, GError **error) {
334 41 : const gchar *args[6] = ZERO_INIT;
335 41 : gboolean success = FALSE;
336 41 : gchar *output = NULL;
337 41 : BDFSXfsInfo *ret = NULL;
338 41 : gchar **lines = NULL;
339 41 : gchar **line_p = NULL;
340 41 : gchar *val_start = NULL;
341 41 : g_autofree gchar* mountpoint = NULL;
342 :
343 41 : if (!check_deps (&avail_deps, DEPS_XFS_ADMIN_MASK, deps, DEPS_LAST, &deps_check_lock, error))
344 0 : return NULL;
345 :
346 41 : ret = g_new0 (BDFSXfsInfo, 1);
347 :
348 41 : success = get_uuid_label (device, &(ret->uuid), &(ret->label), error);
349 41 : if (!success) {
350 : /* error is already populated */
351 0 : bd_fs_xfs_info_free (ret);
352 0 : return NULL;
353 : }
354 :
355 : /* It is important to use xfs_spaceman for a mounted filesystem
356 : since xfs_db might return old information. xfs_info would be
357 : able to do the job for us (running xfs_spaceman or xfs_db
358 : depending on whether the fs is mounted), but it doesn't pass
359 : "-r" to xfs_db, which is important to avoid spurious udev
360 : events just for reading information.
361 : */
362 :
363 41 : mountpoint = bd_fs_get_mountpoint (device, NULL);
364 41 : if (mountpoint) {
365 25 : args[0] = "xfs_spaceman";
366 25 : args[1] = "-c";
367 25 : args[2] = "info";
368 25 : args[3] = mountpoint;
369 25 : args[4] = NULL;
370 : } else {
371 16 : args[0] = "xfs_db";
372 16 : args[1] = "-r";
373 16 : args[2] = "-c";
374 16 : args[3] = "info";
375 16 : args[4] = device;
376 16 : args[5] = NULL;
377 : }
378 :
379 41 : success = bd_utils_exec_and_capture_output (args, NULL, &output, error);
380 41 : if (!success) {
381 : /* error is already populated */
382 0 : bd_fs_xfs_info_free (ret);
383 0 : return NULL;
384 : }
385 :
386 41 : lines = g_strsplit (output, "\n", 0);
387 41 : g_free (output);
388 41 : line_p = lines;
389 : /* find the beginning of the (data) section we are interested in */
390 246 : while (line_p && *line_p && !g_str_has_prefix (*line_p, "data"))
391 205 : line_p++;
392 41 : if (!line_p || !(*line_p)) {
393 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_PARSE, "Failed to parse xfs file system information");
394 0 : g_strfreev (lines);
395 0 : bd_fs_xfs_info_free (ret);
396 0 : return NULL;
397 : }
398 :
399 : /* extract data from something like this: "data = bsize=4096 blocks=262400, imaxpct=25" */
400 41 : val_start = strchr (*line_p, '=');
401 41 : if (!val_start) {
402 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_PARSE, "Failed to parse xfs file system information");
403 0 : g_strfreev (lines);
404 0 : bd_fs_xfs_info_free (ret);
405 0 : return NULL;
406 : }
407 41 : val_start++;
408 984 : while (isspace (*val_start))
409 943 : val_start++;
410 41 : if (g_str_has_prefix (val_start, "bsize")) {
411 41 : val_start = strchr (val_start, '=');
412 41 : if (!val_start) {
413 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_PARSE, "Failed to parse xfs file system information");
414 0 : g_strfreev (lines);
415 0 : bd_fs_xfs_info_free (ret);
416 0 : return NULL;
417 : }
418 41 : val_start++;
419 41 : ret->block_size = g_ascii_strtoull (val_start, NULL, 0);
420 : } else {
421 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_PARSE, "Failed to parse xfs file system information");
422 0 : g_strfreev (lines);
423 0 : bd_fs_xfs_info_free (ret);
424 0 : return NULL;
425 : }
426 328 : while (isdigit (*val_start) || isspace(*val_start))
427 287 : val_start++;
428 41 : if (g_str_has_prefix (val_start, "blocks")) {
429 41 : val_start = strchr (val_start, '=');
430 41 : if (!val_start) {
431 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_PARSE, "Failed to parse xfs file system information");
432 0 : g_strfreev (lines);
433 0 : bd_fs_xfs_info_free (ret);
434 0 : return NULL;
435 : }
436 41 : val_start++;
437 41 : ret->block_count = g_ascii_strtoull (val_start, NULL, 0);
438 : } else {
439 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_PARSE, "Failed to parse xfs file system information");
440 0 : g_strfreev (lines);
441 0 : bd_fs_xfs_info_free (ret);
442 0 : return NULL;
443 : }
444 41 : g_strfreev (lines);
445 :
446 41 : return ret;
447 : }
448 :
449 : /**
450 : * bd_fs_xfs_resize:
451 : * @mpoint: the mount point of the file system to resize
452 : * @new_size: new requested size for the file system *in file system blocks* (see bd_fs_xfs_get_info())
453 : * (if 0, the file system is adapted to the underlying block device)
454 : * @extra: (nullable) (array zero-terminated=1): extra options for the resize (right now
455 : * passed to the 'xfs_growfs' utility)
456 : * @error: (out) (optional): place to store error (if any)
457 : *
458 : * Returns: whether the file system mounted on @mpoint was successfully resized or not
459 : *
460 : * Tech category: %BD_FS_TECH_XFS-%BD_FS_TECH_MODE_RESIZE
461 : */
462 16 : gboolean bd_fs_xfs_resize (const gchar *mpoint, guint64 new_size, const BDExtraArg **extra, GError **error) {
463 16 : const gchar *args[5] = {"xfs_growfs", NULL, NULL, NULL, NULL};
464 16 : gchar *size_str = NULL;
465 16 : gboolean ret = FALSE;
466 :
467 16 : if (!check_deps (&avail_deps, DEPS_XFS_GROWFS_MASK, deps, DEPS_LAST, &deps_check_lock, error))
468 0 : return FALSE;
469 :
470 16 : if (new_size != 0) {
471 3 : args[1] = "-D";
472 : /* xfs_growfs doesn't understand bytes, just a number of blocks */
473 3 : size_str = g_strdup_printf ("%"G_GUINT64_FORMAT, new_size);
474 3 : args[2] = size_str;
475 3 : args[3] = mpoint;
476 : } else
477 13 : args[1] = mpoint;
478 :
479 16 : ret = bd_utils_exec_and_report_error (args, extra, error);
480 :
481 16 : g_free (size_str);
482 16 : return ret;
483 : }
|