LCOV - code coverage report
Current view: top level - plugins/fs - exfat.c (source / functions) Coverage Total Hit
Test: libblockdev Coverage Report Lines: 77.7 % 166 129
Test Date: 2026-01-26 13:19:28 Functions: 83.3 % 12 10
Legend: Lines: hit not hit

            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              : }
        

Generated by: LCOV version 2.0-1