/* * Copyright (C) 2021 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/online.h" #include /* * file contains: * :\n * vg:\n * dev:\n\0 * * It's possible that vg and dev may not exist. */ static int _copy_pvid_file_field(const char *field, char *buf, int bufsize, char *out, int outsize) { char *p; int i = 0; if (!(p = strstr(buf, field))) return 0; p += strlen(field); while (1) { if (*p == '\n') break; if (*p == '\0') break; if (p >= (buf + bufsize)) return 0; if (i >= outsize-1) return 0; out[i] = *p; i++; p++; } return i ? 1 : 0; } #define MAX_PVID_FILE_SIZE 512 int online_pvid_file_read(char *path, int *major, int *minor, char *vgname, char *devname) { char buf[MAX_PVID_FILE_SIZE] = { 0 }; int fd, rv; fd = open(path, O_RDONLY); if (fd < 0) { log_warn("Failed to open %s", path); return 0; } rv = read(fd, buf, sizeof(buf) - 1); if (close(fd)) log_sys_debug("close", path); if (!rv || rv < 0) { log_warn("No info in %s", path); return 0; } buf[rv] = 0; /* \0 terminated buffer */ if (sscanf(buf, "%d:%d", major, minor) != 2) { log_warn("No device numbers in %s", path); return 0; } if (vgname) { if (!strstr(buf, "vg:")) { log_debug("No vgname in %s", path); vgname[0] = '\0'; goto copy_dev; } if (!_copy_pvid_file_field("vg:", buf, MAX_PVID_FILE_SIZE, vgname, NAME_LEN)) { log_warn("Ignoring invalid vg field in %s", path); vgname[0] = '\0'; goto copy_dev; } if (!validate_name(vgname)) { log_warn("Ignoring invalid vgname in %s (%s)", path, vgname); vgname[0] = '\0'; goto copy_dev; } } copy_dev: if (devname) { if (!strstr(buf, "dev:")) { log_debug("No devname in %s", path); devname[0] = '\0'; goto out; } if (!_copy_pvid_file_field("dev:", buf, MAX_PVID_FILE_SIZE, devname, NAME_LEN)) { log_warn("Ignoring invalid devname field in %s", path); devname[0] = '\0'; goto out; } if (strncmp(devname, "/dev/", 5)) { log_warn("Ignoring invalid devname in %s (%s)", path, devname); devname[0] = '\0'; goto out; } } out: return 1; } void free_po_list(struct dm_list *list) { struct pv_online *po, *po2; dm_list_iterate_items_safe(po, po2, list) { dm_list_del(&po->list); free(po); } } int get_pvs_online(struct dm_list *pvs_online, const char *vgname) { char path[PATH_MAX]; char file_vgname[NAME_LEN]; char file_devname[NAME_LEN]; DIR *dir; struct dirent *de; struct pv_online *po; int file_major = 0, file_minor = 0; if (!(dir = opendir(PVS_ONLINE_DIR))) return 0; while ((de = readdir(dir))) { if (de->d_name[0] == '.') continue; if (strlen(de->d_name) != ID_LEN) continue; memset(path, 0, sizeof(path)); snprintf(path, sizeof(path), "%s/%s", PVS_ONLINE_DIR, de->d_name); file_major = 0; file_minor = 0; memset(file_vgname, 0, sizeof(file_vgname)); memset(file_devname, 0, sizeof(file_devname)); if (!online_pvid_file_read(path, &file_major, &file_minor, file_vgname, file_devname)) continue; if (vgname && strcmp(file_vgname, vgname)) continue; if (!(po = zalloc(sizeof(*po)))) continue; memcpy(po->pvid, de->d_name, ID_LEN); if (file_major || file_minor) po->devno = MKDEV(file_major, file_minor); if (file_vgname[0]) strncpy(po->vgname, file_vgname, NAME_LEN); if (file_devname[0]) strncpy(po->devname, file_devname, NAME_LEN); log_debug("Found PV online %s for VG %s %s", path, vgname, file_devname); dm_list_add(pvs_online, &po->list); } if (closedir(dir)) log_sys_debug("closedir", PVS_ONLINE_DIR); log_debug("Found PVs online %d for %s", dm_list_size(pvs_online), vgname ?: "all"); return 1; } /* * When a PV goes offline, remove the vg online file for that VG * (even if other PVs for the VG are still online). This means * that the vg will be activated again when it becomes complete. */ void online_vg_file_remove(const char *vgname) { char path[PATH_MAX]; if (dm_snprintf(path, sizeof(path), "%s/%s", VGS_ONLINE_DIR, vgname) < 0) { log_error("Path %s/%s is too long.", VGS_ONLINE_DIR, vgname); return; } log_debug("Unlink vg online: %s", path); if (unlink(path) && (errno != ENOENT)) log_sys_debug("unlink", path); } int online_vg_file_create(struct cmd_context *cmd, const char *vgname) { char path[PATH_MAX]; int fd; if (dm_snprintf(path, sizeof(path), "%s/%s", VGS_ONLINE_DIR, vgname) < 0) { log_error_pvscan(cmd, "Path %s/%s is too long.", VGS_ONLINE_DIR, vgname); return 0; } log_debug("Create vg online: %s", path); fd = open(path, O_CREAT | O_EXCL | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR); if (fd < 0) { log_debug("Failed to create %s: %d", path, errno); return 0; } /* We don't care about syncing, these files are not even persistent. */ if (close(fd)) log_sys_debug("close", path); return 1; } int online_pvid_file_create(struct cmd_context *cmd, struct device *dev, const char *vgname) { char path[PATH_MAX]; char buf[MAX_PVID_FILE_SIZE] = { 0 }; char file_vgname[NAME_LEN]; char file_devname[NAME_LEN]; char devname[NAME_LEN]; int devnamelen; int file_major = 0, file_minor = 0; int major, minor; int fd; int rv; int len; int len1 = 0; int len2 = 0; int len3 = 0; major = (int)MAJOR(dev->dev); minor = (int)MINOR(dev->dev); if (dm_snprintf(path, sizeof(path), "%s/%s", PVS_ONLINE_DIR, dev->pvid) < 0) { log_error_pvscan(cmd, "Path %s/%s is too long.", PVS_ONLINE_DIR, dev->pvid); return 0; } if ((len1 = dm_snprintf(buf, sizeof(buf), "%d:%d\n", major, minor)) < 0) { log_error_pvscan(cmd, "Cannot create online file path for %s %d:%d.", dev_name(dev), major, minor); return 0; } if (vgname) { if ((len2 = dm_snprintf(buf + len1, sizeof(buf) - len1, "vg:%s\n", vgname)) < 0) { log_print_unless_silent("Incomplete online file for %s %d:%d vg %s.", dev_name(dev), major, minor, vgname); /* can still continue without vgname */ len2 = 0; } } devnamelen = dm_snprintf(devname, sizeof(devname), "%s", dev_name(dev)); if ((devnamelen > 5) && (devnamelen < NAME_LEN-1)) { if ((len3 = dm_snprintf(buf + len1 + len2, sizeof(buf) - len1 - len2, "dev:%s\n", devname)) < 0) { log_print_unless_silent("Incomplete devname in online file for %s.", dev_name(dev)); /* can continue without devname */ len3 = 0; } } len = len1 + len2 + len3; log_debug("Create pv online: %s %d:%d %s", path, major, minor, dev_name(dev)); fd = open(path, O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR); if (fd < 0) { if (errno == EEXIST) goto check_duplicate; log_error_pvscan(cmd, "Failed to create online file for %s path %s error %d", dev_name(dev), path, errno); return 0; } while (len > 0) { rv = write(fd, buf, len); if (rv < 0) { /* file exists so it still works in part */ log_warn("Cannot write online file for %s to %s error %d", dev_name(dev), path, errno); if (close(fd)) log_sys_debug("close", path); return 1; } len -= rv; } /* We don't care about syncing, these files are not even persistent. */ if (close(fd)) log_sys_debug("close", path); return 1; check_duplicate: /* * If a PVID online file already exists for this PVID, check if the * file contains a different device number, and if so we may have a * duplicate PV. * * FIXME: disable autoactivation of the VG somehow? * The VG may or may not already be activated when a dupicate appears. * Perhaps write a new field in the pv online or vg online file? */ memset(file_vgname, 0, sizeof(file_vgname)); memset(file_devname, 0, sizeof(file_devname)); online_pvid_file_read(path, &file_major, &file_minor, file_vgname, file_devname); if ((file_major == major) && (file_minor == minor)) { log_debug("Existing online file for %d:%d", major, minor); return 1; } /* Don't know how vgname might not match, but it's not good so fail. */ if ((file_major != major) || (file_minor != minor)) log_error_pvscan(cmd, "PV %s %d:%d is duplicate for PVID %s on %d:%d %s.", dev_name(dev), major, minor, dev->pvid, file_major, file_minor, file_devname); if (file_vgname[0] && vgname && strcmp(file_vgname, vgname)) log_error_pvscan(cmd, "PV %s has unexpected VG %s vs %s.", dev_name(dev), vgname, file_vgname); return 0; } int online_pvid_file_exists(const char *pvid) { char path[PATH_MAX] = { 0 }; struct stat buf; int rv; if (dm_snprintf(path, sizeof(path), "%s/%s", PVS_ONLINE_DIR, pvid) < 0) { log_debug(INTERNAL_ERROR "Path %s/%s is too long.", PVS_ONLINE_DIR, pvid); return 0; } log_debug("Check pv online: %s", path); rv = stat(path, &buf); if (!rv) { log_debug("Check pv online %s: yes", pvid); return 1; } log_debug("Check pv online %s: no", pvid); return 0; } int get_pvs_lookup(struct dm_list *pvs_online, const char *vgname) { char lookup_path[PATH_MAX] = { 0 }; char path[PATH_MAX] = { 0 }; char line[64]; char pvid[ID_LEN + 1] __attribute__((aligned(8))) = { 0 }; char file_vgname[NAME_LEN]; char file_devname[NAME_LEN]; struct pv_online *po; int file_major = 0, file_minor = 0; FILE *fp; if (dm_snprintf(lookup_path, sizeof(path), "%s/%s", PVS_LOOKUP_DIR, vgname) < 0) return_0; if (!(fp = fopen(lookup_path, "r"))) return_0; while (fgets(line, sizeof(line), fp)) { memcpy(pvid, line, ID_LEN); if (strlen(pvid) != ID_LEN) goto_bad; memset(path, 0, sizeof(path)); snprintf(path, sizeof(path), "%s/%s", PVS_ONLINE_DIR, pvid); file_major = 0; file_minor = 0; memset(file_vgname, 0, sizeof(file_vgname)); memset(file_devname, 0, sizeof(file_devname)); if (!online_pvid_file_read(path, &file_major, &file_minor, file_vgname, file_devname)) goto_bad; /* * PVs without metadata will not have a vgname in their pvid * file, but the purpose of using the lookup file is that we * know the PV is for this VG even without the pvid vgname * field. */ if (vgname && file_vgname[0] && strcmp(file_vgname, vgname)) { /* Should never happen */ log_error("Incorrect VG lookup file %s PVID %s %s.", vgname, pvid, file_vgname); goto_bad; } if (!(po = zalloc(sizeof(*po)))) goto_bad; memcpy(po->pvid, pvid, ID_LEN); if (file_major || file_minor) po->devno = MKDEV(file_major, file_minor); if (file_vgname[0]) strncpy(po->vgname, file_vgname, NAME_LEN-1); if (file_devname[0]) strncpy(po->devname, file_devname, NAME_LEN-1); log_debug("Found PV online lookup %s for VG %s on %s", path, vgname, file_devname); dm_list_add(pvs_online, &po->list); } log_debug("Found PVs online lookup %d for %s", dm_list_size(pvs_online), vgname); fclose(fp); return 1; bad: free_po_list(pvs_online); fclose(fp); return 0; } void online_dir_setup(struct cmd_context *cmd) { struct stat st; int rv; if (!stat(DEFAULT_RUN_DIR, &st)) goto do_pvs; log_debug("Creating run_dir."); dm_prepare_selinux_context(DEFAULT_RUN_DIR, S_IFDIR); rv = mkdir(DEFAULT_RUN_DIR, 0755); dm_prepare_selinux_context(NULL, 0); if ((rv < 0) && stat(DEFAULT_RUN_DIR, &st)) log_error_pvscan(cmd, "Failed to create %s %d", DEFAULT_RUN_DIR, errno); do_pvs: if (!stat(PVS_ONLINE_DIR, &st)) goto do_vgs; log_debug("Creating pvs_online_dir."); dm_prepare_selinux_context(PVS_ONLINE_DIR, S_IFDIR); rv = mkdir(PVS_ONLINE_DIR, 0755); dm_prepare_selinux_context(NULL, 0); if ((rv < 0) && stat(PVS_ONLINE_DIR, &st)) log_error_pvscan(cmd, "Failed to create %s %d", PVS_ONLINE_DIR, errno); do_vgs: if (!stat(VGS_ONLINE_DIR, &st)) goto do_lookup; log_debug("Creating vgs_online_dir."); dm_prepare_selinux_context(VGS_ONLINE_DIR, S_IFDIR); rv = mkdir(VGS_ONLINE_DIR, 0755); dm_prepare_selinux_context(NULL, 0); if ((rv < 0) && stat(VGS_ONLINE_DIR, &st)) log_error_pvscan(cmd, "Failed to create %s %d", VGS_ONLINE_DIR, errno); do_lookup: if (!stat(PVS_LOOKUP_DIR, &st)) return; log_debug("Creating pvs_lookup_dir."); dm_prepare_selinux_context(PVS_LOOKUP_DIR, S_IFDIR); rv = mkdir(PVS_LOOKUP_DIR, 0755); dm_prepare_selinux_context(NULL, 0); if ((rv < 0) && stat(PVS_LOOKUP_DIR, &st)) log_error_pvscan(cmd, "Failed to create %s %d", PVS_LOOKUP_DIR, errno); } void online_lookup_file_remove(const char *vgname) { char path[PATH_MAX]; if (dm_snprintf(path, sizeof(path), "%s/%s", PVS_LOOKUP_DIR, vgname) < 0) { log_error("Path %s/%s is too long.", PVS_LOOKUP_DIR, vgname); return; } log_debug("Unlink pvs_lookup: %s", path); if (unlink(path) && (errno != ENOENT)) log_sys_debug("unlink", path); } static int _online_pvid_file_remove(char *pvid) { char path[PATH_MAX]; if (dm_snprintf(path, sizeof(path), "%s/%s", PVS_ONLINE_DIR, pvid) < 0) return_0; if (!unlink(path)) return 1; return 0; } /* * Reboot automatically clearing tmpfs on /run is the main method of removing * online files. It's important to note that removing the online files for a * VG is not a technical requirement for anything and could easily be skipped * if it had any downside. It's only done to clean up the space used in /run * by the online files, e.g. if there happens to be an extreme amount of * vgcreate/pvscan/vgremove between reboots that are leaving a large number of * useless online files consuming tmpfs space. */ void online_vgremove(struct volume_group *vg) { char pvid[ID_LEN + 1] __attribute__((aligned(8))) = { 0 }; struct pv_list *pvl; /* * online files may not exist for the vg if there has been no * pvscans or autoactivation. */ online_vg_file_remove(vg->name); online_lookup_file_remove(vg->name); dm_list_iterate_items(pvl, &vg->pvs) { memcpy(pvid, &pvl->pv->id.uuid, ID_LEN); _online_pvid_file_remove(pvid); } }