Line data Source code
1 : /*
2 : * Copyright (C) 2019 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 "f2fs.h"
24 : #include "fs.h"
25 : #include "common.h"
26 :
27 : static volatile guint avail_deps = 0;
28 : static volatile guint avail_shrink_deps = 0;
29 : static GMutex deps_check_lock;
30 :
31 : #define DEPS_MKFSF2FS 0
32 : #define DEPS_MKFSF2FS_MASK (1 << DEPS_MKFSF2FS)
33 : #define DEPS_CHECKF2FS 1
34 : #define DEPS_CHECKF2FS_MASK (1 << DEPS_CHECKF2FS)
35 : #define DEPS_FSCKF2FS 2
36 : #define DEPS_FSCKF2FS_MASK (1 << DEPS_FSCKF2FS)
37 : #define DEPS_DUMPF2FS 3
38 : #define DEPS_DUMPF2FS_MASK (1 << DEPS_DUMPF2FS)
39 : #define DEPS_RESIZEF2FS 4
40 : #define DEPS_RESIZEF2FS_MASK (1 << DEPS_RESIZEF2FS)
41 :
42 : #define DEPS_LAST 5
43 :
44 : static const UtilDep deps[DEPS_LAST] = {
45 : {"mkfs.f2fs", NULL, NULL, NULL},
46 : {"fsck.f2fs", "1.11.0", "-V", "fsck.f2fs\\s+([\\d\\.]+).+"},
47 : {"fsck.f2fs", NULL, NULL, NULL},
48 : {"dump.f2fs", NULL, NULL, NULL},
49 : {"resize.f2fs", NULL, NULL, NULL}
50 : };
51 :
52 : /* shrinking needs newer version of f2fs */
53 : #define SHRINK_DEPS_RESIZEF2FS 0
54 : #define SHRINK_DEPS_RESIZEF2FS_MASK (1 << SHRINK_DEPS_RESIZEF2FS)
55 :
56 : #define SHRINK_DEPS_LAST 1
57 :
58 : static const UtilDep shrink_deps[SHRINK_DEPS_LAST] = {
59 : {"resize.f2fs", "1.12.0", "-V", "resize.f2fs\\s+([\\d\\.]+).+"}
60 : };
61 :
62 : static guint32 fs_mode_util[BD_FS_MODE_LAST+1] = {
63 : DEPS_MKFSF2FS_MASK, /* mkfs */
64 : 0, /* wipe */
65 : DEPS_CHECKF2FS_MASK, /* check */
66 : DEPS_FSCKF2FS_MASK, /* repair */
67 : 0, /* set-label */
68 : DEPS_DUMPF2FS_MASK, /* query */
69 : DEPS_RESIZEF2FS_MASK, /* resize */
70 : 0 /* set-uuid */
71 : };
72 :
73 :
74 : /* option to get version was added in 1.11.0 so we need to cover situation
75 : where the version is too old to check the version */
76 7 : static gboolean can_check_f2fs_version (UtilDep dep, GError **error) {
77 7 : gboolean available = FALSE;
78 7 : GError *loc_error = NULL;
79 :
80 7 : available = bd_utils_check_util_version (dep.name, dep.version,
81 : dep.ver_arg, dep.ver_regexp,
82 : &loc_error);
83 7 : if (!available) {
84 2 : if (g_error_matches (loc_error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_UTIL_UNKNOWN_VER)) {
85 : /* assuming version of f2fs is too low to check version of f2fs */
86 1 : g_clear_error (&loc_error);
87 1 : g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_UTIL_LOW_VER,
88 : "Too low version of %s. At least %s required.",
89 : dep.name, dep.version);
90 1 : return FALSE;
91 : }
92 : } else {
93 : /* just ignore other errors (e.g. version was detected but is still
94 : too low) -- check_deps call below will cover this and create a
95 : better error message for these cases */
96 5 : g_clear_error (&loc_error);
97 : }
98 :
99 6 : return TRUE;
100 : }
101 :
102 : /**
103 : * bd_fs_f2fs_is_tech_avail:
104 : * @tech: the queried tech
105 : * @mode: a bit mask of queried modes of operation (#BDFSTechMode) for @tech
106 : * @error: (out) (optional): place to store error (details about why the @tech-@mode combination is not available)
107 : *
108 : * Returns: whether the @tech-@mode combination is available -- supported by the
109 : * plugin implementation and having all the runtime dependencies available
110 : */
111 : G_GNUC_INTERNAL gboolean
112 106 : bd_fs_f2fs_is_tech_avail (BDFSTech tech G_GNUC_UNUSED, guint64 mode, GError **error) {
113 106 : guint32 required = 0;
114 106 : guint i = 0;
115 :
116 106 : if (mode & BD_FS_TECH_MODE_SET_LABEL) {
117 1 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_TECH_UNAVAIL,
118 : "F2FS doesn't support setting label for an existing device.");
119 1 : return FALSE;
120 : }
121 :
122 105 : if (mode & BD_FS_TECH_MODE_SET_UUID) {
123 1 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_TECH_UNAVAIL,
124 : "F2FS doesn't support setting UUID for an existing device.");
125 1 : return FALSE;
126 : }
127 :
128 104 : if (mode & BD_FS_TECH_MODE_CHECK) {
129 6 : if (!can_check_f2fs_version (deps[DEPS_CHECKF2FS], error))
130 1 : return FALSE;
131 : }
132 :
133 927 : for (i = 0; i <= BD_FS_MODE_LAST; i++)
134 824 : if (mode & (1 << i))
135 105 : required |= fs_mode_util[i];
136 :
137 103 : return check_deps (&avail_deps, required, deps, DEPS_LAST, &deps_check_lock, error);
138 : }
139 :
140 : /**
141 : * bd_fs_f2fs_info_copy: (skip)
142 : *
143 : * Creates a new copy of @data.
144 : */
145 0 : BDFSF2FSInfo* bd_fs_f2fs_info_copy (BDFSF2FSInfo *data) {
146 0 : if (data == NULL)
147 0 : return NULL;
148 :
149 0 : BDFSF2FSInfo *ret = g_new0 (BDFSF2FSInfo, 1);
150 :
151 0 : ret->label = g_strdup (data->label);
152 0 : ret->uuid = g_strdup (data->uuid);
153 0 : ret->sector_size = data->sector_size;
154 0 : ret->sector_count = data->sector_count;
155 0 : ret->features = data->features;
156 :
157 0 : return ret;
158 : }
159 :
160 : /**
161 : * bd_fs_f2fs_info_free: (skip)
162 : *
163 : * Frees @data.
164 : */
165 0 : void bd_fs_f2fs_info_free (BDFSF2FSInfo *data) {
166 0 : if (data == NULL)
167 0 : return;
168 :
169 0 : g_free (data->label);
170 0 : g_free (data->uuid);
171 0 : g_free (data);
172 : }
173 :
174 : G_GNUC_INTERNAL BDExtraArg **
175 6 : bd_fs_f2fs_mkfs_options (BDFSMkfsOptions *options, const BDExtraArg **extra) {
176 6 : GPtrArray *options_array = g_ptr_array_new ();
177 6 : const BDExtraArg **extra_p = NULL;
178 :
179 6 : if (options->label && g_strcmp0 (options->label, "") != 0)
180 1 : g_ptr_array_add (options_array, bd_extra_arg_new ("-l", options->label));
181 :
182 6 : if (options->no_discard)
183 0 : g_ptr_array_add (options_array, bd_extra_arg_new ("-t", "nodiscard"));
184 :
185 6 : if (options->force)
186 2 : g_ptr_array_add (options_array, bd_extra_arg_new ("-f", ""));
187 :
188 6 : if (extra) {
189 0 : for (extra_p = extra; *extra_p; extra_p++)
190 0 : g_ptr_array_add (options_array, bd_extra_arg_copy ((BDExtraArg *) *extra_p));
191 : }
192 :
193 6 : g_ptr_array_add (options_array, NULL);
194 :
195 6 : return (BDExtraArg **) g_ptr_array_free (options_array, FALSE);
196 : }
197 :
198 :
199 : /**
200 : * bd_fs_f2fs_mkfs:
201 : * @device: the device to create a new f2fs fs on
202 : * @extra: (nullable) (array zero-terminated=1): extra options for the creation (right now
203 : * passed to the 'mkfs.f2fs' utility)
204 : * @error: (out) (optional): place to store error (if any)
205 : *
206 : * Returns: whether a new f2fs fs was successfully created on @device or not
207 : *
208 : * Tech category: %BD_FS_TECH_F2FS-%BD_FS_TECH_MODE_MKFS
209 : */
210 18 : gboolean bd_fs_f2fs_mkfs (const gchar *device, const BDExtraArg **extra, GError **error) {
211 18 : const gchar *args[3] = {"mkfs.f2fs", device, NULL};
212 :
213 18 : if (!check_deps (&avail_deps, DEPS_MKFSF2FS_MASK, deps, DEPS_LAST, &deps_check_lock, error))
214 0 : return FALSE;
215 :
216 18 : return bd_utils_exec_and_report_error (args, extra, error);
217 : }
218 :
219 : /**
220 : * bd_fs_f2fs_check:
221 : * @device: the device containing the file system to check
222 : * @extra: (nullable) (array zero-terminated=1): extra options for the repair (right now
223 : * passed to the 'fsck.f2fs' utility)
224 : * @error: (out) (optional): place to store error (if any)
225 : *
226 : * Returns: whether an f2fs file system on the @device is clean or not
227 : *
228 : * Tech category: %BD_FS_TECH_F2FS-%BD_FS_TECH_MODE_CHECK
229 : */
230 3 : gboolean bd_fs_f2fs_check (const gchar *device, const BDExtraArg **extra, GError **error) {
231 3 : const gchar *args[4] = {"fsck.f2fs", "--dry-run", device, NULL};
232 3 : gint status = 0;
233 3 : gboolean ret = FALSE;
234 :
235 3 : if (!bd_fs_f2fs_is_tech_avail (BD_FS_TECH_F2FS, BD_FS_TECH_MODE_CHECK, error))
236 0 : return FALSE;
237 :
238 3 : ret = bd_utils_exec_and_report_status_error (args, extra, &status, error);
239 3 : if (!ret && (status == 255)) {
240 : /* no error should be reported for exit code 255 -- there are errors on the filesystem */
241 0 : g_clear_error (error);
242 : }
243 3 : return ret;
244 : }
245 :
246 : /**
247 : * bd_fs_f2fs_repair:
248 : * @device: the device containing the file system to repair
249 : * @extra: (nullable) (array zero-terminated=1): extra options for the repair (right now
250 : * passed to the 'fsck.f2fs' utility)
251 : * @error: (out) (optional): place to store error (if any)
252 : *
253 : * Returns: whether an f2fs file system on the @device was successfully repaired
254 : * (if needed) or not (error is set in that case)
255 : *
256 : * Tech category: %BD_FS_TECH_F2FS-%BD_FS_TECH_MODE_REPAIR
257 : */
258 3 : gboolean bd_fs_f2fs_repair (const gchar *device, const BDExtraArg **extra, GError **error) {
259 3 : const gchar *args[4] = {"fsck.f2fs", "-a", device, NULL};
260 :
261 3 : if (!check_deps (&avail_deps, DEPS_FSCKF2FS_MASK, deps, DEPS_LAST, &deps_check_lock, error))
262 0 : return FALSE;
263 :
264 3 : return bd_utils_exec_and_report_error (args, extra, error);
265 : }
266 :
267 : /**
268 : * bd_fs_f2fs_get_info:
269 : * @device: the device containing the file system to get info for
270 : * @error: (out) (optional): place to store error (if any)
271 : *
272 : * Returns: (transfer full): information about the file system on @device or
273 : * %NULL in case of error
274 : *
275 : * Tech category: %BD_FS_TECH_F2FS-%BD_FS_TECH_MODE_QUERY
276 : */
277 8 : BDFSF2FSInfo* bd_fs_f2fs_get_info (const gchar *device, GError **error) {
278 8 : const gchar *argv[3] = {"dump.f2fs", device, NULL};
279 8 : gchar *output = NULL;
280 8 : gboolean success = FALSE;
281 8 : BDFSF2FSInfo*ret = NULL;
282 8 : gchar **lines = NULL;
283 8 : gchar **line_p = NULL;
284 8 : gchar *val_start = NULL;
285 :
286 8 : if (!check_deps (&avail_deps, DEPS_DUMPF2FS_MASK, deps, DEPS_LAST, &deps_check_lock, error))
287 0 : return NULL;
288 :
289 8 : success = bd_utils_exec_and_capture_output (argv, NULL, &output, error);
290 8 : if (!success) {
291 : /* error is already populated from the call above or just empty
292 : output */
293 0 : return NULL;
294 : }
295 :
296 8 : ret = g_new0 (BDFSF2FSInfo, 1);
297 :
298 8 : success = get_uuid_label (device, &(ret->uuid), &(ret->label), error);
299 8 : if (!success) {
300 : /* error is already populated */
301 0 : bd_fs_f2fs_info_free (ret);
302 0 : g_free (output);
303 0 : return NULL;
304 : }
305 :
306 8 : lines = g_strsplit (output, "\n", 0);
307 8 : g_free (output);
308 8 : line_p = lines;
309 :
310 137 : while (line_p && *line_p && !g_str_has_prefix (*line_p, "Info: sector size"))
311 129 : line_p++;
312 8 : if (!line_p || !(*line_p)) {
313 : /* Sector size is not printed with dump.f2fs 1.15 */
314 8 : ret->sector_size = 0;
315 : } else {
316 : /* extract data from something like this: "Info: sector size = 4096" */
317 0 : val_start = strchr (*line_p, '=');
318 0 : val_start++;
319 0 : ret->sector_size = g_ascii_strtoull (val_start, NULL, 0);
320 : }
321 :
322 8 : line_p = lines;
323 88 : while (line_p && *line_p && !g_str_has_prefix (*line_p, "Info: total FS sectors"))
324 80 : line_p++;
325 8 : if (!line_p || !(*line_p)) {
326 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_PARSE, "Failed to parse F2FS file system information");
327 0 : g_strfreev (lines);
328 0 : bd_fs_f2fs_info_free (ret);
329 0 : return NULL;
330 : }
331 :
332 : /* extract data from something like this: "Info: total sectors = 3932160 (15360 MB)" */
333 8 : val_start = strchr (*line_p, '=');
334 8 : val_start++;
335 8 : ret->sector_count = g_ascii_strtoull (val_start, NULL, 0);
336 :
337 8 : line_p = lines;
338 56 : while (line_p && *line_p && !g_str_has_prefix (*line_p, "Info: superblock features"))
339 48 : line_p++;
340 8 : if (!line_p || !(*line_p)) {
341 0 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_PARSE, "Failed to parse F2FS file system information");
342 0 : g_strfreev (lines);
343 0 : bd_fs_f2fs_info_free (ret);
344 0 : return NULL;
345 : }
346 :
347 : /* extract data from something like this: "Info: superblock features = 0" */
348 8 : val_start = strchr (*line_p, '=');
349 8 : val_start++;
350 8 : ret->features = g_ascii_strtoull (val_start, NULL, 16);
351 :
352 8 : g_strfreev (lines);
353 8 : return ret;
354 : }
355 :
356 : /**
357 : * bd_fs_f2fs_resize:
358 : * @device: the device containing the file system to resize
359 : * @new_size: new requested size for the file system *in file system sectors* (see bd_fs_f2fs_get_info())
360 : * (if 0, the file system is adapted to the underlying block device)
361 : * @safe: whether to perform safe resize or not (does not resize metadata)
362 : * @extra: (nullable) (array zero-terminated=1): extra options for the resize (right now
363 : * passed to the 'resize.f2fs' utility)
364 : * @error: (out) (optional): place to store error (if any)
365 : *
366 : * Returns: whether the file system on @device was successfully resized or not
367 : *
368 : * Tech category: %BD_FS_TECH_F2FS-%BD_FS_TECH_MODE_RESIZE
369 : */
370 2 : gboolean bd_fs_f2fs_resize (const gchar *device, guint64 new_size, gboolean safe, const BDExtraArg **extra, GError **error) {
371 2 : const gchar *args[6] = {"resize.f2fs", NULL, NULL, NULL, NULL, NULL};
372 2 : gchar *size_str = NULL;
373 2 : gboolean ret = FALSE;
374 2 : guint next_arg = 1;
375 2 : BDFSF2FSInfo *info = NULL;
376 :
377 2 : if (!check_deps (&avail_deps, DEPS_RESIZEF2FS_MASK, deps, DEPS_LAST, &deps_check_lock, error))
378 0 : return FALSE;
379 :
380 2 : if (safe) {
381 2 : if (!can_check_f2fs_version (shrink_deps[SHRINK_DEPS_RESIZEF2FS], error) ||
382 1 : !check_deps (&avail_shrink_deps, SHRINK_DEPS_RESIZEF2FS_MASK, shrink_deps, SHRINK_DEPS_LAST, &deps_check_lock, error)) {
383 : /* f2fs version is too low to either even check its version or
384 : to perform safe resize (shrink) */
385 0 : g_prefix_error (error, "Can't perform safe resize: ");
386 0 : return FALSE;
387 : }
388 : }
389 :
390 2 : info = bd_fs_f2fs_get_info (device, error);
391 2 : if (!info) {
392 : /* error is already populated */
393 0 : return FALSE;
394 : }
395 :
396 2 : if (new_size != 0 && new_size < info->sector_count && !safe) {
397 : /* resize.f2fs prints error and returns 0 in this case */
398 1 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_INVAL,
399 : "F2FS filesystem doesn't support shrinking without using the 'safe' option");
400 1 : bd_fs_f2fs_info_free (info);
401 1 : return FALSE;
402 : }
403 1 : bd_fs_f2fs_info_free (info);
404 :
405 1 : if (safe) {
406 1 : args[next_arg++] = "-s";
407 : }
408 :
409 1 : if (new_size != 0) {
410 1 : args[next_arg++] = "-t";
411 1 : size_str = g_strdup_printf ("%"G_GUINT64_FORMAT, new_size);
412 1 : args[next_arg++] = size_str;
413 1 : args[next_arg++] = device;
414 : } else
415 0 : args[next_arg++] = device;
416 :
417 1 : ret = bd_utils_exec_and_report_error (args, extra, error);
418 :
419 1 : g_free (size_str);
420 1 : return ret;
421 : }
422 :
423 : /**
424 : * bd_fs_f2fs_check_label:
425 : * @label: label to check
426 : * @error: (out) (optional): place to store error
427 : *
428 : * Returns: whether @label is a valid label for the f2fs file system or not
429 : * (reason is provided in @error)
430 : *
431 : * Tech category: always available
432 : */
433 3 : gboolean bd_fs_f2fs_check_label (const gchar *label, GError **error) {
434 3 : size_t len = 0;
435 :
436 3 : len = strlen (label);
437 3 : if (len > 512) {
438 1 : g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_LABEL_INVALID,
439 : "Label for F2FS filesystem must be at most 512 characters long.");
440 1 : return FALSE;
441 : }
442 :
443 2 : return TRUE;
444 : }
|