diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile.in | 1 | ||||
-rw-r--r-- | lib/device/dev-type.c | 111 | ||||
-rw-r--r-- | lib/device/dev-type.h | 3 | ||||
-rw-r--r-- | lib/device/filesystem.c | 484 | ||||
-rw-r--r-- | lib/device/filesystem.h | 54 | ||||
-rw-r--r-- | lib/metadata/lv_manip.c | 803 | ||||
-rw-r--r-- | lib/metadata/metadata-exported.h | 4 | ||||
-rw-r--r-- | lib/metadata/metadata.c | 4 | ||||
-rw-r--r-- | lib/metadata/metadata.h | 2 |
9 files changed, 1400 insertions, 66 deletions
diff --git a/lib/Makefile.in b/lib/Makefile.in index 3ab5cb2f1..3380b28fb 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -40,6 +40,7 @@ SOURCES =\ device/dev-luks.c \ device/dev-dasd.c \ device/dev-lvm1-pool.c \ + device/filesystem.c \ device/online.c \ device/parse_vpd.c \ display/display.c \ diff --git a/lib/device/dev-type.c b/lib/device/dev-type.c index 1dc895b54..f89dc7b56 100644 --- a/lib/device/dev-type.c +++ b/lib/device/dev-type.c @@ -26,6 +26,11 @@ #ifdef BLKID_WIPING_SUPPORT #include <blkid.h> +/* + * FIXME: recent addition to blkid.h copied here. + * Remove this and require a recent libblkid version from configure. + */ +#define BLKID_SUBLKS_FSINFO (1 << 11) /* read and define fs properties from superblock */ #endif #ifdef UDEV_SYNC_SUPPORT @@ -838,6 +843,102 @@ int get_fs_block_size(const char *pathname, uint32_t *fs_block_size) return 0; } } + +int get_fs_type(const char *pathname, char *fstype) +{ + char *str = NULL; + + if ((str = blkid_get_tag_value(NULL, "TYPE", pathname))) { + log_debug("Found blkid TYPE %s on %s", str, pathname); + strncpy(fstype, str, FSTYPE_MAX); + free(str); + return 1; + } else { + log_debug("No blkid TYPE for fs on %s", pathname); + return 0; + } +} + +int fs_get_blkid(const char *pathname, struct fs_info *fsi) +{ + blkid_probe probe = NULL; + const char *str; + size_t len; + uint64_t fslastblock = 0; + unsigned int fsblocksize = 0; + int no_block_size = 0, no_fslastblock = 0, no_fsblocksize = 0; + int rc; + + if (!(probe = blkid_new_probe_from_filename(pathname))) { + log_error("Failed libblkid probe setup for %s", pathname); + return 0; + } + + blkid_probe_enable_superblocks(probe, 1); + blkid_probe_set_superblocks_flags(probe, + BLKID_SUBLKS_LABEL | BLKID_SUBLKS_LABELRAW | + BLKID_SUBLKS_UUID | BLKID_SUBLKS_UUIDRAW | + BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE | + BLKID_SUBLKS_USAGE | BLKID_SUBLKS_VERSION | + BLKID_SUBLKS_MAGIC | BLKID_SUBLKS_FSINFO); + rc = blkid_do_safeprobe(probe); + if (rc < 0) { + log_error("Failed libblkid probe for %s", pathname); + blkid_free_probe(probe); + return 0; + } else if (rc == 1) { + /* no file system on the device */ + log_print("No file system found on %s.", pathname); + fsi->nofs = 1; + blkid_free_probe(probe); + return 1; + } + + if (!blkid_probe_lookup_value(probe, "TYPE", &str, &len) && len) + strncpy(fsi->fstype, str, sizeof(fsi->fstype)-1); + else { + /* any difference from blkid_do_safeprobe rc=1? */ + log_print("No file system type on %s.", pathname); + fsi->nofs = 1; + blkid_free_probe(probe); + return 1; + } + + if (!blkid_probe_lookup_value(probe, "BLOCK_SIZE", &str, &len) && len) + fsi->block_size_bytes = atoi(str); + else + no_block_size = 1; + + if (!blkid_probe_lookup_value(probe, "FSLASTBLOCK", &str, &len) && len) + fslastblock = strtoull(str, NULL, 0); + else + no_fslastblock = 1; + + if (!blkid_probe_lookup_value(probe, "FSBLOCKSIZE", &str, &len) && len) + fsblocksize = (unsigned int)atoi(str); + else + no_fsblocksize = 1; + + blkid_free_probe(probe); + + /* We don't expect to get this info for luks */ + if (strcmp(fsi->fstype, "crypto_LUKS") && (no_block_size || no_fslastblock || no_fsblocksize)) { + log_print("Missing libblkid %s%s%sfor %s", + no_block_size ? "BLOCK_SIZE " : "", + no_fslastblock ? "FSLASTBLOCK " : "", + no_fsblocksize ? "FSBLOCKSIZE " : "", + pathname); + } + + if (fslastblock && fsblocksize) + fsi->fs_last_byte = fslastblock * fsblocksize; + + log_debug("libblkid TYPE %s BLOCK_SIZE %d FSLASTBLOCK %llu FSBLOCKSIZE %u fs_last_byte %llu", + fsi->fstype, fsi->block_size_bytes, (unsigned long long)fslastblock, fsblocksize, + (unsigned long long)fsi->fs_last_byte); + return 1; +} + #else int get_fs_block_size(const char *pathname, uint32_t *fs_block_size) { @@ -845,6 +946,16 @@ int get_fs_block_size(const char *pathname, uint32_t *fs_block_size) *fs_block_size = 0; return 0; } +int get_fs_type(const char *pathname, char *fstype) +{ + log_debug("Disabled blkid TYPE for fs."); + return 0; +} +int fs_get_blkid(const char *pathname, struct fs_info *fsi) +{ + log_debug("Disabled blkid for fs info."); + return 0; +} #endif #ifdef BLKID_WIPING_SUPPORT diff --git a/lib/device/dev-type.h b/lib/device/dev-type.h index 17c997641..33157d577 100644 --- a/lib/device/dev-type.h +++ b/lib/device/dev-type.h @@ -18,6 +18,7 @@ #include "lib/device/device.h" #include "lib/display/display.h" #include "lib/label/label.h" +#include "lib/device/filesystem.h" #define NUMBER_OF_MAJORS 4096 @@ -102,6 +103,8 @@ int dev_is_nvme(struct dev_types *dt, struct device *dev); int dev_is_lv(struct device *dev); int get_fs_block_size(const char *pathname, uint32_t *fs_block_size); +int get_fs_type(const char *pathname, char *fstype); +int fs_get_blkid(const char *pathname, struct fs_info *fsi); int dev_is_used_by_active_lv(struct cmd_context *cmd, struct device *dev, int *used_by_lv_count, char **used_by_dm_name, char **used_by_vg_uuid, char **used_by_lv_uuid); diff --git a/lib/device/filesystem.c b/lib/device/filesystem.c new file mode 100644 index 000000000..4bdb88863 --- /dev/null +++ b/lib/device/filesystem.c @@ -0,0 +1,484 @@ +/* + * Copyright (C) 2022 Red Hat, Inc. All rights reserved. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser 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 "base/memory/zalloc.h" +#include "lib/misc/lib.h" +#include "lib/commands/toolcontext.h" +#include "lib/device/device.h" +#include "lib/device/dev-type.h" +#include "lib/misc/lvm-exec.h" + +#include <dirent.h> +#include <mntent.h> +#include <sys/ioctl.h> + +/* + * crypt offset is usually the LUKS header size but can be larger. + * The LUKS header is usually 2MB for LUKS1 and 16MB for LUKS2. + * The offset needs to be subtracted from the LV size to get the + * size used to resize the crypt device. + */ +static int _get_crypt_table_offset(dev_t crypt_devt, uint32_t *offset_bytes) +{ + struct dm_task *dmt = dm_task_create(DM_DEVICE_TABLE); + uint64_t start, length; + char *target_type = NULL; + void *next = NULL; + char *params = NULL; + char offset_str[32] = { 0 }; + int copy_offset = 0; + int spaces = 0; + int i, i_off = 0; + + if (!dmt) + return_0; + + if (!dm_task_set_major_minor(dmt, (int)MAJOR(crypt_devt), (int)MINOR(crypt_devt), 0)) { + dm_task_destroy(dmt); + return_0; + } + + /* Non-blocking status read */ + if (!dm_task_no_flush(dmt)) + log_warn("WARNING: Can't set no_flush for dm status."); + + if (!dm_task_run(dmt)) { + dm_task_destroy(dmt); + return_0; + } + + next = dm_get_next_target(dmt, next, &start, &length, &target_type, ¶ms); + + if (!target_type || !params || strcmp(target_type, "crypt")) { + dm_task_destroy(dmt); + return_0; + } + + /* + * get offset from params string: + * <cipher> <key> <iv_offset> <device> <offset> [<#opt_params> <opt_params>] + * <offset> is reported in 512 byte sectors. + */ + for (i = 0; i < strlen(params); i++) { + if (params[i] == ' ') { + spaces++; + if (spaces == 4) + copy_offset = 1; + if (spaces == 5) + break; + continue; + } + if (!copy_offset) + continue; + + offset_str[i_off++] = params[i]; + + if (i_off == sizeof(offset_str)) { + offset_str[0] = '\0'; + break; + } + } + dm_task_destroy(dmt); + + if (!offset_str[0]) + return_0; + + *offset_bytes = ((uint32_t)strtoul(offset_str, NULL, 0) * 512); + return 1; +} + +/* + * Set the path of the dm-crypt device, i.e. /dev/dm-N, that is using the LV. + */ +static int _get_crypt_path(dev_t lv_devt, char *lv_path, char *crypt_path) +{ + char holders_path[PATH_MAX]; + char *holder_name; + DIR *dr; + struct stat st; + struct dirent *de; + int ret = 0; + + if (dm_snprintf(holders_path, sizeof(holders_path), "%sdev/block/%d:%d/holders", + dm_sysfs_dir(), (int)MAJOR(lv_devt), (int)MINOR(lv_devt)) < 0) + return_0; + + /* If the crypt dev is not active, there will be no LV holder. */ + if (stat(holders_path, &st)) { + log_error("Missing %s for %s", crypt_path, lv_path); + return 0; + } + + if (!(dr = opendir(holders_path))) { + log_error("Cannot open %s", holders_path); + return 0; + } + + while ((de = readdir(dr))) { + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + continue; + + holder_name = de->d_name; + + if (strncmp(holder_name, "dm", 2)) { + log_error("Unrecognized holder %s of %s", holder_name, lv_path); + ret = 0; + break; + } + + /* We could read the holder's dm uuid to verify it's a crypt dev. */ + + if (dm_snprintf(crypt_path, PATH_MAX, "/dev/%s", holder_name) < 0) { + ret = 0; + stack; + break; + } + ret = 1; + break; + } + closedir(dr); + if (ret) + log_debug("Found holder %s of %s.", crypt_path, lv_path); + else + log_debug("No holder in %s", holders_path); + return ret; +} + +int fs_get_info(struct cmd_context *cmd, struct logical_volume *lv, + struct fs_info *fsi, int include_mount) +{ + char lv_path[PATH_MAX]; + char crypt_path[PATH_MAX]; + struct stat st_lv; + struct stat st_crypt; + struct stat st_top; + struct stat stme; + struct fs_info info; + FILE *fme = NULL; + struct mntent *me; + int ret; + + if (dm_snprintf(lv_path, PATH_MAX, "%s%s/%s", lv->vg->cmd->dev_dir, + lv->vg->name, lv->name) < 0) { + log_error("Couldn't create LV path for %s.", display_lvname(lv)); + return 0; + } + + if (stat(lv_path, &st_lv) < 0) { + log_error("Failed to get LV path %s", lv_path); + return 0; + } + + memset(&info, 0, sizeof(info)); + + if (!fs_get_blkid(lv_path, &info)) { + log_error("No file system info from blkid for %s", display_lvname(lv)); + return 0; + } + + if (fsi->nofs) + return 1; + + /* + * If there's a LUKS dm-crypt layer over the LV, then + * return fs info from that layer, setting needs_crypt + * to indicate a crypt layer between the fs and LV. + */ + if (!strcmp(info.fstype, "crypto_LUKS")) { + if (!_get_crypt_path(st_lv.st_rdev, lv_path, crypt_path)) { + log_error("Cannot find active LUKS dm-crypt device using %s.", + display_lvname(lv)); + return 0; + } + + if (stat(crypt_path, &st_crypt) < 0) { + log_error("Failed to get crypt path %s", crypt_path); + return 0; + } + + memset(&info, 0, sizeof(info)); + + log_print("File system found on crypt device %s on LV %s.", + crypt_path, display_lvname(lv)); + + if (!fs_get_blkid(crypt_path, &info)) { + log_error("No file system info from blkid for dm-crypt device %s on LV %s.", + crypt_path, display_lvname(lv)); + return 0; + } + *fsi = info; + fsi->needs_crypt = 1; + fsi->crypt_devt = st_crypt.st_rdev; + memcpy(fsi->fs_dev_path, crypt_path, PATH_MAX); + st_top = st_crypt; + + if (!_get_crypt_table_offset(st_crypt.st_rdev, &fsi->crypt_offset_bytes)) { + log_error("Failed to get crypt data offset."); + return 0; + } + } else { + *fsi = info; + memcpy(fsi->fs_dev_path, lv_path, PATH_MAX); + st_top = st_lv; + } + + if (!include_mount) + return 1; + + if (!(fme = setmntent("/etc/mtab", "r"))) + return_0; + + ret = 1; + + while ((me = getmntent(fme))) { + if (strcmp(me->mnt_type, fsi->fstype)) + continue; + if (me->mnt_dir[0] != '/') + continue; + if (me->mnt_fsname[0] != '/') + continue; + if (stat(me->mnt_dir, &stme) < 0) + continue; + if (stme.st_dev != st_top.st_rdev) + continue; + + log_debug("fs_get_info %s is mounted \"%s\"", fsi->fs_dev_path, me->mnt_dir); + fsi->mounted = 1; + strncpy(fsi->mount_dir, me->mnt_dir, PATH_MAX-1); + } + endmntent(fme); + + fsi->unmounted = !fsi->mounted; + return ret; +} + +#define FS_CMD_MAX_ARGS 6 + +int fs_fsck_command(struct cmd_context *cmd, struct logical_volume *lv, struct fs_info *fsi) +{ + const char *argv[FS_CMD_MAX_ARGS + 4]; + int args = 0; + int status; + + if (strncmp(fsi->fstype, "ext", 3)) { + log_error("fsck not supported for %s.", fsi->fstype); + return_0; + } + + /* + * ext234: e2fsck -f -p path + * TODO: replace -p with -y based on yes_ARG, or other? + */ + + argv[0] = E2FSCK_PATH; /* defined by configure */ + argv[++args] = "-f"; + argv[++args] = "-p"; + argv[++args] = fsi->fs_dev_path; + argv[++args] = NULL; + + log_print("Checking file system %s with %s on %s...", + fsi->fstype, E2FSCK_PATH, display_lvname(lv)); + + if (!exec_cmd(cmd, argv, &status, 1)) { + log_error("e2fsck failed on %s.", display_lvname(lv)); + return 0; + } + + log_print("Checked file system %s on %s.", fsi->fstype, display_lvname(lv)); + return 1; +} + +int fs_reduce_command(struct cmd_context *cmd, struct logical_volume *lv, struct fs_info *fsi, + uint64_t newsize_bytes) +{ + char newsize_kb_str[16] = { 0 }; + const char *argv[FS_CMD_MAX_ARGS + 4]; + int args = 0; + int status; + + if (!strncmp(fsi->fstype, "ext", 3)) { + if (dm_snprintf(newsize_kb_str, 16, "%lluk", (unsigned long long)(newsize_bytes/1024)) < 0) + return_0; + + /* + * ext234 shrink: resize2fs path newsize + */ + argv[0] = RESIZE2FS_PATH; /* defined by configure */ + argv[++args] = fsi->fs_dev_path; + argv[++args] = newsize_kb_str; + argv[++args] = NULL; + + } else { + log_error("fs reduce not supported for %s.", fsi->fstype); + return_0; + } + + log_print("Reducing file system %s on %s...", fsi->fstype, display_lvname(lv)); + + if (!exec_cmd(cmd, argv, &status, 1)) { + log_error("Failed to reduce %s file system on %s.", fsi->fstype, display_lvname(lv)); + return 0; + } + + log_print("Reduced file system %s to %s (%llu bytes) on %s.", + fsi->fstype, display_size(cmd, newsize_bytes/512), + (unsigned long long)newsize_bytes, display_lvname(lv)); + return 1; +} + +int fs_extend_command(struct cmd_context *cmd, struct logical_volume *lv, struct fs_info *fsi) +{ + const char *argv[FS_CMD_MAX_ARGS + 4]; + int args = 0; + int status; + + if (!strncmp(fsi->fstype, "ext", 3)) { + /* TODO: include -f if lvm command inclues -f ? */ + argv[0] = RESIZE2FS_PATH; /* defined by configure */ + argv[++args] = fsi->fs_dev_path; + argv[++args] = NULL; + + } else if (!strcmp(fsi->fstype, "xfs")) { + argv[0] = XFS_GROWFS_PATH; /* defined by configure */ + argv[++args] = fsi->fs_dev_path; + argv[++args] = NULL; + + } else { + log_error("Extend not supported for %s file system.", fsi->fstype); + return_0; + } + + log_print("Extending file system %s on %s...", fsi->fstype, display_lvname(lv)); + + if (!exec_cmd(cmd, argv, &status, 1)) { + log_error("Failed to extend %s file system on %s.", fsi->fstype, display_lvname(lv)); + return 0; + } + + log_print("Extended file system %s on %s.", fsi->fstype, display_lvname(lv)); + return 1; +} + +int fs_mount_command(struct cmd_context *cmd, struct logical_volume *lv, struct fs_info *fsi, + int reuse_mount_dir) +{ + char mountdir[PATH_MAX]; + const char *argv[FS_CMD_MAX_ARGS + 4]; + int args = 0; + int status; + + if (reuse_mount_dir) { + if (!fsi->mount_dir[0]) { + log_error("Cannot remount fs without previous mount dir."); + return 0; + } + memcpy(mountdir, fsi->mount_dir, PATH_MAX); + } else { + if (dm_snprintf(mountdir, sizeof(mountdir), "/tmp/%s_XXXXXX", cmd->name) < 0) + return_0; + if (!mkdtemp(mountdir)) { + log_error("Failed to create temp dir for mount: %s", strerror(errno)); + return 0; + } + memcpy(fsi->mount_dir, mountdir, PATH_MAX); + fsi->temp_mount_dir = 1; + } + + argv[0] = MOUNT_PATH; /* defined by configure */ + argv[++args] = fsi->fs_dev_path; + argv[++args] = mountdir; + argv[++args] = NULL; + + log_print("Mounting %s.", display_lvname(lv)); + + if (!exec_cmd(cmd, argv, &status, 1)) { + log_error("Failed to mount file system on %s at %s.", display_lvname(lv), mountdir); + return 0; + } + + log_print("Mounted %s at %s dir %s.", display_lvname(lv), + reuse_mount_dir ? "original" : "temporary", mountdir); + return 1; +} + +int fs_unmount_command(struct cmd_context *cmd, struct logical_volume *lv, struct fs_info *fsi) +{ + const char *argv[FS_CMD_MAX_ARGS + 4]; + int args = 0; + int status; + + argv[0] = UMOUNT_PATH; /* defined by configure */ + argv[++args] = fsi->fs_dev_path; + argv[++args] = NULL; + + log_print("Unmounting %s.", display_lvname(lv)); + + if (!exec_cmd(cmd, argv, &status, 1)) { + log_error("Failed to unmount file system on %s.", display_lvname(lv)); + return 0; + } + + log_print("Unmounted %s.", display_lvname(lv)); + + if (fsi->temp_mount_dir) { + if (rmdir(fsi->mount_dir) < 0) + log_print("Error removing temp dir %s", fsi->mount_dir); + fsi->mount_dir[0] = '\0'; + fsi->temp_mount_dir = 0; + } + return 1; +} + +int crypt_resize_command(struct cmd_context *cmd, dev_t crypt_devt, uint64_t newsize_bytes) +{ + char crypt_path[PATH_MAX]; + const char *argv[FS_CMD_MAX_ARGS + 4]; + char size_str[16] = { 0 }; + int args = 0; + int status; + + if (dm_snprintf(crypt_path, sizeof(crypt_path), "/dev/dm-%d", (int)MINOR(crypt_devt)) < 0) + return_0; + + if (dm_snprintf(size_str, sizeof(size_str), "%llu", (unsigned long long)newsize_bytes/512) < 0) + return_0; + + argv[0] = CRYPTSETUP_PATH; /* defined by configure */ + argv[++args] = "resize"; + if (newsize_bytes) { + argv[++args] = "--size"; + argv[++args] = size_str; + } + argv[++args] = crypt_path; + argv[++args] = NULL; + + log_print("Resizing crypt device %s...", crypt_path); + + if (!exec_cmd(cmd, argv, &status, 1)) { + log_error("Failed to cryptsetup resize %s to %s (%llu sectors)", + crypt_path, display_size(cmd, newsize_bytes/512), + (unsigned long long)newsize_bytes/512); + return 0; + } + + if (newsize_bytes) + log_print("Resized crypt device %s to %s (%llu sectors)", + crypt_path, display_size(cmd, newsize_bytes/512), + (unsigned long long)newsize_bytes/512); + else + log_print("Resized crypt device %s.", crypt_path); + + return 1; +} + diff --git a/lib/device/filesystem.h b/lib/device/filesystem.h new file mode 100644 index 000000000..1e83469cb --- /dev/null +++ b/lib/device/filesystem.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 Red Hat, Inc. All rights reserved. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#ifndef _FILESYSTEM_H +#define _FILESYSTEM_H + +#define FSTYPE_MAX 16 + +struct fs_info { + char fstype[FSTYPE_MAX]; + char mount_dir[PATH_MAX]; + char fs_dev_path[PATH_MAX]; /* usually lv dev, can be crypt dev */ + unsigned int block_size_bytes; /* 512 or 4k */ + uint64_t fs_last_byte; /* last byte on the device used by the fs */ + uint32_t crypt_offset_bytes; /* offset in bytes of crypt data on LV */ + dev_t crypt_devt; /* dm-crypt device between the LV and FS */ + + unsigned nofs:1; + unsigned unmounted:1; + unsigned mounted:1; + unsigned temp_mount_dir:1; + /* for resizing */ + unsigned needs_reduce:1; + unsigned needs_extend:1; + unsigned needs_fsck:1; + unsigned needs_unmount:1; + unsigned needs_mount:1; + unsigned needs_crypt:1; +}; + +int fs_get_info(struct cmd_context *cmd, struct logical_volume *lv, + struct fs_info *fsi, int include_mount); +int fs_fsck_command(struct cmd_context *cmd, struct logical_volume *lv, struct fs_info *fsi); +int fs_reduce_command(struct cmd_context *cmd, struct logical_volume *lv, struct fs_info *fsi, + uint64_t newsize_bytes); +int fs_extend_command(struct cmd_context *cmd, struct logical_volume *lv, struct fs_info *fsi); +int fs_mount_command(struct cmd_context *cmd, struct logical_volume *lv, struct fs_info *fsi, + int reuse_mount_dir); +int fs_unmount_command(struct cmd_context *cmd, struct logical_volume *lv, struct fs_info *fsi); + +int crypt_resize_command(struct cmd_context *cmd, dev_t crypt_devt, uint64_t newsize_bytes); + +#endif diff --git a/lib/metadata/lv_manip.c b/lib/metadata/lv_manip.c index 22f8324e2..91bc6bd0d 100644 --- a/lib/metadata/lv_manip.c +++ b/lib/metadata/lv_manip.c @@ -31,6 +31,7 @@ #include "lib/locking/lvmlockd.h" #include "lib/label/label.h" #include "lib/misc/lvm-signal.h" +#include "lib/device/filesystem.h" #ifdef HAVE_BLKZEROOUT #include <sys/ioctl.h> @@ -4946,13 +4947,6 @@ static int _lv_reduce_confirmation(struct logical_volume *lv, return 0; } - if (lv_is_vdo(lv) && !info.exists) { - log_error("Logical volume %s must be activated " - "before reducing device size.", - display_lvname(lv)); - return 0; - } - if (!info.exists) return 1; @@ -5933,7 +5927,7 @@ static void _setup_params_for_extend_metadata(struct logical_volume *lv, lp->percent = PERCENT_NONE; lp->segtype = mseg->segtype; lp->mirrors = seg_is_mirrored(mseg) ? lv_mirror_count(lv) : 0; - lp->resizefs = 0; + lp->fsopt[0] = '\0'; lp->stripes = lp->mirrors ? mseg->area_count / lp->mirrors : 0; lp->stripe_size = mseg->stripe_size; } @@ -5974,6 +5968,628 @@ static int _lv_resize_check_used(struct logical_volume *lv) return 1; } +/* + * --fs checksize: check fs size and allow the lv to reduce if the fs is not + * using the affected space, i.e. the fs does not need to be + * resized. fail the command without reducing the fs or lv if + * the fs is using the affected space. + * + * --fs resize_remount: resize the fs, mounting/unmounting the fs as needed, + * but avoiding mounting/unmounted when possible. + * + * --fs resize_keepmount: resize the fs without changing the current + * mount/unmount state. fail the command without reducing the + * fs or lv if the fs resize would require mounting or unmounting. + * + * --fs resize_unmount: resize the fs only while it's unmounted ("offline"), + * unmounting the fs if needed. fail the commandn without + * reducing the fs or lv if the fs resize would require having + * the fs mounted. + * + * --fs resize_fsadm: old method using fsadm script to do everything + * + * --fs resize: equivalent to resize_remount. + */ +static int _fs_reduce_allow(struct cmd_context *cmd, struct logical_volume *lv, + struct lvresize_params *lp, uint64_t newsize_bytes_lv, + uint64_t newsize_bytes_fs, struct fs_info *fsi) +{ + const char *fs_reduce_cmd = ""; + const char *cmp_desc = ""; + int equal = 0, smaller = 0, larger = 0; + int is_ext_fstype = 0; + int confirm_mount_change = 0; + + /* + * Allow reducing the LV for other fs types if the fs is not using + * space that's being reduced. + */ + if (!strcmp(fsi->fstype, "ext2") || + !strcmp(fsi->fstype, "ext3") || + !strcmp(fsi->fstype, "ext4") || + !strcmp(fsi->fstype, "xfs")) { + log_debug("Found fs %s last_byte %llu newsize_bytes_fs %llu", + fsi->fstype, + (unsigned long long)fsi->fs_last_byte, + (unsigned long long)newsize_bytes_fs); + if (!strncmp(fsi->fstype, "ext", 3)) { + is_ext_fstype = 1; + fs_reduce_cmd = " resize2fs"; + } + } + + if (!fsi->mounted) + log_print("File system %s%s found on %s.", + fsi->fstype, fsi->needs_crypt ? "+crypto_LUKS" : "", + display_lvname(lv)); + else + log_print("File system %s%s found on %s mounted at %s.", + fsi->fstype, fsi->needs_crypt ? "+crypto_LUKS" : "", + display_lvname(lv), fsi->mount_dir); + + if (!fsi->fs_last_byte) { + log_error("File system size unknown: update libblkid for FSLASTBLOCK, or see --fs resize_fsadm."); + return 0; + } + + if ((equal = (fsi->fs_last_byte == newsize_bytes_fs))) + cmp_desc = "equal to"; + else if ((smaller = (fsi->fs_last_byte < newsize_bytes_fs))) + cmp_desc = "smaller than"; + else if ((larger = (fsi->fs_last_byte > newsize_bytes_fs))) + cmp_desc = "larger than"; + + log_print("File system size (%s) is %s the requested size (%s).", + display_size(cmd, fsi->fs_last_byte/512), cmp_desc, + display_size(cmd, newsize_bytes_fs/512)); + + /* + * FS reduce is not needed, it's not using the affected space. + */ + if (smaller || equal) { + log_print("File system reduce is not needed, skipping."); + fsi->needs_reduce = 0; + return 1; + } + + /* + * FS reduce is required, but checksize does not allow it. + */ + if (!strcmp(lp->fsopt, "checksize")) { + if (is_ext_fstype) + log_error("File system reduce is required (see resize2fs or --resizefs.)"); + else + log_error("File system reduce is required and not supported (%s).", fsi->fstype); + return 0; + } + + /* + * FS reduce required, ext* supports it, xfs does not. + */ + if (is_ext_fstype) { + log_print("File system reduce is required using resize2fs."); + } else { + log_error("File system reduce is required and not supported (%s).", fsi->fstype); + return 0; + } + + /* + * Set fstype-specific requirements for running fs resize command. + * ext2,3,4 require the fs to be unmounted to shrink with resize2fs, + * and they require e2fsck to be run first, unless resize2fs -f is used. + */ + if (is_ext_fstype) { + /* it's traditional to run fsck before shrink */ + if (!lp->nofsck) + fsi->needs_fsck = 1; + + /* ext2,3,4 require fs to be unmounted to shrink */ + if (fsi->mounted) + fsi->needs_unmount = 1; + + fsi->needs_reduce = 1; + } else { + /* + * Shouldn't reach here since no other fs types get this far. + * A future fs supporting shrink may require the fs to be + * mounted or unmounted to run the fs shrink command. + * set fsi->needs_unmount or fs->needs_mount according to + * the fs-specific shrink command's requirement. + */ + log_error("File system %s: fs reduce not implemented.", fsi->fstype); + return 0; + } + + /* + * FS reduce may require mounting or unmounting, check the fsopt value + * from the user, and the current mount state to decide if fs resize + * can be done. + */ + if (!strcmp(lp->fsopt, "resize_keepmount")) { + /* can't mount|unmount to run fs resize */ + if (fsi->needs_mount) { + log_error("File system needs to be mounted to reduce fs (see resize_remount)."); + return 0; + } + if (fsi->needs_unmount) { + log_error("File system needs to be unmounted to reduce fs (see resize_remount)."); + return 0; + } + } else if (!strcmp(lp->fsopt, "resize_unmount")) { + /* we can unmount if needed to run fs resize */ + if (fsi->needs_mount) { + log_error("File system needs to be mounted to reduce fs (see resize_remount)."); + return 0; + } + } else if (!strcmp(lp->fsopt, "resize_remount")) { + /* we can mount|unmount as needed to run fs resize */ + } else if (!strcmp(lp->fsopt, "resize")) { + /* equivalent to resize_remount, but confirm mount change */ + if (fsi->needs_mount || fsi->needs_unmount) + confirm_mount_change = 1; + } else { + log_error("Unknown file system resize option: %s", lp->fsopt); + return 0; + } + + /* + * If future file systems can be reduced while mounted, then suppress + * needs_fsck here if the fs is already mounted. + */ + + if (fsi->needs_unmount) + log_print("File system unmount is needed for reduce."); + if (fsi->needs_fsck) + log_print("File system fsck will be run before reduce."); + if (fsi->needs_mount) + log_print("File system mount is needed for reduce."); + if (fsi->needs_crypt) + log_print("cryptsetup resize is needed for reduce."); + + /* + * Use a confirmation prompt because mount|unmount is needed, and + * the generic "resize" option was used (i.e. the user did not give + * specific direction about how to handle mounting|unmounting such as + * resize_unmount, resize_remount, resize_keepmount, so ask them.) + */ + if (!lp->yes && confirm_mount_change) { + if (yes_no_prompt("Continue with %s file system reduce steps:%s%s%s%s%s? [y/n]:", + fsi->fstype, + fsi->needs_unmount ? " unmount," : "", + fsi->needs_fsck ? " fsck," : "", + fsi->needs_mount ? " mount," : "", + fsi->needs_crypt ? " cryptsetup," : "", + fsi->needs_reduce ? fs_reduce_cmd : "") == 'n') { + log_error("File system not reduced."); + return 0; + } + } + + return 1; +} + +static int _fs_extend_allow(struct cmd_context *cmd, struct logical_volume *lv, + struct lvresize_params *lp, struct fs_info *fsi) +{ + const char *fs_extend_cmd = ""; + int is_ext_fstype = 0; + int confirm_mount_change = 0; + + if (!strcmp(fsi->fstype, "ext2") || + !strcmp(fsi->fstype, "ext3") || + !strcmp(fsi->fstype, "ext4") || + !strcmp(fsi->fstype, "xfs")) { + log_debug("Found fs %s last_byte %llu", + fsi->fstype, (unsigned long long)fsi->fs_last_byte); + if (!strncmp(fsi->fstype, "ext", 3)) + is_ext_fstype = 1; + } else { + log_error("File system extend is not supported (%s).", fsi->fstype); + return 0; + } + + if (!fsi->mounted) + log_print("File system %s%s found on %s.", + fsi->fstype, fsi->needs_crypt ? "+crypto_LUKS" : "", + display_lvname(lv)); + else + log_print("File system %s%s found on %s mounted at %s.", + fsi->fstype, fsi->needs_crypt ? "+crypto_LUKS" : "", + display_lvname(lv), fsi->mount_dir); + + /* + * FS extend may require mounting or unmounting, check the fsopt value + * from the user, and the current mount state to decide if fs extend + * can be done. + */ + + if (is_ext_fstype) { + fs_extend_cmd = " resize2fs"; + + /* + * ext* can be extended while it's mounted or unmounted. If + * the fs is unmounted, it's traditional to run fsck before + * running the fs extend. + * + * resize_keepmount: don't change mount condition. + * if mounted: fs_extend + * if unmounted: fsck, fs_extend + * + * resize_unmount: extend offline, so unmount first if mounted. + * if mounted: unmount, fsck, fs_extend + * if unmounted: fsck, fs_extend + * + * resize_remount|resize: do any mount or unmount that's necessary, + * avoiding unnecessary mounting/unmounting. + * if mounted: fs_extend + * if unmounted: fsck, fs_extend + */ + if (!strcmp(lp->fsopt, "resize_keepmount")) { + if (fsi->mounted) + fsi->needs_extend = 1; + else if (fsi->unmounted) { + fsi->needs_fsck = 1; + fsi->needs_extend = 1; + } + } else if (!strcmp(lp->fsopt, "resize_unmount")) { + if (fsi->mounted) { + fsi->needs_unmount = 1; + fsi->needs_fsck = 1; + fsi->needs_extend = 1; + } else if (fsi->unmounted) { + fsi->needs_fsck = 1; + fsi->needs_extend = 1; + } + } else if (!strcmp(lp->fsopt, "resize_remount") || !strcmp(lp->fsopt, "resize")) { + if (fsi->mounted) + fsi->needs_extend = 1; + else if (fsi->unmounted) { + fsi->needs_fsck = 1; + fsi->needs_extend = 1; + } + } + + if (lp->nofsck) + fsi->needs_fsck = 0; + + } else if (!strcmp(fsi->fstype, "xfs")) { + fs_extend_cmd = " xfs_growfs"; + + /* + * xfs must be mounted to extend. + * + * resize_keepmount: don't change mount condition. + * if mounted: fs_extend + * if unmounted: fail + * + * resize_unmount: extend offline, so unmount first if mounted. + * if mounted: fail + * if unmounted: fail + * + * resize_remount|resize: do any mount or unmount that's necessary, + * avoiding unnecessary mounting/unmounting. + * if mounted: fs_extend + * if unmounted: mount, fs_extend + */ + if (!strcmp(lp->fsopt, "resize_keepmount")) { + if (fsi->mounted) + fsi->needs_extend = 1; + else if (fsi->unmounted) { + log_error("File system must be mounted to extend (see resize_remount)."); + return 0; + } + } else if (!strcmp(lp->fsopt, "resize_unmount")) { + log_error("File system must be mounted to extend (see resize_remount)."); + return 0; + } else if (!strcmp(lp->fsopt, "resize_remount") || !strcmp(lp->fsopt, "resize")) { + if (fsi->mounted) + fsi->needs_extend = 1; + else if (fsi->unmounted) { + fsi->needs_mount = 1; + fsi->needs_extend = 1; + } + } + + } else { + /* shouldn't reach here */ + log_error("File system type %s not handled.", fsi->fstype); + return 0; + } + + /* + * Skip needs_fsck if the fs is mounted and we can extend the fs while + * it's mounted. + */ + if (fsi->mounted && !fsi->needs_unmount && fsi->needs_fsck) { + log_print("File system fsck skipped for extending mounted fs."); + fsi->needs_fsck = 0; + } + + if (fsi->needs_unmount) + log_print("File system unmount is needed for extend."); + if (fsi->needs_fsck) + log_print("File system fsck will be run before extend."); + if (fsi->needs_mount) + log_print("File system mount is needed for extend."); + if (fsi->needs_crypt) + log_print("cryptsetup resize is needed for extend."); + + /* + * Use a confirmation prompt because mount|unmount is needed, and + * the generic "resize" option was used (i.e. the user did not give + * specific direction about how to handle mounting|unmounting such + * as resize_umount, resize_remount, resize_keepmount, so ask them.) + */ + if (!strcmp(lp->fsopt, "resize") && (fsi->needs_mount || fsi->needs_unmount)) + confirm_mount_change = 1; + + if (!lp->yes && confirm_mount_change) { + if (yes_no_prompt("Continue with %s file system extend steps:%s%s%s%s%s? [y/n]:", + fsi->fstype, + fsi->needs_unmount ? " unmount," : "", + fsi->needs_fsck ? " fsck," : "", + fsi->needs_mount ? " mount," : "", + fsi->needs_crypt ? " cryptsetup," : "", + fsi->needs_extend ? fs_extend_cmd : "") == 'n') { + log_error("File system not extended."); + return 0; + } + } + + return 1; +} + +static int _fs_reduce(struct cmd_context *cmd, struct logical_volume *lv, + struct lvresize_params *lp) +{ + struct fs_info fsinfo; + struct fs_info fsinfo2; + uint64_t newsize_bytes_lv; + uint64_t newsize_bytes_fs; + int mounted = 0, unmounted = 0; + int ret = 0; + + memset(&fsinfo, 0, sizeof(fsinfo)); + memset(&fsinfo2, 0, sizeof(fsinfo)); + + if (!fs_get_info(cmd, lv, &fsinfo, 1)) + goto_out; + + if (fsinfo.nofs) { + ret = 1; + goto_out; + } + + /* extent_size units is SECTOR_SIZE (512) */ + newsize_bytes_lv = lp->extents * lv->vg->extent_size * SECTOR_SIZE; + newsize_bytes_fs = newsize_bytes_lv; + + /* + * If needs_crypt, then newsize_bytes passed to fs_reduce_command() and + * crypt_resize_command() needs to be decreased by the offset of crypt + * data on the LV (usually the size of the LUKS header which is usually + * 2MB for LUKS1 and 16MB for LUKS2.) + */ + if (fsinfo.needs_crypt) { + newsize_bytes_fs -= fsinfo.crypt_offset_bytes; + log_print("File system size %llub is adjusted for crypt data offset %ub.", + (unsigned long long)newsize_bytes_fs, fsinfo.crypt_offset_bytes); + } + + /* + * Based on the --fs command option, the fs type, the last block used, + * and the mount state, determine if LV reduce is allowed. If not + * returns 0 and lvreduce should fail. If allowed, returns 1 and sets + * fsinfo.needs_* for any steps that are required to reduce the LV. + */ + if (!_fs_reduce_allow(cmd, lv, lp, newsize_bytes_lv, newsize_bytes_fs, &fsinfo)) + goto_out; + + /* + * Uncommon special case in which the FS does not need to be shrunk, + * but the crypt dev over the LV should be shrunk to correspond with + * the LV size, so that the FS does not see an incorrect device size. + */ + if (!fsinfo.needs_reduce && fsinfo.needs_crypt && !test_mode()) { + ret = crypt_resize_command(cmd, fsinfo.crypt_devt, newsize_bytes_fs); + goto out; + } + + /* + * fs reduce is not needed to reduce the LV. + */ + if (!fsinfo.needs_reduce) { + ret = 1; + goto_out; + } + + if (test_mode()) { + if (fsinfo.needs_unmount) + log_print("Skip unmount in test mode."); + if (fsinfo.needs_fsck) + log_print("Skip fsck in test mode."); + if (fsinfo.needs_mount) + log_print("Skip mount in test mode."); + if (fsinfo.needs_crypt) + log_print("Skip cryptsetup in test mode."); + log_print("Skip fs reduce in test mode."); + ret = 1; + goto out; + } + + /* + * mounting, unmounting, fsck, and shrink command can all take a long + * time to run, and this lvm command should not block other lvm + * commands from running during that time, so release the vg lock + * around the long-running steps, and reacquire after. + */ + unlock_vg(cmd, lv->vg, lv->vg->name); + + if (fsinfo.needs_unmount) { + if (!fs_unmount_command(cmd, lv, &fsinfo)) + goto_out; + unmounted = 1; + } + + if (fsinfo.needs_fsck) { + if (!fs_fsck_command(cmd, lv, &fsinfo)) + goto_out; + } + + if (fsinfo.needs_mount) { + if (!fs_mount_command(cmd, lv, &fsinfo, 0)) + goto_out; + mounted = 1; + } + + if (!fs_reduce_command(cmd, lv, &fsinfo, newsize_bytes_fs)) + goto_out; + + if (fsinfo.needs_crypt) { + if (!crypt_resize_command(cmd, fsinfo.crypt_devt, newsize_bytes_fs)) + goto_out; + } + + if (!lock_vol(cmd, lv->vg->name, LCK_VG_WRITE, NULL)) { + log_error("Failed to lock VG, cannot reduce LV."); + ret = 0; + goto out; + } + + /* + * Check that the vg wasn't changed while it was unlocked. + * (can_use_one_scan: check just one mda in the vg for changes) + */ + cmd->can_use_one_scan = 1; + if (scan_text_mismatch(cmd, lv->vg->name, NULL)) { + log_error("VG was changed during fs operations, cannot reduce LV."); + ret = 0; + goto out; + } + + /* + * Re-check the fs last block which should now be less than the + * requested (reduced) LV size. + */ + if (!fs_get_info(cmd, lv, &fsinfo2, 0)) + goto_out; + + if (fsinfo.fs_last_byte && (fsinfo2.fs_last_byte > newsize_bytes_fs)) { + log_error("File system last byte %llu is greater than new size %llu bytes.", + (unsigned long long)fsinfo2.fs_last_byte, + (unsigned long long)newsize_bytes_fs); + goto_out; + } + + ret = 1; + + /* + * put the fs back into the original mounted|unmounted state. + */ + if (!strcmp(lp->fsopt, "resize_remount") || !strcmp(lp->fsopt, "resize")) { + if (mounted && !fs_unmount_command(cmd, lv, &fsinfo)) + ret = 0; + if (unmounted && !fs_mount_command(cmd, lv, &fsinfo, 1)) + ret = 0; + } + out: + return ret; +} + +static int _fs_extend(struct cmd_context *cmd, struct logical_volume *lv, + struct lvresize_params *lp) +{ + struct fs_info fsinfo; + int mounted = 0, unmounted = 0; + int ret = 0; + + memset(&fsinfo, 0, sizeof(fsinfo)); + + if (!fs_get_info(cmd, lv, &fsinfo, 1)) + goto_out; + + if (fsinfo.nofs) { + ret = 1; + goto_out; + } + + /* + * Decide if fs should be extended based on the --fs option, + * the fs type and the mount state. + */ + if (!_fs_extend_allow(cmd, lv, lp, &fsinfo)) + goto_out; + + /* + * fs extend is not needed + */ + if (!fsinfo.needs_extend) { + ret = 1; + goto_out; + } + + if (test_mode()) { + if (fsinfo.needs_unmount) + log_print("Skip unmount in test mode."); + if (fsinfo.needs_fsck) + log_print("Skip fsck in test mode."); + if (fsinfo.needs_mount) + log_print("Skip mount in test mode."); + if (fsinfo.needs_crypt) + log_print("Skip cryptsetup in test mode."); + log_print("Skip fs extend in test mode."); + ret = 1; + goto out; + } + + /* + * mounting, unmounting and extend command can all take a long + * time to run, and this lvm command should not block other lvm + * commands from running during that time, so release the vg + * lock around the long-running steps. + */ + unlock_vg(cmd, lv->vg, lv->vg->name); + + if (fsinfo.needs_unmount) { + if (!fs_unmount_command(cmd, lv, &fsinfo)) + goto_out; + unmounted = 1; + } + + if (fsinfo.needs_fsck) { + if (!fs_fsck_command(cmd, lv, &fsinfo)) + goto_out; + } + + if (fsinfo.needs_crypt) { + if (!crypt_resize_command(cmd, fsinfo.crypt_devt, 0)) + goto_out; + } + + if (fsinfo.needs_mount) { + if (!fs_mount_command(cmd, lv, &fsinfo, 0)) + goto_out; + mounted = 1; + } + + if (!fs_extend_command(cmd, lv, &fsinfo)) + goto_out; + + ret = 1; + + /* + * put the fs back into the original mounted|unmounted state. + */ + if (!strcmp(lp->fsopt, "resize_remount") || !strcmp(lp->fsopt, "resize")) { + if (mounted && !fs_unmount_command(cmd, lv, &fsinfo)) + ret = 0; + if (unmounted && !fs_mount_command(cmd, lv, &fsinfo, 1)) + ret = 0; + } + out: + return ret; +} + int lv_resize(struct cmd_context *cmd, struct logical_volume *lv, struct lvresize_params *lp) { @@ -5989,7 +6605,9 @@ int lv_resize(struct cmd_context *cmd, struct logical_volume *lv, int meta_size_matches = 0; int is_extend = (lp->resize == LV_EXTEND); int is_reduce = (lp->resize == LV_REDUCE); + int is_active = 0; int activated = 0; + int activated_checksize = 0; int status; int ret = 0; @@ -6019,26 +6637,6 @@ int lv_resize(struct cmd_context *cmd, struct logical_volume *lv, } /* - * resizefs applies to the LV command arg, not to the top LV or lower - * resizable LV. - */ - if (lp->resizefs) { - if (!lv_is_active(lv)) { - log_error("Logical volume %s must be activated before resizing filesystem.", - display_lvname(lv)); - return 0; - } - /* types of LVs that can hold a file system */ - if (!(lv_is_linear(lv) || lv_is_striped(lv) || lv_is_raid(lv) || - lv_is_mirror(lv) || lv_is_thin_volume(lv) || lv_is_vdo(lv) || - lv_is_cache(lv) || lv_is_writecache(lv))) { - log_print_unless_silent("Ignoring --resizefs for LV type %s.", - seg ? seg->segtype->name : "unknown"); - lp->resizefs = 0; - } - } - - /* * Figure out which LVs are going to be extended, and set params * to the requested extents/size for each. Some LVs are extended * only by extending an underlying LV. Extending some top level @@ -6197,10 +6795,6 @@ int lv_resize(struct cmd_context *cmd, struct logical_volume *lv, if (!_lv_resize_check_type(lv_main, lp)) return_0; - - if (is_reduce && !main_size_matches && !lp->resizefs && - !_lv_reduce_confirmation(lv, lp)) - return_0; } /* @@ -6265,46 +6859,123 @@ int lv_resize(struct cmd_context *cmd, struct logical_volume *lv, * So active temporarily pool LV (with on disk metadata) then use * suspend and resume and deactivate pool LV, instead of searching for * an active thin volume. + * + * FIXME: why are thin pools activated where other LV types return + * error if inactive? */ if (lv_is_thin_pool(lv_top) && !lv_is_active(lv_top)) { - if (!activation()) { - log_error("Cannot resize %s without using device-mapper kernel driver.", - display_lvname(lv_top)); - return_0; + if (!activation()) { + log_error("Cannot activate to resize %s without using device-mapper kernel driver.", + display_lvname(lv_top)); + return 0; } if (!activate_lv(cmd, lv_top)) { log_error("Failed to activate %s.", display_lvname(lv_top)); - return_0; + return 0; } + if (!sync_local_dev_names(cmd)) + stack; activated = 1; } /* + * Disable fsopt checksize for lvextend. + */ + if (is_extend && !strcmp(lp->fsopt, "checksize")) + lp->fsopt[0] = '\0'; + + /* + * Disable fsopt if LV type cannot hold a file system. + */ + if (lp->fsopt[0] && + !(lv_is_linear(lv) || lv_is_striped(lv) || lv_is_raid(lv) || + lv_is_mirror(lv) || lv_is_thin_volume(lv) || lv_is_vdo(lv) || + lv_is_cache(lv) || lv_is_writecache(lv))) { + log_print_unless_silent("Ignoring fs resizing options for LV type %s.", + seg ? seg->segtype->name : "unknown"); + lp->fsopt[0] = '\0'; + } + + /* + * Using an option to resize the fs has always/traditionally required + * the LV to already be active, so keep that behavior. Reducing an + * inactive LV will activate the LV to look for a fs that would be + * damaged. + */ + is_active = lv_is_active(lv_top); + + if (is_reduce && !is_active && !strcmp(lp->fsopt, "checksize")) { + if (!activate_lv(cmd, lv_top)) { + log_error("Failed to activate %s to check for fs.", display_lvname(lv_top)); + goto out; + } + if (!sync_local_dev_names(cmd)) + stack; + activated_checksize = 1; + + } else if (lp->fsopt[0] && !is_active) { + log_error("Logical volume %s must be active for file system %s.", + display_lvname(lv_top), lp->fsopt); + goto out; + } + + /* + * Return an error without resizing the LV if the user requested + * a file system resize when no file system exists on the LV. + * (fs checksize does not require a fs to exist.) + */ + if (lp->fsopt[0] && strcmp(lp->fsopt, "checksize") && lp->user_set_fs) { + char lv_path[PATH_MAX]; + char fstype[FSTYPE_MAX]; + + if (dm_snprintf(lv_path, sizeof(lv_path), "%s%s/%s", cmd->dev_dir, + lv_top->vg->name, lv_top->name) < 0) { + log_error("Couldn't create LV path for %s.", display_lvname(lv_top)); + goto out; + } + if (!get_fs_type(lv_path, fstype)) { + log_error("File system not found for --resizefs or --fs options."); + goto out; + } + } + + /* + * Warn and confirm if checksize has been disabled for reduce. + */ + if (is_reduce && !lp->fsopt[0] && !_lv_reduce_confirmation(lv_top, lp)) + goto_out; + + /* * If the LV is locked due to being active, this lock call is a no-op. * Otherwise, this acquires a transient lock on the lv (not PERSISTENT) */ if (!lockd_lv_resize(cmd, lv_top, "ex", 0, lp)) goto_out; - /* - * Check the file system, and shrink the fs before reducing lv. - * TODO: libblkid fs type, fs last_block, mount state, - * unlock vg, mount|unmount if needed, fork fs shrink, - * lock vg, rescan devs, recheck fs type last_block. - * (at end mount|unmount if needed to restore initial state.) - */ - if (lp->resizefs && !lp->nofsck && + /* Part of old approach to fs handling using fsadm. */ + if (!strcmp(lp->fsopt, "resize_fsadm") && !lp->nofsck && !_fsadm_cmd(FSADM_CMD_CHECK, lv_top, 0, lp->yes, lp->force, &status)) { if (status != FSADM_CHECK_FAILS_FOR_MOUNTED) { log_error("Filesystem check failed."); goto out; } - /* some filesystems support online resize */ } - if (lp->resizefs && is_reduce && - !_fsadm_cmd(FSADM_CMD_RESIZE, lv_top, lp->extents, lp->yes, lp->force, NULL)) { - log_error("Filesystem resize failed."); - goto out; + + if (is_reduce && lp->fsopt[0]) { + if (!strcmp(lp->fsopt, "resize_fsadm")) { + /* Old approach to fs handling using fsadm. */ + if (!_fsadm_cmd(FSADM_CMD_RESIZE, lv_top, lp->extents, lp->yes, lp->force, NULL)) { + log_error("Filesystem resize failed."); + goto out; + } + } else { + /* New approach to fs handling using fs info. */ + if (!_fs_reduce(cmd, lv_top, lp)) + goto_out; + } + + if (activated_checksize && !deactivate_lv(cmd, lv_top)) + log_warn("Problem deactivating %s.", display_lvname(lv_top)); } /* @@ -6319,9 +6990,9 @@ int lv_resize(struct cmd_context *cmd, struct logical_volume *lv, * Remove any striped raid reshape space for LV resizing (not common). */ if (lv_meta && first_seg(lv_meta)->reshape_len && !lv_raid_free_reshape_space(lv_meta)) - return_0; + goto_out; if (lv_main && first_seg(lv_main)->reshape_len && !lv_raid_free_reshape_space(lv_main)) - return_0; + goto_out; /* * The core of the actual lv resizing. @@ -6376,22 +7047,28 @@ int lv_resize(struct cmd_context *cmd, struct logical_volume *lv, stack; } - /* - * Extend the file system. - * TODO: libblkid fs type, mount state, - * unlock vg, mount|unmount if needed, fork fs grow, - * mount|unmount if needed to restore initial state. - */ - if (lp->resizefs && is_extend && - !_fsadm_cmd(FSADM_CMD_RESIZE, lv_top, lp->extents, lp->yes, lp->force, NULL)) { - log_warn("Filesystem resize failed."); - goto out; + if (is_extend && lp->fsopt[0]) { + if (!strcmp(lp->fsopt, "resize_fsadm")) { + /* Old approach to fs handling using fsadm. */ + if (!_fsadm_cmd(FSADM_CMD_RESIZE, lv_top, lp->extents, lp->yes, lp->force, NULL)) { + log_error("File system extend error."); + lp->extend_fs_error = 1; + goto out; + } + } else { + /* New approach to fs handling using fs info. */ + if (!_fs_extend(cmd, lv_top, lp)) { + log_error("File system extend error."); + lp->extend_fs_error = 1; + goto out; + } + } } ret = 1; out: - if (activated) { + if (activated || activated_checksize) { if (!sync_local_dev_names(cmd)) stack; if (!deactivate_lv(cmd, lv_top)) diff --git a/lib/metadata/metadata-exported.h b/lib/metadata/metadata-exported.h index 4fb6ad93a..21dd39646 100644 --- a/lib/metadata/metadata-exported.h +++ b/lib/metadata/metadata-exported.h @@ -674,8 +674,9 @@ struct lvresize_params { int force; int nosync; int nofsck; - int resizefs; int use_policies; + int user_set_fs; + char fsopt[32]; /* set by --resizefs|--fs, empty for --fs ignore */ const struct segment_type *segtype; unsigned mirrors; @@ -695,6 +696,7 @@ struct lvresize_params { int approx_alloc; int extents_are_pes; /* Is 'extents' counting PEs or LEs? */ int size_changed; /* Was there actually a size change */ + int extend_fs_error; /* FS extend error after LV extend success */ const char *lockopt; char *lockd_lv_refresh_path; /* set during resize to use for refresh at the end */ diff --git a/lib/metadata/metadata.c b/lib/metadata/metadata.c index 95f25eef8..30b2c1779 100644 --- a/lib/metadata/metadata.c +++ b/lib/metadata/metadata.c @@ -4533,7 +4533,7 @@ void vg_write_commit_bad_mdas(struct cmd_context *cmd, struct volume_group *vg) * reread metadata. */ -static bool _scan_text_mismatch(struct cmd_context *cmd, const char *vgname, const char *vgid) +bool scan_text_mismatch(struct cmd_context *cmd, const char *vgname, const char *vgid) { DM_LIST_INIT(mda_list); struct mda_list *mdal, *safe; @@ -4706,7 +4706,7 @@ static struct volume_group *_vg_read(struct cmd_context *cmd, * probably unnecessary; all commands could likely just check a single mda. */ - if (lvmcache_scan_mismatch(cmd, vgname, vgid) || _scan_text_mismatch(cmd, vgname, vgid)) { + if (lvmcache_scan_mismatch(cmd, vgname, vgid) || scan_text_mismatch(cmd, vgname, vgid)) { log_debug_metadata("Rescanning devices for %s %s", vgname, writing ? "rw" : ""); if (writing) lvmcache_label_rescan_vg_rw(cmd, vgname, vgid); diff --git a/lib/metadata/metadata.h b/lib/metadata/metadata.h index 6c39ff845..5cebe9508 100644 --- a/lib/metadata/metadata.h +++ b/lib/metadata/metadata.h @@ -538,5 +538,7 @@ void set_pv_devices(struct format_instance *fid, struct volume_group *vg); int get_visible_lvs_using_pv(struct cmd_context *cmd, struct volume_group *vg, struct device *dev, struct dm_list *lvs_list); +bool scan_text_mismatch(struct cmd_context *cmd, const char *vgname, const char *vgid); + #endif |