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 <stdio.h>
24 :
25 : #include "ntfs.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_MKNTFS 0
33 : #define DEPS_MKNTFS_MASK (1 << DEPS_MKNTFS)
34 : #define DEPS_NTFSFIX 1
35 : #define DEPS_NTFSFIX_MASK (1 << DEPS_NTFSFIX)
36 : #define DEPS_NTFSRESIZE 2
37 : #define DEPS_NTFSRESIZE_MASK (1 << DEPS_NTFSRESIZE)
38 : #define DEPS_NTFSLABEL 3
39 : #define DEPS_NTFSLABEL_MASK (1 << DEPS_NTFSLABEL)
40 : #define DEPS_NTFSINFO 4
41 : #define DEPS_NTFSINFO_MASK (1 << DEPS_NTFSINFO)
42 :
43 : #define DEPS_LAST 5
44 :
45 : static const UtilDep deps[DEPS_LAST] = {
46 : {"mkntfs", NULL, NULL, NULL},
47 : {"ntfsfix", NULL, NULL, NULL},
48 : {"ntfsresize", NULL, NULL, NULL},
49 : {"ntfslabel", NULL, NULL, NULL},
50 : {"ntfsinfo", NULL, NULL, NULL},
51 : };
52 :
53 : static guint32 fs_mode_util[BD_FS_MODE_LAST+1] = {
54 : DEPS_MKNTFS_MASK, /* mkfs */
55 : 0, /* wipe */
56 : DEPS_NTFSFIX_MASK, /* check */
57 : DEPS_NTFSFIX_MASK, /* repair */
58 : DEPS_NTFSLABEL_MASK, /* set-label */
59 : DEPS_NTFSINFO_MASK, /* query */
60 : DEPS_NTFSRESIZE_MASK, /* resize */
61 : DEPS_NTFSLABEL_MASK /* set-uuid */
62 : };
63 :
64 :
65 : /**
66 : * bd_fs_ntfs_is_tech_avail:
67 : * @tech: the queried tech
68 : * @mode: a bit mask of queried modes of operation (#BDFSTechMode) for @tech
69 : * @error: (out) (optional): place to store error (details about why the @tech-@mode combination is not available)
70 : *
71 : * Returns: whether the @tech-@mode combination is available -- supported by the
72 : * plugin implementation and having all the runtime dependencies available
73 : */
74 : G_GNUC_INTERNAL gboolean
75 100 : bd_fs_ntfs_is_tech_avail (BDFSTech tech G_GNUC_UNUSED, guint64 mode, GError **error) {
76 100 : guint32 required = 0;
77 100 : guint i = 0;
78 900 : for (i = 0; i <= BD_FS_MODE_LAST; i++)
79 800 : if (mode & (1 << i))
80 474 : required |= fs_mode_util[i];
81 :
82 100 : return check_deps (&avail_deps, required, deps, DEPS_LAST, &deps_check_lock, error);
83 : }
84 :
85 : /**
86 : * bd_fs_ntfs_info_copy: (skip)
87 : *
88 : * Creates a new copy of @data.
89 : */
90 0 : BDFSNtfsInfo* bd_fs_ntfs_info_copy (BDFSNtfsInfo *data) {
91 0 : if (data == NULL)
92 0 : return NULL;
93 :
94 0 : BDFSNtfsInfo *ret = g_new0 (BDFSNtfsInfo, 1);
95 :
96 0 : ret->label = g_strdup (data->label);
97 0 : ret->uuid = g_strdup (data->uuid);
98 0 : ret->size = data->size;
99 0 : ret->free_space = data->free_space;
100 :
101 0 : return ret;
102 : }
103 :
104 : /**
105 : * bd_fs_ntfs_info_free: (skip)
106 : *
107 : * Frees @data.
108 : */
109 0 : void bd_fs_ntfs_info_free (BDFSNtfsInfo *data) {
110 0 : if (data == NULL)
111 0 : return;
112 :
113 0 : g_free (data->label);
114 0 : g_free (data->uuid);
115 0 : g_free (data);
116 : }
117 :
118 : G_GNUC_INTERNAL BDExtraArg **
119 4 : bd_fs_ntfs_mkfs_options (BDFSMkfsOptions *options, const BDExtraArg **extra) {
120 4 : GPtrArray *options_array = g_ptr_array_new ();
121 4 : const BDExtraArg **extra_p = NULL;
122 :
123 4 : if (options->label && g_strcmp0 (options->label, "") != 0)
124 1 : g_ptr_array_add (options_array, bd_extra_arg_new ("-L", options->label));
125 :
126 4 : if (options->dry_run)
127 2 : g_ptr_array_add (options_array, bd_extra_arg_new ("-n", ""));
128 :
129 4 : if (extra) {
130 0 : for (extra_p = extra; *extra_p; extra_p++)
131 0 : g_ptr_array_add (options_array, bd_extra_arg_copy ((BDExtraArg *) *extra_p));
132 : }
133 :
134 4 : g_ptr_array_add (options_array, NULL);
135 :
136 4 : return (BDExtraArg **) g_ptr_array_free (options_array, FALSE);
137 : }
138 :
139 : /**
140 : * bd_fs_ntfs_mkfs:
141 : * @device: the device to create a new ntfs fs on
142 : * @extra: (nullable) (array zero-terminated=1): extra options for the creation (right now
143 : * passed to the 'mkntfs' utility)
144 : * @error: (out) (optional): place to store error (if any)
145 : *
146 : * Returns: whether a new NTFS fs was successfully created on @device or not
147 : *
148 : * Tech category: %BD_FS_TECH_NTFS-%BD_FS_TECH_MODE_MKFS
149 : */
150 18 : gboolean bd_fs_ntfs_mkfs (const gchar *device, const BDExtraArg **extra, GError **error) {
151 18 : const gchar *args[5] = {"mkntfs", "-f", "-F", device, NULL};
152 :
153 18 : if (!check_deps (&avail_deps, DEPS_MKNTFS_MASK, deps, DEPS_LAST, &deps_check_lock, error))
154 0 : return FALSE;
155 :
156 18 : return bd_utils_exec_and_report_error (args, extra, error);
157 : }
158 :
159 : /**
160 : * bd_fs_ntfs_check:
161 : * @device: the device containing the file system to check
162 : * @extra: (nullable) (array zero-terminated=1): extra options for the repair (right now
163 : * passed to the 'ntfsfix' utility)
164 : * @error: (out) (optional): place to store error (if any)
165 : *
166 : * Returns: whether an ntfs file system on the @device is clean or not
167 : *
168 : * Tech category: %BD_FS_TECH_NTFS-%BD_FS_TECH_MODE_CHECK
169 : */
170 2 : gboolean bd_fs_ntfs_check (const gchar *device, const BDExtraArg **extra, GError **error) {
171 2 : const gchar *args[4] = {"ntfsfix", "-n", device, NULL};
172 2 : gint status = 0;
173 2 : gboolean ret = FALSE;
174 :
175 2 : if (!check_deps (&avail_deps, DEPS_NTFSFIX_MASK, deps, DEPS_LAST, &deps_check_lock, error))
176 0 : return FALSE;
177 :
178 2 : ret = bd_utils_exec_and_report_status_error (args, extra, &status, error);
179 2 : if (!ret && (status == 1)) {
180 : /* no error should be reported for exit code 1 -- Recoverable errors have been detected */
181 0 : g_clear_error (error);
182 : }
183 2 : return ret;
184 : }
185 :
186 : /**
187 : * bd_fs_ntfs_repair:
188 : * @device: the device containing the file system to repair
189 : * @extra: (nullable) (array zero-terminated=1): extra options for the repair (right now
190 : * passed to the 'ntfsfix' utility)
191 : * @error: (out) (optional): place to store error (if any)
192 : *
193 : * Returns: whether an NTFS file system on the @device was successfully repaired
194 : * (if needed) or not (error is set in that case)
195 : *
196 : * Tech category: %BD_FS_TECH_NTFS-%BD_FS_TECH_MODE_REPAIR
197 : */
198 9 : gboolean bd_fs_ntfs_repair (const gchar *device, const BDExtraArg **extra, GError **error) {
199 9 : const gchar *args[4] = {"ntfsfix", "-d", device, NULL};
200 :
201 9 : if (!check_deps (&avail_deps, DEPS_NTFSFIX_MASK, deps, DEPS_LAST, &deps_check_lock, error))
202 0 : return FALSE;
203 :
204 9 : return bd_utils_exec_and_report_error (args, extra, error);
205 : }
206 :
207 : /**
208 : * bd_fs_ntfs_set_label:
209 : * @device: the device containing the file system to set the label for
210 : * @label: label to set
211 : * @error: (out) (optional): place to store error (if any)
212 : *
213 : * Returns: whether the label of the NTFS file system on the @device was
214 : * successfully set or not
215 : *
216 : * Tech category: %BD_FS_TECH_NTFS-%BD_FS_TECH_MODE_SET_LABEL
217 : */
218 5 : gboolean bd_fs_ntfs_set_label (const gchar *device, const gchar *label, GError **error) {
219 5 : const gchar *args[4] = {"ntfslabel", device, label, NULL};
220 :
221 5 : if (!check_deps (&avail_deps, DEPS_NTFSLABEL_MASK, deps, DEPS_LAST, &deps_check_lock, error))
222 0 : return FALSE;
223 :
224 5 : return bd_utils_exec_and_report_error (args, NULL, error);
225 : }
226 :
227 : /**
228 : * bd_fs_ntfs_check_label:
229 : * @label: label to check
230 : * @error: (out) (optional): place to store error
231 : *
232 : * Returns: whether @label is a valid label for the ntfs file system or not
233 : * (reason is provided in @error)
234 : *
235 : * Tech category: always available
236 : */
237 4 : gboolean bd_fs_ntfs_check_label (const gchar *label, GError **error) {
238 4 : if (strlen (label) > 128) {
239 2 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_LABEL_INVALID,
240 : "Label for NTFS filesystem must be at most 128 characters long.");
241 2 : return FALSE;
242 : }
243 :
244 2 : return TRUE;
245 : }
246 :
247 : /**
248 : * bd_fs_ntfs_set_uuid:
249 : * @device: the device containing the file system to set the UUID (serial number) for
250 : * @uuid: (nullable): UUID to set or %NULL to generate a new one
251 : * @error: (out) (optional): place to store error (if any)
252 : *
253 : * Returns: whether the UUID of the NTFS file system on the @device was
254 : * successfully set or not
255 : *
256 : * Tech category: %BD_FS_TECH_NTFS-%BD_FS_TECH_MODE_SET_UUID
257 : */
258 4 : gboolean bd_fs_ntfs_set_uuid (const gchar *device, const gchar *uuid, GError **error) {
259 4 : gboolean ret = FALSE;
260 4 : const gchar *args[4] = {"ntfslabel", device, NULL, NULL};
261 :
262 4 : if (!check_deps (&avail_deps, DEPS_NTFSLABEL_MASK, deps, DEPS_LAST, &deps_check_lock, error))
263 0 : return FALSE;
264 :
265 4 : if (!uuid)
266 2 : args[2] = g_strdup ("--new-serial");
267 2 : else if (strlen (uuid) == 16)
268 2 : args[2] = g_strdup_printf ("--new-serial=%s", uuid);
269 0 : else if (strlen (uuid) == 8)
270 0 : args[2] = g_strdup_printf ("--new-half-serial=%s", uuid);
271 : else {
272 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_FAIL,
273 : "Invalid format of UUID/serial number for NTFS filesystem.");
274 0 : return FALSE;
275 : }
276 :
277 4 : ret = bd_utils_exec_and_report_error (args, NULL, error);
278 :
279 4 : g_free ((gchar *) args[2]);
280 4 : return ret;
281 : }
282 :
283 : /**
284 : * bd_fs_ntfs_check_uuid:
285 : * @uuid: UUID to check
286 : * @error: (out) (optional): place to store error
287 : *
288 : * Returns: whether @uuid is a valid UUID for the ntfs file system or not
289 : * (reason is provided in @error)
290 : *
291 : * Tech category: always available
292 : */
293 4 : gboolean bd_fs_ntfs_check_uuid (const gchar *uuid, GError **error) {
294 4 : size_t len = 0;
295 :
296 4 : len = strlen (uuid);
297 4 : if (len != 8 && len != 16) {
298 1 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_UUID_INVALID,
299 : "UUID for NTFS filesystem must be either 8 or 16 characters long.");
300 1 : return FALSE;
301 : }
302 :
303 35 : for (size_t i = 0; i < len; i++) {
304 33 : if (!g_ascii_isxdigit (uuid[i])) {
305 1 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_UUID_INVALID,
306 : "UUID for NTFS filesystem must be a hexadecimal number.");
307 1 : return FALSE;
308 : }
309 : }
310 :
311 2 : return TRUE;
312 : }
313 :
314 :
315 : /**
316 : * bd_fs_ntfs_resize:
317 : * @device: the device the file system of which to resize
318 : * @new_size: new requested size for the file system in bytes (if 0, the file system
319 : * is adapted to the underlying block device)
320 : * @error: (out) (optional): place to store error (if any)
321 : *
322 : * Returns: whether the file system on @device was successfully resized or not
323 : *
324 : * Tech category: %BD_FS_TECH_NTFS-%BD_FS_TECH_MODE_RESIZE
325 : */
326 5 : gboolean bd_fs_ntfs_resize (const gchar *device, guint64 new_size, GError **error) {
327 5 : const gchar *args[6] = {"ntfsresize", "--no-progress-bar", NULL, NULL, NULL, NULL};
328 5 : gboolean ret = FALSE;
329 :
330 5 : if (!check_deps (&avail_deps, DEPS_NTFSRESIZE_MASK, deps, DEPS_LAST, &deps_check_lock, error))
331 0 : return FALSE;
332 :
333 5 : if (new_size != 0) {
334 3 : args[2] = "-s";
335 3 : args[3] = g_strdup_printf ("%"G_GUINT64_FORMAT, new_size);
336 3 : args[4] = device;
337 : } else {
338 2 : args[2] = device;
339 : }
340 5 : ret = bd_utils_exec_and_report_error (args, NULL, error);
341 :
342 5 : g_free ((gchar *) args[3]);
343 5 : return ret;
344 : }
345 :
346 : /**
347 : * bd_fs_ntfs_get_info:
348 : * @device: the device containing the file system to get info for (device must
349 : not be mounted, trying to get info for a mounted device will result
350 : in an error)
351 : * @error: (out) (optional): place to store error (if any)
352 : *
353 : * Returns: (transfer full): information about the file system on @device or
354 : * %NULL in case of error
355 : *
356 : * Tech category: %BD_FS_TECH_NTFS-%BD_FS_TECH_MODE_QUERY
357 : */
358 17 : BDFSNtfsInfo* bd_fs_ntfs_get_info (const gchar *device, GError **error) {
359 17 : const gchar *args[4] = {"ntfsinfo", "-m", device, NULL};
360 17 : gboolean success = FALSE;
361 17 : gchar *output = NULL;
362 17 : BDFSNtfsInfo *ret = NULL;
363 17 : gchar **lines = NULL;
364 17 : gchar **line_p = NULL;
365 17 : gchar *val_start = NULL;
366 17 : g_autofree gchar* mountpoint = NULL;
367 17 : GError *l_error = NULL;
368 17 : size_t cluster_size = 0;
369 :
370 17 : if (!check_deps (&avail_deps, DEPS_NTFSINFO_MASK, deps, DEPS_LAST, &deps_check_lock, error))
371 0 : return NULL;
372 :
373 17 : mountpoint = bd_fs_get_mountpoint (device, &l_error);
374 17 : if (mountpoint != NULL) {
375 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_NOT_MOUNTED,
376 : "Can't get NTFS file system information for '%s': Device is mounted.", device);
377 0 : return NULL;
378 : } else {
379 17 : if (l_error != NULL) {
380 0 : g_propagate_prefixed_error (error, l_error, "Error when trying to get mountpoint for '%s': ", device);
381 0 : return NULL;
382 : }
383 : }
384 :
385 17 : ret = g_new0 (BDFSNtfsInfo, 1);
386 :
387 17 : success = get_uuid_label (device, &(ret->uuid), &(ret->label), error);
388 17 : if (!success) {
389 : /* error is already populated */
390 0 : bd_fs_ntfs_info_free (ret);
391 0 : return NULL;
392 : }
393 :
394 17 : success = bd_utils_exec_and_capture_output (args, NULL, &output, error);
395 17 : if (!success) {
396 : /* error is already populated */
397 0 : bd_fs_ntfs_info_free (ret);
398 0 : return NULL;
399 : }
400 :
401 17 : lines = g_strsplit (output, "\n", 0);
402 17 : g_free (output);
403 17 : line_p = lines;
404 :
405 : /* find the beginning of the (data) section we are interested in */
406 153 : while (line_p && *line_p && !strstr (*line_p, "Cluster Size"))
407 136 : line_p++;
408 17 : if (!line_p || !(*line_p)) {
409 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_PARSE, "Failed to parse NTFS file system information");
410 0 : g_strfreev (lines);
411 0 : bd_fs_ntfs_info_free (ret);
412 0 : return NULL;
413 : }
414 :
415 : /* extract data from something like this: "Cluster Size: 4096" */
416 17 : val_start = strchr (*line_p, ':');
417 17 : val_start++;
418 17 : cluster_size = g_ascii_strtoull (val_start, NULL, 0);
419 :
420 51 : while (line_p && *line_p && !strstr (*line_p, "Volume Size in Clusters"))
421 34 : line_p++;
422 17 : if (!line_p || !(*line_p)) {
423 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_PARSE, "Failed to parse NTFS file system information");
424 0 : g_strfreev (lines);
425 0 : bd_fs_ntfs_info_free (ret);
426 0 : return NULL;
427 : }
428 :
429 : /* extract data from something like this: "Volume Size in Clusters: 15314943" */
430 17 : val_start = strchr (*line_p, ':');
431 17 : val_start++;
432 17 : ret->size = g_ascii_strtoull (val_start, NULL, 0) * cluster_size;
433 :
434 595 : while (line_p && *line_p && !strstr (*line_p, "Free Clusters"))
435 578 : line_p++;
436 17 : if (!line_p || !(*line_p)) {
437 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_PARSE, "Failed to parse NTFS file system information");
438 0 : g_strfreev (lines);
439 0 : bd_fs_ntfs_info_free (ret);
440 0 : return NULL;
441 : }
442 :
443 : /* extract data from something like this: "Free Clusters: 7812655 (51,0%)" */
444 17 : val_start = strchr (*line_p, ':');
445 17 : val_start++;
446 17 : ret->free_space = g_ascii_strtoull (val_start, NULL, 0) * cluster_size;
447 :
448 17 : g_strfreev (lines);
449 :
450 17 : return ret;
451 : }
452 :
453 : /**
454 : * bd_fs_ntfs_get_min_size:
455 : * @device: the device containing the file system to get min size for
456 : * @error: (out) (optional): place to store error (if any)
457 : *
458 : * Returns: smallest shrunken filesystem size as reported by ntfsresize
459 : * in case of error 0 is returned and @error is set
460 : *
461 : * Tech category: %BD_FS_TECH_NTFS-%BD_FS_TECH_MODE_RESIZE
462 : */
463 2 : guint64 bd_fs_ntfs_get_min_size (const gchar *device, GError **error) {
464 2 : const gchar *args[4] = {"ntfsresize", "--info", device, NULL};
465 2 : gboolean success = FALSE;
466 2 : gchar *output = NULL;
467 2 : gchar **lines = NULL;
468 2 : gchar **line_p = NULL;
469 2 : guint64 min_size = 0;
470 2 : gint scanned = 0;
471 :
472 2 : if (!check_deps (&avail_deps, DEPS_NTFSRESIZE_MASK, deps, DEPS_LAST, &deps_check_lock, error))
473 0 : return FALSE;
474 :
475 2 : success = bd_utils_exec_and_capture_output (args, NULL, &output, error);
476 2 : if (!success)
477 : /* error is already populated */
478 0 : return 0;
479 :
480 2 : lines = g_strsplit (output, "\n", 0);
481 2 : g_free (output);
482 :
483 24 : for (line_p=lines; *line_p; line_p++) {
484 24 : if (g_str_has_prefix (*line_p, "You might resize at")) {
485 2 : scanned = sscanf (*line_p, "You might resize at %" G_GUINT64_FORMAT " bytes %*s.",
486 : &min_size);
487 2 : if (scanned != 1) {
488 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_FAIL,
489 : "Failed to get minimum size for '%s'", device);
490 0 : g_strfreev (lines);
491 0 : return 0;
492 : } else {
493 2 : g_strfreev (lines);
494 2 : return min_size;
495 : }
496 : }
497 : }
498 :
499 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_FAIL,
500 : "Failed to get minimum size for '%s'", device);
501 0 : g_strfreev (lines);
502 0 : return 0;
503 : }
|