summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile.in1
-rw-r--r--lib/device/dev-type.c111
-rw-r--r--lib/device/dev-type.h3
-rw-r--r--lib/device/filesystem.c484
-rw-r--r--lib/device/filesystem.h54
-rw-r--r--lib/metadata/lv_manip.c803
-rw-r--r--lib/metadata/metadata-exported.h4
-rw-r--r--lib/metadata/metadata.c4
-rw-r--r--lib/metadata/metadata.h2
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, &params);
+
+ 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