1
0
mirror of git://sourceware.org/git/lvm2.git synced 2024-12-21 13:34:40 +03:00
lvm2/lib/device/device_id.c
David Teigland 47f8bda051 lvremove: remove device_id for PVs on LVs
When PVs are created on LVs, remove the devices file entries
for the PVs when the LVs are removed.  In general, the devices
file entries should be removed with lvmdevices --deldev when
the LVs are removed (lvremove is the equivalent of detaching
a device from the system when layering PVs on LVs.)
This change is effectively an automatic lvmdevices --deldev
command that is built into lvremove when the LV has a PV on it.
2024-05-22 15:32:17 -05:00

4311 lines
123 KiB
C

/*
* Copyright (C) 2020 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/device_id.h"
#include "lib/device/dev-type.h"
#include "lib/label/label.h"
#include "lib/misc/crc.h"
#include "lib/metadata/metadata.h"
#include "lib/format_text/layout.h"
#include "lib/cache/lvmcache.h"
#include "lib/datastruct/str_list.h"
#include "lib/metadata/metadata-exported.h"
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <dirent.h>
#include <locale.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/sysmacros.h>
#define DEVICES_FILE_MAJOR 1
#define DEVICES_FILE_MINOR 1
#define VERSION_LINE_MAX 256
static int _devices_fd = -1;
static int _using_devices_file;
static int _devices_file_locked;
static char _devices_lockfile[PATH_MAX];
static char _devices_file_version[VERSION_LINE_MAX];
static const char _searched_file[] = DEFAULT_RUN_DIR "/searched_devnames";
static const char _searched_file_new[] = DEFAULT_RUN_DIR "/searched_devnames_new";
static const char _searched_file_dir[] = DEFAULT_RUN_DIR;
/* Only for displaying in lvmdevices command output. */
char devices_file_hostname_orig[PATH_MAX];
char devices_file_product_uuid_orig[PATH_MAX];
/*
* The input string pvid may be of any length, it's often
* read from system.devices, which can be edited.
* These pvid strings are often compared to pvids in the
* form char pvid[ID_LEN+1] using memcmp with ID_LEN.
*
* . ignore any pvid characters over ID_LEN
* . return a buffer is ID_LEN+1 in size, even
* if the pvid string is shorter.
*/
char *strdup_pvid(char *pvid)
{
char *buf;
if (!(buf = zalloc(ID_LEN + 1)))
return_NULL;
dm_strncpy(buf, pvid, ID_LEN + 1);
return buf;
}
char *devices_file_version(void)
{
return _devices_file_version;
}
static void _searched_devnames_create(struct cmd_context *cmd,
int search_pvids_count, uint32_t search_pvids_hash,
int search_devs_count, uint32_t search_devs_hash)
{
FILE *fp;
time_t t;
int dir_fd;
int fflush_errno = 0;
int fclose_errno = 0;
/*
* cmd->devicesfile is set when using a devices file other
* than the default system.devices, and at least for now,
* the searched_devnames temp file is only used for commands
* using system.devices.
*/
if (cmd->devicesfile)
return;
/* in case previous file was left */
if (unlink(_searched_file_new) < 0 && errno != ENOENT)
log_sys_debug("unlink", _searched_file_new);
/*
* No file lock is used to coordinate concurrent attempts to create
* the temp file, so we expect this fopen may file sometimes.
*/
if (!(fp = fopen(_searched_file_new, "wx"))) {
log_debug("searched_devnames_create error fopen %d", errno);
return;
}
if ((dir_fd = open(_searched_file_dir, O_RDONLY)) < 0) {
log_debug("searched_devnames_create error open dir %d", errno);
if (fclose(fp))
log_sys_debug("fclose", _searched_file);
if (unlink(_searched_file_new) < 0 && errno != ENOENT)
log_sys_debug("unlink", _searched_file_new);
return;
}
t = time(NULL);
/* comment to help with debugging */
fprintf(fp, "# Created by LVM command %s pid %d system.devices %s at %s",
cmd->name, getpid(), _devices_file_version[0] ? _devices_file_version : "none", ctime(&t));
fprintf(fp, "pvids: %d %u\n", search_pvids_count, search_pvids_hash);
fprintf(fp, "devs: %d %u\n", search_devs_count, search_devs_hash);
if (fflush(fp))
fflush_errno = errno;
if (fclose(fp))
fclose_errno = errno;
if (fflush_errno || fclose_errno) {
log_debug("searched_devnames_create error fflush %d fclose %d",
fflush_errno, fclose_errno);
if (unlink(_searched_file_new) < 0 && errno != ENOENT)
log_sys_debug("unlink", _searched_file_new);
goto out;
}
if (rename(_searched_file_new, _searched_file) < 0) {
log_debug("searched_devnames_create error rename %d", errno);
if (unlink(_searched_file_new) < 0 && errno != ENOENT)
log_sys_debug("unlink", _searched_file_new);
goto out;
}
log_debug("searched_devnames created pvids %d %u devs %d %u",
search_pvids_count, search_pvids_hash, search_devs_count, search_devs_hash);
out:
if (fsync(dir_fd) < 0)
stack;
if (close(dir_fd) < 0)
stack;
}
void unlink_searched_devnames(struct cmd_context *cmd)
{
if (cmd->devicesfile)
return;
if (unlink(_searched_file) < 0) {
if (errno != ENOENT)
log_sys_debug("unlink", _searched_file);
} else
log_debug("unlink %s", _searched_file);
}
/*
* Consistent hashes between commands depend on the devs and pvids being
* processed in the same order by each command. For devs this is true because
* we process the devs using dev_iter which is a btree ordered by devno keys.
* For pvids this is true because the cmd->use_devices list order comes from
* the order of lines in system.devices.
*/
static int _searched_devnames_exists(struct cmd_context *cmd,
int search_pvids_count, uint32_t search_pvids_hash,
int search_devs_count, uint32_t search_devs_hash)
{
FILE *fp;
char line[PATH_MAX];
uint32_t pvids_hash_file = 0;
uint32_t devs_hash_file = 0;
int pvids_ok = 0;
int devs_ok = 0;
int pvids_count_file = 0;
int devs_count_file = 0;
int ret = 0;
if (cmd->devicesfile)
return 0;
if (!(fp = fopen(_searched_file, "r")))
return 0;
while (fgets(line, sizeof(line), fp)) {
if (line[0] == '#')
continue;
if (!strncmp(line, "pvids: ", 7)) {
if (sscanf(line + 7, "%d %u", &pvids_count_file, &pvids_hash_file) != 2)
goto out;
if (pvids_count_file != search_pvids_count)
goto out;
if (pvids_hash_file != search_pvids_hash)
goto out;
pvids_ok = 1;
} else if (!strncmp(line, "devs: ", 6)) {
if (sscanf(line + 6, "%d %u", &devs_count_file, &devs_hash_file) != 2)
goto out;
if (devs_count_file != search_devs_count)
goto out;
if (devs_hash_file != search_devs_hash)
goto out;
devs_ok = 1;
} else {
goto out;
}
}
if (pvids_ok && devs_ok)
ret = 1;
out:
if (fflush(fp) < 0)
log_sys_debug("fflush", _searched_file);
if (fsync(fileno(fp)) < 0)
log_sys_debug("fsync", _searched_file);
if (fclose(fp))
log_sys_debug("fclose", _searched_file);
log_debug("searched_devnames %s file pvids %d %u devs %d %u search pvids %d %u devs %d %u",
ret ? "match" : "differ",
pvids_count_file, pvids_hash_file, devs_count_file, devs_hash_file,
search_pvids_count, search_pvids_hash, search_devs_count, search_devs_hash);
if (!ret && unlink(_searched_file) < 0 && errno != ENOENT)
log_sys_debug("unlink", _searched_file);
return ret;
}
/*
* Check if the device_id saved in the VG metadata matches the actual device_id
* on the device used for the PV.
*/
int pv_device_id_is_stale(const struct physical_volume *pv)
{
struct dev_use *du;
if (!pv->vg || !pv->vg->cmd)
return 0;
if (!pv->device_id || !pv->device_id_type)
return 0;
if (!(du = get_du_for_dev(pv->vg->cmd, pv->dev)))
return 0;
if (!du->idname)
return 0;
if (du->idtype != idtype_from_str(pv->device_id_type))
return 1;
if (strcmp(du->idname, pv->device_id))
return 1;
return 0;
}
/*
* How the devices file and device IDs are used by an ordinary command:
*
* 1. device_ids_read() reads the devices file, and adds a 'struct dev_use'
* to cmd->use_devices for each entry. These are the devices lvm
* can use, but we do not yet know which devnames they correspond to.
* 2. dev_cache_scan() gets a list of all devices (devnames) on the system,
* and adds a 'struct device' to dev-cache for each.
* 3. device_ids_match() matches du entries from the devices file
* with devices from dev-cache. With this complete, we know the
* devnames to use for each of the entries in the devices file.
* 4. label_scan (or equivalent) iterates through all devices in
* dev-cache, checks each one with filters, which excludes many,
* and reads lvm headers and metadata from the devs that pass the
* filters. lvmcache is populated with summary info about each PV
* during this phase.
* 5. device_ids_validate() checks if the PVIDs saved in the devices
* file are correct based on the PVIDs read from disk in the
* previous step. If not it updates the devices file.
*
* cmd->use_devices reflect the entries in the devices file.
* When reading the devices file, a 'du' struct is added to use_devices
* for each entry.
* When adding devices to the devices file, a new du struct is added
* to use_devices, and then a new file entry is written for each du.
*
* After reading the devices file, we want to match each du from
* the file to an actual device on the system. We look at struct device's
* in dev-cache to find one that matches each du, based on the device_id.
* When a match is made, du->dev is set, and DEV_MATCHED_USE_ID is set
* in the dev.
*
* After the use_devices entries are matched to system devices,
* label_scan can be called to filter and scan devices. After
* label_scan, device_ids_validate() is called to check if the
* PVID read from each device matches the PVID recorded in the
* devices file for the device.
*
* A device can have multiple device IDs, e.g. a dev could have
* both a wwid and a serial number, but only one of these IDs is
* used as the device ID in the devices file, e.g. the wwid is
* preferred so that would be used in the devices file.
* Each of the different types of device IDs can be saved in
* dev->ids list (struct dev_id). So, one dev may have multiple
* entries in dev->ids, e.g. one for wwid and one for serial.
* The dev_id struct that is actually being used for the device
* is set in dev->id.
* The reason for saving multiple IDs in dev->ids is because
* the process of matching devs to devices file entries can
* involve repeatedly checking other dev_id types for a given
* device, so we save each type as it is read to avoid rereading
* the same id type many times.
*/
void free_du(struct dev_use *du)
{
free(du->idname);
free(du->devname);
free(du->pvid);
free(du);
}
void free_dus(struct dm_list *dus)
{
struct dev_use *du, *safe;
dm_list_iterate_items_safe(du, safe, dus) {
dm_list_del(&du->list);
free_du(du);
}
}
void free_did(struct dev_id *id)
{
if (id->idname && strlen(id->idname))
free(id->idname);
free(id);
}
void free_dids(struct dm_list *ids)
{
struct dev_id *id, *safe;
dm_list_iterate_items_safe(id, safe, ids) {
dm_list_del(&id->list);
free_did(id);
}
}
/* More than one _ in a row is replaced with one _ */
static void _reduce_repeating_underscores(char *buf, size_t bufsize)
{
char *tmpbuf;
unsigned us = 0, i, j = 0;
if (!(tmpbuf = strndup(buf, bufsize-1)))
return;
memset(buf, 0, bufsize);
for (i = 0; tmpbuf[i]; ++i) {
if (tmpbuf[i] == '_')
us++;
else
us = 0;
if (us == 1)
buf[j++] = '_';
else if (us > 1)
continue;
else
buf[j++] = tmpbuf[i];
if (j == bufsize)
break;
}
buf[bufsize-1] = '\0';
free(tmpbuf);
}
static void _remove_leading_underscores(char *buf, size_t bufsize)
{
char *tmpbuf;
unsigned i, j = 0;
if (buf[0] != '_')
return;
if (!(tmpbuf = strndup(buf, bufsize-1)))
return;
memset(buf, 0, bufsize);
for (i = 0; i < strlen(tmpbuf); i++) {
if (!j && tmpbuf[i] == '_')
continue;
buf[j++] = tmpbuf[i];
if (j == bufsize)
break;
}
free(tmpbuf);
}
static void _remove_trailing_underscores(char *buf, int bufsize)
{
char *end;
end = buf + strlen(buf) - 1;
while ((end > buf) && (*end == '_'))
end--;
end[1] = '\0';
}
static int _read_sys_block(struct cmd_context *cmd, struct device *dev,
const char *suffix, char *sysbuf, int sysbufsize,
int binary, int *retlen)
{
char path[PATH_MAX];
const char *sysfs_dir;
dev_t devt = dev->dev;
dev_t prim = 0;
int ret;
sysfs_dir = cmd->device_id_sysfs_dir ?: dm_sysfs_dir();
retry:
if (dm_snprintf(path, sizeof(path), "%sdev/block/%d:%d/%s",
sysfs_dir, (int)MAJOR(devt), (int)MINOR(devt), suffix) < 0) {
log_error("Failed to create sysfs path for %s", dev_name(dev));
return 0;
}
if (binary) {
ret = get_sysfs_binary(path, sysbuf, sysbufsize, retlen);
if (ret && !*retlen)
ret = 0;
} else {
ret = get_sysfs_value(path, sysbuf, sysbufsize, 0);
if (ret && !sysbuf[0])
ret = 0;
}
if (ret) {
sysbuf[sysbufsize - 1] = '\0';
return 1;
}
if (prim)
goto fail;
/* in case it failed because dev is a partition... */
ret = dev_get_primary_dev(cmd->dev_types, dev, &prim);
if (ret == 2) {
devt = prim;
goto retry;
}
fail:
return 0;
}
int read_sys_block(struct cmd_context *cmd, struct device *dev,
const char *suffix, char *sysbuf, int sysbufsize)
{
return _read_sys_block(cmd, dev, suffix, sysbuf, sysbufsize, 0, NULL);
}
int read_sys_block_binary(struct cmd_context *cmd, struct device *dev,
const char *suffix, char *sysbuf, int sysbufsize,
int *retlen)
{
return _read_sys_block(cmd, dev, suffix, sysbuf, sysbufsize, 1, retlen);
}
static int _dm_uuid_has_prefix(char *sysbuf, const char *prefix)
{
if (!strncmp(sysbuf, prefix, strlen(prefix)))
return 1;
/*
* If it's a kpartx partitioned dm device the dm uuid will
* be part%d-<prefix>... e.g. part1-mpath-abc...
* Check for the prefix after the part%-
*/
if (!strncmp(sysbuf, "part", 4)) {
const char *dash = strchr(sysbuf, '-');
if (!dash)
return 0;
if (!strncmp(dash + 1, prefix, strlen(prefix)))
return 1;
}
return 0;
}
/* the dm uuid uses the wwid of the underlying dev */
int dev_has_mpath_uuid(struct cmd_context *cmd, struct device *dev, const char **idname_out)
{
char sysbuf[PATH_MAX] = { 0 };
const char *idname;
if (!read_sys_block(cmd, dev, "dm/uuid", sysbuf, sizeof(sysbuf)))
return 0;
if (!_dm_uuid_has_prefix(sysbuf, "mpath-"))
return 0;
if (!idname_out)
return 1;
if (!(idname = strdup(sysbuf)))
return_0;
*idname_out = idname;
return 1;
}
static int _dev_has_crypt_uuid(struct cmd_context *cmd, struct device *dev, const char **idname_out)
{
char sysbuf[PATH_MAX] = { 0 };
const char *idname;
if (!read_sys_block(cmd, dev, "dm/uuid", sysbuf, sizeof(sysbuf)))
return 0;
if (!_dm_uuid_has_prefix(sysbuf, "CRYPT-"))
return 0;
if (!idname_out)
return 1;
if (!(idname = strdup(sysbuf)))
return_0;
*idname_out = idname;
return 1;
}
static int _dev_has_lvmlv_uuid(struct cmd_context *cmd, struct device *dev, const char **idname_out)
{
char sysbuf[PATH_MAX] = { 0 };
const char *idname;
if (!read_sys_block(cmd, dev, "dm/uuid", sysbuf, sizeof(sysbuf)))
return 0;
if (!_dm_uuid_has_prefix(sysbuf, "LVM-"))
return 0;
if (!idname_out)
return 1;
if (!(idname = strdup(sysbuf)))
return_0;
*idname_out = idname;
return 1;
}
/*
* The numbers 1,2,3 for NAA,EUI,T10 are part of the standard
* and are used in the vpd data.
*/
static int _wwid_type_num(char *id)
{
if (!strncmp(id, "naa.", 4))
return 3;
else if (!strncmp(id, "eui.", 4))
return 2;
else if (!strncmp(id, "t10.", 4))
return 1;
else
return 0; /* any unrecognized, non-standard prefix */
}
int wwid_type_to_idtype(int wwid_type)
{
switch (wwid_type) {
case 3: return DEV_ID_TYPE_WWID_NAA;
case 2: return DEV_ID_TYPE_WWID_EUI;
case 1: return DEV_ID_TYPE_WWID_T10;
case 0: return DEV_ID_TYPE_SYS_WWID;
default: return -1;
}
}
int idtype_to_wwid_type(int idtype)
{
switch (idtype) {
case DEV_ID_TYPE_WWID_NAA: return 3;
case DEV_ID_TYPE_WWID_EUI: return 2;
case DEV_ID_TYPE_WWID_T10: return 1;
case DEV_ID_TYPE_SYS_WWID: return 0;
default: return -1;
}
}
void free_wwids(struct dm_list *ids)
{
struct dev_wwid *dw, *safe;
dm_list_iterate_items_safe(dw, safe, ids) {
dm_list_del(&dw->list);
free(dw);
}
}
/*
* wwid type 8 "scsi name string" (which includes "iqn" names) is
* included in vpd_pg83, but we currently do not use these for
* device ids (maybe in the future.)
* They can still be checked by dev-mpath when looking for a device
* in /etc/multipath/wwids.
*/
struct dev_wwid *dev_add_wwid(char *id, int id_type, struct dm_list *ids)
{
struct dev_wwid *dw;
if (!id_type)
id_type = _wwid_type_num(id);
if (!(dw = zalloc(sizeof(*dw))))
return_NULL;
/* Copy id string with upto DEV_WWID_SIZE characters */
dm_strncpy(dw->id, id, sizeof(dw->id));
dw->type = id_type;
dm_list_add(ids, &dw->list);
return dw;
}
#define VPD_SIZE 4096
int dev_read_vpd_wwids(struct cmd_context *cmd, struct device *dev)
{
char vpd_data[VPD_SIZE] = { 0 };
int vpd_datalen = 0;
dev->flags |= DEV_ADDED_VPD_WWIDS;
if (!read_sys_block_binary(cmd, dev, "device/vpd_pg83", (char *)vpd_data, VPD_SIZE, &vpd_datalen))
return 0;
if (!vpd_datalen)
return 0;
/* adds dev_wwid entry to dev->wwids for each id in vpd data */
parse_vpd_ids((const unsigned char *)vpd_data, vpd_datalen, &dev->wwids);
return 1;
}
int dev_read_sys_wwid(struct cmd_context *cmd, struct device *dev,
char *outbuf, int outbufsize, struct dev_wwid **dw_out)
{
char buf[DEV_WWID_SIZE] = { 0 };
struct dev_wwid *dw;
int is_t10 = 0;
int ret;
unsigned i;
dev->flags |= DEV_ADDED_SYS_WWID;
ret = read_sys_block(cmd, dev, "device/wwid", buf, sizeof(buf));
if (!ret || !buf[0]) {
/* the wwid file is not under device for nvme devs */
ret = read_sys_block(cmd, dev, "wwid", buf, sizeof(buf));
}
if (!ret || !buf[0])
return 0;
for (i = 0; i < sizeof(buf) - 4; i++) {
if (buf[i] == ' ')
continue;
if (!strncmp(&buf[i], "t10", 3))
is_t10 = 1;
break;
}
/*
* Remove leading and trailing spaces.
* Replace internal spaces with underscores.
* t10 wwids have multiple sequential spaces
* replaced by a single underscore.
*/
if (is_t10)
format_t10_id((const unsigned char *)buf, sizeof(buf), (unsigned char *)outbuf, outbufsize);
else
format_general_id((const char *)buf, sizeof(buf), (unsigned char *)outbuf, outbufsize);
/* Note, if wwids are also read from vpd, this same wwid will be added again. */
if (!(dw = dev_add_wwid(buf, 0, &dev->wwids)))
return_0;
if (dw_out)
*dw_out = dw;
return 1;
}
static int _dev_read_sys_serial(struct cmd_context *cmd, struct device *dev,
char *outbuf, int outbufsize)
{
char buf[VPD_SIZE] = { 0 };
const char *devname;
int vpd_datalen = 0;
/*
* Look in
* /sys/dev/block/major:minor/device/serial
* /sys/dev/block/major:minor/device/vpd_pg80
* /sys/class/block/vda/serial
* (Only virtio disks /dev/vdx are known to use /sys/class/block/vdx/serial.)
*/
read_sys_block(cmd, dev, "device/serial", buf, sizeof(buf));
if (buf[0]) {
format_general_id((const char *)buf, sizeof(buf), (unsigned char *)outbuf, outbufsize);
if (outbuf[0])
return 1;
}
if (read_sys_block_binary(cmd, dev, "device/vpd_pg80", buf, VPD_SIZE, &vpd_datalen) && vpd_datalen) {
parse_vpd_serial((const unsigned char *)buf, outbuf, outbufsize);
if (outbuf[0])
return 1;
}
devname = dev_name(dev);
if (!strncmp(devname, "/dev/vd", 7)) {
char path[PATH_MAX];
char vdx[8] = { 0 };
const char *sysfs_dir;
const char *base;
unsigned i, j = 0;
int ret;
/* /dev/vda to vda */
base = dm_basename(devname);
/* vda1 to vda */
for (i = 0; base[i]; ++i) {
if (isdigit(base[i]))
break;
vdx[j] = base[i];
j++;
}
sysfs_dir = cmd->device_id_sysfs_dir ?: dm_sysfs_dir();
if (dm_snprintf(path, sizeof(path), "%s/class/block/%s/serial", sysfs_dir, vdx) < 0)
return 0;
ret = get_sysfs_value(path, buf, sizeof(buf), 0);
if (ret && !buf[0])
ret = 0;
if (ret) {
format_general_id((const char *)buf, sizeof(buf), (unsigned char *)outbuf, outbufsize);
if (buf[0])
return 1;
}
}
return 0;
}
const char *device_id_system_read(struct cmd_context *cmd, struct device *dev, uint16_t idtype)
{
char sysbuf[PATH_MAX] = { 0 };
char sysbuf2[PATH_MAX] = { 0 };
const char *idname = NULL;
struct dev_wwid *dw;
unsigned i;
if (idtype == DEV_ID_TYPE_SYS_WWID)
dev_read_sys_wwid(cmd, dev, sysbuf, sizeof(sysbuf), NULL);
else if (idtype == DEV_ID_TYPE_SYS_SERIAL)
_dev_read_sys_serial(cmd, dev, sysbuf, sizeof(sysbuf));
else if (idtype == DEV_ID_TYPE_MPATH_UUID) {
read_sys_block(cmd, dev, "dm/uuid", sysbuf, sizeof(sysbuf));
/* if (strncmp(sysbuf, "mpath", 5)) sysbuf[0] = '\0'; */
}
else if (idtype == DEV_ID_TYPE_CRYPT_UUID) {
read_sys_block(cmd, dev, "dm/uuid", sysbuf, sizeof(sysbuf));
/* if (strncmp(sysbuf, "CRYPT", 5)) sysbuf[0] = '\0'; */
}
else if (idtype == DEV_ID_TYPE_LVMLV_UUID) {
read_sys_block(cmd, dev, "dm/uuid", sysbuf, sizeof(sysbuf));
/* if (strncmp(sysbuf, "LVM", 3)) sysbuf[0] = '\0'; */
}
else if (idtype == DEV_ID_TYPE_MD_UUID) {
read_sys_block(cmd, dev, "md/uuid", sysbuf, sizeof(sysbuf));
}
else if (idtype == DEV_ID_TYPE_LOOP_FILE) {
read_sys_block(cmd, dev, "loop/backing_file", sysbuf, sizeof(sysbuf));
/* if backing file is deleted, fall back to devname */
if (strstr(sysbuf, "(deleted)"))
sysbuf[0] = '\0';
}
else if (idtype == DEV_ID_TYPE_DEVNAME) {
if (dm_list_empty(&dev->aliases))
goto_bad;
if (!(idname = strdup(dev_name(dev))))
goto_bad;
return idname;
}
else if (idtype == DEV_ID_TYPE_WWID_NAA ||
idtype == DEV_ID_TYPE_WWID_EUI ||
idtype == DEV_ID_TYPE_WWID_T10) {
if (!(dev->flags & DEV_ADDED_VPD_WWIDS))
dev_read_vpd_wwids(cmd, dev);
dm_list_iterate_items(dw, &dev->wwids) {
if (idtype_to_wwid_type(idtype) == dw->type)
return strdup(dw->id);
}
return NULL;
}
/*
* Replace all spaces, quotes, control chars with underscores.
* sys_wwid, sys_serial, and wwid_* have already been handled,
* and with slightly different replacement (see format_t10_id,
* format_general_id.)
*/
if ((idtype != DEV_ID_TYPE_SYS_WWID) &&
(idtype != DEV_ID_TYPE_SYS_SERIAL) &&
(idtype != DEV_ID_TYPE_WWID_NAA) &&
(idtype != DEV_ID_TYPE_WWID_EUI) &&
(idtype != DEV_ID_TYPE_WWID_T10)) {
for (i = 0; sysbuf[i]; ++i) {
if ((sysbuf[i] == '"') ||
isblank(sysbuf[i]) ||
isspace(sysbuf[i]) ||
iscntrl(sysbuf[i]))
sysbuf[i] = '_';
}
}
/*
* Reduce actual leading and trailing underscores for sys_wwid
* and sys_serial, since underscores were previously used as
* replacements for leading/trailing spaces which are now ignored.
* Also reduce any actual repeated underscores in t10 wwid since
* multiple repeated spaces were also once replaced by underscores.
*/
if ((idtype == DEV_ID_TYPE_SYS_WWID) ||
(idtype == DEV_ID_TYPE_SYS_SERIAL)) {
memcpy(sysbuf2, sysbuf, sizeof(sysbuf2));
_remove_leading_underscores(sysbuf2, sizeof(sysbuf2));
_remove_trailing_underscores(sysbuf2, sizeof(sysbuf2));
if (idtype == DEV_ID_TYPE_SYS_WWID && !strncmp(sysbuf2, "t10", 3) && strstr(sysbuf2, "__"))
_reduce_repeating_underscores(sysbuf2, sizeof(sysbuf2));
if (memcmp(sysbuf, sysbuf2, sizeof(sysbuf)))
log_debug("device_id_system_read reduced underscores %s to %s", sysbuf, sysbuf2);
memcpy(sysbuf, sysbuf2, sizeof(sysbuf));
}
if (!sysbuf[0])
goto bad;
if (!(idname = strdup(sysbuf)))
goto_bad;
return idname;
bad:
return NULL;
}
static int device_id_system_read_preferred(struct cmd_context *cmd, struct device *dev,
uint16_t *new_idtype, const char **new_idname)
{
const char *idname = NULL;
uint16_t idtype;
if (MAJOR(dev->dev) == cmd->dev_types->device_mapper_major) {
if (dev_has_mpath_uuid(cmd, dev, &idname)) {
idtype = DEV_ID_TYPE_MPATH_UUID;
goto id_done;
}
if (_dev_has_crypt_uuid(cmd, dev, &idname)) {
idtype = DEV_ID_TYPE_CRYPT_UUID;
goto id_done;
}
if (_dev_has_lvmlv_uuid(cmd, dev, &idname)) {
idtype = DEV_ID_TYPE_LVMLV_UUID;
goto id_done;
}
}
/* TODO: kpartx partitions on loop devs. */
if (MAJOR(dev->dev) == cmd->dev_types->loop_major) {
idtype = DEV_ID_TYPE_LOOP_FILE;
if ((idname = device_id_system_read(cmd, dev, idtype)))
goto id_done;
goto id_last;
}
if (MAJOR(dev->dev) == cmd->dev_types->md_major) {
idtype = DEV_ID_TYPE_MD_UUID;
if ((idname = device_id_system_read(cmd, dev, idtype)))
goto id_done;
goto id_last;
}
if (MAJOR(dev->dev) == cmd->dev_types->drbd_major) {
/* TODO */
goto id_last;
}
idtype = DEV_ID_TYPE_SYS_WWID;
if ((idname = device_id_system_read(cmd, dev, idtype)))
goto id_done;
idtype = DEV_ID_TYPE_WWID_NAA;
if ((idname = device_id_system_read(cmd, dev, idtype)))
goto id_done;
idtype = DEV_ID_TYPE_WWID_EUI;
if ((idname = device_id_system_read(cmd, dev, idtype)))
goto id_done;
idtype = DEV_ID_TYPE_WWID_T10;
if ((idname = device_id_system_read(cmd, dev, idtype)))
goto id_done;
idtype = DEV_ID_TYPE_SYS_SERIAL;
if ((idname = device_id_system_read(cmd, dev, idtype)))
goto id_done;
id_last:
idtype = DEV_ID_TYPE_DEVNAME;
if ((idname = device_id_system_read(cmd, dev, idtype)))
goto id_done;
return 0;
id_done:
*new_idtype = idtype;
*new_idname = idname;
return 1;
}
/*
* Check if this dev would use a stable idtype or if it
* would use DEV_ID_TYPE_DEVNAME.
*/
static int _dev_has_stable_id(struct cmd_context *cmd, struct device *dev)
{
char sysbuf[PATH_MAX] = { 0 };
struct dev_id *id;
const char *idname;
/*
* An idtype other than DEVNAME is stable, i.e. it doesn't change after
* reboot or device reattach.
* An id on dev->ids with idtype set and !idname means that idtype does
* not exist for the dev. (Optimization to avoid repeated negative
* system_read.)
*/
dm_list_iterate_items(id, &dev->ids) {
/*
* An unfortunate special case to work around a previous lvm version
* where wwid's containing "QEMU HARDDISK" were ignored, which would
* generally cause the device to have IDTYPE=devname. On reboot,
* when the dev name changes, the search for a new device may use
* the search_for_devnames="auto" setting which uses this function
* to decide if a dev should be checked as the renamed device or not.
* It's not if it has a wwid, since the renamed dev we're looking for
* would be using sys_wwid if it had a wwid. Now that QEMU wwids
* are used, we still have to check devs with a QEMU wwid to see if
* it's the renamed dev.
*/
if (((id->idtype == DEV_ID_TYPE_SYS_WWID) || (id->idtype == DEV_ID_TYPE_WWID_T10)) &&
id->idname && strstr(id->idname, "QEMU"))
continue;
if ((id->idtype != DEV_ID_TYPE_DEVNAME) && id->idname)
return 1;
}
/*
* Use device_id_system_read() instead of read_sys_block() when
* system_read ignores some values from sysfs.
*/
if ((idname = device_id_system_read(cmd, dev, DEV_ID_TYPE_SYS_WWID))) {
/* see comment above */
if (!strstr(idname, "QEMU")) {
free((void*)idname);
return 1;
}
free((void*)idname);
return 0;
}
if ((idname = device_id_system_read(cmd, dev, DEV_ID_TYPE_SYS_SERIAL))) {
free((void*)idname);
return 1;
}
if ((MAJOR(dev->dev) == cmd->dev_types->loop_major) &&
(idname = device_id_system_read(cmd, dev, DEV_ID_TYPE_LOOP_FILE))) {
free((void*)idname);
return 1;
}
if ((MAJOR(dev->dev) == cmd->dev_types->device_mapper_major)) {
if (!read_sys_block(cmd, dev, "dm/uuid", sysbuf, sizeof(sysbuf)))
goto_out;
if (_dm_uuid_has_prefix(sysbuf, "mpath-"))
return 1;
if (_dm_uuid_has_prefix(sysbuf, "CRYPT-"))
return 1;
if (_dm_uuid_has_prefix(sysbuf, "LVM-"))
return 1;
}
if ((MAJOR(dev->dev) == cmd->dev_types->md_major) &&
read_sys_block(cmd, dev, "md/uuid", sysbuf, sizeof(sysbuf)))
return 1;
if (!(dev->flags & DEV_ADDED_VPD_WWIDS))
dev_read_vpd_wwids(cmd, dev);
if (!dm_list_empty(&dev->wwids))
return 1;
out:
/* DEV_ID_TYPE_DEVNAME would be used for this dev. */
return 0;
}
const char *idtype_to_str(uint16_t idtype)
{
if (idtype == DEV_ID_TYPE_SYS_WWID)
return "sys_wwid";
if (idtype == DEV_ID_TYPE_SYS_SERIAL)
return "sys_serial";
if (idtype == DEV_ID_TYPE_DEVNAME)
return "devname";
if (idtype == DEV_ID_TYPE_MPATH_UUID)
return "mpath_uuid";
if (idtype == DEV_ID_TYPE_CRYPT_UUID)
return "crypt_uuid";
if (idtype == DEV_ID_TYPE_LVMLV_UUID)
return "lvmlv_uuid";
if (idtype == DEV_ID_TYPE_MD_UUID)
return "md_uuid";
if (idtype == DEV_ID_TYPE_LOOP_FILE)
return "loop_file";
if (idtype == DEV_ID_TYPE_WWID_NAA)
return "wwid_naa";
if (idtype == DEV_ID_TYPE_WWID_EUI)
return "wwid_eui";
if (idtype == DEV_ID_TYPE_WWID_T10)
return "wwid_t10";
return "unknown";
}
uint16_t idtype_from_str(const char *str)
{
if (!strcmp(str, "sys_wwid"))
return DEV_ID_TYPE_SYS_WWID;
if (!strcmp(str, "sys_serial"))
return DEV_ID_TYPE_SYS_SERIAL;
if (!strcmp(str, "devname"))
return DEV_ID_TYPE_DEVNAME;
if (!strcmp(str, "mpath_uuid"))
return DEV_ID_TYPE_MPATH_UUID;
if (!strcmp(str, "crypt_uuid"))
return DEV_ID_TYPE_CRYPT_UUID;
if (!strcmp(str, "lvmlv_uuid"))
return DEV_ID_TYPE_LVMLV_UUID;
if (!strcmp(str, "md_uuid"))
return DEV_ID_TYPE_MD_UUID;
if (!strcmp(str, "loop_file"))
return DEV_ID_TYPE_LOOP_FILE;
if (!strcmp(str, "wwid_naa"))
return DEV_ID_TYPE_WWID_NAA;
if (!strcmp(str, "wwid_eui"))
return DEV_ID_TYPE_WWID_EUI;
if (!strcmp(str, "wwid_t10"))
return DEV_ID_TYPE_WWID_T10;
return 0;
}
const char *dev_idtype_for_metadata(struct cmd_context *cmd, struct device *dev)
{
const char *str;
if (!cmd->enable_devices_file)
return NULL;
if (!dev || !dev->id || !dev->id->idtype || (dev->id->idtype == DEV_ID_TYPE_DEVNAME))
return NULL;
str = idtype_to_str(dev->id->idtype);
if (!strcmp(str, "unknown"))
return NULL;
return str;
}
const char *dev_idname_for_metadata(struct cmd_context *cmd, struct device *dev)
{
if (!cmd->enable_devices_file)
return NULL;
if (!dev || !dev->id || !dev->id->idtype || (dev->id->idtype == DEV_ID_TYPE_DEVNAME))
return NULL;
return dev->id->idname;
}
static const char *_dev_idname(struct device *dev, uint16_t idtype)
{
struct dev_id *id;
dm_list_iterate_items(id, &dev->ids) {
if (id->idtype != idtype)
continue;
if (!id->idname)
continue;
return id->idname;
}
return NULL;
}
static int _dev_has_id(struct device *dev, uint16_t idtype, const char *idname)
{
struct dev_id *id;
dm_list_iterate_items(id, &dev->ids) {
if (id->idtype != idtype)
continue;
if (!id->idname)
continue;
if (!strcmp(idname, id->idname))
return 1;
}
return 0;
}
static void _copy_idline_str(char *src, char *dst, int len)
{
char *s, *d = dst;
memset(dst, 0, len);
if (!(s = strchr(src, '=')))
return;
s++;
while ((*s == ' ') && (s < src + len))
s++;
while ((*s != ' ') && (*s != '\0') && (*s != '\n') && (s < src + len)) {
*d = *s;
s++;
d++;
}
dst[len-1] = '\0';
}
int device_ids_read(struct cmd_context *cmd)
{
char line[PATH_MAX];
char buf[PATH_MAX];
char check_id[PATH_MAX];
char *idtype, *idname, *devname, *pvid, *part;
struct dev_use *du;
FILE *fp;
uint32_t comment_hash = 0;
uint32_t hash = INITIAL_CRC;
int ignore_hash = 0;
int line_error;
int product_uuid_found = 0;
int hostname_found = 0;
int ret = 1;
if (!cmd->enable_devices_file)
return 1;
/*
* Note: lvmdevices calls device_ids_read() a second
* time to get the original entries to compare with
* updated entries. Prior to calling it again, it
* moves the cmd->use_devices entries out of the way.
* Otherwise, device_ids_read() should only be called
* once at the start of a command.
*/
if (!dm_list_empty(&cmd->use_devices)) {
/* shouldn't happen */
log_debug("device_ids_read already done");
return 1;
}
log_debug("device_ids_read %s", cmd->devices_file_path);
if (!(fp = fopen(cmd->devices_file_path, "r"))) {
log_warn("Cannot open devices file to read.");
return 0;
}
while (fgets(line, sizeof(line), fp)) {
/* Special value for testing */
if (!strncmp(line, "# HASH=0", 8)) {
ignore_hash = 1;
continue;
}
if (!strncmp(line, "# HASH", 6)) {
_copy_idline_str(line, buf, sizeof(buf));
errno = 0;
comment_hash = (uint32_t)strtoul(buf, NULL, 10);
if (errno) {
log_debug("Devices file invalid hash value errno %d", errno);
comment_hash = 0;
}
continue;
}
if (line[0] == '#')
continue;
/* Old version wrote this but it's not used. */
if (!strncmp(line, "SYSTEMID", 8))
continue;
hash = calc_crc(hash, (uint8_t *)line, strlen(line));
if (!strncmp(line, "HOSTNAME", 8)) {
_copy_idline_str(line, check_id, sizeof(check_id));
log_debug("read devices file hostname %s", check_id);
/* Save original for lvmdevices output. */
if (!strcmp(cmd->name, "lvmdevices"))
dm_strncpy(devices_file_hostname_orig, check_id, sizeof(devices_file_hostname_orig));
if (!cmd->device_ids_check_hostname)
continue;
hostname_found = 1;
if (cmd->hostname && strcmp(cmd->hostname, check_id)) {
log_debug("Devices file hostname %s vs local %s.",
check_id[0] ? check_id : "none", cmd->hostname ?: "none");
cmd->device_ids_refresh_trigger = 1;
}
continue;
}
if (!strncmp(line, "PRODUCT_UUID", 12)) {
_copy_idline_str(line, check_id, sizeof(check_id));
log_debug("read devices file product_uuid %s", check_id);
/* Save original for lvmdevices output. */
if (!strcmp(cmd->name, "lvmdevices"))
dm_strncpy(devices_file_product_uuid_orig, check_id, sizeof(devices_file_product_uuid_orig));
if (!cmd->device_ids_check_product_uuid)
continue;
product_uuid_found = 1;
if ((!cmd->product_uuid && check_id[0]) ||
(cmd->product_uuid && strcmp(cmd->product_uuid, check_id))) {
log_debug("Devices file product_uuid %s vs local %s.",
check_id[0] ? check_id : "none", cmd->product_uuid ?: "none");
cmd->device_ids_refresh_trigger = 1;
}
continue;
}
if (!strncmp(line, "VERSION", 7)) {
_copy_idline_str(line, _devices_file_version, sizeof(_devices_file_version));
log_debug("read devices file version %s", _devices_file_version);
continue;
}
idtype = strstr(line, "IDTYPE");
idname = strstr(line, "IDNAME");
devname = strstr(line, "DEVNAME");
pvid = strstr(line, "PVID");
part = strstr(line, "PART");
line_error = 0;
/* These two are the minimum required. */
if (!idtype || !idname)
continue;
if (!(du = zalloc(sizeof(struct dev_use)))) {
log_warn("WARNING: failed to process devices file entry.");
continue;
}
_copy_idline_str(idtype, buf, PATH_MAX);
if (buf[0])
du->idtype = idtype_from_str(buf);
_copy_idline_str(idname, buf, PATH_MAX);
if (buf[0] && (buf[0] != '.')) {
if (!(du->idname = strdup(buf)))
line_error = 1;
}
if (devname) {
_copy_idline_str(devname, buf, PATH_MAX);
if (buf[0] && (buf[0] != '.')) {
if (!(du->devname = strdup(buf)))
line_error = 1;
}
}
if (pvid) {
_copy_idline_str(pvid, buf, PATH_MAX);
if (buf[0] && (buf[0] != '.')) {
/*
* Caution: pvids are usually stored as
* char pvid[ID_LEN+1], and use memcmp/memcpy
* with ID_LEN. So, strdup_pvid is used to
* ensure the buffer for du->pvid is ID_LEN+1.
* Then, memcmp/memcpy with ID_LEN will work,
* and printing du->pvid with %s will work.
*/
if (!(du->pvid = strdup_pvid(buf)))
line_error = 1;
}
}
if (part) {
_copy_idline_str(part, buf, PATH_MAX);
if (buf[0] && (buf[0] != '.'))
du->part = atoi(buf);
}
if (line_error) {
log_warn("WARNING: failed to process devices file entry.");
free_du(du);
continue;
}
dm_list_add(&cmd->use_devices, &du->list);
}
if (fclose(fp))
stack;
log_debug("Devices file comment hash %u calc hash %u", comment_hash, hash);
if (ignore_hash)
cmd->devices_file_hash_ignore = 1;
else if (hash != comment_hash)
cmd->devices_file_hash_mismatch = 1;
if (!product_uuid_found && cmd->device_ids_check_product_uuid) {
cmd->device_ids_refresh_trigger = 1;
log_debug("Devices file refresh: missing product_uuid");
} else if ((!product_uuid_found && !hostname_found) &&
(cmd->device_ids_check_product_uuid || cmd->device_ids_check_hostname)) {
cmd->device_ids_refresh_trigger = 1;
log_debug("Devices file refresh: missing product_uuid and hostname");
}
return ret;
}
#define BACKUP_NAME_LEN 35
#define BACKUP_NAME_SIZE BACKUP_NAME_LEN+1 /* +1 null byte */
static int _filter_backup_files(const struct dirent *de)
{
if (strlen(de->d_name) != BACKUP_NAME_LEN)
return 0;
if (strncmp(de->d_name, "system.devices-", 15))
return 0;
return 1;
}
static void devices_file_backup(struct cmd_context *cmd, char *fc, char *fb, time_t *tp, uint32_t df_counter)
{
struct dirent *de;
struct dirent **namelist = NULL;
DIR *dir;
FILE *fp = NULL;
struct tm *tm;
char dirpath[PATH_MAX];
char path[PATH_MAX];
char datetime_str[48];
char de_date_str[16];
char de_time_str[16];
char de_count_str[16];
char low_name[BACKUP_NAME_SIZE] = { 0 }; /* oldest backup file name */
uint32_t low_date = 0, low_time = 0, low_count = 0;
uint32_t de_date, de_time, de_count;
unsigned int backup_limit = 0, backup_count = 0, remove_count;
int sort_count;
int dir_fd;
int i;
/* Skip backup with --devicesfile <name>, only back up default system.devices. */
if (cmd->devicesfile)
return;
if (!(backup_limit = (unsigned int)find_config_tree_int(cmd, devices_devicesfile_backup_limit_CFG, NULL)))
return;
if (dm_snprintf(dirpath, sizeof(dirpath), "%s/devices/backup/", cmd->system_dir) < 0) {
stack;
return;
}
if (!dm_create_dir(dirpath)) {
stack;
return;
}
if (!(dir = opendir(dirpath))) {
log_sys_debug("opendir", dirpath);
return;
}
tm = localtime(tp);
strftime(datetime_str, sizeof(datetime_str), "%Y%m%d.%H%M%S", tm);
/* system.devices-YYYYMMDD.HHMMSS.000N (fixed length 35) */
if (dm_snprintf(path, sizeof(path), "%s/devices/backup/system.devices-%s.%04u",
cmd->system_dir, datetime_str, df_counter) < 0)
goto_out;
if (!(fp = fopen(path, "w+"))) {
log_warn("WARNING: Failed to create backup file %s", path);
goto out;
}
if (fputs(fc, fp) < 0) {
log_warn("WARNING: Failed to write backup file %s", path);
goto out;
}
if (fputs(fb, fp) < 0) {
log_warn("WARNING: Failed to write backup file %s", path);
goto out;
}
if (fflush(fp) < 0) {
log_warn("WARNING: Failed to write backup file %s", path);
goto out;
}
if (fsync(fileno(fp)) < 0) {
log_warn("WARNING: Failed to sync backup file %s", path);
goto out;
}
if (fclose(fp))
stack;
fp = NULL;
log_debug("Wrote backup %s", path);
/* Possibly remove old backup files per configurable limit on backup files. */
while ((de = readdir(dir))) {
if (de->d_name[0] == '.')
continue;
if (strlen(de->d_name) != BACKUP_NAME_LEN)
continue;
memset(de_date_str, 0, sizeof(de_date_str));
memset(de_time_str, 0, sizeof(de_time_str));
memset(de_count_str, 0, sizeof(de_count_str));
/*
* Save the oldest backup file name.
* system.devices-YYYYMMDD.HHMMSS.NNNN
* 12345678901234567890123456789012345 (len 35)
* date YYYYMMDD is 8 chars 16-23
* time HHMMSS is 6 chars 25-30
* count NNNN is 4 chars 32-35
*/
memcpy(de_date_str, de->d_name+15, 8);
memcpy(de_time_str, de->d_name+24, 6);
memcpy(de_count_str, de->d_name+31, 4);
de_date = (uint32_t)strtoul(de_date_str, NULL, 10);
de_time = (uint32_t)strtoul(de_time_str, NULL, 10);
de_count = (uint32_t)strtoul(de_count_str, NULL, 10);
if (!low_date ||
(de_date < low_date) ||
(de_date == low_date && de_time < low_time) ||
(de_date == low_date && de_time == low_time && de_count < low_count)) {
dm_strncpy(low_name, de->d_name, sizeof(low_name));
low_date = de_date;
low_time = de_time;
low_count = de_count;
}
backup_count++;
}
if (backup_count <= backup_limit)
goto out;
remove_count = backup_count - backup_limit;
if ((dir_fd = dirfd(dir)) < 0) {
log_sys_debug("dirfd", dirpath);
goto out;
}
/* The common case removes the oldest file and can avoid sorting. */
if (remove_count == 1 && low_name[0]) {
log_debug("Remove backup %s", low_name);
if (unlinkat(dir_fd, low_name, 0) < 0 && errno != ENOENT)
log_sys_debug("unlinkat", low_name);
goto out;
}
/* Remove the n oldest files by sorting system.devices-*. */
setlocale(LC_COLLATE, "C"); /* Avoid sorting by locales */
sort_count = scandir(dirpath, &namelist, _filter_backup_files, alphasort);
setlocale(LC_COLLATE, "");
if (sort_count < 0) {
log_warn("WARNING: Failed to sort backup devices files.");
goto out;
}
log_debug("Limit backup %u found %u sorted %d removing %u.",
backup_limit, backup_count, sort_count, remove_count);
for (i = 0; namelist && i < sort_count; i++) {
if (remove_count) {
log_debug("Remove backup %s", namelist[i]->d_name);
if (unlinkat(dir_fd, namelist[i]->d_name, 0) < 0 && errno != ENOENT)
log_sys_debug("unlinkat", namelist[i]->d_name);
remove_count--;
}
free(namelist[i]);
}
free(namelist);
out:
if (fp && fclose(fp))
stack;
if (closedir(dir))
log_sys_debug("closedir", dirpath);
}
int device_ids_write(struct cmd_context *cmd)
{
char dirpath[PATH_MAX];
char tmppath[PATH_MAX];
char version_buf[VERSION_LINE_MAX] = {0};
char fc[1024]; /* devices file comments (buf of commented lines) */
char *fb = NULL; /* devices file contents (buf of uncommented lines) */
FILE *fp = NULL;
int dir_fd = -1;
time_t t;
struct dev_use *du;
const char *devname;
const char *pvid;
uint32_t df_major = 0, df_minor = 0, df_counter = 0;
uint32_t hash = 0;
int names_len = 0;
int len, pos, num = 0;
int fb_size, fb_bytes, fc_bytes;
int file_exists;
int ret = 0;
if (!cmd->enable_devices_file && !cmd->pending_devices_file)
return 1;
/*
* pending_devices_file: setup_devices found no system devices file
* exists and has not enabled the devices file, but may want to
* create a new devices file here and enable it.
*
* If this is pvcreate/vgcreate with the system devices file,
* and the devices file doesn't exist, then we may not want to
* create one for the new PVs created. This is because doing so
* would cause existing PVs on the system to be left out and not
* be visible. So, if the pvcreate/vgcreate have seen existing PVs
* during the label scan, then skip creating/writing a new system
* devices file. But, if they have not seen any other PVs, then
* create a new system devices file here with the newly created PVs.
* The idea is that pvcreate/vgcreate of the first PVs is probably
* system installation, and we'd like to have a devices file created
* automatically during installation. (The installer could also touch
* the devices file to create it, and that would cause
* pvcreate/vgcreate to always populate it.)
*/
file_exists = devices_file_exists(cmd);
log_debug("device_ids_write create %d edit %d pending %d exists %d version %s devicesfile %s",
cmd->create_edit_devices_file, cmd->edit_devices_file, cmd->pending_devices_file, file_exists,
_devices_file_version[0] ? _devices_file_version : ".", cmd->devicesfile ?: ".");
if (cmd->pending_devices_file && cmd->create_edit_devices_file && !cmd->devicesfile && !file_exists &&
(!strncmp(cmd->name, "pvcreate", 8) || !strncmp(cmd->name, "vgcreate", 8))) {
/* If any PVs were seen during scan then don't create a new devices file. */
if (lvmcache_vg_info_count()) {
log_print_unless_silent("Not creating system devices file due to existing VGs.");
free_dus(&cmd->use_devices);
return 1;
}
log_print_unless_silent("Creating devices file %s", cmd->devices_file_path);
cmd->enable_devices_file = 1;
}
/* Total length of all devnames and idnames, used to estimate file size. */
dm_list_iterate_items(du, &cmd->use_devices) {
if (du->devname)
names_len += strlen(du->devname);
if (du->idname)
names_len += strlen(du->idname);
}
if (test_mode())
return 1;
if (_devices_file_version[0]) {
if (sscanf(_devices_file_version, "%u.%u.%u", &df_major, &df_minor, &df_counter) != 3) {
/* don't update a file we can't parse */
log_warn("WARNING: not updating devices file with unparsed version.");
return 0;
}
if (df_major > DEVICES_FILE_MAJOR) {
/* don't update a file with a newer major version */
log_warn("WARNING: not updating devices file with larger major version.");
return 0;
}
}
if (dm_snprintf(dirpath, sizeof(dirpath), "%s/devices", cmd->system_dir) < 0)
goto_out;
if (dm_snprintf(tmppath, sizeof(tmppath), "%s_new", cmd->devices_file_path) < 0)
goto_out;
/* in case a previous file was left */
if (unlink(tmppath) < 0 && errno != ENOENT)
log_sys_debug("unlink", tmppath);
if (!(fp = fopen(tmppath, "w+"))) {
log_warn("Cannot open to write %s.", tmppath);
goto out;
}
if ((dir_fd = open(dirpath, O_RDONLY)) < 0) {
log_sys_debug("open", dirpath);
goto out;
}
/*
* Estimate the size of the new system.devices:
* names_len is the length of all devnames and idnames,
* 256 bytes for PRODUCT_UUID/HOSTNAME and VERSION lines,
* 128 bytes per device entry for other fields.
*/
fb_size = names_len + 256 + (128 * dm_list_size(&cmd->use_devices));
if (!(fb = malloc(fb_size))) {
log_error("Failed to allocate buffer size %d for devices file.", fb_size);
goto out;
}
len = fb_size;
pos = 0;
/* if product_uuid is included, then hostname is unnecessary */
if (cmd->product_uuid && cmd->device_ids_check_product_uuid)
num = snprintf(fb + pos, len - pos, "PRODUCT_UUID=%s\n", cmd->product_uuid);
else if (cmd->hostname && cmd->device_ids_check_hostname)
num = snprintf(fb + pos, len - pos, "HOSTNAME=%s\n", cmd->hostname);
if (num >= len - pos) {
log_error("Failed to write buffer for devices file content.");
goto out;
}
pos += num;
if (dm_snprintf(version_buf, VERSION_LINE_MAX, "VERSION=%u.%u.%u", DEVICES_FILE_MAJOR, DEVICES_FILE_MINOR, df_counter+1) < 0)
goto_out;
num = snprintf(fb + pos, len - pos, "%s\n", version_buf);
if (num >= len - pos) {
log_error("Failed to write buffer for devices file content.");
goto out;
}
pos += num;
/* as if we had read this version in case we want to write again */
memset(_devices_file_version, 0, sizeof(_devices_file_version));
_copy_idline_str(version_buf, _devices_file_version, sizeof(_devices_file_version));
dm_list_iterate_items(du, &cmd->use_devices) {
devname = du->dev ? dev_name(du->dev) : du->devname;
if (!devname || devname[0] != '/')
devname = ".";
if (!du->pvid || !du->pvid[0] || (du->pvid[0] == '.'))
pvid = ".";
else
pvid = du->pvid;
if (du->part) {
num = snprintf(fb + pos, len - pos, "IDTYPE=%s IDNAME=%s DEVNAME=%s PVID=%s PART=%d\n",
idtype_to_str(du->idtype) ?: ".",
du->idname ?: ".", devname, pvid, du->part);
} else {
num = snprintf(fb + pos, len - pos, "IDTYPE=%s IDNAME=%s DEVNAME=%s PVID=%s\n",
idtype_to_str(du->idtype) ?: ".",
du->idname ?: ".", devname, pvid);
}
if (num >= len - pos) {
log_error("Failed to write buffer for devices file content.");
goto out;
}
pos += num;
}
fb_bytes = pos;
fb[fb_bytes] = '\0';
if (!cmd->devices_file_hash_ignore)
hash = calc_crc(INITIAL_CRC, (const uint8_t *)fb, fb_bytes);
t = time(NULL);
if ((fc_bytes = snprintf(fc, sizeof(fc),
"# LVM uses devices listed in this file.\n" \
"# Created by LVM command %s%s pid %d at %s" \
"# HASH=%u\n",
cmd->name, cmd->device_ids_auto_import ? " (auto)" : "",
getpid(), ctime(&t), hash)) < 0) {
log_error("Failed to write buffer for devices file content.");
goto out;
}
fc[fc_bytes] = '\0';
if (fputs(fc, fp) < 0) {
log_error("Failed to write devices file header.");
goto out;
}
if (fputs(fb, fp) < 0) {
log_error("Failed to write devices file.");
goto out;
}
if (fflush(fp) < 0)
goto_out;
if (fsync(fileno(fp)) < 0)
goto_out;
if (fclose(fp) < 0) {
fp = NULL;
goto_out;
}
fp = NULL;
if (rename(tmppath, cmd->devices_file_path) < 0) {
log_error("Failed to replace devices file.");
goto out;
}
if (fsync(dir_fd) < 0)
stack;
if (close(dir_fd) < 0)
stack;
dir_fd = -1;
ret = 1;
log_debug("Wrote devices file %s hash %u hashed size %u total size %u",
version_buf, hash, fb_bytes, fb_bytes + fc_bytes);
devices_file_backup(cmd, fc, fb, &t, df_counter+1);
out:
if (fb)
free(fb);
if (fp && fclose(fp))
log_sys_debug("fclose", tmppath);
if ((dir_fd != -1) && close(dir_fd))
log_sys_debug("close", dirpath);
return ret;
}
static void _device_ids_update_try(struct cmd_context *cmd)
{
int held = 0;
if (cmd->expect_missing_vg_device) {
log_print_unless_silent("Devices file update skipped.");
return;
}
/*
* Use a non-blocking lock since it's not essential to
* make this update, the next cmd will make these changes
* if we skip it this update.
* If this command already holds an ex lock on the
* devices file, lock_devices_file ex succeeds and
* held is set.
* If we get the lock, only update the devices file if
* it's not been changed since we read it.
*/
if (!lock_devices_file_try(cmd, LOCK_EX, &held)) {
log_debug("Skip devices file update (busy).");
} else {
if (device_ids_version_unchanged(cmd)) {
if (!device_ids_write(cmd))
stack;
} else
log_debug("Skip devices file update (changed).");
}
if (!held)
unlock_devices_file(cmd);
}
int device_ids_version_unchanged(struct cmd_context *cmd)
{
char line[PATH_MAX];
char version_buf[VERSION_LINE_MAX];
FILE *fp;
if (!(fp = fopen(cmd->devices_file_path, "r"))) {
log_warn("WARNING: cannot open devices file to read.");
return 0;
}
while (fgets(line, sizeof(line), fp)) {
if (line[0] == '#')
continue;
if (!strncmp(line, "VERSION", 7)) {
if (fclose(fp))
stack;
_copy_idline_str(line, version_buf, sizeof(version_buf));
log_debug("check devices file version %s prev %s", version_buf, _devices_file_version);
if (!strcmp(version_buf, _devices_file_version))
return 1;
return 0;
}
}
if (fclose(fp))
stack;
return 0;
}
int device_ids_use_devname(struct cmd_context *cmd)
{
struct dev_use *du;
dm_list_iterate_items(du, &cmd->use_devices) {
if (du->idtype == DEV_ID_TYPE_DEVNAME)
return 1;
}
return 0;
}
int device_ids_use_lvmlv(struct cmd_context *cmd)
{
struct dev_use *du;
dm_list_iterate_items(du, &cmd->use_devices) {
if (du->idtype == DEV_ID_TYPE_LVMLV_UUID)
return 1;
}
return 0;
}
struct dev_use *get_du_for_devno(struct cmd_context *cmd, dev_t devno)
{
struct dev_use *du;
dm_list_iterate_items(du, &cmd->use_devices) {
if (du->dev && du->dev->dev == devno)
return du;
}
return NULL;
}
struct dev_use *get_du_for_dev(struct cmd_context *cmd, struct device *dev)
{
struct dev_use *du;
dm_list_iterate_items(du, &cmd->use_devices) {
if (du->dev == dev)
return du;
}
return NULL;
}
struct dev_use *get_du_for_pvid(struct cmd_context *cmd, const char *pvid)
{
struct dev_use *du;
dm_list_iterate_items(du, &cmd->use_devices) {
if (!du->pvid)
continue;
if (!memcmp(du->pvid, pvid, ID_LEN))
return du;
}
return NULL;
}
struct dev_use *get_du_for_devname(struct cmd_context *cmd, const char *devname)
{
struct dev_use *du;
dm_list_iterate_items(du, &cmd->use_devices) {
if (!du->devname)
continue;
if (!strcmp(du->devname, devname))
return du;
}
return NULL;
}
struct dev_use *get_du_for_device_id(struct cmd_context *cmd, uint16_t idtype, const char *idname)
{
struct dev_use *du;
dm_list_iterate_items(du, &cmd->use_devices) {
if (du->idname && (du->idtype == idtype) && !strcmp(du->idname, idname))
return du;
}
return NULL;
}
/*
* Add or update entry for this dev.
* . add an entry to dev->ids and point dev->id to it
* . add or update entry in cmd->use_devices
*/
int device_id_add(struct cmd_context *cmd, struct device *dev, const char *pvid_arg,
const char *idtype_arg, const char *id_arg, int use_idtype_only)
{
char pvid[ID_LEN+1] = { 0 };
uint16_t idtype = 0;
const char *idname = NULL;
const char *check_idname = NULL;
const char *update_matching_kind = NULL;
const char *update_matching_name = NULL;
struct dev_use *du, *update_du = NULL, *du_dev, *du_pvid, *du_devname, *du_devid;
struct dev_id *id;
int found_id = 0;
int part = 0;
if (!dev_get_partition_number(dev, &part))
return_0;
/* Ensure valid dev_name(dev) below. */
if (dm_list_empty(&dev->aliases))
return_0;
/*
* When enable_devices_file=0 and pending_devices_file=1 we let
* pvcreate/vgcreate add new du's to cmd->use_devices. These du's may
* be written to a new system devices file in device_ids_write, or they
* may not, or devices_file_write may decide not to write a new system
* devices file and devices file may remain disabled.
*/
if (!cmd->enable_devices_file && !cmd->pending_devices_file)
return 1;
/*
* The pvid_arg may be passed from a 'struct id' (pv->id) which
* may not have a terminating \0.
* Make a terminated copy to use as a string.
*/
memcpy(&pvid, pvid_arg, ID_LEN);
/*
* Choose the device_id type for the device being added.
* possible breakage:
* . if the kernel changes what it prints from sys/wwid (e.g. from
* the t10 value to the naa value for the dev), this would break
* matching du to dev unless lvm tries to match all of the dev's
* different wwids from vpd_pg83 against sys_wwid entries.
* . adding a new device_id type into the devices file breaks prior
* lvm versions that attempt to use the devices file from the new
* lvm version.
* . using a value for sys_wwid that comes from vpd_pg83 and not
* sys/wwid (e.g. taking a naa wwid from vpd_pg83 when sys/wwid
* is printing the t10 wwid) would break prior lvm versions that
* only match a du against the sys/wwid values.
*/
if (idtype_arg) {
if (!(idtype = idtype_from_str(idtype_arg))) {
if (use_idtype_only) {
log_error("The specified --deviceidtype %s is unknown.", idtype_arg);
return 0;
}
log_warn("WARNING: ignoring unknown device_id type %s.", idtype_arg);
} else {
if (id_arg) {
if ((idname = strdup(id_arg)))
goto id_done;
log_warn("WARNING: ignoring device_id name %s.", id_arg);
}
if ((idname = device_id_system_read(cmd, dev, idtype)))
goto id_done;
if (use_idtype_only) {
log_error("The specified --deviceidtype %s is not available for %s.", idtype_arg, dev_name(dev));
return 0;
}
log_warn("WARNING: ignoring deviceidtype %s which is not available for device.", idtype_arg);
idtype = 0;
}
}
if (!device_id_system_read_preferred(cmd, dev, &idtype, &idname))
return_0;
if (!idname)
return_0;
id_done:
/*
* Create a dev_id struct for the new idtype on dev->ids.
*/
dm_list_iterate_items(id, &dev->ids) {
if (id->idtype == idtype) {
found_id = 1;
break;
}
}
if (found_id && idname && (!id->idname || strcmp(id->idname, idname))) {
log_debug("Replacing device id %s old %s new %s",
idtype_to_str(id->idtype), id->idname ?: ".", idname);
dm_list_del(&id->list);
free_did(id);
found_id = 0;
}
if (!found_id) {
if (!(id = zalloc(sizeof(struct dev_id)))) {
free((char *)idname);
return_0;
}
id->idtype = idtype;
id->idname = (char *)idname;
dm_list_add(&dev->ids, &id->list);
} else
free((char*)idname);
dev->id = id;
dev->flags |= DEV_MATCHED_USE_ID;
idname = NULL;
idtype = 0;
/*
* "dev" is the device we are adding.
* "id" is the device_id it's using, set in dev->id.
*
* Update the cmd->use_devices list for the new device. The
* use_devices list will be used to update the devices file.
*
* The dev being added can potentially overlap existing entries
* in various ways. If one of the existing entries is truely for
* this device being added, then we want to update that entry.
* If some other existing entries are not for the same device, but
* have some overlapping values, then we want to try to update
* those other entries to fix any incorrect info.
*/
/* Is there already an entry matched to this device? */
du_dev = get_du_for_dev(cmd, dev);
/* Is there already an entry matched to this device's pvid? */
du_pvid = get_du_for_pvid(cmd, pvid);
/* Is there already an entry using this device's name? */
du_devname = get_du_for_devname(cmd, dev_name(dev));
/* Is there already an entry using the device_id for this device? */
du_devid = get_du_for_device_id(cmd, id->idtype, id->idname);
if (du_dev)
log_debug("device_id_add %s pvid %s matches entry %p dev %s",
dev_name(dev), pvid, du_dev, dev_name(du_dev->dev));
if (du_pvid)
log_debug("device_id_add %s pvid %s matches entry %p dev %s with same pvid %s",
dev_name(dev), pvid, du_pvid, du_pvid->dev ? dev_name(du_pvid->dev) : ".",
du_pvid->pvid);
if (du_devid)
log_debug("device_id_add %s pvid %s matches entry %p dev %s with same device_id %d %s",
dev_name(dev), pvid, du_devid, du_devid->dev ? dev_name(du_devid->dev) : ".",
du_devid->idtype, du_devid->idname);
if (du_devname)
log_debug("device_id_add %s pvid %s matches entry %p dev %s with same devname %s",
dev_name(dev), pvid, du_devname, du_devname->dev ? dev_name(du_devname->dev) : ".",
du_devname->devname);
if (du_pvid && (du_pvid->dev != dev))
log_warn("WARNING: adding device %s with PVID %s which is already used for %s device_id %s.",
dev_name(dev), pvid, du_pvid->dev ? dev_name(du_pvid->dev) : "missing device",
du_pvid->idname ?: "none");
if (du_devid && (du_devid->dev != dev)) {
if (!du_devid->dev) {
log_warn("WARNING: adding device %s with idname %s which is already used for missing device.",
dev_name(dev), id->idname);
} else {
int ret1, ret2;
dev_t devt1, devt2;
/* Check if both entries are partitions of the same device. */
ret1 = dev_get_primary_dev(cmd->dev_types, dev, &devt1);
ret2 = dev_get_primary_dev(cmd->dev_types, du_devid->dev, &devt2);
if ((ret1 == 2) && (ret2 == 2) && (devt1 == devt2)) {
log_debug("Using separate entries for partitions of same device %s part %d %s part %d.",
dev_name(dev), part, dev_name(du_devid->dev), du_devid->part);
} else {
log_warn("WARNING: adding device %s with idname %s which is already used for %s.",
dev_name(dev), id->idname, dev_name(du_devid->dev));
}
}
}
/*
* If one of the existing entries (du_dev, du_pvid, du_devid, du_devname)
* is truely for the same device that is being added, then set update_du to
* that existing entry to be updated.
*/
if (du_dev) {
update_du = du_dev;
dm_list_del(&update_du->list);
update_matching_kind = "device";
update_matching_name = dev_name(dev);
} else if (du_pvid) {
/*
* If the device_id of the existing entry for PVID is the same
* as the device_id of the device being added, then update the
* existing entry. If the device_ids differ, then the devices
* have duplicate PVIDs, and the new device gets a new entry
* (if we allow it to be added.)
*/
if (du_pvid->idtype == id->idtype)
check_idname = strdup(id->idname);
else
check_idname = device_id_system_read(cmd, dev, du_pvid->idtype);
if (!du_pvid->idname || (check_idname && !strcmp(check_idname, du_pvid->idname))) {
update_du = du_pvid;
dm_list_del(&update_du->list);
update_matching_kind = "PVID";
update_matching_name = pvid;
} else {
if (!cmd->current_settings.yes &&
yes_no_prompt("Add device with duplicate PV to devices file?") == 'n') {
log_print_unless_silent("Device not added.");
free((void *)check_idname);
return 1;
}
}
} else if (du_devid) {
/*
* Do we create a new du or update the existing du?
* If it's the same device, update the existing du,
* but if it's two devices with the same device_id, then
* create a new du.
*
* We know that 'dev' has device_id 'id'.
* Check if du_devid->dev is different from 'dev'
* and that du_devid->idname matches id.
* If so, then there are two different devices with
* the same device_id (create a new du for dev.)
* If not, then update the existing du_devid.
*/
if (du_devid->dev == dev) {
/* update the existing entry with matching devid */
update_du = du_devid;
dm_list_del(&update_du->list);
update_matching_kind = "device_id";
update_matching_name = id->idname;
}
}
free((void *)check_idname);
if (!update_du) {
log_debug("Adding new entry to devices file for %s PVID %s %s %s.",
dev_name(dev), pvid, idtype_to_str(id->idtype), id->idname);
if (!(du = zalloc(sizeof(struct dev_use))))
return_0;
} else {
du = update_du;
log_debug("Updating existing entry in devices file for %s that matches %s %s.",
dev_name(dev), update_matching_kind, update_matching_name);
}
free(du->idname);
free(du->devname);
free(du->pvid);
du->idtype = id->idtype;
du->idname = strdup(id->idname);
du->devname = strdup(dev_name(dev));
du->dev = dev;
du->pvid = strdup_pvid(pvid);
dev_get_partition_number(dev, &du->part);
if (!du->idname || !du->devname || !du->pvid) {
free_du(du);
return_0;
}
dm_list_add(&cmd->use_devices, &du->list);
return 1;
}
/*
* Update entry for this dev.
* Set PVID=.
* update entry in cmd->use_devices
*/
void device_id_pvremove(struct cmd_context *cmd, struct device *dev)
{
struct dev_use *du;
if (!cmd->enable_devices_file)
return;
if (!(du = get_du_for_dev(cmd, dev))) {
log_warn("WARNING: devices to use does not include %s", dev_name(dev));
return;
}
if (du->pvid) {
free(du->pvid);
du->pvid = NULL;
}
}
/*
* Remove LVMLV_UUID entries from system.devices for LVs that were removed.
* lvremove vg/lv where a PV exists on vg/lv does an automatic
* lvmdevices --deldev /dev/vg/lv
*/
void device_id_lvremove(struct cmd_context *cmd, struct dm_list *removed_uuids)
{
struct dev_use *du;
struct dm_str_list *sl;
int found = 0;
if (!device_ids_use_lvmlv(cmd))
return;
dm_list_iterate_items(sl, removed_uuids) {
if (!(du = get_du_for_device_id(cmd, DEV_ID_TYPE_LVMLV_UUID, sl->str)))
continue;
found++;
}
if (!found)
return;
if (!lock_devices_file(cmd, LOCK_EX))
return;
/*
* Clear cmd->use_devices which may no longer be an accurate
* representation of system.devices, since another command may have
* changed system.devices after this command read and unlocked it.
*/
free_dus(&cmd->use_devices);
/*
* Reread system.devices, recreating cmd->use_devices.
*/
if (!device_ids_read(cmd)) {
log_debug("Failed to read devices file");
goto out;
}
found = 0;
dm_list_iterate_items(sl, removed_uuids) {
if (!(du = get_du_for_device_id(cmd, DEV_ID_TYPE_LVMLV_UUID, sl->str)))
continue;
log_debug("Removing devices file entry for device_id %s", sl->str);
dm_list_del(&du->list);
free_du(du);
found++;
}
if (!found)
goto out;
if (!device_ids_write(cmd))
log_debug("Failed to write devices file");
out:
unlock_devices_file(cmd);
}
void device_id_update_vg_uuid(struct cmd_context *cmd, struct volume_group *vg, struct id *old_vg_id)
{
struct dev_use *du;
struct lv_list *lvl;
char old_vgid[ID_LEN+1] = { 0 };
char new_vgid[ID_LEN+1] = { 0 };
char old_idname[PATH_MAX];
int update = 0;
if (!cmd->enable_devices_file)
return;
/* Without this setting there is no stacking LVs on PVs. */
if (!cmd->scan_lvs)
return;
/* Check if any devices file entries are stacked on LVs. */
if (!device_ids_use_lvmlv(cmd))
return;
memcpy(old_vgid, old_vg_id, ID_LEN);
memcpy(new_vgid, &vg->id, ID_LEN);
/*
* for each LV in VG, if there is a du for that LV (meaning a PV exists
* on the LV), then update the du idname, replacing the old vgid with
* the new vgid.
*/
dm_list_iterate_items(lvl, &vg->lvs) {
memset(old_idname, 0, sizeof(old_idname));
memcpy(old_idname, "LVM-", 4);
memcpy(old_idname+4, old_vgid, ID_LEN);
memcpy(old_idname+4+ID_LEN, &lvl->lv->lvid.id[1], ID_LEN);
if ((du = get_du_for_device_id(cmd, DEV_ID_TYPE_LVMLV_UUID, old_idname))) {
log_debug("device_id update %s pvid %s vgid %s to %s",
du->devname ?: ".", du->pvid ?: ".", old_vgid, new_vgid);
memcpy(du->idname+4, new_vgid, ID_LEN);
update = 1;
if (du->dev && du->dev->id && (du->dev->id->idtype == DEV_ID_TYPE_LVMLV_UUID))
memcpy(du->dev->id->idname+4, new_vgid, ID_LEN);
}
}
if (update &&
!device_ids_write(cmd))
stack;
unlock_devices_file(cmd);
}
static int _idtype_compatible_with_major_number(struct cmd_context *cmd, int idtype, unsigned major)
{
/* devname can be used with any kind of device */
if (idtype == DEV_ID_TYPE_DEVNAME)
return 1;
if (idtype == DEV_ID_TYPE_MPATH_UUID ||
idtype == DEV_ID_TYPE_CRYPT_UUID ||
idtype == DEV_ID_TYPE_LVMLV_UUID)
return (major == cmd->dev_types->device_mapper_major);
if (idtype == DEV_ID_TYPE_MD_UUID)
return (major == cmd->dev_types->md_major);
if (idtype == DEV_ID_TYPE_LOOP_FILE)
return (major == cmd->dev_types->loop_major);
if (major == cmd->dev_types->device_mapper_major)
return (idtype == DEV_ID_TYPE_MPATH_UUID ||
idtype == DEV_ID_TYPE_CRYPT_UUID ||
idtype == DEV_ID_TYPE_LVMLV_UUID ||
idtype == DEV_ID_TYPE_DEVNAME);
if (major == cmd->dev_types->md_major)
return (idtype == DEV_ID_TYPE_MD_UUID ||
idtype == DEV_ID_TYPE_DEVNAME);
if (major == cmd->dev_types->loop_major)
return (idtype == DEV_ID_TYPE_LOOP_FILE ||
idtype == DEV_ID_TYPE_DEVNAME);
return 1;
}
static int _match_dm_names(struct cmd_context *cmd, char *idname, struct device *dev)
{
struct device *dev2;
struct stat buf;
/*
* An alternate dm name may already be in dev-cache from dev_cache_scan(),
* in which case both names point to the same struct device.
*/
dev2 = dev_cache_get_existing(cmd, idname, NULL);
if (dev2 && (dev == dev2)) {
log_debug("Match dm names %s %s for %d:%d (from cache)",
dev_name(dev), idname, (int)MAJOR(dev->dev), (int)MINOR(dev->dev));
return 1;
}
if (dev2)
return 0;
/*
* Optimized commands (like pvscan) can avoid a full dev_cache_scan(),
* in which case all the dev aliases will not already exist in dev-cache,
* so check if the system has a device with the given name.
*/
if (!strncmp(idname, "/dev/dm-", 8) || !strncmp(idname, "/dev/mapper/", 12)) {
if (stat(idname, &buf))
return 0;
if ((MAJOR(buf.st_rdev) == cmd->dev_types->device_mapper_major) &&
(MINOR(buf.st_rdev) == MINOR(dev->dev))) {
log_debug("Match dm names %s %s for %d:%d (from stat)",
dev_name(dev), idname, (int)MAJOR(dev->dev), (int)MINOR(dev->dev));
return 1;
}
}
return 0;
}
/*
* du is a devices file entry. dev is any device on the system.
* check if du is for dev by comparing the device's ids to du->idname.
*
* check for a dev->ids entry with du->idtype, if found compare it,
* if not, system_read idtype for the dev, add entry to dev->ids,
* compare it to du to check if it matches.
*
* When a match is found, set up links among du/id/dev.
*/
static int _match_du_to_dev(struct cmd_context *cmd, struct dev_use *du, struct device *dev)
{
char du_idname[PATH_MAX];
struct dev_id *id;
const char *idname;
int part;
/*
* The idname will be removed from an entry with devname type when the
* devname is read and found to hold a different PVID than the PVID in
* the entry. At that point we only have the PVID and no known
* location for it.
*/
if (!du->idname || !du->idtype) {
/*
log_debug("Mismatch device_id %s %s %s to %s",
du->idtype ? idtype_to_str(du->idtype) : "idtype_missing",
du->idname ? du->idname : "idname_missing",
du->devname ? du->devname : "devname_missing",
dev_name(dev));
*/
return 0;
}
/*
* Some idtypes can only match devices with a specific major number,
* so we can skip trying to match certain du entries based simply on
* the major number of dev.
*/
if (!_idtype_compatible_with_major_number(cmd, du->idtype, (int)MAJOR(dev->dev))) {
/*
log_debug("Mismatch device_id %s %s to %s: wrong major",
idtype_to_str(du->idtype), du->idname ?: ".", dev_name(dev));
*/
return 0;
}
if (!dev_get_partition_number(dev, &part)) {
/*
log_debug("Mismatch device_id %s %s to %s: no partition",
idtype_to_str(du->idtype), du->idname ?: ".", dev_name(dev));
*/
return 0;
}
if (part != du->part) {
/*
log_debug("Mismatch device_id %s %s to %s: wrong partition %d vs %d",
idtype_to_str(du->idtype), du->idname ?: ".", dev_name(dev), du->part, part);
*/
return 0;
}
if (du->idtype == DEV_ID_TYPE_DEVNAME) {
int is_dm = (MAJOR(dev->dev) == cmd->dev_types->device_mapper_major);
idname = dev_name(dev);
if (!strcmp(du->idname, idname) || (is_dm && _match_dm_names(cmd, du->idname, dev))) {
if (!(id = zalloc(sizeof(struct dev_id))))
return_0;
id->idtype = DEV_ID_TYPE_DEVNAME;
id->idname = strdup(du->idname);
dm_list_add(&dev->ids, &id->list);
du->dev = dev;
dev->id = id;
dev->flags |= DEV_MATCHED_USE_ID;
log_debug("Match %s %s to %s",
idtype_to_str(du->idtype), du->idname, dev_name(dev));
return 1;
}
return 0;
}
/*
* sys_wwid and sys_serial were saved in the past with leading and
* trailing spaces replaced with underscores, and t10 wwids also had
* repeated internal spaces replaced with one underscore each. Now we
* ignore leading and trailing spaces and replace multiple repeated
* spaces with one underscore in t10 wwids. In order to handle
* system.devices entries created by older versions, modify the IDNAME
* value that's read (du->idname) to remove leading and trailing
* underscores, and reduce repeated underscores to one in t10 wwids.
*
* Example: wwid is reported as " t10.123 456 " (without quotes)
* Previous versions would save this in system.devices as: __t10.123__456__
* Current versions will save this in system.devices as: t10.123_456
* device_id_system_read() now returns: t10.123_456
* When this code reads __t10.123__456__ from system.devices, that
* string is modified to t10.123_456 so that it will match the value
* returned from device_id_system_read().
*/
strncpy(du_idname, du->idname, PATH_MAX-1);
if (((du->idtype == DEV_ID_TYPE_SYS_WWID) || (du->idtype == DEV_ID_TYPE_SYS_SERIAL)) &&
strchr(du_idname, '_')) {
_remove_leading_underscores(du_idname, sizeof(du_idname));
_remove_trailing_underscores(du_idname, sizeof(du_idname));
if (du->idtype == DEV_ID_TYPE_SYS_WWID && !strncmp(du_idname, "t10", 3) && strstr(du_idname, "__"))
_reduce_repeating_underscores(du_idname, sizeof(du_idname));
}
/*
* Try to match du with ids that have already been read for the dev
* (and saved on dev->ids to avoid rereading.)
*/
dm_list_iterate_items(id, &dev->ids) {
if (!id->idname)
continue;
if (id->idtype == du->idtype) {
if (!strcmp(id->idname, du_idname)) {
du->dev = dev;
dev->id = id;
dev->flags |= DEV_MATCHED_USE_ID;
log_debug("Match %s %s to %s",
idtype_to_str(du->idtype), du_idname, dev_name(dev));
return 1;
}
return 0;
}
}
if (!(id = zalloc(sizeof(struct dev_id))))
return_0;
idname = device_id_system_read(cmd, dev, du->idtype);
/*
* Save this id for the dev, even if it doesn't exist (NULL)
* or doesn't match du. This avoids system_read of this idtype
* repeatedly, and the saved id will be found in the loop
* over dev->ids above.
*/
id->idtype = du->idtype;
id->idname = (char *)idname;
dm_list_add(&dev->ids, &id->list);
if (idname && !strcmp(idname, du_idname)) {
du->dev = dev;
dev->id = id;
dev->flags |= DEV_MATCHED_USE_ID;
log_debug("Match %s %s to %s",
idtype_to_str(du->idtype), idname, dev_name(dev));
return 1;
}
/*
log_debug("Mismatch device_id %s %s to %s: idname %s",
idtype_to_str(du->idtype), du->idname ?: ".", dev_name(dev), idname ?: ".");
*/
/*
* Make the du match this device if the dev has a vpd_pg83 wwid
* that matches du->idname, even if the sysfs wwid for dev did
* not match the du->idname. This could happen if sysfs changes
* which wwid it reports (there are often multiple), or if lvm in
* the future selects a sys_wwid value from vpd_pg83 data rather
* than from the sysfs wwid.
*
* TODO: update the df entry IDTYPE somewhere?
*/
if (du->idtype == DEV_ID_TYPE_SYS_WWID) {
struct dev_wwid *dw;
if (!(dev->flags & DEV_ADDED_VPD_WWIDS))
dev_read_vpd_wwids(cmd, dev);
dm_list_iterate_items(dw, &dev->wwids) {
if (!strcmp(dw->id, du_idname)) {
if (!(id = zalloc(sizeof(struct dev_id))))
return_0;
/* wwid types are 1,2,3 and idtypes are DEV_ID_TYPE_ */
id->idtype = wwid_type_to_idtype(dw->type);
id->idname = strdup(dw->id);
dm_list_add(&dev->ids, &id->list);
du->dev = dev;
dev->id = id;
dev->flags |= DEV_MATCHED_USE_ID;
log_debug("Match %s %s to %s: using vpd_pg83 %s %s",
idtype_to_str(du->idtype), du_idname, dev_name(dev),
idtype_to_str(id->idtype), id->idname ?: ".");
du->idtype = id->idtype;
return 1;
}
}
}
return 0;
}
int device_ids_match_dev(struct cmd_context *cmd, struct device *dev)
{
struct dev_use *du;
/* First check the du entry with matching devname since it's likely correct. */
if ((du = get_du_for_devname(cmd, dev_name(dev)))) {
if (_match_du_to_dev(cmd, du, dev))
return 1;
}
/* Check all du entries since the devname could have changed. */
dm_list_iterate_items(du, &cmd->use_devices) {
if (!_match_du_to_dev(cmd, du, dev))
continue;
return 1;
}
return 0;
}
/*
* For each entry on cmd->use_devices (entries in the devices file),
* find a struct device from dev-cache. They are paired based strictly
* on the device id.
*
* This must not open or read devices. This function cannot use filters.
* filters are applied after this, and the filters may open devs in the first
* nodata filtering. The second filtering, done after label_scan has read
* a device, is allowed to read a device to evaluate filters that need to see
* data from the dev.
*
* When a device id of a particular type is obtained for a dev, a id for that
* type is saved in dev->ids in case it needs to be checked again.
*
* When a device in dev-cache is matched to an entry in the devices file
* (a struct dev_use), then:
* . du->dev = dev;
* . dev->id = id;
* . dev->flags |= DEV_MATCHED_USE_ID;
*
* Later when filter-deviceid is run to exclude devices that are not
* included in the devices file, the filter checks if DEV_MATCHED_USE_ID
* is set which means that the dev matches a devices file entry and
* passes the filter.
*/
void device_ids_match_device_list(struct cmd_context *cmd)
{
struct dev_use *du;
dm_list_iterate_items(du, &cmd->use_devices) {
if (du->dev)
continue;
if (!(du->dev = dev_cache_get_existing(cmd, du->devname, NULL))) {
log_warn("Device not found for %s.", du->devname);
} else {
/* Should we set dev->id? Which idtype? Use --deviceidtype? */
du->dev->flags |= DEV_MATCHED_USE_ID;
}
}
}
void device_ids_match(struct cmd_context *cmd)
{
struct dev_iter *iter;
struct dev_use *du;
struct device *dev;
int found;
if (cmd->enable_devices_list) {
device_ids_match_device_list(cmd);
return;
}
if (!cmd->enable_devices_file)
return;
log_debug("Matching devices file entries to devices");
/*
* We would set cmd->filter_deviceid_skip but we are disabling
* all filters (dev_cache_get NULL arg) so it's not necessary.
*/
/*
* First try matching entries with IDTYPE other than devname.
* We don't want a false idtype=devname match to interfere
* with matching a proper idtype.
*/
dm_list_iterate_items(du, &cmd->use_devices) {
if (du->idtype == DEV_ID_TYPE_DEVNAME)
continue;
/* TODO: when does this happen? */
/* already matched */
if (du->dev) {
log_debug("Match %s %s PVID %s: done previously %s",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".",
dev_name(du->dev));
continue;
}
/*
* du->devname from the devices file is the last known
* device name. It may be incorrect, but it's usually
* correct, so it's an efficient place to check for a
* match first.
*
* NULL filter is used because we are just setting up the
* the du/dev pairs in preparation for using the filters.
*/
if (du->devname &&
(dev = dev_cache_get_existing(cmd, du->devname, NULL))) {
/* On successful match, du, dev, and id are linked. */
if (_match_du_to_dev(cmd, du, dev)) {
log_debug("Match %s %s PVID %s: done %s (immediate)",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".",
dev_name(du->dev));
continue;
} else {
log_debug("Match %s %s PVID %s: wrong devname %s",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".",
du->devname);
}
}
/*
* Iterate through all devs and try to match du.
*
* If a match is made here it means the du->devname is wrong,
* so the devices file should be updated with a new devname.
*
* NULL filter is used because we are just setting up the
* the du/dev pairs in preparation for using the filters.
*/
found = 0;
if (!(iter = dev_iter_create(NULL, 0)))
continue;
while ((dev = dev_iter_get(cmd, iter))) {
/* skip a dev that's already matched to another entry */
if (dev->flags & DEV_MATCHED_USE_ID)
continue;
if (_match_du_to_dev(cmd, du, dev)) {
log_debug("Match %s %s PVID %s: done %s",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".",
dev_name(du->dev));
found = 1;
break;
}
}
dev_iter_destroy(iter);
if (!found)
log_debug("Match %s %s PVID %s: no device matches",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".");
}
/*
* Next match entries with IDTYPE=devname, which is only
* based on matching devname, so somewhat likely to be wrong
* and need correcting in device_ids_validate/device_ids_search.
*/
dm_list_iterate_items(du, &cmd->use_devices) {
if (du->idtype != DEV_ID_TYPE_DEVNAME)
continue;
if (!du->idname) {
log_debug("Match %s %s PVID %s: no idname",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".");
continue;
}
/* TODO: when does this happen? */
/* already matched */
if (du->dev) {
log_debug("Match %s %s PVID %s: done previously %s",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".",
dev_name(du->dev));
continue;
}
if (!(dev = dev_cache_get_existing(cmd, du->idname, NULL))) {
log_debug("Match %s %s PVID %s: idname not found",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".");
continue;
}
if (dev->flags & DEV_MATCHED_USE_ID) {
log_debug("Match %s %s PVID %s: dev %s already matched to an entry",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".", dev_name(dev));
continue;
}
if (_match_du_to_dev(cmd, du, dev)) {
log_debug("Match %s %s PVID %s: done %s",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".",
dev_name(du->dev));
continue;
}
log_debug("Match %s %s PVID %s: no device matches",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".");
}
if (!cmd->print_device_id_not_found)
return;
/*
* Look for entries in devices file for which we found no device.
*/
dm_list_iterate_items(du, &cmd->use_devices) {
/* Found a device for this entry. */
if (du->dev && (du->dev->flags & DEV_MATCHED_USE_ID))
continue;
/* This shouldn't be possible. */
if (du->dev && !(du->dev->flags & DEV_MATCHED_USE_ID)) {
log_error("Device %s not matched to device_id", dev_name(du->dev));
continue;
}
/* A detached device would get here which isn't uncommon. */
if ((du->idtype == DEV_ID_TYPE_DEVNAME) && du->devname)
log_debug("Devices file PVID %s last seen on %s not found.",
du->pvid ?: "none",
du->devname ?: "none");
else if (du->idtype == DEV_ID_TYPE_DEVNAME)
log_debug("Devices file PVID %s not found.",
du->pvid ?: "none");
else if (du->devname)
log_debug("Devices file %s %s PVID %s last seen on %s not found.",
idtype_to_str(du->idtype),
du->idname ?: "none",
du->pvid ?: "none",
du->devname);
else
log_debug("Devices file %s %s PVID %s not found.",
idtype_to_str(du->idtype),
du->idname ?: "none",
du->pvid ?: "none");
}
}
static void _get_devs_with_serial_numbers(struct cmd_context *cmd, struct dm_list *serial_str_list, struct dm_list *devs)
{
struct dev_iter *iter;
struct device *dev;
struct device_list *devl;
struct dev_id *id;
const char *idname;
if (!(iter = dev_iter_create(NULL, 0)))
return;
while ((dev = dev_iter_get(cmd, iter))) {
/* if serial has already been read for this dev then use it */
dm_list_iterate_items(id, &dev->ids) {
if (id->idtype == DEV_ID_TYPE_SYS_SERIAL && id->idname) {
if (str_list_match_item(serial_str_list, id->idname)) {
if (!(devl = dm_pool_zalloc(cmd->mem, sizeof(*devl))))
goto next_continue;
devl->dev = dev;
dm_list_add(devs, &devl->list);
}
goto next_continue;
}
}
/* just copying the no-data filters in similar device_ids_search */
if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "sysfs"))
continue;
if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "type"))
continue;
if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "usable"))
continue;
if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "mpath"))
continue;
if ((idname = device_id_system_read(cmd, dev, DEV_ID_TYPE_SYS_SERIAL))) {
if (str_list_match_item(serial_str_list, idname)) {
if (!(devl = dm_pool_zalloc(cmd->mem, sizeof(*devl))))
goto next_free;
if (!(id = zalloc(sizeof(struct dev_id))))
goto next_free;
id->idtype = DEV_ID_TYPE_SYS_SERIAL;
id->idname = (char *)idname;
dm_list_add(&dev->ids, &id->list);
devl->dev = dev;
dm_list_add(devs, &devl->list);
idname = NULL;
}
}
next_free:
if (idname)
free((char *)idname);
next_continue:
continue;
}
dev_iter_destroy(iter);
}
/*
* This is called after devices are scanned to compare what was found on disks
* vs what's in the devices file. The devices file could be outdated and need
* correcting; the authoritative data is what's on disk. Now that we have read
* the device labels and know the PVID's from disk we can check the PVID's in
* use_devices entries from the devices file.
*/
void device_ids_validate(struct cmd_context *cmd, struct dm_list *scanned_devs, int using_hints, int noupdate, int *update_needed)
{
struct dm_list wrong_devs;
struct device *dev = NULL;
struct device_list *devl;
struct dev_use *du, *du2;
struct dev_id *id;
const char *devname;
char *tmpdup;
int update_file = 0;
dm_list_init(&wrong_devs);
cmd->device_ids_invalid = 0;
if (!cmd->enable_devices_file)
return;
log_debug("Validating devices file entries");
dm_list_iterate_items(du, &cmd->use_devices) {
log_debug("Validating %s %s PVID %s: initial match %s",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".",
du->dev ? dev_name(du->dev) : "not set");
}
/*
* Validate entries with proper device id types.
* idname is the authority for pairing du and dev.
*/
dm_list_iterate_items(du, &cmd->use_devices) {
if (!du->dev)
continue;
/* For this idtype the idname match is unreliable. */
if (du->idtype == DEV_ID_TYPE_DEVNAME)
continue;
dev = du->dev;
/*
* scanned_devs are the devices that have been scanned,
* so they are the only devs we can verify PVID for.
*/
if (scanned_devs && !device_list_find_dev(scanned_devs, dev)) {
log_debug("Validate %s %s PVID %s on %s: not scanned",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".", dev_name(dev));
continue;
}
/*
* The matched device could not be read so we do not have
* the PVID from disk and cannot verify the devices file entry.
*/
if (dev->flags & DEV_SCAN_NOT_READ) {
log_debug("Validate %s %s PVID %s on %s: not read",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".", dev_name(dev));
continue;
}
/*
* du and dev may have been matched, but the dev could still
* have been excluded by other filters during label scan.
* This shouldn't generally happen, but if it does the user
* probably wants to do something about it.
*/
if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "persistent")) {
log_debug("Validate %s %s PVID %s on %s: filtered (%s)",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".",
dev_name(dev), dev_filtered_reason(dev));
continue;
}
/*
* If the PVID doesn't match, don't assume that the serial
* number is correct, since serial numbers may not be unique.
* Search for the PVID on other devs in device_ids_check_serial.
*/
if ((du->idtype == DEV_ID_TYPE_SYS_SERIAL) && du->pvid && du->idname &&
memcmp(dev->pvid, du->pvid, ID_LEN)) {
log_debug("Validate %s %s PVID %s on %s: wrong PVID %s",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".",
dev_name(dev), dev->pvid);
log_debug("suspect device id serial %s for %s", du->idname, dev_name(dev));
if (!str_list_add(cmd->mem, &cmd->device_ids_check_serial, dm_pool_strdup(cmd->mem, du->idname)))
stack;
cmd->device_ids_invalid = 1;
continue;
}
/*
* If the du pvid from the devices file does not match the
* pvid read from disk, replace the du pvid with the pvid from
* disk and update the pvid in the devices file entry.
*/
if (dev->pvid[0]) {
if (!du->pvid || memcmp(dev->pvid, du->pvid, ID_LEN)) {
log_debug("Validate %s %s PVID %s on %s: wrong PVID %s",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".",
dev_name(dev), dev->pvid);
log_warn("Device %s has PVID %s (devices file %s)",
dev_name(dev), dev->pvid, du->pvid ?: "none");
if (!(tmpdup = strdup_pvid(dev->pvid)))
continue;
free(du->pvid);
du->pvid = tmpdup;
update_file = 1;
cmd->device_ids_invalid = 1;
}
} else {
if (du->pvid && (du->pvid[0] != '.')) {
log_debug("Validate %s %s PVID %s on %s: wrong PVID %s",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".",
dev_name(dev), dev->pvid);
log_warn("Device %s has no PVID (devices file %s)",
dev_name(dev), du->pvid);
free(du->pvid);
du->pvid = NULL;
update_file = 1;
cmd->device_ids_invalid = 1;
}
}
log_debug("Validate %s %s PVID %s on %s: correct",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".",
dev_name(dev));
/*
* Avoid thrashing changes to the devices file during
* startup due to device names that are still being
* established. Commands that may run during startup
* should set this flag.
*/
if (cmd->ignore_device_name_mismatch)
continue;
if (!du->devname || strcmp(dev_name(du->dev), du->devname)) {
log_debug("Validate %s %s PVID %s on %s: outdated DEVNAME %s",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".",
dev_name(dev), du->devname ?: "none");
if (!(tmpdup = strdup(dev_name(du->dev))))
continue;
free(du->devname);
du->devname = tmpdup;
update_file = 1;
cmd->device_ids_invalid = 1;
}
}
/*
* Validate entries with DEVNAME device id type.
* pvid is the authority for pairing du and dev.
*/
dm_list_iterate_items(du, &cmd->use_devices) {
if (du->idtype != DEV_ID_TYPE_DEVNAME)
continue;
if (!du->pvid)
continue;
/*
* Correctly matched du and dev.
* The DEVNAME hint could still need an update.
*/
if (du->dev && !memcmp(du->dev->pvid, du->pvid, ID_LEN)) {
dev = du->dev;
devname = dev_name(du->dev);
log_debug("Validate %s %s PVID %s on %s: correct",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".", devname);
/* This shouldn't happen since idname was used to match du and dev */
if (!du->idname || strcmp(devname, du->idname)) {
log_warn("WARNING: fixing devices file IDNAME %s for PVID %s device %s",
du->idname ?: ".", du->pvid, dev_name(dev));
if (!(tmpdup = strdup(devname)))
continue;
free(du->idname);
du->idname = tmpdup;
update_file = 1;
cmd->device_ids_invalid = 1;
}
/* Fix the DEVNAME field if it's outdated. */
if (!du->devname || strcmp(devname, du->devname)) {
log_debug("Validate %s %s PVID %s on %s: outdated DEVNAME %s",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".", devname,
du->devname ?: ".");
if (!(tmpdup = strdup(devname)))
continue;
free(du->devname);
du->devname = tmpdup;
update_file = 1;
cmd->device_ids_invalid = 1;
}
continue;
}
/*
* Incorrectly matched du and dev, or unconfirmed match due to
* the dev not being scanned/read (so we don't know the PVID the dev.)
* Disassociate the dev from the du. If wrong_devs are not paired to
* any du at the end, then those devs are cleared from lvmcache,
* since we don't want the command to see or use devs not included
* in the devices file.
*/
if (du->dev) {
dev = du->dev;
devname = dev_name(du->dev);
if ((scanned_devs && !device_list_find_dev(scanned_devs, du->dev)) ||
(du->dev->flags & DEV_SCAN_NOT_READ)) {
log_debug("Validate %s %s PVID %s on %s: not scanned",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".", devname);
} else {
log_debug("Validate %s %s PVID %s on %s: wrong PVID %s.",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".", devname, dev->pvid);
if ((devl = dm_pool_zalloc(cmd->mem, sizeof(*devl)))) {
devl->dev = du->dev;
dm_list_add(&wrong_devs, &devl->list);
}
cmd->device_ids_invalid = 1;
}
du->dev->flags &= ~DEV_MATCHED_USE_ID;
du->dev->id = NULL;
du->dev = NULL;
}
/*
* Find a new dev that matches du, using the devs that have
* been scanned for a label so far. The identity of this du is
* it's pvid, the dev is variable, so if another dev has this
* pvid, then reset all the du values to correspond to the new
* dev.
*/
if ((dev = dev_cache_get_by_pvid(cmd, du->pvid))) {
char *dup_devname1, *dup_devname2, *dup_devname3;
devname = dev_name(dev);
log_debug("Validate %s %s PVID %s: found on %s",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".", devname);
dup_devname1 = strdup(devname);
dup_devname2 = strdup(devname);
dup_devname3 = strdup(devname);
id = zalloc(sizeof(struct dev_id));
if (!dup_devname1 || !dup_devname2 || !dup_devname3 || !id) {
free(dup_devname1);
free(dup_devname2);
free(dup_devname3);
free(id);
stack;
continue;
}
free(du->idname);
free(du->devname);
free_dids(&dev->ids);
du->idname = dup_devname1;
du->devname = dup_devname2;
id->idname = dup_devname3;
du->dev = dev;
dev->id = id;
dev->flags |= DEV_MATCHED_USE_ID;
dm_list_add(&dev->ids, &id->list);
dev_get_partition_number(dev, &du->part);
update_file = 1;
cmd->device_ids_invalid = 1;
continue;
}
}
/*
* Each remaining du that's not matched to a dev (no du->dev set) is
* subject to device_ids_search which will look for unmatched pvids on
* devs that have not been scanned yet.
*/
dm_list_iterate_items(du, &cmd->use_devices) {
/*
* Only search for devname type entries unless the refresh
* trigger is set due to a machine change, in which case
* we look for missing PVIDs on new devs with real idtypes.
*/
if ((du->idtype != DEV_ID_TYPE_DEVNAME) && !cmd->device_ids_refresh_trigger)
continue;
if (!du->pvid)
continue;
if (du->dev)
continue;
log_debug("Validate %s %s PVID %s: no device found",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".");
}
/*
* For each du with no matching dev, if du->pvid is being used in
* another entry with a properly matching dev, then clear du->pvid.
*/
dm_list_iterate_items(du, &cmd->use_devices) {
if (du->idtype != DEV_ID_TYPE_DEVNAME)
continue;
if (!du->pvid)
continue;
if (du->dev)
continue;
dm_list_iterate_items(du2, &cmd->use_devices) {
if (du == du2)
continue;
if (du2->idtype != DEV_ID_TYPE_DEVNAME)
continue;
if (!du2->pvid)
continue;
if (!du2->dev)
continue;
if (memcmp(du->pvid, du2->pvid, ID_LEN))
continue;
/*
* du2 is correctly matched to a dev using this pvid,
* so drop the pvid from du.
* TODO: it would make sense to clear IDNAME, but
* can we handle entries with no IDNAME?
*/
log_debug("Validate %s %s PVID %s: no device found, remove incorrect PVID",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".");
free(du->pvid);
free(du->devname);
du->pvid = NULL;
du->devname = NULL;
update_file = 1;
cmd->device_ids_invalid = 1;
break;
}
}
/*
* devs that were wrongly matched to a du and are not being
* used in another correct du should be dropped.
*/
dm_list_iterate_items(devl, &wrong_devs) {
if (!get_du_for_dev(cmd, devl->dev)) {
log_debug("Drop incorrectly matched %s", dev_name(devl->dev));
cmd->filter->wipe(cmd, cmd->filter, devl->dev, NULL);
lvmcache_del_dev(devl->dev);
}
}
/*
* When dev names change and a PVID is found on a new device, there
* could be an another devname entry with the same device name but a
* blank PVID, which we remove here.
*/
dm_list_iterate_items(du, &cmd->use_devices) {
if (du->idtype != DEV_ID_TYPE_DEVNAME)
continue;
if (!du->idname)
continue;
if (!du->pvid)
continue;
if (!du->dev)
continue;
dm_list_iterate_items(du2, &cmd->use_devices) {
if (du == du2)
continue;
if (du2->idtype != DEV_ID_TYPE_DEVNAME)
continue;
if (!du2->idname)
continue;
if (strcmp(du->idname, du2->idname))
continue;
if (!du2->pvid) {
log_debug("Validate %s %s PVID none: remove entry with repeated devname",
idtype_to_str(du2->idtype), du2->idname ?: ".");
dm_list_del(&du2->list);
free_du(du2);
update_file = 1;
cmd->device_ids_invalid = 1;
}
break;
}
}
/*
* Set invalid if an entry using IDNAME=devname has not
* been matched to a device. It's possible that the device
* with the PVID has a new name, different from the IDNAME
* value. device_ids_search needs to search system devs
* for the PVID. The same applies when the IDNAME field
* has no value.
*/
dm_list_iterate_items(du, &cmd->use_devices) {
if (cmd->device_ids_invalid)
break;
/* FIXME: we shouldn't be setting idname to '.' so that check should be unnecessary */
if (!du->idname || (du->idname[0] == '.')) {
log_debug("Validate %s %s PVID %s: no idname, set invalid.",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".");
cmd->device_ids_invalid = 1;
}
if ((du->idtype == DEV_ID_TYPE_DEVNAME) && !du->dev && du->pvid) {
log_debug("Validate %s %s PVID %s: no device for idtype devname, set invalid.",
idtype_to_str(du->idtype), du->idname ?: ".", du->pvid ?: ".");
cmd->device_ids_invalid = 1;
}
}
/*
* When info in the devices file has become incorrect,
* try another search for PVIDs on renamed devices.
*/
if (update_file)
unlink_searched_devnames(cmd);
if (update_file && update_needed)
*update_needed = 1;
/* FIXME: for wrong devname cases, wait to write new until device_ids_search? */
/*
* If an update is needed and allowed, then try lock and
* device_ids_write(). The update is not required and will be done by a
* subsequent command if it's not done here.
*/
if (update_file) {
if (noupdate)
log_debug("Validated device ids: invalid=%d, update disabled.", cmd->device_ids_invalid);
else {
log_debug("Validated device ids: invalid=%d, trying to update devices file.", cmd->device_ids_invalid);
_device_ids_update_try(cmd);
}
} else if (cmd->devices_file_hash_mismatch) {
/*
* The file was edited externally since lvm last wrote it, so the hash should be
* updated and the file backed up.
*/
if (noupdate)
log_debug("Validated device ids: hash mismatch, update disabled.");
else {
log_debug("Validated device ids: hash mismatch, trying to update devices file.");
_device_ids_update_try(cmd);
}
} else {
log_debug("Validated device ids: invalid=%d, no update needed.", cmd->device_ids_invalid);
}
/*
* label_scan can use hints to scan only the devs for a specific
* VG as an optimization. If that limited subset of devs were
* all matched properly in the devices file, then override
* device_ids_invalid which may be set due to other entries
* not being matched, which this command doesn't care about.
*/
if (using_hints && scanned_devs) {
int found_scanned = 1;
dm_list_iterate_items(devl, scanned_devs) {
du = get_du_for_dev(cmd, devl->dev);
if (du && !memcmp(du->pvid, devl->dev->pvid, ID_LEN))
continue;
found_scanned = 0;
break;
}
if (found_scanned && cmd->device_ids_invalid) {
log_debug("Override device_ids_invalid for complete hints.");
cmd->device_ids_invalid = 0;
}
}
}
/*
* Validate entries with suspect sys_serial values. A sys_serial du (devices
* file entry) matched a device with the same serial number, but the PVID did
* not match. Check if multiple devices have the same serial number, and if so
* pair the devs to the du's based on PVID. This requires searching all devs
* for the given serial number, and then reading the PVID from all those devs.
* This may involve reading labels from devs outside the devices file.
* (This could also be done for duplicate wwids if needed.)
*/
void device_ids_check_serial(struct cmd_context *cmd, struct dm_list *scan_devs,
int noupdate, int *update_needed)
{
struct dm_list dus_check; /* dev_use_list */
struct dm_list devs_check; /* device_list */
struct dm_list prev_devs; /* device_id_list */
struct dev_use_list *dul;
struct device_list *devl, *devl2;
struct device_id_list *dil;
struct device *dev;
struct dev_use *du;
char *tmpdup;
int update_file = 0;
int has_pvid;
int found;
int count;
int err;
dm_list_init(&dus_check);
dm_list_init(&devs_check);
dm_list_init(&prev_devs);
/*
* Create list of du's with a suspect serial number. These du's will
* be rematched to a device using pvid. The device_ids_check_serial
* list was created by device_ids_validate() when it found that the
* PVID on the dev did not match the PVID in the du that was paired
* with the dev.
*/
dm_list_iterate_items(du, &cmd->use_devices) {
if (du->dev && du->idname && (du->idtype == DEV_ID_TYPE_SYS_SERIAL) &&
str_list_match_item(&cmd->device_ids_check_serial, du->idname)) {
if (!(dul = dm_pool_zalloc(cmd->mem, sizeof(*dul))))
continue;
dul->du = du;
dm_list_add(&dus_check, &dul->list);
}
}
/*
* Create list of devs on the system with suspect serial numbers.
* Read the serial number of each dev in dev cache, and return
* devs that match the suspect serial numbers.
*/
log_debug("Finding all devs with suspect serial numbers.");
_get_devs_with_serial_numbers(cmd, &cmd->device_ids_check_serial, &devs_check);
/*
* Read the PVID from any devs_check entries that have not been scanned
* yet (this is where some devs outside the devices file may be read.)
* If the dev has no PVID or is excluded by filters, then there's no
* point in trying to match it to one of the dus_check entries.
*/
log_debug("Reading and filtering %d devs with suspect serial numbers.", dm_list_size(&devs_check));
dm_list_iterate_items_safe(devl, devl2, &devs_check) {
const char *idname;
if (!(idname = _dev_idname(devl->dev, DEV_ID_TYPE_SYS_SERIAL))) {
log_debug("serial missing for %s", dev_name(devl->dev));
continue;
}
if (devl->dev->flags & DEV_SCAN_FOUND_LABEL) {
log_debug("serial %s pvid %s %s", idname, devl->dev->pvid, dev_name(devl->dev));
continue;
}
if (devl->dev->flags & DEV_SCAN_FOUND_NOLABEL) {
log_debug("serial %s nolabel %s", idname, dev_name(devl->dev));
continue;
}
dev = devl->dev;
has_pvid = 0;
err = label_read_pvid(dev, &has_pvid);
if (!err || !has_pvid) {
log_debug("serial %s no pvid %s", idname, dev_name(devl->dev));
dm_list_del(&devl->list);
continue;
}
/* data-based filters use data read by label_read_pvid */
if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "partitioned") ||
!cmd->filter->passes_filter(cmd, cmd->filter, dev, "signature") ||
!cmd->filter->passes_filter(cmd, cmd->filter, dev, "md") ||
!cmd->filter->passes_filter(cmd, cmd->filter, dev, "fwraid")) {
log_debug("serial %s pvid %s filtered %s", idname, devl->dev->pvid, dev_name(devl->dev));
dm_list_del(&devl->list);
}
}
log_debug("Checking %d PVs with suspect serial numbers.", dm_list_size(&devs_check));
/*
* Unpair du's and dev's that were matched using suspect serial numbers
* so that things can be matched again using PVID. If current pairings
* are correct they will just be matched again. Save the previous
* pairings so that we can detect when a wrong pairing was corrected.
*/
dm_list_iterate_items(dul, &dus_check) {
if (!dul->du->dev)
continue;
if (!dul->du->pvid)
continue;
/* save previously matched devs so they can be dropped from
lvmcache at the end if they are no longer used */
if (!(dil = dm_pool_zalloc(cmd->mem, sizeof(*dil))))
continue;
du = dul->du;
dil->dev = du->dev;
memcpy(dil->pvid, du->pvid, ID_LEN);
dm_list_add(&prev_devs, &dil->list);
du->dev->flags &= ~DEV_MATCHED_USE_ID;
du->dev = NULL;
}
/*
* Match du to a dev based on PVID.
*/
dm_list_iterate_items(dul, &dus_check) {
if (!dul->du->pvid)
continue;
log_debug("Matching suspect serial device id %s PVID %s prev %s",
dul->du->idname, dul->du->pvid, dul->du->devname);
found = 0;
dm_list_iterate_items(devl, &devs_check) {
if (!memcmp(dul->du->pvid, devl->dev->pvid, ID_LEN)) {
/* pair dev and du */
du = dul->du;
dev = devl->dev;
du->dev = dev;
dev->flags |= DEV_MATCHED_USE_ID;
log_debug("Match suspect serial device id %s PVID %s to %s",
du->idname, du->pvid, dev_name(dev));
/* update file if this dev pairing is new or different */
if (!(dil = device_id_list_find_dev(&prev_devs, dev)))
update_file = 1;
else if (memcmp(dil->pvid, du->pvid, ID_LEN))
update_file = 1;
found = 1;
break;
}
}
if (!found)
log_debug("Match PVID failed in %d devs checked.", dm_list_size(&devs_check));
}
/*
* Handle du's with suspect serial numbers that did not have a match
* based on PVID in the previous loop. If the du matches a device
* based on the serial number, and there is only one instance of that
* serial number on the system, then assume that the PVID in the
* devices file is outdated and pair the du and dev, and update the
* PVID in the devices file. (This is what's done for du and dev with
* matching wwid but unmatching PVID.)
*/
dm_list_iterate_items(dul, &dus_check) {
du = dul->du;
/* matched in previous loop using pvid */
if (du->dev)
continue;
log_debug("Matching suspect serial device id %s unmatched PVID %s prev %s",
du->idname, du->pvid, du->devname);
dev = NULL;
count = 0;
/* count the number of devs using this serial number */
dm_list_iterate_items(devl, &devs_check) {
if (_dev_has_id(devl->dev, DEV_ID_TYPE_SYS_SERIAL, du->idname)) {
dev = devl->dev;
count++;
}
if (count > 1)
break;
}
if (count != 1) {
log_debug("No device matches devices file PVID %s with duplicate serial number %s previously %s.",
du->pvid, du->idname, du->devname);
continue;
}
log_debug("Device %s with serial number %s has PVID %s (devices file %s)",
dev_name(dev), du->idname, dev->pvid, du->pvid ?: "none");
if (!(tmpdup = strdup_pvid(dev->pvid)))
continue;
free(du->pvid);
du->pvid = tmpdup;
du->dev = dev;
dev->flags |= DEV_MATCHED_USE_ID;
update_file = 1;
}
/*
* label_scan() was done based on the original du/dev matches, so if
* there were some changes made to the du/dev matches above, then we
* may need to correct the results of the label_scan:
*
* . if some devices were scanned in label_scan, but those devs are no
* longer matched to any du, then we need to clear the scanned info
* from those devs from lvmcache.
*
* . if some devices were not scanned in label_scan, but those devs are
* now matched to a du, then we need to run label_scan on those devs to
* populate lvmcache with info from them (the caller does this.)
*/
/*
* Find devs that were previously matched to a du but now are not.
* Clear the filter state and lvmcache info for them.
*/
dm_list_iterate_items(dil, &prev_devs) {
if (!get_du_for_dev(cmd, dil->dev)) {
log_debug("Drop incorrectly matched serial %s", dev_name(dil->dev));
cmd->filter->wipe(cmd, cmd->filter, dil->dev, NULL);
lvmcache_del_dev(dil->dev);
}
}
/*
* Find devs that are now matched to a du but were not previously
* scanned by label_scan (DEV_SCAN_FOUND_LABEL). The caller will
* call label_scan on the devs returned in the list.
*/
dm_list_iterate_items(dul, &dus_check) {
if (!(dev = dul->du->dev))
continue;
if (!(dev->flags & DEV_SCAN_FOUND_LABEL)) {
if (!(devl = dm_pool_zalloc(cmd->mem, sizeof(*devl))))
continue;
devl->dev = dev;
dm_list_add(scan_devs, &devl->list);
}
}
/*
* Look for dus_check entries that were originally matched to a dev
* but now are not. Warn about these like device_ids_match() would.
*/
dm_list_iterate_items(dul, &dus_check) {
if (!dul->du->dev) {
du = dul->du;
log_debug("Devices file %s %s PVID %s not found.",
idtype_to_str(du->idtype),
du->idname ?: "none",
du->pvid ?: "none");
if (du->devname) {
free(du->devname);
du->devname = NULL;
update_file = 1;
}
}
}
if (update_file && update_needed)
*update_needed = 1;
if (update_file && !noupdate)
_device_ids_update_try(cmd);
}
/*
* Devices with IDNAME=devname that are mistakenly included by filter-deviceid
* due to a devname change are fully scanned and added to lvmcache.
* device_ids_validate() catches this by seeing that the pvid on the device
* doesn't match what's in the devices file, and then excludes the dev, and
* drops the lvmcache info for the dev. It would be nicer to catch the issue
* earlier, before the dev is fully scanned (and populated in lvmcache). This
* could be done by checking the devices file for the pvid right after the dev
* header is read and before scanning more metadata. label_scan could read the
* pvid from the pv_header and check it prior to calling _text_read().
* Currently it's _text_read() that first gets the pvid from the dev, and
* passes it to lvmcache_add() which sets it in dev->pvid.
*
* This function searches devs for missing PVIDs, and for those found
* updates the du structs (devices file entries) and writes an updated
* devices file.
*
* TODO: should we disable find_renamed_devs entirely when the command
* is using a non-system devices file?
*/
void device_ids_search(struct cmd_context *cmd, struct dm_list *new_devs,
int all_ids, int noupdate, int *update_needed)
{
struct device *dev;
struct dev_use *du;
struct dev_id *id;
struct dev_iter *iter;
struct device_list *devl; /* holds struct device */
struct device_id_list *dil, *dil2; /* holds struct device + pvid */
struct dm_list search_pvids; /* list of device_id_list */
struct dm_list search_devs; /* list of device_list */
const char *devname;
int update_file = 0;
int found = 0;
int not_found = 0;
int search_mode_none;
int search_mode_auto;
int search_mode_all;
int search_pvids_count = 0;
int search_devs_count = 0;
uint32_t search_pvids_hash = INITIAL_CRC;
uint32_t search_devs_hash = INITIAL_CRC;
dm_list_init(&search_pvids);
dm_list_init(&search_devs);
if (!cmd->enable_devices_file)
return;
/*
* When the product_uuid/hostname change (refresh_trigger is set), or
* when --refresh is included with lvmdevices --check|--update (all_ids
* is set), this function expands from correcting renamed IDTYPE=devname
* entries to looking for missing PVIDs with any IDTYPE, and assigning new
* IDTYPE/IDNAME values for a PVID entry if it's found elsewhere.
* (e.g. a PVID that has moved to a device with a new wwid.) We require
* the --refresh option with update|check because otherwise a PVID may
* be picked up from an old cloned/snapshotted device, and lvm would
* begin using that old clone rather than the actual PV.
*
* Note: refresh_trigger=1 means that product_uuid/hostname has changed,
* which means that the devices file should be updated with that new
* value, even if no device ids need updates themselves.
* With --refresh (all_ids=1), an update may not be needed at all.
*/
if (cmd->device_ids_refresh_trigger || all_ids) {
search_mode_all = 1;
search_mode_none = 0;
search_mode_auto = 0;
} else {
search_mode_all = !strcmp(cmd->search_for_devnames, "all");
search_mode_none = !strcmp(cmd->search_for_devnames, "none");
search_mode_auto = !strcmp(cmd->search_for_devnames, "auto");
}
/*
* Create search_pvids which is a list of PVIDs that
* we want to locate on some device.
*/
dm_list_iterate_items(du, &cmd->use_devices) {
if (!du->pvid)
continue;
if (du->dev)
continue;
/*
* When device_ids_refresh_trigger/all_ids is set, it means
* that a PVID may be relocated to a new device, even when the
* entry and/or device have a stable id type, like wwid.
* Ordinarily, we assume that only entries using the devname
* id type will need to be located on new devices.
*/
if (!cmd->device_ids_refresh_trigger && !all_ids &&
(du->idtype != DEV_ID_TYPE_DEVNAME))
continue;
log_debug("Search for PVID %s.", du->pvid);
if (search_mode_none)
continue;
if (!(dil = dm_pool_zalloc(cmd->mem, sizeof(*dil))))
continue;
memcpy(dil->pvid, du->pvid, ID_LEN);
dm_list_add(&search_pvids, &dil->list);
search_pvids_count++;
search_pvids_hash = calc_crc(search_pvids_hash, (const uint8_t *)du->pvid, ID_LEN);
}
/* No unmatched PVIDs to search for, and no system id to update. */
if (dm_list_empty(&search_pvids) && !cmd->device_ids_refresh_trigger)
return;
log_debug("Search for PVIDs %d trigger %d all_ids %d search all %d auto %d none %d",
dm_list_size(&search_pvids), cmd->device_ids_refresh_trigger, all_ids,
search_mode_all, search_mode_auto, search_mode_none);
if (dm_list_empty(&search_pvids) && cmd->device_ids_refresh_trigger) {
update_file = 1;
goto out;
}
/*
* Now we want to look at devs on the system that were previously
* rejected by filter-deviceid (based on a devname device id) to check
* if the missing PVID is on a device with a new name.
*/
log_debug("Search for PVIDs filtering.");
/*
* Initial list of devs to search, eliminating any that have already
* been matched, or don't pass filters that do not read dev. We do not
* want to modify the command's existing filter chain (the persistent
* filter), in the process of doing this search outside the deviceid
* filter.
*/
if (!(iter = dev_iter_create(NULL, 0)))
return;
while ((dev = dev_iter_get(cmd, iter))) {
if (dev->flags & DEV_MATCHED_USE_ID)
continue;
if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "sysfs"))
continue;
if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "type"))
continue;
if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "usable"))
continue;
if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "mpath"))
continue;
if (!(devl = dm_pool_zalloc(cmd->mem, sizeof(*devl))))
continue;
devl->dev = dev;
dm_list_add(&search_devs, &devl->list);
search_devs_count++;
search_devs_hash = calc_crc(search_devs_hash, (const uint8_t *)&devl->dev->dev, sizeof(dev_t));
}
dev_iter_destroy(iter);
/*
* A previous command searched for devnames and found nothing, so it
* created the searched file to tell us not to bother. Without this, a
* device that's permanently detached (and identified by devname) would
* cause every command to search for it. If the detached device is
* later attached, it will generate a pvscan, and pvscan will unlink
* the searched file, so a subsequent lvm command will do the search
* again. In future perhaps we could add a policy to automatically
* remove a devices file entry that's not been found for some time.
*/
if (!cmd->device_ids_refresh_trigger && !all_ids &&
_searched_devnames_exists(cmd, search_pvids_count, search_pvids_hash,
search_devs_count, search_devs_hash)) {
log_debug("Search for PVIDs skipped for matching %s", _searched_file);
return;
}
log_debug("Search for PVIDs reading labels.");
/*
* Read the dev to get the pvid, and run the filters that will use the
* data that has been read to get the pvid. Like above, we do not want
* to modify the command's existing filter chain or the persistent
* filter values.
*/
dm_list_iterate_items(devl, &search_devs) {
int has_pvid;
dev = devl->dev;
/*
* As an optimization for locating new devs for IDTYPE=devname
* entries, we can just check devs that would also use
* ID_TYPE_DEVNAME themselves. i.e. a ID_TYPE_DEVNAME entry
* would not appear on a device that has a wwid. So, if a
* dev in search_list_devs has a proper/stable device id
* (e.g. wwid, serial, loop, mpath), then we don't need to
* read it to check for missing PVIDs.
*
* search_for_devnames="all" means we should search every
* device, so we skip this optimization.
*
* TODO: in auto mode should we look in other non-system
* devices files and skip any devs included in those?
*
* Note that a user can override a stable id type and use
* devname for a device's id, in which case this optimization
* can prevent a search from finding a renamed dev. So, if a
* user forces a devname id, then they should probably also
* set search_for_devnames=all.
*/
if (search_mode_auto && _dev_has_stable_id(cmd, dev)) {
log_debug("Search for PVIDs skip %s (stable id)", dev_name(dev));
continue;
}
log_debug("Search for PVIDs on %s", dev_name(dev));
/*
* Reads 4K from the start of the disk.
* Returns 0 if the dev cannot be read.
* Looks for LVM header, and sets dev->pvid if the device is a PV.
* Sets has_pvid=1 if the dev has an lvm PVID.
* This loop may look at and skip many non-LVM devices.
*/
if (!label_read_pvid(dev, &has_pvid))
continue;
if (!has_pvid)
continue;
/*
* These filters will use the block of data from bcache that
* was read label_read_pvid(), and may read other
* data blocks beyond that.
*/
if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "partitioned"))
goto next;
if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "signature"))
goto next;
if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "md"))
goto next;
if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "fwraid"))
goto next;
/*
* Check if the the PVID returned from label_read is one we are looking for.
* The loop below looks at search_pvids entries that have dil->dev set.
* This loop continues checking after all search_pvids entries have been
* matched in order to check if the PVID is on duplicate devs.
*/
dm_list_iterate_items_safe(dil, dil2, &search_pvids) {
if (!memcmp(dil->pvid, dev->pvid, ID_LEN)) {
if (dil->dev) {
log_warn("WARNING: found PVID %s on multiple devices %s %s.",
dil->pvid, dev_name(dil->dev), dev_name(dev));
log_warn("WARNING: duplicate PVIDs should be changed to be unique.");
log_warn("WARNING: use lvmdevices to select a device for PVID %s.", dil->pvid);
dm_list_del(&dil->list);
} else {
log_debug("Search for PVID %s found on %s.", dil->pvid, dev_name(dev));
dil->dev = dev;
}
}
}
next:
label_scan_invalidate(dev);
}
/*
* The use_devices entries (repesenting the devices file) are
* updated for the new devices on which the PVs reside. The new
* correct devs are set as dil->dev on search_pvids entries.
*
* The du/dev/id are set up and linked for the new devs.
*
* The command's full filter chain is updated for the new devs now that
* filter-deviceid will pass.
*/
dm_list_iterate_items(dil, &search_pvids) {
char *new_idname, *new_idname2, *new_devname;
uint16_t new_idtype;
if (!dil->dev || dm_list_empty(&dil->dev->aliases)) {
not_found++;
continue;
}
dev = dil->dev;
devname = dev_name(dev);
found++;
if (!(du = get_du_for_pvid(cmd, dil->pvid))) {
/* shouldn't happen */
continue;
}
new_idtype = 0;
new_idname = NULL;
new_idname2 = NULL;
new_devname = NULL;
if (cmd->device_ids_refresh_trigger || all_ids) {
if (!device_id_system_read_preferred(cmd, dev, &new_idtype, (const char **)&new_idname))
continue;
new_idname2 = strdup(new_idname);
new_devname = strdup(devname);
log_print_unless_silent("Devices file PVID %s has new device ID %s %s from %s.",
du->pvid ?: "", idtype_to_str(new_idtype), new_idname ?: "", devname);
} else {
/* Use the new device name as the new idname. */
new_idtype = DEV_ID_TYPE_DEVNAME;
new_idname = strdup(devname);
new_idname2 = strdup(devname);
new_devname = strdup(devname);
log_debug("Found new device name %s for PVID %s.", devname, du->pvid ?: "");
}
id = zalloc(sizeof(struct dev_id));
if (!id || !new_devname || !new_idname || !new_idname2) {
free(id);
free(new_idname);
free(new_idname2);
free(new_devname);
stack;
continue;
}
free(du->idname);
free(du->devname);
free_dids(&dev->ids);
du->idtype = new_idtype;
du->idname = new_idname;
du->devname = new_devname;
du->dev = dev;
id->idtype = new_idtype;
id->idname = new_idname2;
dev->id = id;
dev->flags |= DEV_MATCHED_USE_ID;
dm_list_add(&dev->ids, &id->list);
dev_get_partition_number(dev, &du->part);
update_file = 1;
}
dm_list_iterate_items(dil, &search_pvids) {
if (!dil->dev)
continue;
dev = dil->dev;
cmd->filter->wipe(cmd, cmd->filter, dev, NULL);
if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, NULL)) {
/* I don't think this would happen */
log_warn("WARNING: new device %s for PVID %s is excluded: %s.",
dev_name(dev), dil->pvid, dev_filtered_reason(dev));
if (du) /* Should not happen 'du' is NULL */
du->dev = NULL;
dev->flags &= ~DEV_MATCHED_USE_ID;
}
}
out:
/*
* try lock and device_ids_write(), the update is not required and will
* be done by a subsequent command if it's not done here.
*
* This command could have already done an earlier device_ids_update_try
* (successfully or not) in device_ids_validate().
*/
if (update_file && noupdate) {
log_debug("Search for PVIDs update disabled");
} else if (update_file) {
log_debug("Search for PVIDs updating devices file");
_device_ids_update_try(cmd);
} else {
log_debug("Search for PVIDs found no updates");
}
if (update_file && update_needed)
*update_needed = 1;
/*
* The entries in search_pvids with a dev set are the new devs found
* for the PVIDs that we want to return to the caller in a device_list
* format.
*/
dm_list_iterate_items(dil, &search_pvids) {
if (!dil->dev)
continue;
dev = dil->dev;
if (!(devl = dm_pool_zalloc(cmd->mem, sizeof(*devl))))
continue;
devl->dev = dev;
dm_list_add(new_devs, &devl->list);
}
/*
* Prevent more devname searches by subsequent commands, in case the
* pvids not found were from devices that are permanently detached. If
* a new PV appears, pvscan will run and do unlink_searched_file.
* Also, if the hints code detects that the hints file becomes invalid
* due to new system devs, then searched_devnames is also unlinked.
* So, the searched_devnames temp file should not prevent a missing
* device from being found if it's attached later.
*
* Any lvmdevices command removes searched_devnames temp file prior to
* running, and don't create the temp file from any lvmdevices command;
* this is not among the commands we want to optimize.
*
* Note: the searched_devnames temp file only suppresses searches for
* missing PVIDs with IDTYPE=devname that may have a new device name.
* It does not suppress searches for missing PVIDs when done for
* refresh, where PVIDs of any idtype are searched for.
*/
if (!cmd->device_ids_refresh_trigger && !all_ids && not_found && !found &&
strcmp(cmd->name, "lvmdevices"))
_searched_devnames_create(cmd, search_pvids_count, search_pvids_hash,
search_devs_count, search_devs_hash);
}
int devices_file_touch(struct cmd_context *cmd)
{
struct stat buf;
char dirpath[PATH_MAX];
int fd;
if (dm_snprintf(dirpath, sizeof(dirpath), "%s/devices", cmd->system_dir) < 0) {
log_error("Failed to copy devices dir path");
return 0;
}
if (stat(dirpath, &buf)) {
log_error("Cannot create devices file, missing devices directory %s.", dirpath);
return 0;
}
fd = open(cmd->devices_file_path, O_CREAT, S_IRUSR | S_IWUSR);
if (fd < 0) {
log_debug("Failed to create %s %d", cmd->devices_file_path, errno);
return 0;
}
if (close(fd))
stack;
return 1;
}
int devices_file_exists(struct cmd_context *cmd)
{
struct stat buf;
if (!cmd->devices_file_path[0])
return 0;
if (stat(cmd->devices_file_path, &buf))
return 0;
return 1;
}
/*
* If a command also uses the global lock, the global lock
* is acquired first, then the devices file is locked.
*
* There are three categories of commands in terms of
* reading/writing the devices file:
*
* 1. Commands that we know intend to modify the file,
* lvmdevices --add|--del, vgimportdevices,
* pvcreate/vgcreate/vgextend, pvchange --uuid,
* vgimportclone.
*
* 2. Most other commands that do not modify the file.
*
* 3. Commands from 2 that find something to correct in
* the devices file during device_ids_validate().
* These corrections are not essential and can be
* skipped, they will just be done by a subsequent
* command if they are not done.
*
* Locking for each case:
*
* 1. lock ex, read file, write file, unlock
*
* (In general, the command sets edit_devices_file or
* create_edit_devices_file, then setup_devices() is called,
* maybe directly, or by way of calling the traditional
* process_each->label_scan->setup_devices. setup_devices
* sees {create}_edit_devices_file which causes it to do
* lock_devices_file(EX) before creating/reading the file.)
*
* 2. lock sh, read file, unlock, (validate ok)
*
* 3. lock sh, read file, unlock, validate wants update,
* lock ex (nonblocking - skip update if fails),
* read file, check file is unchanged from prior read,
* write file, unlock
*/
static int _lock_devices_file(struct cmd_context *cmd, int mode, int nonblock, int *held)
{
const char *lock_dir;
const char *filename;
int fd;
int op = mode;
int ret;
if (!cmd->enable_devices_file || cmd->nolocking)
return 1;
_using_devices_file = 1;
if (_devices_file_locked == mode) {
/* can happen when a command holds an ex lock and does an update in device_ids_validate */
/* can happen when vgimportdevices calls this directly, followed later by setup_devices */
if (held)
*held = 1;
return 1;
}
if (_devices_file_locked) {
/* shouldn't happen */
log_warn("WARNING: devices file already locked %d", mode);
return 0;
}
if (!(lock_dir = find_config_tree_str(cmd, global_locking_dir_CFG, NULL)))
return_0;
if (!(filename = cmd->devicesfile ?: find_config_tree_str(cmd, devices_devicesfile_CFG, NULL)))
return_0;
if (dm_snprintf(_devices_lockfile, sizeof(_devices_lockfile), "%s/D_%s", lock_dir, filename) < 0)
return_0;
if (nonblock)
op |= LOCK_NB;
if (_devices_fd != -1) {
/* shouldn't happen */
log_warn("WARNING: devices file lock file already open %d", _devices_fd);
return 0;
}
fd = open(_devices_lockfile, O_CREAT|O_RDWR, S_IRUSR | S_IWUSR);
if (fd < 0) {
log_debug("lock_devices_file open errno %d", errno);
if (cmd->sysinit || cmd->ignorelockingfailure)
return 1;
return 0;
}
ret = flock(fd, op);
if (!ret) {
_devices_fd = fd;
_devices_file_locked = mode;
return 1;
}
log_debug("lock_devices_file flock errno %d", errno);
if (close(fd))
stack;
if (cmd->sysinit || cmd->ignorelockingfailure)
return 1;
return 0;
}
int lock_devices_file(struct cmd_context *cmd, int mode)
{
return _lock_devices_file(cmd, mode, 0, NULL);
}
int lock_devices_file_try(struct cmd_context *cmd, int mode, int *held)
{
return _lock_devices_file(cmd, mode, 1, held);
}
void unlock_devices_file(struct cmd_context *cmd)
{
int ret;
if (!cmd->enable_devices_file || cmd->nolocking || !_using_devices_file)
return;
if (!_devices_file_locked && cmd->sysinit)
return;
if (_devices_fd == -1) {
/* shouldn't happen */
log_warn("WARNING: devices file unlock no fd");
return;
}
if (!_devices_file_locked)
log_warn("WARNING: devices file unlock not locked");
ret = flock(_devices_fd, LOCK_UN);
if (ret)
log_warn("WARNING: devices file unlock errno %d", errno);
_devices_file_locked = 0;
if (close(_devices_fd))
stack;
_devices_fd = -1;
}
void devices_file_init(struct cmd_context *cmd)
{
dm_list_init(&cmd->use_devices);
dm_list_init(&cmd->device_ids_check_serial);
}
void devices_file_exit(struct cmd_context *cmd)
{
if (!cmd->enable_devices_file)
return;
free_dus(&cmd->use_devices);
if (_devices_fd == -1)
return;
unlock_devices_file(cmd);
}