1
0
mirror of git://sourceware.org/git/lvm2.git synced 2024-12-21 13:34:40 +03:00
lvm2/lib/device/filesystem.c
2024-10-02 13:31:54 +02:00

584 lines
15 KiB
C

/*
* 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 "lib/activate/dev_manager.h"
#include <dirent.h>
#include <mntent.h>
#include <sys/ioctl.h>
static const char *_lvresize_fs_helper_path;
static const char *_get_lvresize_fs_helper_path(void)
{
if (!_lvresize_fs_helper_path)
_lvresize_fs_helper_path = getenv("LVRESIZE_FS_HELPER_PATH");
if (!_lvresize_fs_helper_path)
_lvresize_fs_helper_path = LVRESIZE_FS_HELPER_PATH; /* from configure, usually in /usr/libexec */
return _lvresize_fs_helper_path;
}
/*
* 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 dirent *de;
int ret = 0;
if (dm_snprintf(holders_path, sizeof(holders_path), "%sdev/block/%u:%u/holders",
dm_sysfs_dir(), MAJOR(lv_devt), MINOR(lv_devt)) < 0) {
log_error("Couldn't create holder path for %s.", lv_path);
return 0;
}
/* If the crypt dev is not active, there will be no LV holder. */
if (!(dr = opendir(holders_path))) {
if (errno == ENOENT)
log_error("Missing %s for %s.", crypt_path, lv_path);
else
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;
}
if (closedir(dr))
log_sys_debug("closedir", holders_path);
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 lv_crypt_is_active(struct cmd_context *cmd, char *lv_path)
{
char crypt_path[PATH_MAX] = { 0 };
struct stat st_lv;
if (stat(lv_path, &st_lv) < 0) {
log_error("Failed to get LV path %s", lv_path);
return 0;
}
return _get_crypt_path(st_lv.st_rdev, lv_path, crypt_path);
}
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] = { 0 };
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 fd;
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;
}
memset(&info, 0, sizeof(info));
log_print_unless_silent("Checking crypt device %s on LV %s.",
crypt_path, display_lvname(lv));
if ((fd = open(crypt_path, O_RDONLY)) < 0) {
log_error("Failed to open crypt path %s.", crypt_path);
return 0;
}
if ((ret = fstat(fd, &st_crypt)) < 0)
log_sys_error("fstat", crypt_path);
else if ((ret = ioctl(fd, BLKGETSIZE64, &info.crypt_dev_size_bytes)) < 0)
log_error("Failed to get crypt device size %s.", crypt_path);
if (close(fd))
log_sys_debug("close", crypt_path);
if (ret < 0)
return 0;
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;
/*
* Note: used swap devices are not considered as mount points,
* hence they're not listed in /etc/mtab, we'd need to read the
* /proc/swaps instead. We don't need it at this moment though,
* but if we do once, read the /proc/swaps here if fsi->fstype == "swap".
*/
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;
}
int fs_mount_state_is_misnamed(struct cmd_context *cmd, struct logical_volume *lv, char *lv_path, char *fstype)
{
FILE *fp;
char proc_line[PATH_MAX];
char proc_fstype[FSTYPE_MAX];
char proc_devpath[PATH_MAX];
char proc_mntpath[PATH_MAX];
char mtab_mntpath[PATH_MAX] = { 0 };
char dm_devpath[PATH_MAX];
char tmp_path[PATH_MAX];
char *dm_name;
struct stat st_lv;
struct stat stme;
FILE *fme = NULL;
struct mntent *me;
int renamed = 0;
int dev_match, dir_match;
if (stat(lv_path, &st_lv) < 0) {
log_error("Failed to get LV path %s", lv_path);
return 0;
}
/*
* If LVs have been renamed while their file systems were mounted, then
* inconsistencies appear in the device path and mount point info
* provided by getmntent and /proc/mounts. If there's any
* inconsistency or duplication of info for the LV name or the mount
* point, then give up and don't try fs resize which is likely to fail
* due to kernel problems where mounts reference old device names
* causing fs resizing tools to fail.
*/
if (!(fme = setmntent("/etc/mtab", "r")))
return_0;
while ((me = getmntent(fme))) {
if (strcmp(me->mnt_type, 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_lv.st_rdev)
continue;
if (!_dm_strncpy(mtab_mntpath, me->mnt_dir, sizeof(mtab_mntpath)))
continue; /* Ignore too long unsupported paths */
break;
}
endmntent(fme);
if (mtab_mntpath[0])
log_debug("%s mtab mntpath %s", display_lvname(lv), mtab_mntpath);
/*
* In mtab dir path, replace each ascii space character with the
* four characters \040 which is how /proc/mounts represents spaces.
* The mnt dir from /etc/mtab and /proc/mounts are compared below.
*/
if (strchr(mtab_mntpath, ' ')) {
unsigned i, j = 0;
memcpy(tmp_path, mtab_mntpath, sizeof(tmp_path));
memset(mtab_mntpath, 0, sizeof(mtab_mntpath));
for (i = 0; i < sizeof(tmp_path); i++) {
if (tmp_path[i] == ' ') {
mtab_mntpath[j++] = '\\';
mtab_mntpath[j++] = '0';
mtab_mntpath[j++] = '4';
mtab_mntpath[j++] = '0';
continue;
}
mtab_mntpath[j++] = tmp_path[i];
}
}
if (!(dm_name = dm_build_dm_name(cmd->mem, lv->vg->name, lv->name, NULL)))
return_0;
if ((dm_snprintf(dm_devpath, sizeof(dm_devpath), "%s/%s", dm_dir(), dm_name) < 0))
return_0;
if (!(fp = fopen("/proc/mounts", "r")))
return_0;
while (fgets(proc_line, sizeof(proc_line), fp)) {
if (proc_line[0] != '/')
continue;
if (sscanf(proc_line, "%"
DM_TO_STRING(PATH_MAX) "s %"
DM_TO_STRING(PATH_MAX) "s %"
DM_TO_STRING(PATH_MAX) "s", proc_devpath, proc_mntpath, proc_fstype) != 3)
continue;
if (strcmp(fstype, proc_fstype))
continue;
/*
* When an LV is mounted on two dirs, it appears in /proc/mounts twice as
* /dev/mapper/vg-lvol0 on /foo type xfs ...
* /dev/mapper/vg-lvol0 on /bar type xfs ...
* All entries match dm_devpath, one entry matches mntpath,
* and other entries don't match mntpath.
*
* When an LV is mounted on one dir, and is renamed from lvol0 to lvol1,
* it appears in /proc/mounts once as
* /dev/mapper/vg-lvol0 on /foo type xfs ...
*/
dir_match = !strcmp(mtab_mntpath, proc_mntpath);
dev_match = !strcmp(dm_devpath, proc_devpath);
if (!dir_match && !dev_match)
continue;
if (dev_match && !dir_match) {
log_debug("LV %s mounted at %s also mounted at %s.",
dm_devpath, mtab_mntpath, proc_mntpath);
continue;
}
if (!dev_match && dir_match) {
log_error("LV %s mounted at %s may have been renamed (from %s).",
dm_devpath, proc_mntpath, proc_devpath);
renamed = 1;
}
}
if (fclose(fp))
stack;
if (renamed) {
log_error("File system resizing not supported: fs utilities do not support renamed devices.");
return 1;
}
return 0;
}
#define FS_CMD_MAX_ARGS 16
int crypt_resize_script(struct cmd_context *cmd, struct logical_volume *lv, struct fs_info *fsi,
uint64_t newsize_bytes_fs)
{
char crypt_path[PATH_MAX];
char newsize_str[16] = { 0 };
const char *argv[FS_CMD_MAX_ARGS + 4];
int args = 0;
int status;
if (dm_snprintf(newsize_str, sizeof(newsize_str), "%llu", (unsigned long long)newsize_bytes_fs) < 0)
return_0;
if (dm_snprintf(crypt_path, sizeof(crypt_path), "/dev/dm-%u", MINOR(fsi->crypt_devt)) < 0)
return_0;
argv[0] = _get_lvresize_fs_helper_path();
argv[++args] = "--cryptresize";
argv[++args] = "--cryptpath";
argv[++args] = crypt_path;
argv[++args] = "--newsizebytes";
argv[++args] = newsize_str;
argv[++args] = NULL;
if (!exec_cmd(cmd, argv, &status, 1)) {
log_error("Failed to resize crypt dev with lvresize_fs_helper.");
return 0;
}
return 1;
}
/*
* The helper script does the following steps for reduce:
* devpath = $cryptpath ? $cryptpath : $lvpath
* if needs_unmount
* umount $mountdir
* if needs_fsck
* e2fsck -f -p $devpath
* if needs_mount
* mount $devpath $tmpdir
* if $fstype == "ext"
* resize2fs $devpath $newsize_kb
* if needs_crypt
* cryptsetup resize --size $newsize_sectors $cryptpath
*
* Note: when a crypt layer is included, newsize_bytes_fs is smaller
* than newsize_bytes_lv because of the crypt header.
*/
int fs_reduce_script(struct cmd_context *cmd, struct logical_volume *lv, struct fs_info *fsi,
uint64_t newsize_bytes_fs, char *fsmode)
{
char lv_path[PATH_MAX];
char crypt_path[PATH_MAX];
char newsize_str[16] = { 0 };
const char *argv[FS_CMD_MAX_ARGS + 4];
char *devpath;
int args = 0;
int status;
if (dm_snprintf(newsize_str, sizeof(newsize_str), "%llu", (unsigned long long)newsize_bytes_fs) < 0)
return_0;
if (dm_snprintf(lv_path, PATH_MAX, "%s%s/%s", lv->vg->cmd->dev_dir, lv->vg->name, lv->name) < 0)
return_0;
argv[0] = _get_lvresize_fs_helper_path();
argv[++args] = "--fsreduce";
argv[++args] = "--fstype";
argv[++args] = fsi->fstype;
argv[++args] = "--lvpath";
argv[++args] = lv_path;
if (newsize_bytes_fs) {
argv[++args] = "--newsizebytes";
argv[++args] = newsize_str;
}
if (fsi->mounted) {
argv[++args] = "--mountdir";
argv[++args] = fsi->mount_dir;
}
if (fsi->needs_unmount)
argv[++args] = "--unmount";
if (fsi->needs_mount)
argv[++args] = "--mount";
if (fsi->needs_fsck)
argv[++args] = "--fsck";
if (fsi->needs_crypt) {
if (dm_snprintf(crypt_path, sizeof(crypt_path), "/dev/dm-%u", MINOR(fsi->crypt_devt)) < 0)
return_0;
argv[++args] = "--cryptresize";
argv[++args] = "--cryptpath";
argv[++args] = crypt_path;
}
/*
* fsmode manage means the fs should be remounted after
* resizing if it was unmounted.
*/
if (fsi->needs_unmount && !strcmp(fsmode, "manage"))
argv[++args] = "--remount";
argv[++args] = NULL;
devpath = fsi->needs_crypt ? crypt_path : (char *)display_lvname(lv);
log_print_unless_silent("Reducing file system %s to %s (%llu bytes) on %s...",
fsi->fstype, display_size(cmd, newsize_bytes_fs/512),
(unsigned long long)newsize_bytes_fs, devpath);
if (!exec_cmd(cmd, argv, &status, 1)) {
log_error("Failed to reduce file system with lvresize_fs_helper.");
return 0;
}
log_print_unless_silent("Reduced file system %s on %s.", fsi->fstype, devpath);
return 1;
}
/*
* The helper script does the following steps for extend:
* devpath = $cryptpath ? $cryptpath : $lvpath
* if needs_unmount
* umount $mountdir
* if needs_fsck
* e2fsck -f -p $devpath
* if needs_crypt
* cryptsetup resize $cryptpath
* if needs_mount
* mount $devpath $tmpdir
* if $fstype == "ext"
* resize2fs $devpath
* if $fstype == "xfs"
* xfs_growfs $devpath
*
* Note: when a crypt layer is included, newsize_bytes_fs is smaller
* than newsize_bytes_lv because of the crypt header.
*/
int fs_extend_script(struct cmd_context *cmd, struct logical_volume *lv, struct fs_info *fsi,
uint64_t newsize_bytes_fs, char *fsmode)
{
char lv_path[PATH_MAX];
char crypt_path[PATH_MAX];
const char *argv[FS_CMD_MAX_ARGS + 4];
char *devpath;
int args = 0;
int status;
if (dm_snprintf(lv_path, PATH_MAX, "%s%s/%s", lv->vg->cmd->dev_dir, lv->vg->name, lv->name) < 0)
return_0;
argv[0] = _get_lvresize_fs_helper_path();
argv[++args] = "--fsextend";
argv[++args] = "--fstype";
argv[++args] = fsi->fstype;
argv[++args] = "--lvpath";
argv[++args] = lv_path;
if (fsi->mounted) {
argv[++args] = "--mountdir";
argv[++args] = fsi->mount_dir;
}
if (fsi->needs_unmount)
argv[++args] = "--unmount";
if (fsi->needs_mount)
argv[++args] = "--mount";
if (fsi->needs_fsck)
argv[++args] = "--fsck";
if (fsi->needs_crypt) {
if (dm_snprintf(crypt_path, sizeof(crypt_path), "/dev/dm-%u", MINOR(fsi->crypt_devt)) < 0)
return_0;
argv[++args] = "--cryptresize";
argv[++args] = "--cryptpath";
argv[++args] = crypt_path;
}
/*
* fsmode manage means the fs should be remounted after
* resizing if it was unmounted.
*/
if (fsi->needs_unmount && !strcmp(fsmode, "manage"))
argv[++args] = "--remount";
argv[++args] = NULL;
devpath = fsi->needs_crypt ? crypt_path : (char *)display_lvname(lv);
log_print_unless_silent("Extending file system %s to %s (%llu bytes) on %s...",
fsi->fstype, display_size(cmd, newsize_bytes_fs/512),
(unsigned long long)newsize_bytes_fs, devpath);
if (!exec_cmd(cmd, argv, &status, 1)) {
log_error("Failed to extend file system with lvresize_fs_helper.");
return 0;
}
log_print_unless_silent("Extended file system %s on %s.", fsi->fstype, devpath);
return 1;
}