/* * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. * Copyright (C) 2004-2007 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 "base/data-struct/radix-tree.h" #include "lib/misc/lib.h" #include "lib/device/dev-type.h" #include "lib/device/device_id.h" #include "lib/label/label.h" #include "lib/config/config.h" #include "lib/commands/toolcontext.h" #include "device_mapper/misc/dm-ioctl.h" #include "lib/activate/activate.h" #include "lib/misc/lvm-string.h" #include "lib/mm/xlate.h" #ifdef UDEV_SYNC_SUPPORT #include #endif #include #include #include #include /* coverity[unnecessary_header] needed for MuslC */ #include struct dev_iter { union radix_value *values; struct dev_filter *filter; unsigned nr_values; unsigned pos; }; struct dir_list { struct dm_list list; char dir[0]; }; static struct { struct dm_pool *mem; struct radix_tree *names; struct dm_hash_table *vgid_index; struct dm_hash_table *lvid_index; struct dm_list *dm_devs; /* dm_active_device structs with dm UUIDs from DM_DEVICE_LIST (when available) */ struct radix_tree *dm_uuids; /* references dm_devs entries */ struct radix_tree *dm_devnos; /* references dm_devs entries */ struct radix_tree *sysfs_only_devices; /* see comments in _get_device_for_sysfs_dev_name_using_devno */ struct radix_tree *devices; struct dm_regex *preferred_names_matcher; const char *dev_dir; int use_dm_devs_cache; size_t dev_dir_len; int has_scanned; dev_t st_dev; struct dm_list dirs; struct dm_list files; } _cache; #define _zalloc(x) dm_pool_zalloc(_cache.mem, (x)) #define _free(x) dm_pool_free(_cache.mem, (x)) #define _strdup(x) dm_pool_strdup(_cache.mem, (x)) static int _insert(const char *path, const struct stat *info, int rec, int check_with_udev_db); /* Setup non-zero members of passed zeroed 'struct device' */ static void _dev_init(struct device *dev) { dev->fd = -1; dev->bcache_fd = -1; dev->bcache_di = -1; dev->read_ahead = -1; dev->part = -1; dev->ext.enabled = 0; dev->ext.src = DEV_EXT_NONE; dm_list_init(&dev->aliases); dm_list_init(&dev->ids); dm_list_init(&dev->wwids); } void dev_destroy_file(struct device *dev) { if (!(dev->flags & DEV_ALLOCED)) return; free((void *) dm_list_item(dev->aliases.n, struct dm_str_list)->str); free(dev->aliases.n); free(dev); } struct device *dev_create_file(const char *filename, struct device *dev, struct dm_str_list *alias, int use_malloc) { int allocate = !dev; if (allocate) { if (use_malloc) { if (!(dev = zalloc(sizeof(*dev)))) { log_error("struct device allocation failed"); return NULL; } if (!(alias = zalloc(sizeof(*alias)))) { log_error("struct dm_str_list allocation failed"); free(dev); return NULL; } if (!(alias->str = strdup(filename))) { log_error("filename strdup failed"); free(dev); free(alias); return NULL; } } else { if (!(dev = _zalloc(sizeof(*dev)))) { log_error("struct device allocation failed"); return NULL; } if (!(alias = _zalloc(sizeof(*alias)))) { log_error("struct dm_str_list allocation failed"); _free(dev); return NULL; } if (!(alias->str = _strdup(filename))) { log_error("filename strdup failed"); _free(dev); return NULL; } } } else if (!(alias->str = strdup(filename))) { log_error("filename strdup failed"); return NULL; } _dev_init(dev); dev->flags = DEV_REGULAR | ((use_malloc) ? DEV_ALLOCED : 0); dm_list_add(&dev->aliases, &alias->list); return dev; } static struct device *_dev_create(dev_t d) { struct device *dev; if (!(dev = _zalloc(sizeof(*dev)))) { log_error("struct device allocation failed"); return NULL; } _dev_init(dev); dev->dev = d; return dev; } void dev_set_preferred_name(struct dm_str_list *sl, struct device *dev) { /* * Don't interfere with ordering specified in config file. */ if (_cache.preferred_names_matcher) return; log_debug_devs("%s: New preferred name", sl->str); dm_list_del(&sl->list); dm_list_add_h(&dev->aliases, &sl->list); } /* * Check whether path0 or path1 contains the subpath. The path that * *does not* contain the subpath wins (return 0 or 1). If both paths * contain the subpath, return -1. If none of them contains the subpath, * return -2. */ static int _builtin_preference(const char *path0, const char *path1, size_t skip_prefix_count, const char *subpath) { size_t subpath_len; int r0, r1; subpath_len = strlen(subpath); r0 = !strncmp(path0 + skip_prefix_count, subpath, subpath_len); r1 = !strncmp(path1 + skip_prefix_count, subpath, subpath_len); if (!r0 && r1) /* path0 does not have the subpath - it wins */ return 0; else if (r0 && !r1) /* path1 does not have the subpath - it wins */ return 1; else if (r0 && r1) /* both of them have the subpath */ return -1; /* no path has the subpath */ return -2; } static int _apply_builtin_path_preference_rules(const char *path0, const char *path1) { size_t devdir_len = _cache.dev_dir_len; int r; if (!strncmp(path0, _cache.dev_dir, devdir_len) && !strncmp(path1, _cache.dev_dir, devdir_len)) { /* * We're trying to achieve the ordering: * /dev/block/ < /dev/dm-* < /dev/disk/ < /dev/mapper/ < anything else */ /* Prefer any other path over /dev/block/ path. */ if ((r = _builtin_preference(path0, path1, devdir_len, "block/")) >= -1) return r; /* Prefer any other path over /dev/dm-* path. */ if ((r = _builtin_preference(path0, path1, devdir_len, "dm-")) >= -1) return r; /* Prefer any other path over /dev/disk/ path. */ if ((r = _builtin_preference(path0, path1, devdir_len, "disk/")) >= -1) return r; /* Prefer any other path over /dev/mapper/ path. */ if ((r = _builtin_preference(path0, path1, 0, dm_dir())) >= -1) return r; } return -1; } /* Return 1 if we prefer path1 else return 0 */ static int _compare_paths(const char *path0, const char *path1) { int slash0 = 0, slash1 = 0; int m0, m1; const char *p; char p0[PATH_MAX], p1[PATH_MAX]; char *s0, *s1; struct stat stat0, stat1; int r; /* * FIXME Better to compare patterns one-at-a-time against all names. */ if (_cache.preferred_names_matcher) { m0 = dm_regex_match(_cache.preferred_names_matcher, path0); m1 = dm_regex_match(_cache.preferred_names_matcher, path1); if (m0 != m1) { if (m0 < 0) return 1; if (m1 < 0) return 0; if (m0 < m1) return 1; if (m1 < m0) return 0; } } /* Apply built-in preference rules first. */ if ((r = _apply_builtin_path_preference_rules(path0, path1)) >= 0) return r; /* Return the path with fewer slashes */ for (p = path0; p++; p = (const char *) strchr(p, '/')) slash0++; for (p = path1; p++; p = (const char *) strchr(p, '/')) slash1++; if (slash0 < slash1) return 0; if (slash1 < slash0) return 1; dm_strncpy(p0, path0, sizeof(p0)); dm_strncpy(p1, path1, sizeof(p1)); s0 = p0 + 1; s1 = p1 + 1; /* * If we reach here, both paths are the same length. * Now skip past identical path components. */ while (*s0 && *s0 == *s1) s0++, s1++; /* We prefer symlinks - they exist for a reason! * So we prefer a shorter path before the first symlink in the name. * FIXME Configuration option to invert this? */ while (s0 && s1) { if ((s0 = strchr(s0, '/'))) *s0 = '\0'; if ((s1 = strchr(s1, '/'))) *s1 = '\0'; if (lstat(p0, &stat0)) { log_sys_very_verbose("lstat", p0); return 1; } if (lstat(p1, &stat1)) { log_sys_very_verbose("lstat", p1); return 0; } if (S_ISLNK(stat0.st_mode) && !S_ISLNK(stat1.st_mode)) return 0; if (!S_ISLNK(stat0.st_mode) && S_ISLNK(stat1.st_mode)) return 1; if (s0) *s0++ = '/'; if (s1) *s1++ = '/'; } /* ASCII comparison */ if (strcmp(path0, path1) < 0) return 0; return 1; } enum add_hash { NO_HASH, HASH, REHASH }; static int _add_alias(struct device *dev, const char *path, enum add_hash hash) { struct dm_str_list *sl; struct dm_str_list *strl; const char *oldpath; int prefer_old = 1; size_t path_len = strlen(path); if (hash == REHASH) radix_tree_remove(_cache.names, path, path_len); /* Is name already there? */ dm_list_iterate_items(strl, &dev->aliases) if (!strcmp(strl->str, path)) { path = strl->str; goto out; } if (!(path = _strdup(path)) || !(sl = _zalloc(sizeof(*sl)))) { log_error("Failed to add allias to dev cache."); return 0; } if (!strncmp(path, "/dev/nvme", 9)) dev->flags |= DEV_IS_NVME; sl->str = path; if (!dm_list_empty(&dev->aliases)) { oldpath = dm_list_item(dev->aliases.n, struct dm_str_list)->str; prefer_old = _compare_paths(path, oldpath); } if (prefer_old) dm_list_add(&dev->aliases, &sl->list); else dm_list_add_h(&dev->aliases, &sl->list); out: if ((hash != NO_HASH) && !radix_tree_insert_ptr(_cache.names, path, path_len, dev)) { log_error("Couldn't add name to hash in dev cache."); return 0; } return 1; } int get_sysfs_binary(const char *path, char *buf, size_t buf_size, int *retlen) { int ret; int fd; fd = open(path, O_RDONLY); if (fd < 0) return 0; ret = read(fd, buf, buf_size); if (close(fd)) log_sys_debug("close", path); if (ret <= 0) return 0; *retlen = ret; return 1; } /* coverity[+tainted_string_sanitize_content:arg-0] */ static int _sanitize_buffer(const char *buf) { size_t i; if (!strcmp(buf, "..")) return 0; for (i = 0; buf[i]; ++i) if ((buf[i] < ' ') || (buf[i] == '/') || (buf[i] == ':') || (buf[i] == '\\')) return 0; return 1; } int get_sysfs_value(const char *path, char *buf, size_t buf_size, int error_if_no_value) { FILE *fp; size_t len; int r = 0; if (!(fp = fopen(path, "r"))) { if (error_if_no_value) log_sys_debug("fopen", path); return 0; } if (!fgets(buf, buf_size, fp)) { if (error_if_no_value) log_sys_debug("fgets", path); goto out; } if ((len = strlen(buf)) && buf[len - 1] == '\n') buf[--len] = '\0'; if (!len) { if (error_if_no_value) log_error("_get_sysfs_value: %s: no value", path); } else r = 1; out: if (fclose(fp)) log_sys_debug("fclose", path); return r; } /* Change bit ordering for devno to generate more compact bTree */ static inline uint32_t _shuffle_devno(dev_t d) { return cpu_to_be32(d); //return (d & 0xff) << 24 | (d & 0xff00) << 8 | (d & 0xff0000) >> 8 | (d & 0xff000000) >> 24; //return (d & 0xff000000) >> 24 | (d & 0xffff00) | ((d & 0xff) << 24); //return (uint32_t) d; } static struct dm_list *_get_or_add_list_by_index_key(struct dm_hash_table *idx, const char *key) { struct dm_list *list; if ((list = dm_hash_lookup(idx, key))) return list; if (!(list = _zalloc(sizeof(*list)))) { log_error("%s: failed to allocate device list for device cache index.", key); return NULL; } dm_list_init(list); if (!dm_hash_insert(idx, key, list)) { log_error("%s: failed to insert device list to device cache index.", key); return NULL; } return list; } static bool _dev_cache_insert_devno(struct radix_tree *rt, dev_t devno, void *dev) { union radix_value v = { .ptr = dev }; uint32_t key = _shuffle_devno(devno); return radix_tree_insert(rt, &key, sizeof(key), v); } static struct device *_dev_cache_get_dev_by_devno(struct radix_tree *rt, dev_t devno) { uint32_t key = _shuffle_devno(devno); return radix_tree_lookup_ptr(rt, &key, sizeof(key)); } static struct device *_insert_sysfs_dev(dev_t devno, const char *devname) { static struct device _fake_dev = { .flags = DEV_USED_FOR_LV }; struct stat stat0; char path[PATH_MAX]; struct device *dev; if (dm_snprintf(path, sizeof(path), "%s%s", _cache.dev_dir, devname) < 0) { log_error("_insert_sysfs_dev: %s: dm_snprintf failed", devname); return NULL; } if (lstat(path, &stat0) < 0) { /* When device node does not exist return fake entry. * This may happen when i.e. lvm2 device dir != /dev */ log_debug("%s: Not available device node", path); return &_fake_dev; } if (!(dev = _dev_create(devno))) return_NULL; if (!_add_alias(dev, path, NO_HASH)) { _free(dev); return_NULL; } if (!_dev_cache_insert_devno(_cache.sysfs_only_devices, devno, dev)) { log_error("Couldn't add device to binary tree of sysfs-only devices in dev cache."); _free(dev); return NULL; } return dev; } static struct device *_get_device_for_sysfs_dev_name_using_devno(const char *devname) { char path[PATH_MAX]; char buf[PATH_MAX]; int major, minor; dev_t devno; struct device *dev; if (dm_snprintf(path, sizeof(path), "%sblock/%s/dev", dm_sysfs_dir(), devname) < 0) { log_error("_get_device_for_sysfs_dev_name_using_devno: %s: dm_snprintf failed", devname); return NULL; } if (!get_sysfs_value(path, buf, sizeof(buf), 1)) return_NULL; if (sscanf(buf, "%d:%d", &major, &minor) != 2) { log_error("_get_device_for_sysfs_dev_name_using_devno: %s: failed to get major and minor number", devname); return NULL; } devno = MKDEV(major, minor); if (!(dev = _dev_cache_get_dev_by_devno(_cache.devices, devno))) { /* * If we get here, it means the device is referenced in sysfs, but it's not yet in /dev. * This may happen in some rare cases right after LVs get created - we sync with udev * (or alternatively we create /dev content ourselves) while VG lock is held. However, * dev scan is done without VG lock so devices may already be in sysfs, but /dev may * not be updated yet if we call LVM command right after LV creation. This is not a * problem with devtmpfs as there's at least kernel name for device in /dev as soon * as the sysfs item exists, but we still support environments without devtmpfs or * where different directory for dev nodes is used (e.g. our test suite). So track * such devices in _cache.sysfs_only_devices hash for the vgid/lvid check to work still. */ if (!(dev = _dev_cache_get_dev_by_devno(_cache.sysfs_only_devices, devno)) && !(dev = _insert_sysfs_dev(devno, devname))) return_NULL; } return dev; } #define NOT_LVM_UUID "-" static int _get_vgid_and_lvid_for_dev(struct cmd_context *cmd, struct device *dev) { const size_t lvm_prefix_len = sizeof(UUID_PREFIX) - 1; const size_t lvm_uuid_len = sizeof(UUID_PREFIX) - 1 + 2 * ID_LEN; char uuid[DM_UUID_LEN]; size_t uuid_len; if (!dev_dm_uuid(cmd, dev, uuid, sizeof(uuid))) return_0; uuid_len = strlen(uuid); /* * UUID for LV is either "LVM-" or "LVM--", * where vg_uuid and lv_uuid has length of ID_LEN and suffix len is not restricted * (only restricted by whole DM UUID max len). */ if (((uuid_len == lvm_uuid_len) || ((uuid_len > lvm_uuid_len) && (uuid[lvm_uuid_len] == '-'))) && !strncmp(uuid, UUID_PREFIX, lvm_prefix_len)) { /* Separate VGID and LVID part from DM UUID. */ if (!(dev->vgid = dm_pool_strndup(_cache.mem, uuid + lvm_prefix_len, ID_LEN)) || !(dev->lvid = dm_pool_strndup(_cache.mem, uuid + lvm_prefix_len + ID_LEN, ID_LEN))) return_0; } else dev->vgid = dev->lvid = NOT_LVM_UUID; return 1; } static int _index_dev_by_vgid_and_lvid(struct cmd_context *cmd, struct device *dev) { const char *devname = dev_name(dev); char devpath[PATH_MAX]; char path[PATH_MAX]; DIR *d; struct dirent *dirent; struct device *holder_dev; struct dm_list *vgid_list, *lvid_list; struct device_list *dl_vgid, *dl_lvid; int r = 0; if (dev->flags & DEV_USED_FOR_LV) /* already indexed */ return 1; /* Get holders for device. */ if (dm_snprintf(path, sizeof(path), "%sdev/block/%u:%u/holders/", dm_sysfs_dir(), MAJOR(dev->dev), MINOR(dev->dev)) < 0) { log_error("%s: dm_snprintf failed for path to holders directory.", devname); return 0; } if (!(d = opendir(path))) { if (errno == ENOENT) { log_debug("%s: path does not exist, skipping", path); return 1; } log_sys_error("opendir", path); return 0; } /* Iterate over device's holders and look for LVs. */ while ((dirent = readdir(d))) { if (!strcmp(".", dirent->d_name) || !strcmp("..", dirent->d_name)) continue; if (dm_snprintf(devpath, sizeof(devpath), "%s%s", _cache.dev_dir, dirent->d_name) == -1) { log_error("%s: dm_snprintf failed for holder %s device path.", devname, dirent->d_name); goto out; } if (!(holder_dev = dev_cache_get_dev_by_name(devpath))) { /* * Cope with situation where canonical //d_name> * does not exist, but some other node name or symlink exists in * non-standard environments - someone renaming the nodes or using * mknod with different dev names than actual kernel names. * This looks up struct device by major:minor pair which we get * by looking at /sys/block/d_name>/dev sysfs attribute. */ if (!(holder_dev = _get_device_for_sysfs_dev_name_using_devno(dirent->d_name))) { log_error("%s: failed to find associated device structure for holder %s.", devname, devpath); goto out; } } /* We're only interested in a holder which is a DM device. */ if (!dm_is_dm_major(MAJOR(holder_dev->dev))) continue; /* * And if it's a DM device, we're only interested in a holder which is an LVM device. * Get the VG UUID and LV UUID if we don't have that already. */ if (!holder_dev->vgid && !_get_vgid_and_lvid_for_dev(cmd, holder_dev)) goto_out; if (*holder_dev->vgid == *NOT_LVM_UUID) continue; /* * Do not add internal LV devices to index. * If a device is internal, the holder has the same VG UUID as the device. */ if (dm_is_dm_major(MAJOR(dev->dev))) { if (!dev->vgid && !_get_vgid_and_lvid_for_dev(cmd, dev)) goto_out; if (*dev->vgid != *NOT_LVM_UUID && !strcmp(holder_dev->vgid, dev->vgid)) continue; } if (!(vgid_list = _get_or_add_list_by_index_key(_cache.vgid_index, holder_dev->vgid)) || !(lvid_list = _get_or_add_list_by_index_key(_cache.lvid_index, holder_dev->lvid))) goto_out; /* Create dev list items for the holder device. */ if (!(dl_vgid = _zalloc(sizeof(*dl_vgid))) || !(dl_lvid = _zalloc(sizeof(*dl_lvid)))) { log_error("%s: failed to allocate dev list item.", devname); goto out; } dl_vgid->dev = dl_lvid->dev = dev; /* Add dev list item to VGID device list if it's not there already. */ if (!(dev->flags & DEV_USED_FOR_LV)) dm_list_add(vgid_list, &dl_vgid->list); /* Add dev list item to LVID device list. */ dm_list_add(lvid_list, &dl_lvid->list); /* Mark device as used == also indexed in dev cache by VGID and LVID. */ dev->flags |= DEV_USED_FOR_LV; } r = 1; out: if (closedir(d)) log_sys_debug("closedir", path); return r; } struct dm_list *dev_cache_get_dev_list_for_vgid(const char *vgid) { return dm_hash_lookup(_cache.vgid_index, vgid); } struct dm_list *dev_cache_get_dev_list_for_lvid(const char *lvid) { return dm_hash_lookup(_cache.lvid_index, lvid); } /* * Scanning code calls this when it fails to open a device using * this path. The path is dropped from dev-cache. In the next * dev_cache_scan it may be added again, but it could be for a * different device. */ void dev_cache_failed_path(struct device *dev, const char *path) { struct dm_str_list *strl; radix_tree_remove(_cache.names, path, strlen(path)); dm_list_iterate_items(strl, &dev->aliases) { if (!strcmp(strl->str, path)) { dm_list_del(&strl->list); break; } } } /* * Either creates a new dev, or adds an alias to * an existing dev. */ static int _insert_dev(const char *path, dev_t d) { struct device *dev; struct device *dev_by_devt; struct device *dev_by_path; dev_by_devt = _dev_cache_get_dev_by_devno(_cache.devices, d); dev_by_path = dev_cache_get_dev_by_name(path); dev = dev_by_devt; /* * Existing device, existing path points to the same device. */ if (dev_by_devt && dev_by_path && (dev_by_devt == dev_by_path)) { log_debug_devs("Found dev %u:%u %s - exists. %.8s", MAJOR(d), MINOR(d), path, dev->pvid); return 1; } /* * No device or path found, add devt to cache.devices, add name to cache.names. */ if (!dev_by_devt && !dev_by_path) { log_debug_devs("Found dev %u:%u %s - new.", MAJOR(d), MINOR(d), path); if (!(dev = _dev_cache_get_dev_by_devno(_cache.sysfs_only_devices, d))) /* create new device */ if (!(dev = _dev_create(d))) return_0; if (!(_dev_cache_insert_devno(_cache.devices, d, dev))) { log_error("Couldn't insert device into binary tree."); _free(dev); return 0; } if (!_add_alias(dev, path, HASH)) return_0; return 1; } /* * Existing device, path is new, add path as a new alias for the device. */ if (dev_by_devt && !dev_by_path) { log_debug_devs("Found dev %u:%u %s - new alias.", MAJOR(d), MINOR(d), path); if (!_add_alias(dev, path, HASH)) return_0; return 1; } /* * No existing device, but path exists and previously pointed * to a different device. */ if (!dev_by_devt && dev_by_path) { log_debug_devs("Found dev %u:%u %s - new device, path was previously %u:%u.", MAJOR(d), MINOR(d), path, MAJOR(dev_by_path->dev), MINOR(dev_by_path->dev)); if (!(dev = _dev_cache_get_dev_by_devno(_cache.sysfs_only_devices, d))) { /* create new device */ if (!(dev = _dev_create(d))) return_0; } if (!(_dev_cache_insert_devno(_cache.devices, d, dev))) { log_error("Couldn't insert device into binary tree."); _free(dev); return 0; } if (!_add_alias(dev, path, REHASH)) return_0; return 1; } /* * Existing device, and path exists and previously pointed to * a different device. */ if (dev_by_devt && dev_by_path) { log_debug_devs("Found dev %u:%u %s - existing device, path was previously %u:%u.", MAJOR(d), MINOR(d), path, MAJOR(dev_by_path->dev), MINOR(dev_by_path->dev)); if (!_add_alias(dev, path, REHASH)) return_0; return 1; } log_error("Found dev %u:%u %s - failed to use.", MAJOR(d), MINOR(d), path); return 0; } /* * Get rid of extra slashes in the path string. */ static size_t _collapse_slashes(char *str) { char *start = str; char *ptr; int was_slash = 0; for (ptr = str; *ptr; ptr++) { if (*ptr == '/') { if (was_slash) continue; was_slash = 1; } else was_slash = 0; *str++ = *ptr; } *str = 0; return (str - start); } static int _insert_dir(const char *dir) { /* alphanetically! sorted list used by bsearch of * /dev subdirectories that should not contain * any block device, so no reason to scan them. */ static const char _no_scan[][12] = { "bsg/", "bus/", "char/", "cpu/", "dma_heap/", "dri/", "input/", "snd/", "usb/", }; int n, dirent_count, r = 1; struct dirent **dirent = NULL; char path[PATH_MAX]; size_t len; if (!_dm_strncpy(path, dir, sizeof(path))) { log_debug_devs("Dir path %s is too long", path); return 0; } len = _collapse_slashes(path); if (len && path[len - 1] != '/') path[len++] = '/'; if ((len < (5 + sizeof(_no_scan[0]))) && (strncmp("/dev/", path, 5) == 0) && (len > 5)) { path[len] = 0; if (bsearch(path + 5, _no_scan, DM_ARRAY_SIZE(_no_scan), sizeof(_no_scan[0]), (int (*)(const void*, const void*))strcmp)) { /* Skip insertion of directories that can't have block devices */ log_debug("Skipping \"%s\" (no block devices).", path); return 1; } } dirent_count = scandir(dir, &dirent, NULL, alphasort); if (dirent_count > 0) { for (n = 0; n < dirent_count; n++) { if (dirent[n]->d_name[0] == '.') continue; if (!_dm_strncpy(path + len, dirent[n]->d_name, sizeof(path) - len)) { log_debug_devs("Path %s/%s is too long.", dir, dirent[n]->d_name); r = 0; continue; } r &= _insert(path, NULL, 1, 0); } for (n = 0; n < dirent_count; n++) free(dirent[n]); free(dirent); } return r; } static int _dev_cache_iterate_devs_for_index(struct cmd_context *cmd) { struct dev_iter *iter = dev_iter_create(NULL, 0); struct device *dev = NULL; int r = 1; while ((dev = dev_iter_get(NULL, iter))) if (!_index_dev_by_vgid_and_lvid(cmd, dev)) r = 0; return r; } static int _dev_cache_iterate_sysfs_for_index(struct cmd_context *cmd, const char *path) { char devname[PATH_MAX]; DIR *d; struct dirent *dirent; int major, minor; dev_t devno; struct device *dev; int partial_failure = 0; int r = 0; if (!(d = opendir(path))) { log_sys_error("opendir", path); return 0; } while ((dirent = readdir(d))) { if (!strcmp(".", dirent->d_name) || !strcmp("..", dirent->d_name)) continue; if (sscanf(dirent->d_name, "%d:%d", &major, &minor) != 2) { log_error("_dev_cache_iterate_sysfs_for_index: %s: failed " "to get major and minor number", dirent->d_name); partial_failure = 1; continue; } devno = MKDEV(major, minor); if (!(dev = _dev_cache_get_dev_by_devno(_cache.devices, devno)) && !(dev = _dev_cache_get_dev_by_devno(_cache.sysfs_only_devices, devno))) { if (!dm_device_get_name(major, minor, 1, devname, sizeof(devname)) || !(dev = _insert_sysfs_dev(devno, devname))) { partial_failure = 1; continue; } } if (!_index_dev_by_vgid_and_lvid(cmd, dev)) partial_failure = 1; } r = !partial_failure; if (closedir(d)) log_sys_debug("closedir", path); return r; } static int _dev_cache_index_devs(struct cmd_context *cmd) { static int _sysfs_has_dev_block = -1; char path[PATH_MAX]; if (dm_snprintf(path, sizeof(path), "%sdev/block", dm_sysfs_dir()) < 0) { log_error("dev_cache_index_devs: dm_snprintf failed."); return 0; } /* Skip indexing if /sys/dev/block is not available.*/ if (_sysfs_has_dev_block == -1) { struct stat info; if (stat(path, &info) == 0) _sysfs_has_dev_block = 1; else { if (errno == ENOENT) { _sysfs_has_dev_block = 0; return 1; } log_sys_debug("stat", path); return 0; } } else if (!_sysfs_has_dev_block) return 1; if (obtain_device_list_from_udev() && udev_get_library_context()) return _dev_cache_iterate_devs_for_index(cmd); /* with udev */ return _dev_cache_iterate_sysfs_for_index(cmd, path); } #ifdef UDEV_SYNC_SUPPORT static int _device_in_udev_db(const dev_t d) { struct udev *udev; struct udev_device *udev_device; if (!(udev = udev_get_library_context())) return_0; if ((udev_device = udev_device_new_from_devnum(udev, 'b', d))) { udev_device_unref(udev_device); return 1; } return 0; } static int _insert_udev_dir(struct udev *udev, const char *dir) { struct udev_enumerate *udev_enum = NULL; struct udev_list_entry *device_entry, *symlink_entry; const char *entry_name, *node_name, *symlink_name; struct udev_device *device; int r = 1; if (!(udev_enum = udev_enumerate_new(udev))) { log_error("Failed to udev_enumerate_new."); return 0; } if (udev_enumerate_add_match_subsystem(udev_enum, "block")) { log_error("Failed to udev_enumerate_add_match_subsystem."); goto out; } if (udev_enumerate_scan_devices(udev_enum)) { log_error("Failed to udev_enumerate_scan_devices."); goto out; } /* * Report any missing information as "log_very_verbose" only, do not * report it as a "warning" or "error" - the record could be removed * by the time we ask for more info (node name, symlink name...). * Whatever removes *any* block device in the system (even unrelated * to our operation), we would have a warning/error on output then. * That could be misleading. If there's really any problem with missing * information from udev db, we can still have a look at the verbose log. */ udev_list_entry_foreach(device_entry, udev_enumerate_get_list_entry(udev_enum)) { entry_name = udev_list_entry_get_name(device_entry); if (!(device = udev_device_new_from_syspath(udev, entry_name))) { log_very_verbose("udev failed to return a device for entry %s.", entry_name); continue; } if (!(node_name = udev_device_get_devnode(device))) log_very_verbose("udev failed to return a device node for entry %s.", entry_name); else r &= _insert(node_name, NULL, 0, 0); udev_list_entry_foreach(symlink_entry, udev_device_get_devlinks_list_entry(device)) { if (!(symlink_name = udev_list_entry_get_name(symlink_entry))) log_very_verbose("udev failed to return a symlink name for entry %s.", entry_name); else r &= _insert(symlink_name, NULL, 0, 0); } udev_device_unref(device); } out: udev_enumerate_unref(udev_enum); return r; } static void _insert_dirs(struct dm_list *dirs) { struct dir_list *dl; struct udev *udev = NULL; int with_udev; struct stat tinfo; with_udev = obtain_device_list_from_udev() && (udev = udev_get_library_context()); dm_list_iterate_items(dl, &_cache.dirs) { if (stat(dl->dir, &tinfo) < 0) { log_warn("WARNING: Cannot use dir %s, %s.", dl->dir, strerror(errno)); continue; } _cache.st_dev = tinfo.st_dev; if (with_udev) { if (!_insert_udev_dir(udev, dl->dir)) log_debug_devs("%s: Failed to insert devices from " "udev-managed directory to device " "cache fully", dl->dir); } else if (!_insert_dir(dl->dir)) log_debug_devs("%s: Failed to insert devices to " "device cache fully", dl->dir); } } #else /* UDEV_SYNC_SUPPORT */ static int _device_in_udev_db(const dev_t d) { return 0; } static void _insert_dirs(struct dm_list *dirs) { struct dir_list *dl; struct stat tinfo; dm_list_iterate_items(dl, &_cache.dirs) { if (stat(dl->dir, &tinfo) < 0) { log_warn("WARNING: Cannot use dir %s, %s.", dl->dir, strerror(errno)); continue; } _cache.st_dev = tinfo.st_dev; _insert_dir(dl->dir); } } #endif /* UDEV_SYNC_SUPPORT */ static int _insert(const char *path, const struct stat *info, int rec, int check_with_udev_db) { struct stat tinfo; if (!info) { if (stat(path, &tinfo) < 0) { log_sys_very_verbose("stat", path); return 0; } info = &tinfo; } if (check_with_udev_db && !_device_in_udev_db(info->st_rdev)) { log_very_verbose("%s: Not in udev db", path); return 0; } if (S_ISDIR(info->st_mode)) { /* add a directory */ /* check it's not a symbolic link */ if (lstat(path, &tinfo) < 0) { log_sys_very_verbose("lstat", path); return 0; } if (S_ISLNK(tinfo.st_mode)) { log_debug_devs("%s: Symbolic link to directory", path); return 1; } if (info->st_dev != _cache.st_dev) { log_debug_devs("%s: Different filesystem in directory", path); return 1; } if (rec && !_insert_dir(path)) return 0; } else { /* add a device */ if (!S_ISBLK(info->st_mode)) return 1; if (!_insert_dev(path, info->st_rdev)) return 0; } return 1; } static void _drop_all_aliases(struct device *dev) { struct dm_str_list *strl, *strl2; dm_list_iterate_items_safe(strl, strl2, &dev->aliases) { log_debug("Drop alias for %u:%u %s.", MAJOR(dev->dev), MINOR(dev->dev), strl->str); radix_tree_remove(_cache.names, strl->str, strlen(strl->str)); dm_list_del(&strl->list); } } void dev_cache_scan(struct cmd_context *cmd) { log_debug_devs("Creating list of system devices."); _cache.has_scanned = 1; setlocale(LC_COLLATE, "C"); /* Avoid sorting by locales */ _insert_dirs(&_cache.dirs); setlocale(LC_COLLATE, ""); if (cmd->check_devs_used) (void) _dev_cache_index_devs(cmd); } int dev_cache_has_scanned(void) { return _cache.has_scanned; } static int _init_preferred_names(struct cmd_context *cmd) { const struct dm_config_node *cn; const struct dm_config_value *v; struct dm_pool *scratch = NULL; const char **regex; unsigned count = 0; int i, r = 0; _cache.preferred_names_matcher = NULL; if (!(cn = find_config_tree_array(cmd, devices_preferred_names_CFG, NULL)) || cn->v->type == DM_CFG_EMPTY_ARRAY) { log_very_verbose("devices/preferred_names %s: " "using built-in preferences", cn && cn->v->type == DM_CFG_EMPTY_ARRAY ? "is empty" : "not found in config"); return 1; } for (v = cn->v; v; v = v->next) { if (v->type != DM_CFG_STRING) { log_error("preferred_names patterns must be enclosed in quotes"); return 0; } count++; } if (!(scratch = dm_pool_create("preferred device name matcher", 1024))) return_0; if (!(regex = dm_pool_alloc(scratch, sizeof(*regex) * count))) { log_error("Failed to allocate preferred device name " "pattern list."); goto out; } for (v = cn->v, i = count - 1; v; v = v->next, i--) { if (!(regex[i] = dm_pool_strdup(scratch, v->v.str))) { log_error("Failed to allocate a preferred device name " "pattern."); goto out; } } if (!(_cache.preferred_names_matcher = dm_regex_create(_cache.mem, regex, count))) { log_error("Preferred device name pattern matcher creation failed."); goto out; } r = 1; out: dm_pool_destroy(scratch); return r; } int dev_cache_use_dm_devs_cache(void) { return _cache.use_dm_devs_cache; } void dev_cache_destroy_dm_devs(void) { _cache.use_dm_devs_cache = 0; if (_cache.dm_devnos) { radix_tree_destroy(_cache.dm_devnos); _cache.dm_devnos = NULL; } if (_cache.dm_uuids) { radix_tree_destroy(_cache.dm_uuids); _cache.dm_uuids = NULL; } dm_device_list_destroy(&_cache.dm_devs); } int dev_cache_update_dm_devs(void) { struct dm_active_device *dm_dev; unsigned devs_features; uint32_t d; dev_cache_destroy_dm_devs(); if (!get_dm_active_devices(NULL, &_cache.dm_devs, &devs_features)) return 1; if (!(devs_features & DM_DEVICE_LIST_HAS_UUID)) { /* Cache unusable with older kernels without UUIDs in LIST */ dm_device_list_destroy(&_cache.dm_devs); return 1; } /* _cache.dm_devs entries are referenced by radix trees */ /* TODO: if _cache.dm_devs list is small, then skip the overhead of radix trees and just do list searches on dm_devs */ if (!(_cache.dm_devnos = radix_tree_create(NULL, NULL)) || !(_cache.dm_uuids = radix_tree_create(NULL, NULL))) { return_0; // FIXME } /* Insert every active DM device into radix trees */ dm_list_iterate_items(dm_dev, _cache.dm_devs) { d = _shuffle_devno(dm_dev->devno); if (!radix_tree_insert_ptr(_cache.dm_devnos, &d, sizeof(d), dm_dev)) return_0; if (dm_dev->uuid[0] && !radix_tree_insert_ptr(_cache.dm_uuids, dm_dev->uuid, strlen(dm_dev->uuid), dm_dev)) return_0; } //radix_tree_dump(_cache.dm_devnos, stdout); //radix_tree_dump(_cache.dm_uuids, stdout); _cache.use_dm_devs_cache = 1; return 1; } void dev_cache_dm_devs_label_invalidate(struct cmd_context *cmd) { struct dm_active_device *dm_dev; struct device *dev; dm_list_iterate_items(dm_dev, _cache.dm_devs) { if (dm_dev->uuid && strncmp(dm_dev->uuid, UUID_PREFIX, sizeof(UUID_PREFIX) - 1) == 0) { if ((dev = dev_cache_get_by_devt(cmd, dm_dev->devno))) label_scan_invalidate(dev); } } } /* Find active DM device in devs array for given major:minor */ const struct dm_active_device * dev_cache_get_dm_dev_by_devno(struct cmd_context *cmd, dev_t devno) { uint32_t d = _shuffle_devno(devno); if (!_cache.dm_devnos) return NULL; return radix_tree_lookup_ptr(_cache.dm_devnos, &d, sizeof(d)); } /* Find active DM device in devs array for given DM UUID */ const struct dm_active_device * dev_cache_get_dm_dev_by_uuid(struct cmd_context *cmd, const char *dm_uuid) { if (!_cache.dm_uuids) return NULL; return radix_tree_lookup_ptr(_cache.dm_uuids, dm_uuid, strlen(dm_uuid)); } int dev_cache_init(struct cmd_context *cmd) { _cache.names = NULL; if (!(_cache.mem = dm_pool_create("dev_cache", 10 * 1024))) return_0; if (!(_cache.names = radix_tree_create(NULL, NULL)) || !(_cache.vgid_index = dm_hash_create(30)) || !(_cache.lvid_index = dm_hash_create(29))) { dm_pool_destroy(_cache.mem); _cache.mem = 0; return_0; } if (!(_cache.devices = radix_tree_create(NULL, NULL))) { log_error("Couldn't create binary tree for dev-cache."); goto bad; } if (!(_cache.sysfs_only_devices = radix_tree_create(NULL, NULL))) { log_error("Couldn't create binary tree for sysfs-only devices in dev cache."); goto bad; } if (!(_cache.dev_dir = _strdup(cmd->dev_dir))) { log_error("strdup dev_dir failed."); goto bad; } _cache.dev_dir_len = strlen(_cache.dev_dir); dm_list_init(&_cache.dirs); if (!_init_preferred_names(cmd)) goto_bad; return 1; bad: dev_cache_exit(); return 0; } struct dev_visitor { struct radix_tree_iterator it; int close_immediate; int free; unsigned num_open; }; /* * Returns number of devices still open. */ static bool _visit_check_for_open_devices(struct radix_tree_iterator *it, const void *key, size_t keylen, union radix_value v) { struct dev_visitor *vt = container_of(it, struct dev_visitor, it); struct device *dev = v.ptr; if (dev->fd >= 0) { log_error("Device '%s' has been left open (%d remaining references).", dev_name(dev), dev->open_count); vt->num_open++; if (vt->close_immediate && !dev_close_immediate(dev)) stack; } if (vt->free) { free_dids(&dev->ids); free_wwids(&dev->wwids); } return true; } /* * Returns number of devices left open. */ int dev_cache_check_for_open_devices(void) { struct dev_visitor vt = { .it.visit = _visit_check_for_open_devices, }; radix_tree_iterate(_cache.names, NULL, 0, &vt.it); return vt.num_open; } int dev_cache_exit(void) { struct dev_visitor vt = { .it.visit = _visit_check_for_open_devices, .close_immediate = 1, /* close open devices */ .free = 1, /* free dids, wwids */ }; if (_cache.names) { /* check for open devices */ radix_tree_iterate(_cache.names, NULL, 0, &vt.it); if (vt.num_open) log_error(INTERNAL_ERROR "%d device(s) were left open and have been closed.", vt.num_open); } dev_cache_destroy_dm_devs(); if (_cache.mem) dm_pool_destroy(_cache.mem); if (_cache.names) radix_tree_destroy(_cache.names); if (_cache.vgid_index) dm_hash_destroy(_cache.vgid_index); if (_cache.lvid_index) dm_hash_destroy(_cache.lvid_index); if (_cache.devices) radix_tree_destroy(_cache.devices); if (_cache.sysfs_only_devices) radix_tree_destroy(_cache.sysfs_only_devices); memset(&_cache, 0, sizeof(_cache)); return (!vt.num_open); } int dev_cache_add_dir(const char *path) { struct dir_list *dl; struct stat st; size_t len; if (stat(path, &st)) { log_warn("Ignoring %s: %s.", path, strerror(errno)); /* But don't fail */ return 1; } if (!S_ISDIR(st.st_mode)) { log_warn("Ignoring %s: Not a directory.", path); return 1; } len = strlen(path); if (!(dl = _zalloc(sizeof(*dl) + len + 1))) { log_error("dir_list allocation failed"); return 0; } memcpy(dl->dir, path, len + 1); dm_list_add(&_cache.dirs, &dl->list); return 1; } struct device *dev_cache_get_dev_by_name(const char *name) { return radix_tree_lookup_ptr(_cache.names, name, strlen(name)); } static void _remove_alias(struct device *dev, const char *name) { struct dm_str_list *strl; dm_list_iterate_items(strl, &dev->aliases) { if (!strcmp(strl->str, name)) { dm_list_del(&strl->list); return; } } } /* * Check that paths for this dev still refer to the same dev_t. This is known * to drop invalid paths in the case where lvm deactivates an LV, which causes * that LV path to go away, but that LV path is not removed from dev-cache (it * probably should be). Later a new path to a different LV is added to * dev-cache, where the new LV has the same major:minor as the previously * deactivated LV. The new LV will find the existing struct dev, and that * struct dev will have dev->aliases entries that refer to the name of the old * deactivated LV. Those old paths are all invalid and are dropped here. */ void dev_cache_verify_aliases(struct device *dev) { struct dm_str_list *strl, *strl2; struct stat st; dm_list_iterate_items_safe(strl, strl2, &dev->aliases) { if (stat(strl->str, &st) || (st.st_rdev != dev->dev)) { log_debug("Drop alias for %u:%u invalid path %s %u:%u.", MAJOR(dev->dev), MINOR(dev->dev), strl->str, MAJOR(st.st_rdev), MINOR(st.st_rdev)); radix_tree_remove(_cache.names, strl->str, strlen(strl->str)); dm_list_del(&strl->list); } } } static struct device *_dev_cache_get(struct cmd_context *cmd, const char *name, struct dev_filter *f, int existing) { struct device *dev = dev_cache_get_dev_by_name(name); struct stat st; int ret; /* * DEV_REGULAR means that is "dev" is actually a file, not a device. * FIXME: I don't think dev-cache is used for files any more and this * can be dropped? */ if (dev && (dev->flags & DEV_REGULAR)) return dev; if (dev && dm_list_empty(&dev->aliases)) { /* shouldn't happen */ log_warn("Ignoring dev with no valid paths for %s.", name); return NULL; } /* * The requested path is invalid, remove any dev-cache info for it. */ if (stat(name, &st)) { if (dev) { log_debug("Device path %s is invalid for %u:%u %s.", name, MAJOR(dev->dev), MINOR(dev->dev), dev_name(dev)); radix_tree_remove(_cache.names, name, strlen(name)); _remove_alias(dev, name); /* Remove any other names in dev->aliases that are incorrect. */ dev_cache_verify_aliases(dev); } return NULL; } if (dev && dm_list_empty(&dev->aliases)) { /* shouldn't happen */ log_warn("Ignoring dev with no valid paths for %s.", name); return NULL; } if (!S_ISBLK(st.st_mode)) { log_debug("Not a block device %s.", name); return NULL; } /* * dev-cache has incorrect info for the requested path. * Remove incorrect info and then add new dev-cache entry. */ if (dev && (st.st_rdev != dev->dev)) { struct device *dev_by_devt = _dev_cache_get_dev_by_devno(_cache.devices, st.st_rdev); /* * lvm commands create this condition when they * activate/deactivate LVs combined with creating new LVs. * The command does not purge dev structs when deactivating * an LV (which it probably should do), but the better * approach would be not using dev-cache at all for LVs. */ log_debug("Dropping aliases for device entry %u:%u %s for new device %u:%u %s.", MAJOR(dev->dev), MINOR(dev->dev), dev_name(dev), MAJOR(st.st_rdev), MINOR(st.st_rdev), name); _drop_all_aliases(dev); if (dev_by_devt) { log_debug("Dropping aliases for device entry %u:%u %s for new device %u:%u %s.", MAJOR(dev_by_devt->dev), MINOR(dev_by_devt->dev), dev_name(dev_by_devt), MAJOR(st.st_rdev), MINOR(st.st_rdev), name); _drop_all_aliases(dev_by_devt); } #if 0 /* * I think only lvm's own dm devs should be added here, so use * a warning to look for any other unknown cases. */ if (MAJOR(st.st_rdev) != cmd->dev_types->device_mapper_major) { log_warn("WARNING: new device appeared %u:%u %s", MAJOR(st.st_rdev), (MINOR(st.st_rdev)), name); } #endif if (!_insert_dev(name, st.st_rdev)) return_NULL; /* Get the struct dev that was just added. */ dev = dev_cache_get_dev_by_name(name); if (!dev) { log_error("Failed to get device %s", name); return NULL; } goto out; } if (dev && dm_list_empty(&dev->aliases)) { /* shouldn't happen */ log_warn("Ignoring dev with no valid paths for %s.", name); return NULL; } if (!dev && existing) return_NULL; /* * This case should never be hit for a PV. It should only * happen when the command is opening a new LV it has created. * Add an arg to all callers indicating when the arg should be * new (for an LV) and not existing. * FIXME: fix this further by not using dev-cache struct devs * at all for new dm devs (LVs) that lvm uses. Make the * dev-cache contain only devs for PVs. * Places to fix that use a dev for LVs include: * . lv_resize opening lv to discard * . wipe_lv opening lv to zero it * . _extend_sanlock_lv opening lv to extend it * . _write_log_header opening lv to write header * Also, io to LVs should not go through bcache. * bcache should contain only labels and metadata * scanned from PVs. */ if (!dev) { /* * This case should only be used for new devices created by this * command (opening LVs it's created), so if a dev exists for the * dev_t referenced by the name, then drop all aliases for before * _insert_dev adds the new name. lvm commands actually hit this * fairly often when it uses some LV, deactivates the LV, then * creates some new LV which ends up with the same major:minor. * Without dropping the aliases, it's plausible that lvm commands * could end up using the wrong dm device. */ struct device *dev_by_devt = _dev_cache_get_dev_by_devno(_cache.devices, st.st_rdev); if (dev_by_devt) { log_debug("Dropping aliases for %u:%u before adding new path %s.", MAJOR(st.st_rdev), MINOR(st.st_rdev), name); _drop_all_aliases(dev_by_devt); } #if 0 /* * I think only lvm's own dm devs should be added here, so use * a warning to look for any other unknown cases. */ if (MAJOR(st.st_rdev) != cmd->dev_types->device_mapper_major) { log_warn("WARNING: new device appeared %u:%u %s.", MAJOR(st.st_rdev), MINOR(st.st_rdev), name); } #endif if (!_insert_dev(name, st.st_rdev)) return_NULL; /* Get the struct dev that was just added. */ dev = dev_cache_get_dev_by_name(name); if (!dev) { log_error("Failed to get device %s", name); return NULL; } } out: /* * The caller passed a filter if they only want the dev if it * passes filters. */ if (!f) return dev; ret = f->passes_filter(cmd, f, dev, NULL); if (!ret) { log_debug_devs("dev_cache_get filter excludes %s", dev_name(dev)); return NULL; } return dev; } struct device *dev_cache_get_existing(struct cmd_context *cmd, const char *name, struct dev_filter *f) { return _dev_cache_get(cmd, name, f, 1); } struct device *dev_cache_get(struct cmd_context *cmd, const char *name, struct dev_filter *f) { return _dev_cache_get(cmd, name, f, 0); } struct device *dev_cache_get_by_devt(struct cmd_context *cmd, dev_t devt) { struct device *dev = _dev_cache_get_dev_by_devno(_cache.devices, devt); if (dev) return dev; log_debug_devs("No devno %u:%u in dev cache.", MAJOR(devt), MINOR(devt)); return NULL; } struct device *dev_cache_get_by_pvid(struct cmd_context *cmd, const char *pvid) { struct dev_iter *iter = dev_iter_create(NULL, 0); struct device *dev; while ((dev = dev_iter_get(NULL, iter))) if (!memcmp(dev->pvid, pvid, ID_LEN)) break; dev_iter_destroy(iter); return dev; } struct dev_iter *dev_iter_create(struct dev_filter *f, int unused) { struct dev_iter *di = malloc(sizeof(*di)); if (!di) { log_error("dev_iter allocation failed."); return NULL; } if (!radix_tree_values(_cache.devices, NULL, 0, &di->values, &di->nr_values)) { log_error("dev_iter values allocation failed."); free(di); return NULL; } di->pos = 0; di->filter = f; if (di->filter) di->filter->use_count++; return di; } void dev_iter_destroy(struct dev_iter *iter) { if (iter->filter) iter->filter->use_count--; free(iter->values); free(iter); } struct device *dev_iter_get(struct cmd_context *cmd, struct dev_iter *iter) { struct dev_filter *f; struct device *d; int ret; while (iter->pos < iter->nr_values) { d = iter->values[iter->pos++].ptr; ret = 1; f = iter->filter; if (f && cmd && !(d->flags & DEV_REGULAR)) ret = f->passes_filter(cmd, f, d, NULL); if (!f || (d->flags & DEV_REGULAR) || ret) return d; } return NULL; } int dev_fd(struct device *dev) { return dev->fd; } const char *dev_name(const struct device *dev) { if (dev && dev->aliases.n && !dm_list_empty(&dev->aliases)) return dm_list_item(dev->aliases.n, struct dm_str_list)->str; else return unknown_device_name(); } bool dev_cache_has_md_with_end_superblock(struct dev_types *dt) { struct device *dev; struct dev_iter *iter = dev_iter_create(NULL, 0); bool ret = false; while ((dev = dev_iter_get(NULL, iter))) if ((ret = dev_is_md_with_end_superblock(dt, dev))) break; dev_iter_destroy(iter); return ret; } static int _setup_devices_list(struct cmd_context *cmd) { struct dm_str_list *strl; struct dev_use *du; /* * For each --devices arg, add a du to cmd->use_devices. * The du has devname is the devices arg value. */ dm_list_iterate_items(strl, &cmd->deviceslist) { if (!(du = dm_pool_zalloc(cmd->mem, sizeof(struct dev_use)))) return_0; if (!(du->devname = dm_pool_strdup(cmd->mem, strl->str))) return_0; dm_list_add(&cmd->use_devices, &du->list); } return 1; } /* * LVs that use dmeventd need to be accessible using system.devices. * * dmeventd will use dmeventd.devices if it exists, otherwise will use * system.devices. If a VG is covered by dmeventd.devices it should also * be covered by system.devices. The automated vgchange --monitor and * vgchange -aay commands do not currently consider dmeventd.devices. * dmeventd.devices can be useful to prevent commands run by dmeventd * from reading VGs that do not need the service. * * dmeventd gets events for any applicable dm device in the kernel, * and will attempt to run lvm commands for it. These commands * will only use dmeventd.devices or system.devices, so if an LV * that dmeventd is watching is not covered by either of these * devices files, the dmeventd-generated command will fail when * it doesn't find the VG. */ static int _setup_devices_file_dmeventd(struct cmd_context *cmd) { char path[PATH_MAX]; struct stat st; if (!find_config_tree_bool(cmd, devices_use_devicesfile_CFG, NULL)) { cmd->enable_devices_file = 0; return 1; } if (dm_snprintf(path, sizeof(path), "%s/devices/dmeventd.devices", cmd->system_dir) < 0) return_0; if (stat(path, &st)) return 0; cmd->enable_devices_file = 1; dm_strncpy(cmd->devices_file_path, path, sizeof(cmd->devices_file_path)); return 1; } /* * When lvm.conf use_devicesfile=0, then an existing system.devices * is renamed to system.devices-unused..