diff options
Diffstat (limited to 'lib/device/filesystem.c')
-rw-r--r-- | lib/device/filesystem.c | 484 |
1 files changed, 484 insertions, 0 deletions
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; +} + |