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 <sys/types.h>
23 : #include <sys/stat.h>
24 : #include <fcntl.h>
25 : #include <string.h>
26 : #include <errno.h>
27 : #include <sys/ioctl.h>
28 : #include <linux/fs.h>
29 :
30 : #include "udf.h"
31 : #include "fs.h"
32 : #include "common.h"
33 :
34 : static volatile guint avail_deps = 0;
35 : static GMutex deps_check_lock;
36 :
37 : #define DEPS_MKUDFFS 0
38 : #define DEPS_MKUDFFS_MASK (1 << DEPS_MKUDFFS)
39 : #define DEPS_UDFLABEL 1
40 : #define DEPS_UDFLABEL_MASK (1 << DEPS_UDFLABEL)
41 : #define DEPS_UDFINFO 2
42 : #define DEPS_UDFINFO_MASK (1 << DEPS_UDFINFO)
43 :
44 : #define DEPS_LAST 3
45 :
46 : static const UtilDep deps[DEPS_LAST] = {
47 : {"mkudffs", NULL, NULL, NULL},
48 : {"udflabel", NULL, NULL, NULL},
49 : {"udfinfo", NULL, NULL, NULL},
50 : };
51 :
52 : static guint32 fs_mode_util[BD_FS_MODE_LAST+1] = {
53 : DEPS_MKUDFFS_MASK, /* mkfs */
54 : 0, /* wipe */
55 : 0, /* check */
56 : 0, /* repair */
57 : DEPS_UDFLABEL_MASK, /* set-label */
58 : DEPS_UDFINFO_MASK, /* query */
59 : 0, /* resize */
60 : DEPS_UDFLABEL_MASK, /* set-uuid */
61 : };
62 :
63 :
64 : /**
65 : * bd_fs_udf_is_tech_avail:
66 : * @tech: the queried tech
67 : * @mode: a bit mask of queried modes of operation (#BDFSTechMode) for @tech
68 : * @error: (out) (optional): place to store error (details about why the @tech-@mode combination is not available)
69 : *
70 : * Returns: whether the @tech-@mode combination is available -- supported by the
71 : * plugin implementation and having all the runtime dependencies available
72 : */
73 : G_GNUC_INTERNAL gboolean
74 92 : bd_fs_udf_is_tech_avail (BDFSTech tech G_GNUC_UNUSED, guint64 mode, GError **error) {
75 92 : guint32 required = 0;
76 92 : guint i = 0;
77 :
78 92 : if (mode & BD_FS_TECH_MODE_CHECK || mode & BD_FS_TECH_MODE_REPAIR) {
79 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_TECH_UNAVAIL,
80 : "UDF doesn't support checking and reparing.");
81 0 : return FALSE;
82 92 : } else if (mode & BD_FS_TECH_MODE_RESIZE) {
83 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_TECH_UNAVAIL,
84 : "UDF currently doesn't support resizing.");
85 0 : return FALSE;
86 : }
87 :
88 828 : for (i = 0; i <= BD_FS_MODE_LAST; i++)
89 736 : if (mode & (1 << i))
90 184 : required |= fs_mode_util[i];
91 :
92 92 : return check_deps (&avail_deps, required, deps, DEPS_LAST, &deps_check_lock, error);
93 : }
94 :
95 : /**
96 : * bd_fs_udf_info_copy: (skip)
97 : *
98 : * Creates a new copy of @data.
99 : */
100 0 : BDFSUdfInfo* bd_fs_udf_info_copy (BDFSUdfInfo *data) {
101 0 : if (data == NULL)
102 0 : return NULL;
103 :
104 0 : BDFSUdfInfo *ret = g_new0 (BDFSUdfInfo, 1);
105 :
106 0 : ret->label = g_strdup (data->label);
107 0 : ret->uuid = g_strdup (data->uuid);
108 0 : ret->revision = g_strdup (data->revision);
109 0 : ret->lvid = g_strdup (data->lvid);
110 0 : ret->vid = g_strdup (data->vid);
111 0 : ret->block_size = data->block_size;
112 0 : ret->block_count = data->block_count;
113 0 : ret->free_blocks = data->free_blocks;
114 :
115 0 : return ret;
116 : }
117 :
118 : /**
119 : * bd_fs_udf_info_free: (skip)
120 : *
121 : * Frees @data.
122 : */
123 0 : void bd_fs_udf_info_free (BDFSUdfInfo *data) {
124 0 : if (data == NULL)
125 0 : return;
126 :
127 0 : g_free (data->label);
128 0 : g_free (data->uuid);
129 0 : g_free (data->revision);
130 0 : g_free (data->lvid);
131 0 : g_free (data->vid);
132 0 : g_free (data);
133 : }
134 :
135 : /* get a valid UDF Volume Identifier from label */
136 9 : static gchar* get_vid (const gchar *label) {
137 9 : gchar *vid = NULL;
138 9 : const gchar *next_p = NULL;
139 : gunichar unichar;
140 9 : guint pos = 0;
141 :
142 9 : if (!g_utf8_validate (label, -1, NULL))
143 0 : return NULL;
144 :
145 9 : if (g_utf8_strlen (label, -1) <= 15)
146 4 : vid = g_strdup (label);
147 : else {
148 : /* vid can be at most 30 characters (or 15 > 0xFF) so we'll truncate the label if needed */
149 5 : next_p = label;
150 320 : while (next_p && *next_p) {
151 317 : unichar = g_utf8_get_char (next_p);
152 317 : if (unichar > 0xFF) {
153 3 : if (pos < 15) {
154 : /* vid can have at most 15 characters > 0xFF */
155 2 : vid = g_utf8_substring (label, 0, 15);
156 2 : break;
157 1 : } else if (pos < 30) {
158 : /* cut before the "problematic" character */
159 0 : vid = g_utf8_substring (label, 0, pos);
160 0 : break;
161 : }
162 : }
163 :
164 315 : next_p = g_utf8_next_char (next_p);
165 315 : pos++;
166 : }
167 :
168 5 : if (!vid) {
169 : /* we can't have more that 30 characters in vid so cut at 30 */
170 3 : vid = g_utf8_substring (label, 0, 30);
171 : }
172 : }
173 :
174 9 : return vid;
175 : }
176 :
177 : G_GNUC_INTERNAL BDExtraArg **
178 2 : bd_fs_udf_mkfs_options (BDFSMkfsOptions *options, const BDExtraArg **extra) {
179 2 : GPtrArray *options_array = g_ptr_array_new ();
180 2 : const BDExtraArg **extra_p = NULL;
181 2 : g_autofree gchar *vid = NULL;
182 :
183 2 : if (options->label && g_strcmp0 (options->label, "") != 0) {
184 1 : vid = get_vid (options->label);
185 :
186 1 : g_ptr_array_add (options_array, bd_extra_arg_new ("--lvid", options->label));
187 1 : g_ptr_array_add (options_array, bd_extra_arg_new ("--vid", vid));
188 : }
189 :
190 2 : if (options->uuid && g_strcmp0 (options->uuid, "") != 0)
191 0 : g_ptr_array_add (options_array, bd_extra_arg_new ("-u", options->uuid));
192 :
193 2 : if (extra) {
194 0 : for (extra_p = extra; *extra_p; extra_p++)
195 0 : g_ptr_array_add (options_array, bd_extra_arg_copy ((BDExtraArg *) *extra_p));
196 : }
197 :
198 2 : g_ptr_array_add (options_array, NULL);
199 :
200 2 : return (BDExtraArg **) g_ptr_array_free (options_array, FALSE);
201 : }
202 :
203 11 : static gint get_blocksize (const gchar *device, GError **error) {
204 11 : gint fd = -1;
205 11 : gint blksize = 0;
206 :
207 11 : fd = open (device, O_RDONLY);
208 11 : if (fd < 0) {
209 1 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_FAIL,
210 : "Failed to open the device '%s' to get its block size: %s",
211 1 : device, strerror_l (errno, _C_LOCALE));
212 1 : return -1;
213 : }
214 :
215 10 : if (ioctl (fd, BLKSSZGET, &blksize) < 0) {
216 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_FAIL,
217 : "Failed to get block size of the device '%s': %s",
218 0 : device, strerror_l (errno, _C_LOCALE));
219 0 : close (fd);
220 0 : return -1;
221 : }
222 :
223 10 : close (fd);
224 :
225 10 : return blksize;
226 : }
227 :
228 : /**
229 : * bd_fs_udf_mkfs:
230 : * @device: the device to create a new UDF fs on
231 : * @media_type: (nullable): specify the media type or %NULL for default ('hd')
232 : * @revision: (nullable): UDF revision to use or %NULL for default ('2.01')
233 : * @block_size: block size in bytes or 0 for auto detection (device logical block size)
234 : * @extra: (nullable) (array zero-terminated=1): extra options for the creation (right now
235 : * passed to the 'mkudffs' utility)
236 : * @error: (out) (optional): place to store error (if any)
237 : *
238 : * Returns: whether a new UDF fs was successfully created on @device or not
239 : *
240 : * Tech category: %BD_FS_TECH_UDF-%BD_FS_TECH_MODE_MKFS
241 : */
242 12 : gboolean bd_fs_udf_mkfs (const gchar *device, const gchar *media_type, gchar *revision, guint64 block_size, const BDExtraArg **extra, GError **error) {
243 12 : const gchar *args[7] = {"mkudffs", "--utf8", NULL, NULL, NULL, device, NULL};
244 12 : gint detected_bs = -1;
245 12 : gboolean ret = FALSE;
246 :
247 12 : if (!check_deps (&avail_deps, DEPS_MKUDFFS_MASK, deps, DEPS_LAST, &deps_check_lock, error))
248 0 : return FALSE;
249 :
250 12 : if (block_size != 0)
251 1 : args[2] = g_strdup_printf ("--blocksize=%"G_GUINT64_FORMAT"", block_size);
252 : else {
253 11 : detected_bs = get_blocksize (device, error);
254 11 : if (detected_bs < 0)
255 1 : return FALSE;
256 :
257 10 : args[2] = g_strdup_printf ("--blocksize=%d", detected_bs);
258 : }
259 :
260 11 : if (media_type)
261 1 : args[3] = g_strdup_printf ("--media-type=%s", media_type);
262 : else
263 10 : args[3] = g_strdup ("--media-type=hd");
264 :
265 11 : if (revision)
266 1 : args[4] = g_strdup_printf ("--udfrev=%s", revision);
267 : else
268 10 : args[4] = g_strdup ("--udfrev=0x201");
269 :
270 11 : ret = bd_utils_exec_and_report_error (args, extra, error);
271 :
272 11 : g_free ((gchar *) args[2]);
273 11 : g_free ((gchar *) args[3]);
274 11 : g_free ((gchar *) args[4]);
275 :
276 11 : return ret;
277 : }
278 :
279 : /**
280 : * bd_fs_udf_set_label:
281 : * @device: the device containing the file system to set label for
282 : * @label: label to set
283 : * @error: (out) (optional): place to store error (if any)
284 : *
285 : * Note: This sets both Volume Identifier and Logical Volume Identifier. Volume Identifier
286 : * is truncated to 30 or 15 characters to accommodate to the different length limits
287 : * of these labels.
288 : *
289 : * Returns: whether the label of UDF file system on the @device was
290 : * successfully set or not
291 : *
292 : * Tech category: %BD_FS_TECH_UDF-%BD_FS_TECH_MODE_SET_LABEL
293 : */
294 8 : gboolean bd_fs_udf_set_label (const gchar *device, const gchar *label, GError **error) {
295 8 : const gchar *args[6] = {"udflabel", "--utf8", NULL, NULL, device, NULL};
296 8 : g_autofree gchar *vid = NULL;
297 8 : gboolean ret = FALSE;
298 :
299 8 : if (!check_deps (&avail_deps, DEPS_UDFLABEL_MASK, deps, DEPS_LAST, &deps_check_lock, error))
300 0 : return FALSE;
301 :
302 8 : if (!bd_fs_udf_check_label (label, error))
303 0 : return FALSE;
304 :
305 8 : vid = get_vid (label);
306 :
307 8 : args[2] = g_strdup_printf ("--lvid=%s", label);
308 8 : args[3] = g_strdup_printf ("--vid=%s", vid);
309 :
310 8 : ret = bd_utils_exec_and_report_error (args, NULL, error);
311 :
312 8 : g_free ((gchar *) args[2]);
313 8 : g_free ((gchar *) args[3]);
314 :
315 8 : return ret;
316 : }
317 :
318 : /**
319 : * bd_fs_udf_check_label:
320 : * @label: label to check
321 : * @error: (out) (optional): place to store error
322 : *
323 : * Note: This checks only whether @label adheres the length limits for Logical Volume Identifier,
324 : * not the stricter limits for Volume Identifier.
325 : *
326 : * Returns: whether @label is a valid label for the UDF file system or not
327 : * (reason is provided in @error)
328 : *
329 : * Tech category: always available
330 : */
331 14 : gboolean bd_fs_udf_check_label (const gchar *label, GError **error) {
332 14 : const gchar *next_p = NULL;
333 : gunichar unichar;
334 14 : gint len = 0;
335 :
336 14 : if (g_str_is_ascii (label)) {
337 7 : if (strlen (label) > 126) {
338 1 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_LABEL_INVALID,
339 : "Label for UDF filesystem can be at most 126 characters long.");
340 1 : return FALSE;
341 : }
342 :
343 6 : return TRUE;
344 : }
345 :
346 7 : if (g_utf8_validate (label, -1, NULL)) {
347 7 : len = g_utf8_strlen (label, -1);
348 7 : if (len <= 63)
349 : /* utf-8 and <= 63 will be always valid */
350 4 : return TRUE;
351 :
352 3 : if (len > 126) {
353 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_LABEL_INVALID,
354 : "Label for UDF filesystem can be at most 126 characters long.");
355 0 : return FALSE;
356 : }
357 :
358 3 : next_p = label;
359 255 : while (next_p && *next_p) {
360 253 : unichar = g_utf8_get_char (next_p);
361 253 : if (unichar > 0xFF) {
362 1 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_LABEL_INVALID,
363 : "Label for UDF filesystem containing unicode characters above U+FF can "\
364 : "be at most 63 characters long.");
365 1 : return FALSE;
366 : }
367 :
368 252 : next_p = g_utf8_next_char (next_p);
369 : }
370 : } else {
371 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_LABEL_INVALID,
372 : "Label for UDF filesystem must be a valid UTF-8 string.");
373 0 : return FALSE;
374 : }
375 :
376 2 : return TRUE;
377 : }
378 :
379 : /**
380 : * bd_fs_udf_set_uuid:
381 : * @device: the device containing the file system to set the UUID (serial number) for
382 : * @uuid: (nullable): UUID to set or %NULL to generate a new one
383 : * @error: (out) (optional): place to store error (if any)
384 : *
385 : * Returns: whether the UUID of the UDF file system on the @device was
386 : * successfully set or not
387 : *
388 : * Tech category: %BD_FS_TECH_UDF-%BD_FS_TECH_MODE_SET_UUID
389 : */
390 4 : gboolean bd_fs_udf_set_uuid (const gchar *device, const gchar *uuid, GError **error) {
391 4 : gboolean ret = FALSE;
392 4 : const gchar *args[4] = {"udflabel", NULL, device, NULL};
393 :
394 4 : if (!check_deps (&avail_deps, DEPS_UDFLABEL_MASK, deps, DEPS_LAST, &deps_check_lock, error))
395 0 : return FALSE;
396 :
397 4 : if (!uuid)
398 2 : args[1] = g_strdup ("--uuid=random");
399 : else
400 2 : args[1] = g_strdup_printf ("--uuid=%s", uuid);
401 :
402 4 : ret = bd_utils_exec_and_report_error (args, NULL, error);
403 :
404 4 : g_free ((gchar *) args[1]);
405 4 : return ret;
406 : }
407 :
408 : /**
409 : * bd_fs_udf_check_uuid:
410 : * @uuid: UUID to check
411 : * @error: (out) (optional): place to store error
412 : *
413 : * Returns: whether @uuid is a valid UUID for the UDF file system or not
414 : * (reason is provided in @error)
415 : *
416 : * Tech category: always available
417 : */
418 5 : gboolean bd_fs_udf_check_uuid (const gchar *uuid, GError **error) {
419 5 : size_t len = 0;
420 :
421 5 : len = strlen (uuid);
422 5 : if (len != 16) {
423 1 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_UUID_INVALID,
424 : "UUID for UDF filesystem must be 16 characters long.");
425 1 : return FALSE;
426 : }
427 :
428 37 : for (size_t i = 0; i < len; i++) {
429 35 : if (!g_ascii_isxdigit (uuid[i]) || (!g_ascii_isdigit (uuid[i]) && !g_ascii_islower (uuid[i]))) {
430 2 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_UUID_INVALID,
431 : "UUID for UDF filesystem must be a lowercase hexadecimal number.");
432 2 : return FALSE;
433 : }
434 : }
435 :
436 2 : return TRUE;
437 : }
438 :
439 : /**
440 : * parse_udf_vars:
441 : * @str: string to parse
442 : * @num_items: (out): number of parsed items
443 : *
444 : * Returns: (transfer full): a GHashTable containing key-value items parsed from the @string
445 : */
446 14 : static GHashTable* parse_udf_vars (const gchar *str, guint *num_items) {
447 14 : GHashTable *table = NULL;
448 14 : gchar **items = NULL;
449 14 : gchar **item_p = NULL;
450 14 : gchar **key_val = NULL;
451 :
452 14 : table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
453 14 : *num_items = 0;
454 :
455 14 : items = g_strsplit (str, "\n", 0);
456 529 : for (item_p=items; *item_p; item_p++) {
457 515 : if (g_str_has_prefix (*item_p, "start="))
458 123 : continue;
459 392 : key_val = g_strsplit (*item_p, "=", 2);
460 392 : if (g_strv_length (key_val) == 2) {
461 : /* we only want to process valid lines (with the '=' character) */
462 378 : g_hash_table_insert (table, key_val[0], key_val[1]);
463 378 : g_free (key_val);
464 378 : (*num_items)++;
465 : } else
466 : /* invalid line, just free key_val */
467 14 : g_strfreev (key_val);
468 : }
469 :
470 14 : g_strfreev (items);
471 14 : return table;
472 : }
473 :
474 14 : static BDFSUdfInfo* get_udf_data_from_table (GHashTable *table) {
475 14 : BDFSUdfInfo *data = g_new0 (BDFSUdfInfo, 1);
476 14 : gchar *value = NULL;
477 :
478 14 : data->revision = g_strdup ((gchar*) g_hash_table_lookup (table, "udfrev"));
479 14 : data->vid = g_strdup ((gchar*) g_hash_table_lookup (table, "vid"));
480 14 : data->lvid = g_strdup ((gchar*) g_hash_table_lookup (table, "lvid"));
481 :
482 14 : value = (gchar*) g_hash_table_lookup (table, "blocksize");
483 14 : if (value)
484 14 : data->block_size = g_ascii_strtoull (value, NULL, 0);
485 : else
486 0 : data->block_size = 0;
487 :
488 14 : value = (gchar*) g_hash_table_lookup (table, "blocks");
489 14 : if (value)
490 14 : data->block_count = g_ascii_strtoull (value, NULL, 0);
491 : else
492 0 : data->block_count = 0;
493 :
494 14 : value = (gchar*) g_hash_table_lookup (table, "freeblocks");
495 14 : if (value)
496 14 : data->free_blocks = g_ascii_strtoull (value, NULL, 0);
497 : else
498 0 : data->free_blocks = 0;
499 :
500 14 : g_hash_table_destroy (table);
501 :
502 14 : return data;
503 : }
504 :
505 : /**
506 : * bd_fs_udf_get_info:
507 : * @device: the device containing the file system to get info for
508 : * @error: (out) (optional): place to store error (if any)
509 : *
510 : * Returns: (transfer full): information about the file system on @device or
511 : * %NULL in case of error
512 : *
513 : * Tech category: %BD_FS_TECH_UDF-%BD_FS_TECH_MODE_QUERY
514 : */
515 14 : BDFSUdfInfo* bd_fs_udf_get_info (const gchar *device, GError **error) {
516 14 : const gchar *args[4] = {"udfinfo", "--utf8", device, NULL};
517 14 : gboolean success = FALSE;
518 14 : gchar *output = NULL;
519 14 : BDFSUdfInfo *ret = NULL;
520 14 : GHashTable *table = NULL;
521 14 : guint num_items = 0;
522 :
523 14 : if (!check_deps (&avail_deps, DEPS_UDFINFO_MASK, deps, DEPS_LAST, &deps_check_lock, error))
524 0 : return NULL;
525 :
526 14 : success = bd_utils_exec_and_capture_output (args, NULL, &output, error);
527 14 : if (!success) {
528 : /* error is already populated */
529 0 : return NULL;
530 : }
531 :
532 14 : table = parse_udf_vars (output, &num_items);
533 14 : g_free (output);
534 14 : if (!table || (num_items == 0)) {
535 : /* something bad happened or some expected items were missing */
536 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_PARSE, "Failed to parse UDF file system information");
537 0 : if (table)
538 0 : g_hash_table_destroy (table);
539 0 : return NULL;
540 : }
541 :
542 14 : ret = get_udf_data_from_table (table);
543 14 : if (!ret) {
544 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_PARSE, "Failed to parse UDF file system information");
545 0 : return NULL;
546 : }
547 :
548 14 : success = get_uuid_label (device, &(ret->uuid), &(ret->label), error);
549 14 : if (!success) {
550 : /* error is already populated */
551 0 : bd_fs_udf_info_free (ret);
552 0 : return NULL;
553 : }
554 :
555 14 : return ret;
556 : }
|