/* * Copyright (C) 2017 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, see . * * Author: Vratislav Podzimek */ #include #include #include #include #include #include #include "vfat.h" #include "fs.h" #include "common.h" static volatile guint avail_deps = 0; static GMutex deps_check_lock; #define DEPS_MKFSVFAT 0 #define DEPS_MKFSVFAT_MASK (1 << DEPS_MKFSVFAT) #define DEPS_FATLABEL 1 #define DEPS_FATLABEL_MASK (1 << DEPS_FATLABEL) #define DEPS_FSCKVFAT 2 #define DEPS_FSCKVFAT_MASK (1 << DEPS_FSCKVFAT) #define DEPS_LAST 3 static const UtilDep deps[DEPS_LAST] = { {"mkfs.vfat", NULL, NULL, NULL}, {"fatlabel", NULL, NULL, NULL}, {"fsck.vfat", NULL, NULL, NULL}, }; static guint32 fs_mode_util[BD_FS_MODE_LAST+1] = { /* mkfs wipe check repair set-label query resize */ DEPS_MKFSVFAT_MASK, 0, DEPS_FSCKVFAT_MASK, DEPS_FSCKVFAT_MASK, DEPS_FATLABEL_MASK, DEPS_FSCKVFAT_MASK, 0 }; #define UNUSED __attribute__((unused)) #ifdef __clang__ #define ZERO_INIT {} #else #define ZERO_INIT {0} #endif /** * bd_fs_vfat_is_tech_avail: * @tech: the queried tech * @mode: a bit mask of queried modes of operation (#BDFSTechMode) for @tech * @error: (out): place to store error (details about why the @tech-@mode combination is not available) * * Returns: whether the @tech-@mode combination is available -- supported by the * plugin implementation and having all the runtime dependencies available */ gboolean __attribute__ ((visibility ("hidden"))) bd_fs_vfat_is_tech_avail (BDFSTech tech UNUSED, guint64 mode, GError **error) { guint32 required = 0; guint i = 0; for (i = 0; i <= BD_FS_MODE_LAST; i++) if (mode & (1 << i)) required |= fs_mode_util[i]; return check_deps (&avail_deps, required, deps, DEPS_LAST, &deps_check_lock, error); } /** * bd_fs_vfat_info_copy: (skip) * * Creates a new copy of @data. */ BDFSVfatInfo* bd_fs_vfat_info_copy (BDFSVfatInfo *data) { if (data == NULL) return NULL; BDFSVfatInfo *ret = g_new0 (BDFSVfatInfo, 1); ret->label = g_strdup (data->label); ret->uuid = g_strdup (data->uuid); ret->cluster_size = data->cluster_size; ret->cluster_count = data->cluster_count; ret->free_cluster_count = data->free_cluster_count; return ret; } /** * bd_fs_vfat_info_free: (skip) * * Frees @data. */ void bd_fs_vfat_info_free (BDFSVfatInfo *data) { if (data == NULL) return; g_free (data->label); g_free (data->uuid); g_free (data); } /** * set_parted_error: (skip) * * Set error from the parted error stored in 'error_msg'. In case there is none, * the error is set up with an empty string. Otherwise it is set up with the * parted's error message and is a subject to later g_prefix_error() call. * * Returns: whether there was some message from parted or not */ static gboolean set_parted_error (GError **error, BDFsError type) { gchar *error_msg = NULL; error_msg = bd_get_error_msg (); if (error_msg) { g_set_error (error, BD_FS_ERROR, type, " (%s)", error_msg); g_free (error_msg); error_msg = NULL; return TRUE; } else { g_set_error_literal (error, BD_FS_ERROR, type, ""); return FALSE; } } /** * bd_fs_vfat_mkfs: * @device: the device to create a new vfat fs on * @extra: (allow-none) (array zero-terminated=1): extra options for the creation (right now * passed to the 'mkfs.vfat' utility) * @error: (out): place to store error (if any) * * Returns: whether a new vfat fs was successfully created on @device or not * * Tech category: %BD_FS_TECH_VFAT-%BD_FS_TECH_MODE_MKFS */ gboolean bd_fs_vfat_mkfs (const gchar *device, const BDExtraArg **extra, GError **error) { const gchar *args[4] = {"mkfs.vfat", "-I", device, NULL}; if (!check_deps (&avail_deps, DEPS_MKFSVFAT_MASK, deps, DEPS_LAST, &deps_check_lock, error)) return FALSE; return bd_utils_exec_and_report_error (args, extra, error); } /** * bd_fs_vfat_wipe: * @device: the device to wipe an vfat signature from * @error: (out): place to store error (if any) * * Returns: whether an vfat signature was successfully wiped from the @device or * not * * Tech category: %BD_FS_TECH_VFAT-%BD_FS_TECH_MODE_WIPE */ gboolean bd_fs_vfat_wipe (const gchar *device, GError **error) { return wipe_fs (device, "vfat", TRUE, error); } /** * bd_fs_vfat_check: * @device: the device containing the file system to check * @extra: (allow-none) (array zero-terminated=1): extra options for the repair (right now * passed to the 'fsck.vfat' utility) * @error: (out): place to store error (if any) * * Returns: whether an vfat file system on the @device is clean or not * * Tech category: %BD_FS_TECH_VFAT-%BD_FS_TECH_MODE_CHECK */ gboolean bd_fs_vfat_check (const gchar *device, const BDExtraArg **extra, GError **error) { const gchar *args[4] = {"fsck.vfat", "-n", device, NULL}; gint status = 0; gboolean ret = FALSE; if (!check_deps (&avail_deps, DEPS_FSCKVFAT_MASK, deps, DEPS_LAST, &deps_check_lock, error)) return FALSE; ret = bd_utils_exec_and_report_status_error (args, extra, &status, error); if (!ret && (status == 1)) { /* no error should be reported for exit code 1 -- Recoverable errors have been detected */ g_clear_error (error); } return ret; } /** * bd_fs_vfat_repair: * @device: the device containing the file system to repair * @extra: (allow-none) (array zero-terminated=1): extra options for the repair (right now * passed to the 'fsck.vfat' utility) * @error: (out): place to store error (if any) * * Returns: whether an vfat file system on the @device was successfully repaired * (if needed) or not (error is set in that case) * * Tech category: %BD_FS_TECH_VFAT-%BD_FS_TECH_MODE_REPAIR */ gboolean bd_fs_vfat_repair (const gchar *device, const BDExtraArg **extra, GError **error) { const gchar *args[4] = {"fsck.vfat", "-a", device, NULL}; if (!check_deps (&avail_deps, DEPS_FSCKVFAT_MASK, deps, DEPS_LAST, &deps_check_lock, error)) return FALSE; return bd_utils_exec_and_report_error (args, extra, error); } /** * bd_fs_vfat_set_label: * @device: the device containing the file system to set label for * @label: label to set * @error: (out): place to store error (if any) * * Returns: whether the label of vfat file system on the @device was * successfully set or not * * Tech category: %BD_FS_TECH_VFAT-%BD_FS_TECH_MODE_SET_LABEL */ gboolean bd_fs_vfat_set_label (const gchar *device, const gchar *label, GError **error) { const gchar *args[4] = {"fatlabel", device, label, NULL}; if (!check_deps (&avail_deps, DEPS_FATLABEL_MASK, deps, DEPS_LAST, &deps_check_lock, error)) return FALSE; return bd_utils_exec_and_report_error (args, NULL, error); } /** * bd_fs_vfat_get_info: * @device: the device containing the file system to get info for * @error: (out): place to store error (if any) * * Returns: (transfer full): information about the file system on @device or * %NULL in case of error * * Tech category: %BD_FS_TECH_VFAT-%BD_FS_TECH_MODE_QUERY */ BDFSVfatInfo* bd_fs_vfat_get_info (const gchar *device, GError **error) { const gchar *args[4] = {"fsck.vfat", "-nv", device, NULL}; gboolean success = FALSE; BDFSVfatInfo *ret = NULL; gchar *output = NULL; gchar **lines = NULL; gchar **line_p = NULL; gboolean have_cluster_size = FALSE; gboolean have_cluster_count = FALSE; guint64 full_cluster_count = 0; guint64 cluster_count = 0; gchar **key_val = NULL; gint scanned = 0; if (!check_deps (&avail_deps, DEPS_FSCKVFAT_MASK, deps, DEPS_LAST, &deps_check_lock, error)) return NULL; ret = g_new0 (BDFSVfatInfo, 1); success = get_uuid_label (device, &(ret->uuid), &(ret->label), error); if (!success) { /* error is already populated */ bd_fs_vfat_info_free (ret); return NULL; } success = bd_utils_exec_and_capture_output (args, NULL, &output, error); if (!success) { /* error is already populated */ bd_fs_vfat_info_free (ret); return NULL; } lines = g_strsplit (output, "\n", 0); g_free (output); for (line_p=lines; *line_p && (!have_cluster_size || !have_cluster_count); line_p++) { if (!have_cluster_size && g_str_has_suffix (*line_p, "bytes per cluster")) { ret->cluster_size = g_ascii_strtoull (*line_p, NULL, 0); have_cluster_size = TRUE; } else if (!have_cluster_count && g_str_has_prefix (*line_p, device)) { key_val = g_strsplit (*line_p, ",", 2); scanned = sscanf (key_val[1], " %" G_GUINT64_FORMAT "/" "%" G_GUINT64_FORMAT " clusters", &full_cluster_count, &cluster_count); if (scanned != 2) { g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_FAIL, "Failed to get number of FAT clusters for '%s'", device); bd_fs_vfat_info_free (ret); g_strfreev (key_val); g_strfreev (lines); return NULL; } ret->cluster_count = cluster_count; ret->free_cluster_count = cluster_count - full_cluster_count; have_cluster_count = TRUE; g_strfreev (key_val); } } g_strfreev (lines); return ret; } /** * bd_fs_vfat_resize: * @device: the device the file system of which to resize * @new_size: new requested size for the file system (if 0, the file system is * adapted to the underlying block device) * @error: (out): place to store error (if any) * * Returns: whether the file system on @device was successfully resized or not * * Tech category: %BD_FS_TECH_VFAT-%BD_FS_TECH_MODE_RESIZE */ gboolean bd_fs_vfat_resize (const gchar *device, guint64 new_size, GError **error) { PedDevice *ped_dev = NULL; PedGeometry geom = ZERO_INIT; PedGeometry new_geom = ZERO_INIT; PedFileSystem *fs = NULL; PedSector start = 0; PedSector length = 0; gint status = 0; guint64 progress_id = 0; gchar *msg = NULL; msg = g_strdup_printf ("Started resizing vfat filesystem on the device '%s'", device); progress_id = bd_utils_report_started (msg); g_free (msg); ped_dev = ped_device_get (device); if (!ped_dev) { g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_FAIL, "Failed to get ped device for the device '%s'", device); bd_utils_report_finished (progress_id, (*error)->message); return FALSE; } status = ped_device_open (ped_dev); if (status == 0) { g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_FAIL, "Failed to get open the device '%s'", device); bd_utils_report_finished (progress_id, (*error)->message); return FALSE; } status = ped_geometry_init (&geom, ped_dev, start, ped_dev->length); if (status == 0) { g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_FAIL, "Failed to initialize geometry for the device '%s'", device); ped_device_close (ped_dev); bd_utils_report_finished (progress_id, (*error)->message); return FALSE; } fs = ped_file_system_open(&geom); if (!fs) { g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_FAIL, "Failed to read the filesystem on the device '%s'", device); ped_device_close (ped_dev); bd_utils_report_finished (progress_id, (*error)->message); return FALSE; } if (new_size == 0) length = ped_dev->length; else length = (PedSector) ((PedSector) new_size / ped_dev->sector_size); status = ped_geometry_init(&new_geom, ped_dev, start, length); if (status == 0) { g_set_error (error, BD_FS_ERROR, BD_FS_ERROR_FAIL, "Failed to initialize new geometry for the filesystem on '%s'", device); ped_file_system_close (fs); ped_device_close (ped_dev); bd_utils_report_finished (progress_id, (*error)->message); return FALSE; } status = ped_file_system_resize(fs, &new_geom, NULL); if (status == 0) { set_parted_error (error, BD_FS_ERROR_FAIL); g_prefix_error (error, "Failed to resize the filesystem on '%s'", device); ped_file_system_close (fs); ped_device_close (ped_dev); bd_utils_report_finished (progress_id, (*error)->message); return FALSE; } ped_file_system_close (fs); ped_device_close (ped_dev); bd_utils_report_finished (progress_id, "Completed"); return TRUE; }