/* libguestfs - the guestfsd daemon * Copyright (C) 2011-2012 Red Hat Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #define PCRE2_CODE_UNIT_WIDTH 8 #include #include "daemon.h" #include "actions.h" #include "optgroups.h" #include "xstrtol.h" #include "c-ctype.h" #include "ignore-value.h" COMPILE_REGEXP (re_btrfs_balance_status, "Balance on '.*' is (.*)", 0) int optgroup_btrfs_available (void) { return test_mode || (prog_exists ("btrfs") && filesystem_available ("btrfs") > 0); } char * btrfs_get_label (const char *device) { int r; CLEANUP_FREE char *err = NULL; char *out = NULL; size_t len; r = command (&out, &err, "btrfs", "filesystem", "label", device, NULL); if (r == -1) { reply_with_error ("%s", err); free (out); return NULL; } /* Trim trailing \n if present. */ len = strlen (out); if (len > 0 && out[len-1] == '\n') out[len-1] = '\0'; return out; } int btrfs_set_label (const char *device, const char *label) { int r; CLEANUP_FREE char *err = NULL; r = command (NULL, &err, "btrfs", "filesystem", "label", device, label, NULL); if (r == -1) { reply_with_error ("%s", err); return -1; } return 0; } #if defined(__GNUC__) && GUESTFS_GCC_VERSION >= 40800 /* gcc >= 4.8.0 */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstack-usage=" #endif /* Takes optional arguments, consult optargs_bitmask. */ int do_btrfs_filesystem_resize (const char *filesystem, int64_t size) { const size_t MAX_ARGS = 64; CLEANUP_FREE char *buf = NULL, *err = NULL; int r; const char *argv[MAX_ARGS]; size_t i = 0; char size_str[32]; ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "filesystem"); ADD_ARG (argv, i, "resize"); if (optargs_bitmask & GUESTFS_BTRFS_FILESYSTEM_RESIZE_SIZE_BITMASK) { if (size <= 0) { reply_with_error ("size is zero or negative"); return -1; } snprintf (size_str, sizeof size_str, "%" PRIi64, size); ADD_ARG (argv, i, size_str); } else ADD_ARG (argv, i, "max"); buf = sysroot_path (filesystem); if (!buf) { reply_with_error ("malloc"); return -1; } ADD_ARG (argv, i, buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", filesystem, err); return -1; } return 0; } /* Takes optional arguments, consult optargs_bitmask. */ int do_mkfs_btrfs (char *const *devices, int64_t allocstart, int64_t bytecount, const char *datatype, int leafsize, const char *label, const char *metadata, int nodesize, int sectorsize) { const size_t nr_devices = guestfs_int_count_strings (devices); const size_t MAX_ARGS = nr_devices + 64; const char *argv[MAX_ARGS]; size_t i = 0, j; int r; CLEANUP_FREE char *err = NULL; char bytecount_s[64]; char leafsize_s[64]; char nodesize_s[64]; char sectorsize_s[64]; if (nr_devices == 0) { reply_with_error ("list of devices must be non-empty"); return -1; } ADD_ARG (argv, i, "mkfs.btrfs"); /* Optional arguments. */ if (optargs_bitmask & GUESTFS_MKFS_BTRFS_ALLOCSTART_BITMASK) { /* --alloc-start was deprecated in btrfs-progs 4.14.1. Ignore * this option if present. */ } if (optargs_bitmask & GUESTFS_MKFS_BTRFS_BYTECOUNT_BITMASK) { if (bytecount <= 0) { /* actually the minimum is 256MB */ reply_with_error ("bytecount must be > 0"); return -1; } snprintf (bytecount_s, sizeof bytecount_s, "%" PRIi64, bytecount); ADD_ARG (argv, i, "--byte-count"); ADD_ARG (argv, i, bytecount_s); } if (optargs_bitmask & GUESTFS_MKFS_BTRFS_DATATYPE_BITMASK) { if (STRNEQ (datatype, "raid0") && STRNEQ (datatype, "raid1") && STRNEQ (datatype, "raid10") && STRNEQ (datatype, "single")) { reply_with_error ("datatype not one of the allowed values"); return -1; } ADD_ARG (argv, i, "--data"); ADD_ARG (argv, i, datatype); } if (optargs_bitmask & GUESTFS_MKFS_BTRFS_LEAFSIZE_BITMASK) { if (!is_power_of_2 (leafsize) || leafsize <= 0) { reply_with_error ("leafsize must be > 0 and a power of two"); return -1; } snprintf (leafsize_s, sizeof leafsize_s, "%d", leafsize); ADD_ARG (argv, i, "--leafsize"); ADD_ARG (argv, i, leafsize_s); } if (optargs_bitmask & GUESTFS_MKFS_BTRFS_LABEL_BITMASK) { ADD_ARG (argv, i, "--label"); ADD_ARG (argv, i, label); } if (optargs_bitmask & GUESTFS_MKFS_BTRFS_METADATA_BITMASK) { if (STRNEQ (metadata, "raid0") && STRNEQ (metadata, "raid1") && STRNEQ (metadata, "raid10") && STRNEQ (metadata, "single")) { reply_with_error ("metadata not one of the allowed values"); return -1; } ADD_ARG (argv, i, "--metadata"); ADD_ARG (argv, i, metadata); } if (optargs_bitmask & GUESTFS_MKFS_BTRFS_NODESIZE_BITMASK) { if (!is_power_of_2 (nodesize) || nodesize <= 0) { reply_with_error ("nodesize must be > 0 and a power of two"); return -1; } snprintf (nodesize_s, sizeof nodesize_s, "%d", nodesize); ADD_ARG (argv, i, "--nodesize"); ADD_ARG (argv, i, nodesize_s); } if (optargs_bitmask & GUESTFS_MKFS_BTRFS_SECTORSIZE_BITMASK) { if (!is_power_of_2 (sectorsize) || sectorsize <= 0) { reply_with_error ("sectorsize must be > 0 and a power of two"); return -1; } snprintf (sectorsize_s, sizeof sectorsize_s, "%d", sectorsize); ADD_ARG (argv, i, "--sectorsize"); ADD_ARG (argv, i, sectorsize_s); } for (j = 0; j < nr_devices; ++j) ADD_ARG (argv, i, devices[j]); ADD_ARG (argv, i, NULL); for (j = 0; j < nr_devices; ++j) wipe_device_before_mkfs (devices[j]); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", devices[0], err); return -1; } return 0; } #if defined(__GNUC__) && GUESTFS_GCC_VERSION >= 40800 /* gcc >= 4.8.0 */ #pragma GCC diagnostic pop #endif int do_btrfs_subvolume_snapshot (const char *source, const char *dest, int ro, const char *qgroupid) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *source_buf = NULL, *dest_buf = NULL; CLEANUP_FREE char *err = NULL; int r; source_buf = sysroot_path (source); if (source_buf == NULL) { reply_with_perror ("malloc"); return -1; } dest_buf = sysroot_path (dest); if (dest_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "subvolume"); ADD_ARG (argv, i, "snapshot"); /* Optional arguments. */ if ((optargs_bitmask & GUESTFS_BTRFS_SUBVOLUME_SNAPSHOT_RO_BITMASK) && ro) ADD_ARG (argv, i, "-r"); if (optargs_bitmask & GUESTFS_BTRFS_SUBVOLUME_SNAPSHOT_QGROUPID_BITMASK) { ADD_ARG (argv, i, "-i"); ADD_ARG (argv, i, qgroupid); } ADD_ARG (argv, i, source_buf); ADD_ARG (argv, i, dest_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s: %s", source, dest, err); return -1; } return 0; } int do_btrfs_subvolume_delete (const char *subvolume) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *subvolume_buf = NULL; CLEANUP_FREE char *err = NULL; int r; subvolume_buf = sysroot_path (subvolume); if (subvolume_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "subvolume"); ADD_ARG (argv, i, "delete"); ADD_ARG (argv, i, subvolume_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", subvolume, err); return -1; } return 0; } int do_btrfs_subvolume_create (const char *dest, const char *qgroupid) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *dest_buf = NULL; CLEANUP_FREE char *err = NULL; int r; dest_buf = sysroot_path (dest); if (dest_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "subvolume"); ADD_ARG (argv, i, "create"); /* Optional arguments. */ if (optargs_bitmask & GUESTFS_BTRFS_SUBVOLUME_CREATE_QGROUPID_BITMASK) { ADD_ARG (argv, i, "-i"); ADD_ARG (argv, i, qgroupid); } ADD_ARG (argv, i, dest_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", dest, err); return -1; } return 0; } static int mount_vfs_nochroot (const char *options, const char *vfstype, const mountable_t *mountable, const char *mp, const char *user_mp) { CLEANUP_FREE char *options_plus = NULL; const char *device = mountable->device; if (mountable->type == MOUNTABLE_BTRFSVOL) { if (options && strlen (options) > 0) { if (asprintf (&options_plus, "subvol=%s,%s", mountable->volume, options) == -1) { reply_with_perror ("asprintf"); return -1; } } else { if (asprintf (&options_plus, "subvol=%s", mountable->volume) == -1) { reply_with_perror ("asprintf"); return -1; } } } CLEANUP_FREE char *error = NULL; int r; if (vfstype) r = command (NULL, &error, "mount", "-o", options_plus ? options_plus : options, "-t", vfstype, device, mp, NULL); else r = command (NULL, &error, "mount", "-o", options_plus ? options_plus : options, device, mp, NULL); if (r == -1) { reply_with_error ("%s on %s (options: '%s'): %s", device, user_mp, options, error); return -1; } return 0; } static char * mount (const mountable_t *fs) { char *fs_buf; if (fs->type == MOUNTABLE_PATH) { fs_buf = sysroot_path (fs->device); if (fs_buf == NULL) reply_with_perror ("malloc"); } else { fs_buf = strdup ("/tmp/btrfs.XXXXXX"); if (fs_buf == NULL) { reply_with_perror ("strdup"); return NULL; } if (mkdtemp (fs_buf) == NULL) { reply_with_perror ("mkdtemp"); free (fs_buf); return NULL; } if (mount_vfs_nochroot ("", NULL, fs, fs_buf, "") == -1) { if (rmdir (fs_buf) == -1 && errno != ENOENT) perror ("rmdir"); free (fs_buf); return NULL; } } return fs_buf; } static int umount (char *fs_buf, const mountable_t *fs) { if (fs->type != MOUNTABLE_PATH) { CLEANUP_FREE char *err = NULL; if (command (NULL, &err, "umount", fs_buf, NULL) == -1) { reply_with_error ("umount: %s", err); return -1; } if (rmdir (fs_buf) == -1 && errno != ENOENT) { reply_with_perror ("rmdir"); return -1; } } free (fs_buf); return 0; } int do_btrfs_subvolume_set_default (int64_t id, const char *fs) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *fs_buf = NULL; char buf[64]; CLEANUP_FREE char *err = NULL; int r; snprintf (buf, sizeof buf, "%" PRIi64, id); fs_buf = sysroot_path (fs); if (fs_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "subvolume"); ADD_ARG (argv, i, "set-default"); ADD_ARG (argv, i, buf); ADD_ARG (argv, i, fs_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", fs, err); return -1; } return 0; } int do_btrfs_filesystem_sync (const char *fs) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *fs_buf = NULL; CLEANUP_FREE char *err = NULL; int r; fs_buf = sysroot_path (fs); if (fs_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "filesystem"); ADD_ARG (argv, i, "sync"); ADD_ARG (argv, i, fs_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", fs, err); return -1; } return 0; } int do_btrfs_filesystem_balance (const char *fs) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *fs_buf = NULL; CLEANUP_FREE char *err = NULL; int r; fs_buf = sysroot_path (fs); if (fs_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "balance"); ADD_ARG (argv, i, fs_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", fs, err); return -1; } return 0; } /* Test if 'btrfs device add' needs the --force option (added * c.2013-09) in order to work. */ static int test_btrfs_device_add_needs_force (void) { int r; CLEANUP_FREE char *out = NULL, *err = NULL; r = command (&out, &err, "btrfs", "device", "add", "--help", NULL); if (r == -1) { reply_with_error ("%s: %s", "btrfs device add --help", err); return -1; } return strstr (out, "--force") != NULL; } #if defined(__GNUC__) && GUESTFS_GCC_VERSION >= 40800 /* gcc >= 4.8.0 */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstack-usage=" #endif int do_btrfs_device_add (char *const *devices, const char *fs) { static int btrfs_device_add_needs_force = -1; const size_t nr_devices = guestfs_int_count_strings (devices); const size_t MAX_ARGS = nr_devices + 8; const char *argv[MAX_ARGS]; size_t i = 0, j; CLEANUP_FREE char *fs_buf = NULL; CLEANUP_FREE char *err = NULL; int r; if (nr_devices == 0) return 0; if (btrfs_device_add_needs_force == -1) { btrfs_device_add_needs_force = test_btrfs_device_add_needs_force (); if (btrfs_device_add_needs_force == -1) return -1; } fs_buf = sysroot_path (fs); if (fs_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "device"); ADD_ARG (argv, i, "add"); if (btrfs_device_add_needs_force) ADD_ARG (argv, i, "--force"); for (j = 0; j < nr_devices; ++j) ADD_ARG (argv, i, devices[j]); ADD_ARG (argv, i, fs_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", fs, err); return -1; } return 0; } int do_btrfs_device_delete (char *const *devices, const char *fs) { const size_t nr_devices = guestfs_int_count_strings (devices); if (nr_devices == 0) return 0; const size_t MAX_ARGS = nr_devices + 8; const char *argv[MAX_ARGS]; size_t i = 0, j; CLEANUP_FREE char *fs_buf = NULL; CLEANUP_FREE char *err = NULL; int r; fs_buf = sysroot_path (fs); if (fs_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "device"); ADD_ARG (argv, i, "delete"); for (j = 0; j < nr_devices; ++j) ADD_ARG (argv, i, devices[j]); ADD_ARG (argv, i, fs_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", fs, err); return -1; } return 0; } #if defined(__GNUC__) && GUESTFS_GCC_VERSION >= 40800 /* gcc >= 4.8.0 */ #pragma GCC diagnostic pop #endif /* btrfstune command add two new options * -U UUID change fsid to UUID * -u change fsid, use a random one * since v4.1 * We could check wheter 'btrfstune' support * '-u' and '-U UUID' option by checking the output of * 'btrfstune' command. */ static int test_btrfstune_uuid_opt (void) { static int result = -1; if (result != -1) return result; CLEANUP_FREE char *err = NULL; int r = commandr (NULL, &err, "btrfstune", "--help", NULL); if (r == -1) { reply_with_error ("btrfstune: %s", err); return -1; } /* FIXME currently btrfstune do not support '--help'. * If got an invalid options, it will print its usage * in stderr. * We had to check it there. */ if (strstr (err, "-U") == NULL || strstr (err, "-u") == NULL) result = 0; else result = 1; return result; } int do_btrfs_set_seeding (const char *device, int svalue) { CLEANUP_FREE char *err = NULL; int r; const char *s_value = svalue ? "1" : "0"; r = commandr (NULL, &err, "btrfstune", "-S", s_value, device, NULL); if (r == -1) { reply_with_error ("%s: %s", device, err); return -1; } return 0; } int btrfs_set_uuid (const char *device, const char *uuid) { CLEANUP_FREE char *err = NULL; int r; const int has_uuid_opts = test_btrfstune_uuid_opt (); if (has_uuid_opts <= 0) NOT_SUPPORTED (-1, "btrfs filesystems' UUID cannot be changed"); r = commandr (NULL, &err, "btrfstune", "-f", "-U", uuid, device, NULL); if (r == -1) { reply_with_error ("%s: %s", device, err); return -1; } return 0; } int btrfs_set_uuid_random (const char *device) { CLEANUP_FREE char *err = NULL; int r; const int has_uuid_opts = test_btrfstune_uuid_opt (); if (has_uuid_opts <= 0) NOT_SUPPORTED (-1, "btrfs filesystems' UUID cannot be changed"); r = commandr (NULL, &err, "btrfstune", "-f", "-u", device, NULL); if (r == -1) { reply_with_error ("%s: %s", device, err); return -1; } return 0; } /* Takes optional arguments, consult optargs_bitmask. */ int do_btrfs_fsck (const char *device, int64_t superblock, int repair) { CLEANUP_FREE char *err = NULL; int r; size_t i = 0; const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; char super_s[64]; ADD_ARG (argv, i, "btrfsck"); /* Optional arguments. */ if (optargs_bitmask & GUESTFS_BTRFS_FSCK_SUPERBLOCK_BITMASK) { if (superblock < 0) { reply_with_error ("super block offset must be >= 0"); return -1; } snprintf (super_s, sizeof super_s, "%" PRIi64, superblock); ADD_ARG (argv, i, "--super"); ADD_ARG (argv, i, super_s); } if (!(optargs_bitmask & GUESTFS_BTRFS_FSCK_REPAIR_BITMASK)) repair = 0; if (repair) ADD_ARG (argv, i, "--repair"); ADD_ARG (argv, i, device); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", device, err); return -1; } return 0; } /* analyze_line: analyze one line contains key:value pair. * returns the next position following \n. */ static char * analyze_line (char *line, char **key, char **value, char delimiter) { char *p = line; char *next = NULL; char *del_pos = NULL; if (!line || *line == '\0') { *key = NULL; *value = NULL; return NULL; } next = strchr (p, '\n'); if (next) { *next = '\0'; ++next; } /* leading spaces and tabs */ while (*p && c_isspace (*p)) ++p; assert (key); if (*p == delimiter) *key = NULL; else *key = p; del_pos = strchr (p, delimiter); if (del_pos) { *del_pos = '\0'; /* leading spaces and tabs */ do { ++del_pos; } while (*del_pos && c_isspace (*del_pos)); assert (value); *value = del_pos; } else *value = NULL; return next; } char ** do_btrfs_subvolume_show (const char *subvolume) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *subvolume_buf = NULL; CLEANUP_FREE char *err = NULL; CLEANUP_FREE char *out = NULL; char *p, *key = NULL, *value = NULL; CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (ret); int r; subvolume_buf = sysroot_path (subvolume); if (subvolume_buf == NULL) { reply_with_perror ("malloc"); return NULL; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "subvolume"); ADD_ARG (argv, i, "show"); ADD_ARG (argv, i, subvolume_buf); ADD_ARG (argv, i, NULL); r = commandv (&out, &err, argv); if (r == -1) { reply_with_error ("%s: %s", subvolume, err); return NULL; } /* If the path is the btrfs root, `btrfs subvolume show' reports: * is btrfs root [in btrfs-progs < 4.4] * is toplevel subvolume */ if (out && (strstr (out, "is btrfs root") != NULL || strstr (out, "is toplevel subvolume") != NULL)) { reply_with_error ("%s is btrfs root", subvolume); return NULL; } /* If the path is a normal directory, `btrfs subvolume show' reports: * ERROR: is not a subvolume */ if (err && strstr (err, "is not a subvolume")) { reply_with_error ("%s is not a subvolume", subvolume); return NULL; } /* Output is: * * / * Name: root * uuid: c875169e-cf4e-a04d-9959-b667dec36234 * Parent uuid: - * Creation time: 2014-11-13 10:13:08 * Object ID: 256 * Generation (Gen): 6579 * Gen at creation: 5 * Parent: 5 * Top Level: 5 * Flags: - * Snapshot(s): * snapshots/test1 * snapshots/test2 * snapshots/test3 * */ p = analyze_line (out, &key, &value, ':'); if (!p) { reply_with_error ("truncated output: %s", out); return NULL; } /* The first line is the path of the subvolume. */ if (key && !value) { if (add_string (&ret, "path") == -1) return NULL; if (add_string (&ret, key) == -1) return NULL; } else { if (add_string (&ret, key) == -1) return NULL; if (add_string (&ret, value) == -1) return NULL; } /* Read the lines and split into "key: value". */ p = analyze_line (p, &key, &value, ':'); while (key) { /* snapshot is special, see the output above */ if (STREQLEN (key, "Snapshot(s)", sizeof ("Snapshot(s)") - 1)) { char *ss = NULL; int ss_len = 0; if (add_string (&ret, key) == -1) return NULL; p = analyze_line (p, &key, &value, ':'); while (key && !value) { ss = realloc (ss, ss_len + strlen (key) + 1); if (!ss) return NULL; if (ss_len != 0) ss[ss_len++] = ','; memcpy (ss + ss_len, key, strlen (key)); ss_len += strlen (key); ss[ss_len] = '\0'; p = analyze_line (p, &key, &value, ':'); } if (ss) { if (add_string_nodup (&ret, ss) == -1) return NULL; } else { if (add_string (&ret, "") == -1) return NULL; } } else { if (add_string (&ret, key) == -1) return NULL; if (value && !STREQ (value, "-")) { if (add_string (&ret, value) == -1) return NULL; } else { if (add_string (&ret, "") == -1) return NULL; } p = analyze_line (p, &key, &value, ':'); } } if (end_stringsbuf (&ret) == -1) return NULL; return take_stringsbuf (&ret); } int do_btrfs_quota_enable (const mountable_t *fs, int enable) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; char *fs_buf = NULL; CLEANUP_FREE char *err = NULL; int r = -1; fs_buf = mount (fs); if (fs_buf == NULL) goto error; ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "quota"); if (enable) ADD_ARG (argv, i, "enable"); else ADD_ARG (argv, i, "disable"); ADD_ARG (argv, i, fs_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", fs_buf, err); goto error; } error: if (fs_buf && umount (fs_buf, fs) != 0) return -1; return r; } int do_btrfs_quota_rescan (const mountable_t *fs) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; char *fs_buf = NULL; CLEANUP_FREE char *err = NULL; int r = -1; fs_buf = mount (fs); if (fs_buf == NULL) goto error; ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "quota"); ADD_ARG (argv, i, "rescan"); ADD_ARG (argv, i, fs_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", fs_buf, err); goto error; } error: if (fs_buf && umount (fs_buf, fs) != 0) return -1; return r; } int do_btrfs_qgroup_limit (const char *subvolume, int64_t size) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *subvolume_buf = NULL; CLEANUP_FREE char *err = NULL; char size_str[32]; int r; subvolume_buf = sysroot_path (subvolume); if (subvolume_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "qgroup"); ADD_ARG (argv, i, "limit"); snprintf (size_str, sizeof size_str, "%" PRIi64, size); ADD_ARG (argv, i, size_str); ADD_ARG (argv, i, subvolume_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", subvolume, err); return -1; } return 0; } int do_btrfs_qgroup_create (const char *qgroupid, const char *subvolume) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *subvolume_buf = NULL; CLEANUP_FREE char *err = NULL; int r; subvolume_buf = sysroot_path (subvolume); if (subvolume_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "qgroup"); ADD_ARG (argv, i, "create"); ADD_ARG (argv, i, qgroupid); ADD_ARG (argv, i, subvolume_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", subvolume, err); return -1; } return 0; } int do_btrfs_qgroup_destroy (const char *qgroupid, const char *subvolume) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *subvolume_buf = NULL; CLEANUP_FREE char *err = NULL; int r; subvolume_buf = sysroot_path (subvolume); if (subvolume_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "qgroup"); ADD_ARG (argv, i, "destroy"); ADD_ARG (argv, i, qgroupid); ADD_ARG (argv, i, subvolume_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", subvolume, err); return -1; } return 0; } /* btrfs qgroup show command change default output to * binary prefix since v3.18.2, such as KiB; * also introduced '--raw' to keep traditional behaviour. * We could check wheter 'btrfs qgroup show' support '--raw' * option by checking the output of * 'btrfs qgroup show' support --help' command. */ static int test_btrfs_qgroup_show_raw_opt (void) { static int result = -1; if (result != -1) return result; CLEANUP_FREE char *err = NULL; CLEANUP_FREE char *out = NULL; int r = commandr (&out, &err, "btrfs", "qgroup", "show", "--help", NULL); if (r == -1) { reply_with_error ("btrfs qgroup show --help: %s", err); return -1; } if (strstr (out, "--raw") == NULL) result = 0; else result = 1; return result; } guestfs_int_btrfsqgroup_list * do_btrfs_qgroup_show (const char *path) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; const int has_raw_opt = test_btrfs_qgroup_show_raw_opt (); CLEANUP_FREE char *path_buf = NULL; CLEANUP_FREE char *err = NULL; CLEANUP_FREE char *out = NULL; int r; CLEANUP_FREE_STRING_LIST char **lines = NULL; path_buf = sysroot_path (path); if (path_buf == NULL) { reply_with_perror ("malloc"); return NULL; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "qgroup"); ADD_ARG (argv, i, "show"); if (has_raw_opt > 0) ADD_ARG (argv, i, "--raw"); ADD_ARG (argv, i, path_buf); ADD_ARG (argv, i, NULL); r = commandv (&out, &err, argv); if (r == -1) { reply_with_error ("%s: %s", path, err); return NULL; } lines = split_lines (out); if (!lines) return NULL; /* Output of `btrfs qgroup show' is like: * * qgroupid rfer excl * -------- ---- ---- * 0/5 9249849344 9249849344 * */ const size_t nr_qgroups = guestfs_int_count_strings (lines) - 2; guestfs_int_btrfsqgroup_list *ret = NULL; ret = malloc (sizeof *ret); if (!ret) { reply_with_perror ("malloc"); return NULL; } ret->guestfs_int_btrfsqgroup_list_len = nr_qgroups; ret->guestfs_int_btrfsqgroup_list_val = calloc (nr_qgroups, sizeof (struct guestfs_int_btrfsqgroup)); if (ret->guestfs_int_btrfsqgroup_list_val == NULL) { reply_with_perror ("calloc"); goto error; } for (i = 0; i < nr_qgroups; ++i) { char *line = lines[i + 2]; struct guestfs_int_btrfsqgroup *this = &ret->guestfs_int_btrfsqgroup_list_val[i]; if (sscanf (line, "%m[0-9/] %" SCNu64 " %" SCNu64, &this->btrfsqgroup_id, &this->btrfsqgroup_rfer, &this->btrfsqgroup_excl) != 3) { reply_with_error ("cannot parse output of qgroup show command: %s", line); goto error; } } return ret; error: if (ret->guestfs_int_btrfsqgroup_list_val) { for (i = 0; i < nr_qgroups; ++i) free (ret->guestfs_int_btrfsqgroup_list_val[i].btrfsqgroup_id); free (ret->guestfs_int_btrfsqgroup_list_val); } free (ret); return NULL; } int do_btrfs_qgroup_assign (const char *src, const char *dst, const char *path) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *path_buf = NULL; CLEANUP_FREE char *err = NULL; int r; path_buf = sysroot_path (path); if (path_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "qgroup"); ADD_ARG (argv, i, "assign"); ADD_ARG (argv, i, src); ADD_ARG (argv, i, dst); ADD_ARG (argv, i, path_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", path, err); return -1; } return 0; } int do_btrfs_qgroup_remove (const char *src, const char *dst, const char *path) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *path_buf = NULL; CLEANUP_FREE char *err = NULL; int r; path_buf = sysroot_path (path); if (path_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "qgroup"); ADD_ARG (argv, i, "remove"); ADD_ARG (argv, i, src); ADD_ARG (argv, i, dst); ADD_ARG (argv, i, path_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", path, err); return -1; } return 0; } int do_btrfs_scrub_start (const char *path) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *path_buf = NULL; CLEANUP_FREE char *err = NULL; int r; path_buf = sysroot_path (path); if (path_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "scrub"); ADD_ARG (argv, i, "start"); ADD_ARG (argv, i, path_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", path, err); return -1; } return 0; } int do_btrfs_scrub_cancel (const char *path) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *path_buf = NULL; CLEANUP_FREE char *err = NULL; int r; path_buf = sysroot_path (path); if (path_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "scrub"); ADD_ARG (argv, i, "cancel"); ADD_ARG (argv, i, path_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", path, err); return -1; } return 0; } int do_btrfs_scrub_resume (const char *path) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *path_buf = NULL; CLEANUP_FREE char *err = NULL; int r; path_buf = sysroot_path (path); if (path_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "scrub"); ADD_ARG (argv, i, "resume"); ADD_ARG (argv, i, path_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", path, err); return -1; } return 0; } int do_btrfs_balance_pause (const char *path) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *path_buf = NULL; CLEANUP_FREE char *err = NULL; int r; path_buf = sysroot_path (path); if (path_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "balance"); ADD_ARG (argv, i, "pause"); ADD_ARG (argv, i, path_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", path, err); return -1; } return 0; } int do_btrfs_balance_cancel (const char *path) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *path_buf = NULL; CLEANUP_FREE char *err = NULL; int r; path_buf = sysroot_path (path); if (path_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "balance"); ADD_ARG (argv, i, "cancel"); ADD_ARG (argv, i, path_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", path, err); return -1; } return 0; } int do_btrfs_balance_resume (const char *path) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *path_buf = NULL; CLEANUP_FREE char *err = NULL; int r; path_buf = sysroot_path (path); if (path_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "balance"); ADD_ARG (argv, i, "resume"); ADD_ARG (argv, i, path_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", path, err); return -1; } return 0; } /* Takes optional arguments, consult optargs_bitmask. */ int do_btrfs_filesystem_defragment (const char *path, int flush, const char *compress) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *path_buf = NULL; CLEANUP_FREE char *err = NULL; int r; path_buf = sysroot_path (path); if (path_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "filesystem"); ADD_ARG (argv, i, "defragment"); ADD_ARG (argv, i, "-r"); /* Optional arguments. */ if ((optargs_bitmask & GUESTFS_BTRFS_FILESYSTEM_DEFRAGMENT_FLUSH_BITMASK) && flush) ADD_ARG (argv, i, "-f"); if (optargs_bitmask & GUESTFS_BTRFS_FILESYSTEM_DEFRAGMENT_COMPRESS_BITMASK) { if (STREQ (compress, "zlib")) ADD_ARG (argv, i, "-czlib"); else if (STREQ (compress, "lzo")) ADD_ARG (argv, i, "-clzo"); else { reply_with_error ("unknown compress method: %s", compress); return -1; } } ADD_ARG (argv, i, path_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", path, err); return -1; } return 0; } int do_btrfs_rescue_chunk_recover (const char *device) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *err = NULL; int r; ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "rescue"); ADD_ARG (argv, i, "chunk-recover"); ADD_ARG (argv, i, "-y"); ADD_ARG (argv, i, device); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", device, err); return -1; } return 0; } int do_btrfs_rescue_super_recover (const char *device) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *err = NULL; int r; ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "rescue"); ADD_ARG (argv, i, "super-recover"); ADD_ARG (argv, i, "-y"); ADD_ARG (argv, i, device); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", device, err); return -1; } return 0; } guestfs_int_btrfsbalance * do_btrfs_balance_status (const char *path) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *path_buf = NULL; CLEANUP_FREE char *err = NULL; CLEANUP_FREE char *out = NULL; CLEANUP_FREE_STRING_LIST char **lines = NULL; int r; guestfs_int_btrfsbalance *ret; size_t nlines; CLEANUP_PCRE2_MATCH_DATA_FREE pcre2_match_data *match_data = pcre2_match_data_create_from_pattern (re_btrfs_balance_status, NULL); PCRE2_SIZE *ovector; path_buf = sysroot_path (path); if (path_buf == NULL) { reply_with_perror ("malloc"); return NULL; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "balance"); ADD_ARG (argv, i, "status"); ADD_ARG (argv, i, path_buf); ADD_ARG (argv, i, NULL); r = commandv (&out, &err, argv); if (r == -1) { reply_with_error ("%s: %s", path, err); return NULL; } lines = split_lines (out); if (!lines) return NULL; nlines = guestfs_int_count_strings (lines); /* Output of `btrfs balance status' is like: * * running: * * Balance on '/' is running * 3 out of about 8 chunks balanced (3 considered), 62% left * * paused: * * Balance on '/' is paused * 3 out of about 8 chunks balanced (3 considered), 62% left * * no balance running: * * No Balance found on '/' * */ if (nlines < 1) { reply_with_perror ("No balance status output"); return NULL; } ret = calloc (1, sizeof *ret); if (ret == NULL) { reply_with_perror ("calloc"); return NULL; } if (strstr (lines[0], "No balance found on")) { ret->btrfsbalance_status = strdup ("none"); if (ret->btrfsbalance_status == NULL) { reply_with_perror ("strdup"); goto error; } return ret; } if (pcre2_match (re_btrfs_balance_status, (PCRE2_SPTR)lines[0], PCRE2_ZERO_TERMINATED, 0, 0, match_data, NULL) < 0) { reply_with_error ("unexpected output from 'btrfs balance status' command: %s", lines[0]); goto error; } ovector = pcre2_get_ovector_pointer (match_data); if (STREQ (lines[0] + ovector[2], "running")) ret->btrfsbalance_status = strdup ("running"); else if (STREQ (lines[0] + ovector[2], "paused")) ret->btrfsbalance_status = strdup ("paused"); else { reply_with_error ("unexpected output from 'btrfs balance status' command: %s", lines[0]); goto error; } if (nlines < 2) { reply_with_error ("truncated output from 'btrfs balance status' command"); goto error; } if (sscanf (lines[1], "%" SCNu64 " out of about %" SCNu64 " chunks balanced (%" SCNu64 " considered), %" SCNu64 "%% left", &ret->btrfsbalance_balanced, &ret->btrfsbalance_total, &ret->btrfsbalance_considered, &ret->btrfsbalance_left) != 4) { reply_with_perror ("sscanf"); goto error; } return ret; error: free (ret->btrfsbalance_status); free (ret); return NULL; } guestfs_int_btrfsscrub * do_btrfs_scrub_status (const char *path) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *path_buf = NULL; CLEANUP_FREE char *err = NULL; CLEANUP_FREE_STRING_LIST char **lines = NULL; CLEANUP_FREE char *out = NULL; int r; guestfs_int_btrfsscrub *ret; path_buf = sysroot_path (path); if (path_buf == NULL) { reply_with_perror ("malloc"); return NULL; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "scrub"); ADD_ARG (argv, i, "status"); ADD_ARG (argv, i, "-R"); ADD_ARG (argv, i, path_buf); ADD_ARG (argv, i, NULL); r = commandv (&out, &err, argv); if (r == -1) { reply_with_error ("%s: %s", path, err); return NULL; } if (verbose) fprintf (stderr, "output from 'btrfs scrub status -R %s' is:\n%s", path, out); lines = split_lines (out); if (!lines) return NULL; if (guestfs_int_count_strings (lines) < 2) { reply_with_error ("truncated output from 'btrfs scrub status -R' command"); return NULL; } ret = calloc (1, sizeof *ret); if (ret == NULL) { reply_with_perror ("calloc"); return NULL; } /* Output of `btrfs scrub -R status' is like: * * scrub status for 346121d1-1847-40f8-9b7b-2bf3d539c68f * scrub started at Mon Feb 2 17:39:38 2015, running for 93 seconds * data_extents_scrubbed: 136670 * tree_extents_scrubbed: 30023 * data_bytes_scrubbed: 4474441728 * tree_bytes_scrubbed: 491896832 * read_errors: 0 * csum_errors: 0 * verify_errors: 0 * no_csum: 17760 * csum_discards: 197622 * super_errors: 0 * malloc_errors: 0 * uncorrectable_errors: 0 * unverified_errors: 0 * corrected_errors: 0 * last_physical: 10301341696 * * or: * * scrub status for 346121d1-1847-40f8-9b7b-2bf3d539c68f * no stats available */ for (i = 0; lines[i] != NULL; ++i) { if (lines[i][0] != '\t') continue; else if (STREQ (lines[i], "\tno stats available")) return ret; else if (STRPREFIX (lines[i], "\tscrub started at")) continue; else if (sscanf (lines[i], "\tdata_extents_scrubbed: %" SCNu64, &ret->btrfsscrub_data_extents_scrubbed) == 1) continue; else if (sscanf (lines[i], "\ttree_extents_scrubbed: %" SCNu64, &ret->btrfsscrub_tree_extents_scrubbed) == 1) continue; else if (sscanf (lines[i], "\tdata_bytes_scrubbed: %" SCNu64, &ret->btrfsscrub_data_bytes_scrubbed) == 1) continue; else if (sscanf (lines[i], "\ttree_bytes_scrubbed: %" SCNu64, &ret->btrfsscrub_tree_bytes_scrubbed) == 1) continue; else if (sscanf (lines[i], "\tread_errors: %" SCNu64, &ret->btrfsscrub_read_errors) == 1) continue; else if (sscanf (lines[i], "\tcsum_errors: %" SCNu64, &ret->btrfsscrub_csum_errors) == 1) continue; else if (sscanf (lines[i], "\tverify_errors: %" SCNu64, &ret->btrfsscrub_verify_errors) == 1) continue; else if (sscanf (lines[i], "\tno_csum: %" SCNu64, &ret->btrfsscrub_no_csum) == 1) continue; else if (sscanf (lines[i], "\tcsum_discards: %" SCNu64, &ret->btrfsscrub_csum_discards) == 1) continue; else if (sscanf (lines[i], "\tsuper_errors: %" SCNu64, &ret->btrfsscrub_super_errors) == 1) continue; else if (sscanf (lines[i], "\tmalloc_errors: %" SCNu64, &ret->btrfsscrub_malloc_errors) == 1) continue; else if (sscanf (lines[i], "\tuncorrectable_errors: %" SCNu64, &ret->btrfsscrub_uncorrectable_errors) == 1) continue; else if (sscanf (lines[i], "\tunverified_errors: %" SCNu64, &ret->btrfsscrub_unverified_errors) == 1) continue; else if (sscanf (lines[i], "\tcorrected_errors: %" SCNu64, &ret->btrfsscrub_corrected_errors) == 1) continue; else if (sscanf (lines[i], "\tlast_physical: %" SCNu64, &ret->btrfsscrub_last_physical) == 1) continue; else goto error; } if (i < 17) { reply_with_error ("truncated output from 'btrfs scrub status -R' command"); free (ret); return NULL; } return ret; error: reply_with_error ("%s: could not parse btrfs scrub status.", lines[i]); free (ret); return NULL; } int do_btrfstune_seeding (const char *device, int svalue) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *err = NULL; int r; const char *s_value = svalue ? "1" : "0"; ADD_ARG (argv, i, "btrfstune"); ADD_ARG (argv, i, "-S"); ADD_ARG (argv, i, s_value); if (svalue == 0) ADD_ARG (argv, i, "-f"); ADD_ARG (argv, i, device); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", device, err); return -1; } return 0; } int do_btrfstune_enable_extended_inode_refs (const char *device) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *err = NULL; int r; ADD_ARG (argv, i, "btrfstune"); ADD_ARG (argv, i, "-r"); ADD_ARG (argv, i, device); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", device, err); return -1; } return 0; } int do_btrfstune_enable_skinny_metadata_extent_refs (const char *device) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *err = NULL; int r; ADD_ARG (argv, i, "btrfstune"); ADD_ARG (argv, i, "-x"); ADD_ARG (argv, i, device); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", device, err); return -1; } return 0; } #if defined(__GNUC__) && GUESTFS_GCC_VERSION >= 40800 /* gcc >= 4.8.0 */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstack-usage=" #endif int do_btrfs_image (char *const *sources, const char *image, int compresslevel) { const size_t nr_sources = guestfs_int_count_strings (sources); const size_t MAX_ARGS = 64 + nr_sources; const char *argv[MAX_ARGS]; size_t i = 0, j; CLEANUP_FREE char *err = NULL; char compresslevel_s[64]; int r; if (nr_sources == 0) { reply_with_error ("list of sources must be non-empty"); return -1; } ADD_ARG (argv, i, "btrfs-image"); if ((optargs_bitmask & GUESTFS_BTRFS_IMAGE_COMPRESSLEVEL_BITMASK) && compresslevel >= 0) { snprintf (compresslevel_s, sizeof compresslevel_s, "%d", compresslevel); ADD_ARG (argv, i, "-c"); ADD_ARG (argv, i, compresslevel_s); } for (j = 0; j < nr_sources; ++j) ADD_ARG (argv, i, sources[j]); ADD_ARG (argv, i, image); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s %s: %s", sources[0], image, err); return -1; } return 0; } #if defined(__GNUC__) && GUESTFS_GCC_VERSION >= 40800 /* gcc >= 4.8.0 */ #pragma GCC diagnostic pop #endif int do_btrfs_replace (const char *srcdev, const char *targetdev, const char* mntpoint) { const size_t MAX_ARGS = 64; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *err = NULL; CLEANUP_FREE char *path_buf = NULL; int r; path_buf = sysroot_path (mntpoint); if (path_buf == NULL) { reply_with_perror ("malloc"); return -1; } ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "replace"); ADD_ARG (argv, i, "start"); ADD_ARG (argv, i, "-B"); ADD_ARG (argv, i, "-f"); ADD_ARG (argv, i, srcdev); ADD_ARG (argv, i, targetdev); ADD_ARG (argv, i, path_buf); ADD_ARG (argv, i, NULL); r = commandv (NULL, &err, argv); if (r == -1) { reply_with_error ("%s: %s", mntpoint, err); return -1; } return 0; } char ** do_btrfs_filesystem_show (const char *device) { CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (ret); const size_t MAX_ARGS = 16; const char *argv[MAX_ARGS]; size_t i = 0; CLEANUP_FREE char *out = NULL; CLEANUP_FREE char *err = NULL; CLEANUP_FREE_STRING_LIST char **lines = NULL; int r; ADD_ARG (argv, i, "btrfs"); ADD_ARG (argv, i, "filesystem"); ADD_ARG (argv, i, "show"); ADD_ARG (argv, i, device); ADD_ARG (argv, i, NULL); r = commandv (&out, &err, argv); if (r == -1) { reply_with_error ("%s: %s", device, err); return NULL; } lines = split_lines (out); if (!lines) return NULL; if (guestfs_int_count_strings (lines) < 3) { reply_with_error ("truncated output from 'btrfs filesystem show' command"); return NULL; } /* Output of `btrfs filesystem show' is like: * * Label: none uuid: 99a1b6ba-de46-4a93-8f91-7d7685970a6c * Total devices 3 FS bytes used 1.12MiB * devid 1 size 10.00GiB used 2.00GiB path /dev/sda * [...] * * or: * * Label: none uuid: 99a1b6ba-de46-4a93-8f91-7d7685970a6c * Total devices 3 FS bytes used 1.12MiB * devid 1 size 10.00GiB used 2.00GiB path /dev/sda * [...] * *** Some devices missing */ for (i = 1; lines[i] != NULL; ++i) { if (lines[i][0] == 0) continue; if (STRPREFIX (lines[i], "Label: ")) continue; else if (STRPREFIX (lines[i], "\tTotal devices ")) continue; else if (STRPREFIX (lines[i], "\tdevid ")) { const char *p = strstr (lines[i], " path "); const char *end; if (!p) continue; p += strlen (" path "); end = strchrnul (p, ' '); add_sprintf (&ret, "%.*s", (int) (end - p), p); } else if (STRPREFIX (lines[i], "\t*** Some devices missing")) { reply_with_error_errno (ENODEV, "%s: missing devices", device); return NULL; } else if (STRPREFIX (lines[i], "btrfs-progs v") || STRPREFIX (lines[i], "Btrfs v")) { /* Older versions of btrfs-progs output also the version string * (the same as `btrfs --version`. This has been fixed upstream * since v4.3.1, commit e29ec82e4e66042ca55bf8cd9ef609e3b21a7eb7. * To support these older versions, ignore the version line. */ continue; } else { reply_with_error ("unrecognized line in output from 'btrfs filesystem show': %s", lines[i]); return NULL; } } if (end_stringsbuf (&ret) == -1) return NULL; return take_stringsbuf (&ret); } /* btrfs command add a new command * inspect-internal min-dev-size * since v4.2 * We could check whether 'btrfs' supports * 'min-dev-size' command by checking the output of * 'btrfs --help' command. */ static int test_btrfs_min_dev_size (void) { CLEANUP_FREE char *err = NULL, *out = NULL; static int result = -1; const char *cmd_pattern = "btrfs inspect-internal min-dev-size"; int r; if (result != -1) return result; r = commandr (&out, &err, "btrfs", "--help", NULL); if (r == -1) { reply_with_error ("btrfs: %s", err); return -1; } if (strstr (out, cmd_pattern) == NULL) result = 0; else result = 1; return result; } int64_t btrfs_minimum_size (const char *path) { CLEANUP_FREE char *buf = NULL, *err = NULL, *out = NULL; int64_t ret = 0; int r; const int min_size_supported = test_btrfs_min_dev_size (); if (min_size_supported == -1) return -1; else if (min_size_supported == 0) NOT_SUPPORTED (-1, "'btrfs inspect-internal min-dev-size' " "needs btrfs-progs >= 4.2"); buf = sysroot_path (path); if (buf == NULL) { reply_with_perror ("malloc"); return -1; } r = command (&out, &err, "btrfs", "inspect-internal", "min-dev-size", buf, NULL); if (r == -1) { reply_with_error ("%s", err); return -1; } #if __WORDSIZE == 64 #define XSTRTOD64 xstrtol #else #define XSTRTOD64 xstrtoll #endif if (XSTRTOD64 (out, NULL, 10, &ret, NULL) != LONGINT_OK) { reply_with_error ("cannot parse minimum size"); return -1; } #undef XSTRTOD64 return ret; }