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 :
23 : #include "exfat.h"
24 : #include "fs.h"
25 : #include "common.h"
26 :
27 : static volatile guint avail_deps = 0;
28 : static GMutex deps_check_lock;
29 :
30 : #define DEPS_MKEXFAT 0
31 : #define DEPS_MKEXFAT_MASK (1 << DEPS_MKEXFAT)
32 : #define DEPS_FSCKEXFAT 1
33 : #define DEPS_FSCKEXFAT_MASK (1 << DEPS_FSCKEXFAT)
34 : #define DEPS_TUNEEXFAT 2
35 : #define DEPS_TUNEEXFAT_MASK (1 << DEPS_TUNEEXFAT)
36 :
37 : #define DEPS_LAST 4
38 :
39 : static const UtilDep deps[DEPS_LAST] = {
40 : {"mkfs.exfat", NULL, NULL, NULL},
41 : {"fsck.exfat", NULL, NULL, NULL},
42 : {"tune.exfat", NULL, NULL, NULL},
43 : {"tune.exfat", NULL, NULL, NULL},
44 : };
45 :
46 : static guint32 fs_mode_util[BD_FS_MODE_LAST+1] = {
47 : DEPS_MKEXFAT_MASK, /* mkfs */
48 : 0, /* wipe */
49 : DEPS_FSCKEXFAT_MASK, /* check */
50 : DEPS_FSCKEXFAT_MASK, /* repair */
51 : DEPS_TUNEEXFAT_MASK, /* set-label */
52 : DEPS_TUNEEXFAT_MASK, /* query */
53 : 0, /* resize */
54 : DEPS_TUNEEXFAT_MASK, /* set-uuid */
55 : };
56 :
57 : /* line prefixes in tune.exfat output for parsing */
58 : #define BLOCK_SIZE_PREFIX "Block sector size : "
59 : #define BLOCK_SIZE_PREFIX_LEN 20
60 : #define SECTORS_PREFIX "Number of the sectors : "
61 : #define SECTORS_PREFIX_LEN 24
62 : #define CLUSTERS_PREFIX "Number of the clusters : "
63 : #define CLUSTERS_PREFIX_LEN 25
64 :
65 :
66 :
67 : /**
68 : * bd_fs_exfat_is_tech_avail:
69 : * @tech: the queried tech
70 : * @mode: a bit mask of queried modes of operation (#BDFSTechMode) for @tech
71 : * @error: (out) (optional): place to store error (details about why the @tech-@mode combination is not available)
72 : *
73 : * Returns: whether the @tech-@mode combination is available -- supported by the
74 : * plugin implementation and having all the runtime dependencies available
75 : */
76 : G_GNUC_INTERNAL gboolean
77 100 : bd_fs_exfat_is_tech_avail (BDFSTech tech G_GNUC_UNUSED, guint64 mode, GError **error) {
78 100 : guint32 required = 0;
79 100 : guint i = 0;
80 :
81 100 : if (mode & BD_FS_TECH_MODE_RESIZE) {
82 1 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_TECH_UNAVAIL,
83 : "exFAT currently doesn't support resizing.");
84 1 : return FALSE;
85 : }
86 :
87 891 : for (i = 0; i <= BD_FS_MODE_LAST; i++)
88 792 : if (mode & (1 << i))
89 379 : required |= fs_mode_util[i];
90 :
91 99 : return check_deps (&avail_deps, required, deps, DEPS_LAST, &deps_check_lock, error);
92 : }
93 :
94 : /**
95 : * bd_fs_exfat_info_copy: (skip)
96 : *
97 : * Creates a new copy of @data.
98 : */
99 0 : BDFSExfatInfo* bd_fs_exfat_info_copy (BDFSExfatInfo *data) {
100 0 : if (data == NULL)
101 0 : return NULL;
102 :
103 0 : BDFSExfatInfo *ret = g_new0 (BDFSExfatInfo, 1);
104 :
105 0 : ret->label = g_strdup (data->label);
106 0 : ret->uuid = g_strdup (data->uuid);
107 0 : ret->sector_size = data->sector_size;
108 0 : ret->sector_count = data->sector_count;
109 0 : ret->cluster_count = data->cluster_count;
110 :
111 0 : return ret;
112 : }
113 :
114 : /**
115 : * bd_fs_exfat_info_free: (skip)
116 : *
117 : * Frees @data.
118 : */
119 0 : void bd_fs_exfat_info_free (BDFSExfatInfo *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 : G_GNUC_INTERNAL BDExtraArg **
129 1 : bd_fs_exfat_mkfs_options (BDFSMkfsOptions *options, const BDExtraArg **extra) {
130 1 : GPtrArray *options_array = g_ptr_array_new ();
131 1 : const BDExtraArg **extra_p = NULL;
132 :
133 1 : if (options->label && g_strcmp0 (options->label, "") != 0)
134 1 : g_ptr_array_add (options_array, bd_extra_arg_new ("-n", options->label));
135 :
136 1 : if (extra) {
137 0 : for (extra_p = extra; *extra_p; extra_p++)
138 0 : g_ptr_array_add (options_array, bd_extra_arg_copy ((BDExtraArg *) *extra_p));
139 : }
140 :
141 1 : g_ptr_array_add (options_array, NULL);
142 :
143 1 : return (BDExtraArg **) g_ptr_array_free (options_array, FALSE);
144 : }
145 :
146 : /**
147 : * bd_fs_exfat_mkfs:
148 : * @device: the device to create a new exfat fs on
149 : * @extra: (nullable) (array zero-terminated=1): extra options for the creation (right now
150 : * passed to the 'mkfs.exfat' utility)
151 : * @error: (out) (optional): place to store error (if any)
152 : *
153 : * Returns: whether a new exfat fs was successfully created on @device or not
154 : *
155 : * Tech category: %BD_FS_TECH_EXFAT-%BD_FS_TECH_MODE_MKFS
156 : */
157 14 : gboolean bd_fs_exfat_mkfs (const gchar *device, const BDExtraArg **extra, GError **error) {
158 14 : const gchar *args[3] = {"mkfs.exfat", device, NULL};
159 :
160 14 : if (!check_deps (&avail_deps, DEPS_MKEXFAT_MASK, deps, DEPS_LAST, &deps_check_lock, error))
161 0 : return FALSE;
162 :
163 14 : return bd_utils_exec_and_report_error (args, extra, error);
164 : }
165 :
166 : /**
167 : * bd_fs_exfat_check:
168 : * @device: the device containing the file system to check
169 : * @extra: (nullable) (array zero-terminated=1): extra options for the repair (right now
170 : * passed to the 'fsck.exfat' utility)
171 : * @error: (out) (optional): place to store error (if any)
172 : *
173 : * Returns: whether the exfat file system on the @device is clean or not
174 : *
175 : * Tech category: %BD_FS_TECH_EXFAT-%BD_FS_TECH_MODE_CHECK
176 : */
177 3 : gboolean bd_fs_exfat_check (const gchar *device, const BDExtraArg **extra, GError **error) {
178 3 : const gchar *args[4] = {"fsck.exfat", "-n", device, NULL};
179 3 : gint status = 0;
180 3 : gboolean ret = FALSE;
181 :
182 3 : if (!check_deps (&avail_deps, DEPS_FSCKEXFAT_MASK, deps, DEPS_LAST, &deps_check_lock, error))
183 0 : return FALSE;
184 :
185 3 : ret = bd_utils_exec_and_report_status_error (args, extra, &status, error);
186 3 : if (!ret && (status == 1)) {
187 : /* no error should be reported for exit code 1 */
188 0 : g_clear_error (error);
189 : }
190 3 : return ret;
191 : }
192 :
193 : /**
194 : * bd_fs_exfat_repair:
195 : * @device: the device containing the file system to repair
196 : * @extra: (nullable) (array zero-terminated=1): extra options for the repair (right now
197 : * passed to the 'fsck.exfat' utility)
198 : * @error: (out) (optional): place to store error (if any)
199 : *
200 : * Returns: whether the exfat file system on the @device was successfully repaired
201 : * (if needed) or not (error is set in that case)
202 : *
203 : * Tech category: %BD_FS_TECH_EXFAT-%BD_FS_TECH_MODE_REPAIR
204 : */
205 3 : gboolean bd_fs_exfat_repair (const gchar *device, const BDExtraArg **extra, GError **error) {
206 3 : const gchar *args[4] = {"fsck.exfat", "-y", device, NULL};
207 3 : gint status = 0;
208 3 : gboolean ret = FALSE;
209 :
210 3 : if (!check_deps (&avail_deps, DEPS_FSCKEXFAT_MASK, deps, DEPS_LAST, &deps_check_lock, error))
211 0 : return FALSE;
212 :
213 3 : ret = bd_utils_exec_and_report_status_error (args, extra, &status, error);
214 3 : if (!ret && (status == 1)) {
215 : /* no error should be reported for exit code 1 */
216 0 : g_clear_error (error);
217 0 : ret = TRUE;
218 : }
219 :
220 3 : return ret;
221 : }
222 :
223 : /**
224 : * bd_fs_exfat_set_label:
225 : * @device: the device containing the file system to set label for
226 : * @label: label to set
227 : * @error: (out) (optional): place to store error (if any)
228 : *
229 : * Returns: whether the label of exfat file system on the @device was
230 : * successfully set or not
231 : *
232 : * Tech category: %BD_FS_TECH_EXFAT-%BD_FS_TECH_MODE_SET_LABEL
233 : */
234 5 : gboolean bd_fs_exfat_set_label (const gchar *device, const gchar *label, GError **error) {
235 5 : const gchar *args[5] = {"tune.exfat", "-L", label, device, NULL};
236 :
237 5 : if (!check_deps (&avail_deps, DEPS_TUNEEXFAT_MASK, deps, DEPS_LAST, &deps_check_lock, error))
238 0 : return FALSE;
239 :
240 5 : return bd_utils_exec_and_report_error (args, NULL, error);
241 : }
242 :
243 : /**
244 : * bd_fs_exfat_check_label:
245 : * @label: label to check
246 : * @error: (out) (optional): place to store error
247 : *
248 : * Returns: whether @label is a valid label for the exfat file system or not
249 : * (reason is provided in @error)
250 : *
251 : * Tech category: always available
252 : */
253 4 : gboolean bd_fs_exfat_check_label (const gchar *label, GError **error) {
254 : gsize bytes_written;
255 4 : g_autofree gchar *str = NULL;
256 :
257 4 : if (g_utf8_validate (label, -1, NULL)) {
258 4 : str = g_convert (label, -1, "UTF-16LE", "UTF-8", NULL, &bytes_written, NULL);
259 : }
260 :
261 4 : if (!str) {
262 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_LABEL_INVALID,
263 : "Label for exFAT filesystem must be a valid UTF-8 string.");
264 0 : return FALSE;
265 : }
266 :
267 4 : if (bytes_written > 22) {
268 2 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_LABEL_INVALID,
269 : "Label for exFAT filesystem is too long.");
270 2 : return FALSE;
271 : }
272 :
273 2 : return TRUE;
274 : }
275 :
276 : /**
277 : * bd_fs_exfat_set_uuid:
278 : * @device: the device containing the file system to set uuid for
279 : * @uuid: (nullable): volume ID to set or %NULL to generate a new one
280 : * @error: (out) (optional): place to store error (if any)
281 : *
282 : * Returns: whether the volume ID of exFAT file system on the @device was
283 : * successfully set or not
284 : *
285 : * Tech category: %BD_FS_TECH_EXFAT-%BD_FS_TECH_MODE_SET_UUID
286 : */
287 6 : gboolean bd_fs_exfat_set_uuid (const gchar *device, const gchar *uuid, GError **error) {
288 6 : const gchar *args[5] = {"tune.exfat", "-I", NULL, device, NULL};
289 6 : g_autofree gchar *new_uuid = NULL;
290 6 : size_t len = 0;
291 6 : guint32 rand = 0;
292 :
293 6 : if (!check_deps (&avail_deps, DEPS_TUNEEXFAT_MASK, deps, DEPS_LAST, &deps_check_lock, error))
294 0 : return FALSE;
295 :
296 6 : if (!uuid || g_strcmp0 (uuid, "") == 0) {
297 2 : rand = g_random_int ();
298 2 : new_uuid = g_strdup_printf ("0x%08x", rand);
299 2 : args[2] = new_uuid;
300 : } else {
301 4 : if (g_str_has_prefix (uuid, "0x")) {
302 : /* already format taken by tune.exfat: hexadecimal number with the 0x prefix */
303 1 : args[2] = uuid;
304 : } else {
305 3 : len = strlen (uuid);
306 3 : if (len == 9 && uuid[4] == '-') {
307 : /* we want to support vol ID in the "udev format", e.g. "2E24-EC82" */
308 1 : new_uuid = g_new0 (gchar, 11);
309 1 : memcpy (new_uuid, "0x", 2);
310 1 : memcpy (new_uuid + 2, uuid, 4);
311 1 : memcpy (new_uuid + 6, uuid + 5, 4);
312 1 : args[2] = new_uuid;
313 : } else {
314 2 : new_uuid = g_strdup_printf ("0x%s", uuid);
315 2 : args[2] = new_uuid;
316 : }
317 : }
318 : }
319 :
320 6 : return bd_utils_exec_and_report_error (args, NULL, error);
321 : }
322 :
323 : /**
324 : * bd_fs_exfat_check_uuid:
325 : * @uuid: UUID to check
326 : * @error: (out) (optional): place to store error
327 : *
328 : * Returns: whether @uuid is a valid UUID for the exFAT file system or not
329 : * (reason is provided in @error)
330 : *
331 : * Tech category: always available
332 : */
333 8 : gboolean bd_fs_exfat_check_uuid (const gchar *uuid, GError **error) {
334 : guint64 vol_id;
335 8 : gchar *new_uuid = NULL;
336 8 : gchar *endptr = NULL;
337 8 : size_t len = 0;
338 :
339 8 : if (!uuid)
340 0 : return TRUE;
341 :
342 8 : len = strlen (uuid);
343 8 : if (len == 9 && uuid[4] == '-') {
344 2 : new_uuid = g_new0 (gchar, 9);
345 2 : memcpy (new_uuid, uuid, 4);
346 2 : memcpy (new_uuid + 4, uuid + 5, 4);
347 : } else
348 6 : new_uuid = g_strdup (uuid);
349 :
350 8 : vol_id = g_ascii_strtoull (new_uuid, &endptr, 16);
351 8 : if ((vol_id == 0 && endptr == new_uuid) || (endptr && *endptr)) {
352 2 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_UUID_INVALID,
353 : "UUID for exFAT filesystem must be a hexadecimal number.");
354 2 : g_free (new_uuid);
355 2 : return FALSE;
356 : }
357 :
358 6 : if (vol_id > G_MAXUINT32) {
359 1 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_UUID_INVALID,
360 : "UUID for exFAT filesystem must fit into 32 bits.");
361 1 : g_free (new_uuid);
362 1 : return FALSE;
363 : }
364 :
365 5 : g_free (new_uuid);
366 5 : return TRUE;
367 : }
368 :
369 : /**
370 : * bd_fs_exfat_get_info:
371 : * @device: the device containing the file system to get info for
372 : * @error: (out) (optional): place to store error (if any)
373 : *
374 : * Returns: (transfer full): information about the file system on @device or
375 : * %NULL in case of error
376 : *
377 : * Tech category: %BD_FS_TECH_EXFAT-%BD_FS_TECH_MODE_QUERY
378 : */
379 11 : BDFSExfatInfo* bd_fs_exfat_get_info (const gchar *device, GError **error) {
380 11 : const gchar *args[4] = {"tune.exfat", "-v", device, NULL};
381 11 : gboolean success = FALSE;
382 11 : gchar *output = NULL;
383 11 : BDFSExfatInfo *ret = NULL;
384 11 : gchar **lines = NULL;
385 11 : gchar **line_p = NULL;
386 11 : gchar *val_start = NULL;
387 :
388 11 : if (!check_deps (&avail_deps, DEPS_TUNEEXFAT_MASK, deps, DEPS_LAST, &deps_check_lock, error))
389 0 : return NULL;
390 :
391 11 : ret = g_new0 (BDFSExfatInfo, 1);
392 :
393 11 : success = get_uuid_label (device, &(ret->uuid), &(ret->label), error);
394 11 : if (!success) {
395 : /* error is already populated */
396 0 : bd_fs_exfat_info_free (ret);
397 0 : return NULL;
398 : }
399 :
400 11 : success = bd_utils_exec_and_capture_output (args, NULL, &output, error);
401 11 : if (!success) {
402 : /* error is already populated */
403 0 : bd_fs_exfat_info_free (ret);
404 0 : return NULL;
405 : }
406 :
407 11 : lines = g_strsplit (output, "\n", 0);
408 11 : g_free (output);
409 :
410 77 : for (line_p=lines; *line_p; line_p++) {
411 77 : if (ret->sector_size == 0) {
412 55 : val_start = g_strrstr (*line_p, BLOCK_SIZE_PREFIX);
413 55 : if (val_start)
414 11 : ret->sector_size = g_ascii_strtoull (val_start + BLOCK_SIZE_PREFIX_LEN, NULL, 0);
415 : }
416 :
417 77 : if (ret->sector_count == 0) {
418 66 : val_start = g_strrstr (*line_p, SECTORS_PREFIX);
419 66 : if (val_start)
420 11 : ret->sector_count = g_ascii_strtoull (val_start + SECTORS_PREFIX_LEN, NULL, 0);
421 : }
422 :
423 77 : if (ret->cluster_count == 0) {
424 77 : val_start = g_strrstr (*line_p, CLUSTERS_PREFIX);
425 77 : if (val_start)
426 11 : ret->cluster_count = g_ascii_strtoull (val_start + CLUSTERS_PREFIX_LEN, NULL, 0);
427 : }
428 :
429 77 : if (ret->sector_size > 0 && ret->sector_count > 0 && ret->cluster_count > 0)
430 11 : break;
431 : }
432 11 : g_strfreev (lines);
433 :
434 11 : if (ret->sector_size == 0 || ret->sector_count == 0 || ret->cluster_count == 0) {
435 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_FAIL,
436 : "Failed to to parse exFAT info.");
437 0 : bd_fs_exfat_info_free (ret);
438 0 : return NULL;
439 : }
440 :
441 11 : return ret;
442 : }
|