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 <sys/types.h>
23 : #include <sys/stat.h>
24 : #include <fcntl.h>
25 : #include <stdio.h>
26 :
27 : #include "vfat.h"
28 : #include "fs.h"
29 : #include "common.h"
30 :
31 : static volatile guint avail_deps = 0;
32 : static GMutex deps_check_lock;
33 :
34 : #define DEPS_MKFSVFAT 0
35 : #define DEPS_MKFSVFAT_MASK (1 << DEPS_MKFSVFAT)
36 : #define DEPS_FATLABEL 1
37 : #define DEPS_FATLABEL_MASK (1 << DEPS_FATLABEL)
38 : #define DEPS_FSCKVFAT 2
39 : #define DEPS_FSCKVFAT_MASK (1 << DEPS_FSCKVFAT)
40 : #define DEPS_RESIZEVFAT 3
41 : #define DEPS_RESIZEVFAT_MASK (1 << DEPS_RESIZEVFAT)
42 : #define DEPS_FATLABELUUID 4
43 : #define DEPS_FATLABELUUID_MASK (1 << DEPS_FATLABELUUID)
44 :
45 : #define DEPS_LAST 5
46 :
47 : static const UtilDep deps[DEPS_LAST] = {
48 : {"mkfs.vfat", NULL, NULL, NULL},
49 : {"fatlabel", NULL, NULL, NULL},
50 : {"fsck.vfat", NULL, NULL, NULL},
51 : {"vfat-resize", NULL, NULL, NULL},
52 : {"fatlabel", "4.2", "--version", "fatlabel\\s+([\\d\\.]+).+"},
53 : };
54 :
55 : static guint32 fs_mode_util[BD_FS_MODE_LAST+1] = {
56 : DEPS_MKFSVFAT_MASK, /* mkfs */
57 : 0, /* wipe */
58 : DEPS_FSCKVFAT_MASK, /* check */
59 : DEPS_FSCKVFAT_MASK, /* repair */
60 : DEPS_FATLABEL_MASK, /* set-label */
61 : DEPS_FSCKVFAT_MASK, /* query */
62 : DEPS_RESIZEVFAT_MASK, /* resize */
63 : DEPS_FATLABELUUID_MASK, /* set-uuid */
64 : };
65 :
66 :
67 : #ifdef __clang__
68 : #define ZERO_INIT {}
69 : #else
70 : #define ZERO_INIT {0}
71 : #endif
72 :
73 : /**
74 : * bd_fs_vfat_is_tech_avail:
75 : * @tech: the queried tech
76 : * @mode: a bit mask of queried modes of operation (#BDFSTechMode) for @tech
77 : * @error: (out) (optional): place to store error (details about why the @tech-@mode combination is not available)
78 : *
79 : * Returns: whether the @tech-@mode combination is available -- supported by the
80 : * plugin implementation and having all the runtime dependencies available
81 : */
82 : G_GNUC_INTERNAL gboolean
83 9 : bd_fs_vfat_is_tech_avail (BDFSTech tech G_GNUC_UNUSED, guint64 mode, GError **error) {
84 9 : guint32 required = 0;
85 9 : guint i = 0;
86 :
87 81 : for (i = 0; i <= BD_FS_MODE_LAST; i++)
88 72 : if (mode & (1 << i))
89 14 : required |= fs_mode_util[i];
90 :
91 9 : return check_deps (&avail_deps, required, deps, DEPS_LAST, &deps_check_lock, error);
92 : }
93 :
94 : /**
95 : * bd_fs_vfat_info_copy: (skip)
96 : *
97 : * Creates a new copy of @data.
98 : */
99 0 : BDFSVfatInfo* bd_fs_vfat_info_copy (BDFSVfatInfo *data) {
100 0 : if (data == NULL)
101 0 : return NULL;
102 :
103 0 : BDFSVfatInfo *ret = g_new0 (BDFSVfatInfo, 1);
104 :
105 0 : ret->label = g_strdup (data->label);
106 0 : ret->uuid = g_strdup (data->uuid);
107 0 : ret->cluster_size = data->cluster_size;
108 0 : ret->cluster_count = data->cluster_count;
109 0 : ret->free_cluster_count = data->free_cluster_count;
110 :
111 0 : return ret;
112 : }
113 :
114 : /**
115 : * bd_fs_vfat_info_free: (skip)
116 : *
117 : * Frees @data.
118 : */
119 0 : void bd_fs_vfat_info_free (BDFSVfatInfo *data) {
120 0 : if (data == NULL)
121 0 : return;
122 :
123 0 : g_free (data->label);
124 0 : g_free (data->uuid);
125 0 : g_free (data);
126 : }
127 :
128 : /* we want to support vol ID in the "udev format", e.g. "2E24-EC82" */
129 11 : static gchar *_fix_uuid (const gchar *uuid) {
130 11 : gchar *new_uuid = NULL;
131 11 : size_t len = 0;
132 :
133 11 : len = strlen (uuid);
134 11 : if (len == 9 && uuid[4] == '-') {
135 4 : new_uuid = g_new0 (gchar, 9);
136 4 : memcpy (new_uuid, uuid, 4);
137 4 : memcpy (new_uuid + 4, uuid + 5, 4);
138 : } else
139 7 : new_uuid = g_strdup (uuid);
140 :
141 11 : return new_uuid;
142 : }
143 :
144 : G_GNUC_INTERNAL BDExtraArg **
145 4 : bd_fs_vfat_mkfs_options (BDFSMkfsOptions *options, const BDExtraArg **extra) {
146 4 : GPtrArray *options_array = g_ptr_array_new ();
147 4 : const BDExtraArg **extra_p = NULL;
148 : gchar *label;
149 4 : UtilDep dep = {"mkfs.vfat", "4.2", "--help", "mkfs.fat\\s+([\\d\\.]+).+"};
150 4 : gboolean new_vfat = FALSE;
151 4 : gchar *new_uuid = NULL;
152 :
153 4 : if (options->label && g_strcmp0 (options->label, "") != 0) {
154 : /* convert the label uppercase */
155 1 : label = g_ascii_strup (options->label, -1);
156 1 : g_ptr_array_add (options_array, bd_extra_arg_new ("-n", label));
157 1 : g_free (label);
158 : }
159 :
160 4 : if (options->uuid && g_strcmp0 (options->uuid, "") != 0) {
161 1 : new_uuid = _fix_uuid (options->uuid);
162 1 : g_ptr_array_add (options_array, bd_extra_arg_new ("-i", new_uuid));
163 1 : g_free (new_uuid);
164 : }
165 :
166 4 : if (options->force)
167 0 : g_ptr_array_add (options_array, bd_extra_arg_new ("-I", ""));
168 :
169 4 : if (options->no_pt) {
170 : /* only mkfs.vfat >= 4.2 (sometimes) creates the partition table */
171 1 : new_vfat = bd_utils_check_util_version (dep.name, dep.version,
172 : dep.ver_arg, dep.ver_regexp,
173 : NULL);
174 1 : if (new_vfat)
175 1 : g_ptr_array_add (options_array, bd_extra_arg_new ("--mbr=no", ""));
176 : }
177 :
178 4 : if (extra) {
179 6 : for (extra_p = extra; *extra_p; extra_p++)
180 3 : g_ptr_array_add (options_array, bd_extra_arg_copy ((BDExtraArg *) *extra_p));
181 : }
182 :
183 4 : g_ptr_array_add (options_array, NULL);
184 :
185 4 : return (BDExtraArg **) g_ptr_array_free (options_array, FALSE);
186 : }
187 :
188 : /**
189 : * bd_fs_vfat_mkfs:
190 : * @device: the device to create a new vfat fs on
191 : * @extra: (nullable) (array zero-terminated=1): extra options for the creation (right now
192 : * passed to the 'mkfs.vfat' utility)
193 : * @error: (out) (optional): place to store error (if any)
194 : *
195 : * Please remember that FAT labels should always be uppercase.
196 : *
197 : * Returns: whether a new vfat fs was successfully created on @device or not
198 : *
199 : * Tech category: %BD_FS_TECH_VFAT-%BD_FS_TECH_MODE_MKFS
200 : */
201 20 : gboolean bd_fs_vfat_mkfs (const gchar *device, const BDExtraArg **extra, GError **error) {
202 20 : const gchar *args[3] = {"mkfs.vfat", device, NULL};
203 :
204 20 : if (!check_deps (&avail_deps, DEPS_MKFSVFAT_MASK, deps, DEPS_LAST, &deps_check_lock, error))
205 0 : return FALSE;
206 :
207 20 : return bd_utils_exec_and_report_error (args, extra, error);
208 : }
209 :
210 : /**
211 : * bd_fs_vfat_check:
212 : * @device: the device containing the file system to check
213 : * @extra: (nullable) (array zero-terminated=1): extra options for the repair (right now
214 : * passed to the 'fsck.vfat' utility)
215 : * @error: (out) (optional): place to store error (if any)
216 : *
217 : * Returns: whether an vfat file system on the @device is clean or not
218 : *
219 : * Tech category: %BD_FS_TECH_VFAT-%BD_FS_TECH_MODE_CHECK
220 : */
221 2 : gboolean bd_fs_vfat_check (const gchar *device, const BDExtraArg **extra, GError **error) {
222 2 : const gchar *args[4] = {"fsck.vfat", "-n", device, NULL};
223 2 : gint status = 0;
224 2 : gboolean ret = FALSE;
225 :
226 2 : if (!check_deps (&avail_deps, DEPS_FSCKVFAT_MASK, deps, DEPS_LAST, &deps_check_lock, error))
227 0 : return FALSE;
228 :
229 2 : ret = bd_utils_exec_and_report_status_error (args, extra, &status, error);
230 2 : if (!ret && (status == 1)) {
231 : /* no error should be reported for exit code 1 -- Recoverable errors have been detected */
232 0 : g_clear_error (error);
233 : }
234 2 : return ret;
235 : }
236 :
237 : /**
238 : * bd_fs_vfat_repair:
239 : * @device: the device containing the file system to repair
240 : * @extra: (nullable) (array zero-terminated=1): extra options for the repair (right now
241 : * passed to the 'fsck.vfat' utility)
242 : * @error: (out) (optional): place to store error (if any)
243 : *
244 : * Returns: whether an vfat file system on the @device was successfully repaired
245 : * (if needed) or not (error is set in that case)
246 : *
247 : * Tech category: %BD_FS_TECH_VFAT-%BD_FS_TECH_MODE_REPAIR
248 : */
249 1 : gboolean bd_fs_vfat_repair (const gchar *device, const BDExtraArg **extra, GError **error) {
250 1 : const gchar *args[4] = {"fsck.vfat", "-a", device, NULL};
251 1 : gint status = 0;
252 1 : gboolean ret = FALSE;
253 :
254 1 : if (!check_deps (&avail_deps, DEPS_FSCKVFAT_MASK, deps, DEPS_LAST, &deps_check_lock, error))
255 0 : return FALSE;
256 :
257 1 : ret = bd_utils_exec_and_report_status_error (args, extra, &status, error);
258 1 : if (!ret) {
259 0 : if (status == 1) {
260 : /* exit code 1 can also mean "errors have been detected and corrected" so we need
261 : to run fsck again to make sure the filesystem is now clean */
262 0 : g_clear_error (error);
263 0 : ret = bd_utils_exec_and_report_status_error (args, extra, &status, error);
264 : } else
265 : /* FALSE and exit code other than 1 always means error */
266 0 : ret = FALSE;
267 : }
268 :
269 1 : return ret;
270 : }
271 :
272 : /**
273 : * bd_fs_vfat_set_label:
274 : * @device: the device containing the file system to set label for
275 : * @label: label to set
276 : * @error: (out) (optional): place to store error (if any)
277 : *
278 : * Returns: whether the label of vfat file system on the @device was
279 : * successfully set or not
280 : *
281 : * Tech category: %BD_FS_TECH_VFAT-%BD_FS_TECH_MODE_SET_LABEL
282 : */
283 3 : gboolean bd_fs_vfat_set_label (const gchar *device, const gchar *label, GError **error) {
284 3 : const gchar *args[4] = {"fatlabel", device, NULL, NULL};
285 3 : UtilDep dep = {"fatlabel", "4.2", "--version", "fatlabel\\s+([\\d\\.]+).+"};
286 3 : gchar *label_up = NULL;
287 3 : gboolean new_vfat = FALSE;
288 : gboolean ret;
289 :
290 3 : if (!check_deps (&avail_deps, DEPS_FATLABEL_MASK, deps, DEPS_LAST, &deps_check_lock, error))
291 0 : return FALSE;
292 :
293 3 : if (!label || g_strcmp0 (label, "") == 0) {
294 : /* fatlabel >= 4.2 refuses to set empty label */
295 1 : new_vfat = bd_utils_check_util_version (dep.name, dep.version,
296 : dep.ver_arg, dep.ver_regexp,
297 : NULL);
298 1 : if (new_vfat)
299 1 : args[2] = "--reset";
300 : }
301 :
302 : /* forcefully convert the label uppercase in case no reset was requested */
303 3 : if (label && args[2] == NULL) {
304 2 : label_up = g_ascii_strup (label, -1);
305 2 : args[2] = label_up;
306 : }
307 3 : ret = bd_utils_exec_and_report_error (args, NULL, error);
308 3 : g_free (label_up);
309 :
310 3 : return ret;
311 : }
312 :
313 : /**
314 : * bd_fs_vfat_check_label:
315 : * @label: label to check
316 : * @error: (out) (optional): place to store error
317 : *
318 : * Returns: whether @label is a valid label for the vfat file system or not
319 : * (reason is provided in @error)
320 : *
321 : * Tech category: always available
322 : */
323 2 : gboolean bd_fs_vfat_check_label (const gchar *label, GError **error) {
324 2 : const gchar *forbidden = "\"*/:<>?\\|";
325 : guint n;
326 :
327 2 : if (strlen (label) > 11) {
328 1 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_LABEL_INVALID,
329 : "Label for VFAT filesystem must be at most 11 characters long.");
330 1 : return FALSE;
331 : }
332 :
333 : /* VFAT does not allow some characters; as dosfslabel does not enforce this,
334 : * check in advance; also, VFAT only knows upper-case characters, dosfslabel
335 : * enforces this */
336 10 : for (n = 0; forbidden[n] != 0; n++)
337 9 : if (strchr (label, forbidden[n]) != NULL) {
338 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_LABEL_INVALID,
339 : "Invalid label: character '%c' not supported in VFAT labels.",
340 0 : forbidden[n]);
341 0 : return FALSE;
342 : }
343 :
344 1 : return TRUE;
345 : }
346 :
347 : /**
348 : * bd_fs_vfat_set_uuid:
349 : * @device: the device containing the file system to set uuid for
350 : * @uuid: (nullable): volume ID to set or %NULL to generate a new one
351 : * @error: (out) (optional): place to store error (if any)
352 : *
353 : * Returns: whether the volume ID of vfat file system on the @device was
354 : * successfully set or not
355 : *
356 : * Tech category: %BD_FS_TECH_VFAT-%BD_FS_TECH_MODE_SET_UUID
357 : */
358 5 : gboolean bd_fs_vfat_set_uuid (const gchar *device, const gchar *uuid, GError **error) {
359 5 : const gchar *args[5] = {"fatlabel", "-i", device, NULL, NULL};
360 5 : g_autofree gchar *new_uuid = NULL;
361 :
362 5 : if (!check_deps (&avail_deps, DEPS_FATLABELUUID_MASK, deps, DEPS_LAST, &deps_check_lock, error))
363 0 : return FALSE;
364 :
365 5 : if (!uuid || g_strcmp0 (uuid, "") == 0)
366 2 : args[3] = "--reset";
367 : else {
368 3 : new_uuid = _fix_uuid (uuid);
369 3 : args[3] = new_uuid;
370 : }
371 :
372 5 : return bd_utils_exec_and_report_error (args, NULL, error);
373 : }
374 :
375 : /**
376 : * bd_fs_vfat_check_uuid:
377 : * @uuid: UUID to check
378 : * @error: (out) (optional): place to store error
379 : *
380 : * Returns: whether @uuid is a valid UUID for the vfat file system or not
381 : * (reason is provided in @error)
382 : *
383 : * Tech category: always available
384 : */
385 7 : gboolean bd_fs_vfat_check_uuid (const gchar *uuid, GError **error) {
386 : guint64 vol_id;
387 7 : gchar *new_uuid = NULL;
388 7 : gchar *endptr = NULL;
389 :
390 7 : if (!uuid)
391 0 : return TRUE;
392 :
393 7 : new_uuid = _fix_uuid (uuid);
394 :
395 7 : vol_id = g_ascii_strtoull (new_uuid, &endptr, 16);
396 7 : if ((vol_id == 0 && endptr == new_uuid) || (endptr && *endptr)) {
397 2 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_UUID_INVALID,
398 : "UUID for VFAT filesystem must be a hexadecimal number.");
399 2 : g_free (new_uuid);
400 2 : return FALSE;
401 : }
402 :
403 5 : if (vol_id > G_MAXUINT32) {
404 1 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_UUID_INVALID,
405 : "UUID for VFAT filesystem must fit into 32 bits.");
406 1 : g_free (new_uuid);
407 1 : return FALSE;
408 : }
409 :
410 4 : g_free (new_uuid);
411 4 : return TRUE;
412 : }
413 :
414 : /**
415 : * bd_fs_vfat_get_info:
416 : * @device: the device containing the file system to get info for
417 : * @error: (out) (optional): place to store error (if any)
418 : *
419 : * Returns: (transfer full): information about the file system on @device or
420 : * %NULL in case of error
421 : *
422 : * Tech category: %BD_FS_TECH_VFAT-%BD_FS_TECH_MODE_QUERY
423 : */
424 19 : BDFSVfatInfo* bd_fs_vfat_get_info (const gchar *device, GError **error) {
425 19 : const gchar *args[4] = {"fsck.vfat", "-nv", device, NULL};
426 19 : gboolean success = FALSE;
427 19 : BDFSVfatInfo *ret = NULL;
428 19 : gchar *output = NULL;
429 19 : gchar **lines = NULL;
430 19 : gchar **line_p = NULL;
431 19 : gboolean have_cluster_size = FALSE;
432 19 : gboolean have_cluster_count = FALSE;
433 19 : guint64 full_cluster_count = 0;
434 19 : guint64 cluster_count = 0;
435 19 : gchar **key_val = NULL;
436 19 : gint scanned = 0;
437 :
438 19 : if (!check_deps (&avail_deps, DEPS_FSCKVFAT_MASK, deps, DEPS_LAST, &deps_check_lock, error))
439 0 : return NULL;
440 :
441 19 : ret = g_new0 (BDFSVfatInfo, 1);
442 :
443 19 : success = get_uuid_label (device, &(ret->uuid), &(ret->label), error);
444 19 : if (!success) {
445 : /* error is already populated */
446 0 : bd_fs_vfat_info_free (ret);
447 0 : return NULL;
448 : }
449 :
450 19 : success = bd_utils_exec_and_capture_output (args, NULL, &output, error);
451 19 : if (!success) {
452 : /* error is already populated */
453 0 : bd_fs_vfat_info_free (ret);
454 0 : return NULL;
455 : }
456 :
457 19 : lines = g_strsplit (output, "\n", 0);
458 19 : g_free (output);
459 399 : for (line_p=lines; *line_p && (!have_cluster_size || !have_cluster_count); line_p++) {
460 380 : if (!have_cluster_size && g_str_has_suffix (*line_p, "bytes per cluster")) {
461 19 : ret->cluster_size = g_ascii_strtoull (*line_p, NULL, 0);
462 19 : have_cluster_size = TRUE;
463 361 : } else if (!have_cluster_count && g_str_has_prefix (*line_p, device)) {
464 19 : key_val = g_strsplit (*line_p, ",", 2);
465 19 : scanned = sscanf (key_val[1], " %" G_GUINT64_FORMAT "/" "%" G_GUINT64_FORMAT " clusters",
466 : &full_cluster_count, &cluster_count);
467 19 : if (scanned != 2) {
468 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_FAIL,
469 : "Failed to get number of FAT clusters for '%s'", device);
470 0 : bd_fs_vfat_info_free (ret);
471 0 : g_strfreev (key_val);
472 0 : g_strfreev (lines);
473 0 : return NULL;
474 : }
475 19 : ret->cluster_count = cluster_count;
476 19 : ret->free_cluster_count = cluster_count - full_cluster_count;
477 19 : have_cluster_count = TRUE;
478 19 : g_strfreev (key_val);
479 : }
480 : }
481 19 : g_strfreev (lines);
482 :
483 19 : return ret;
484 : }
485 :
486 : /**
487 : * bd_fs_vfat_resize:
488 : * @device: the device the file system of which to resize
489 : * @new_size: new requested size for the file system (if 0, the file system is
490 : * adapted to the underlying block device)
491 : * @error: (out) (optional): place to store error (if any)
492 : *
493 : * Returns: whether the file system on @device was successfully resized or not
494 : *
495 : * Tech category: %BD_FS_TECH_VFAT-%BD_FS_TECH_MODE_RESIZE
496 : */
497 6 : gboolean bd_fs_vfat_resize (const gchar *device, guint64 new_size, GError **error) {
498 6 : g_autofree gchar *size_str = NULL;
499 6 : const gchar *args[4] = {"vfat-resize", device, NULL, NULL};
500 :
501 6 : if (!check_deps (&avail_deps, DEPS_RESIZEVFAT_MASK, deps, DEPS_LAST, &deps_check_lock, error))
502 0 : return FALSE;
503 :
504 6 : if (new_size != 0) {
505 4 : size_str = g_strdup_printf ("%"G_GUINT64_FORMAT, new_size);
506 4 : args[2] = size_str;
507 : }
508 :
509 6 : return bd_utils_exec_and_report_error (args, NULL, error);
510 : }
|