mirror of
git://sourceware.org/git/lvm2.git
synced 2024-12-21 13:34:40 +03:00
devices file: back up each version
Create backup copies of system.devices in /etc/lvm/devices/backup named system.devices-YYYYMMDD.HHMMSS.NNNN. NNNN is the version counter from the file. Each time that an lvm command writes a new system.devices file, it also writes the same file in the backup directory. A new comment line is added to system.devices with HASH=<num> where <num> is a crc calculated from the uncommented lines in system.devices. This lets lvm detect if the file has been modified outside of lvm itself. If system.devices is edited directly, the next time a command reads the file, the crc will not match the HASH value. The command will then rewrite system.devices with the correct HASH value, and create a backup reflecting the edits. A default limit of 50 backup files is kept, configurable by lvm.conf devicesfile_backup_limit (set to 0 to disable backups.)
This commit is contained in:
parent
ee73875ff0
commit
e59027e4f7
@ -189,6 +189,8 @@ struct cmd_context {
|
|||||||
unsigned pvscan_recreate_hints:1; /* enable special case hint handling for pvscan --cache */
|
unsigned pvscan_recreate_hints:1; /* enable special case hint handling for pvscan --cache */
|
||||||
unsigned scan_lvs:1;
|
unsigned scan_lvs:1;
|
||||||
unsigned wipe_outdated_pvs:1;
|
unsigned wipe_outdated_pvs:1;
|
||||||
|
unsigned devices_file_hash_mismatch:1;
|
||||||
|
unsigned devices_file_hash_ignore:1;
|
||||||
unsigned enable_devices_list:1; /* command is using --devices option */
|
unsigned enable_devices_list:1; /* command is using --devices option */
|
||||||
unsigned enable_devices_file:1; /* command is using devices file */
|
unsigned enable_devices_file:1; /* command is using devices file */
|
||||||
unsigned pending_devices_file:1; /* command may create and enable devices file */
|
unsigned pending_devices_file:1; /* command may create and enable devices file */
|
||||||
|
@ -292,6 +292,14 @@ cfg(devices_devicesfile_CFG, "devicesfile", devices_CFG_SECTION, CFG_DEFAULT_COM
|
|||||||
"This should not be used to select a non-system devices file.\n"
|
"This should not be used to select a non-system devices file.\n"
|
||||||
"The --devicesfile option is intended for alternative devices files.\n")
|
"The --devicesfile option is intended for alternative devices files.\n")
|
||||||
|
|
||||||
|
cfg(devices_devicesfile_backup_limit_CFG, "devicesfile_backup_limit", devices_CFG_SECTION, CFG_DEFAULT_COMMENTED, CFG_TYPE_INT, DEFAULT_DEVICESFILE_BACKUP_LIMIT, vsn(2, 3, 23), NULL, 0, NULL,
|
||||||
|
"The max number of backup files to keep in /etc/lvm/devices/backup.\n"
|
||||||
|
"LVM creates a backup of the devices file each time a new\n"
|
||||||
|
"version is created, or each time a modification is detected.\n"
|
||||||
|
"When the max number of backups is reached, the oldest are\n"
|
||||||
|
"removed to remain at the limit. Set to 0 to disable backups.\n"
|
||||||
|
"Only the system devices file is backed up.\n")
|
||||||
|
|
||||||
cfg(devices_search_for_devnames_CFG, "search_for_devnames", devices_CFG_SECTION, CFG_DEFAULT_COMMENTED, CFG_TYPE_STRING, DEFAULT_SEARCH_FOR_DEVNAMES, vsn(2, 3, 12), NULL, 0, NULL,
|
cfg(devices_search_for_devnames_CFG, "search_for_devnames", devices_CFG_SECTION, CFG_DEFAULT_COMMENTED, CFG_TYPE_STRING, DEFAULT_SEARCH_FOR_DEVNAMES, vsn(2, 3, 12), NULL, 0, NULL,
|
||||||
"Look outside of the devices file for missing devname entries.\n"
|
"Look outside of the devices file for missing devname entries.\n"
|
||||||
"A devname entry is used for a device that does not have a stable\n"
|
"A devname entry is used for a device that does not have a stable\n"
|
||||||
|
@ -340,4 +340,6 @@
|
|||||||
|
|
||||||
#define DEFAULT_DEVICE_ID_SYSFS_DIR "/sys/" /* trailing / to match dm_sysfs_dir() */
|
#define DEFAULT_DEVICE_ID_SYSFS_DIR "/sys/" /* trailing / to match dm_sysfs_dir() */
|
||||||
|
|
||||||
|
#define DEFAULT_DEVICESFILE_BACKUP_LIMIT 50
|
||||||
|
|
||||||
#endif /* _LVM_DEFAULTS_H */
|
#endif /* _LVM_DEFAULTS_H */
|
||||||
|
@ -30,6 +30,8 @@
|
|||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <locale.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/file.h>
|
#include <sys/file.h>
|
||||||
#include <sys/sysmacros.h>
|
#include <sys/sysmacros.h>
|
||||||
@ -1167,6 +1169,9 @@ int device_ids_read(struct cmd_context *cmd)
|
|||||||
char *idtype, *idname, *devname, *pvid, *part;
|
char *idtype, *idname, *devname, *pvid, *part;
|
||||||
struct dev_use *du;
|
struct dev_use *du;
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
|
uint32_t comment_hash = 0;
|
||||||
|
uint32_t hash = INITIAL_CRC;
|
||||||
|
int ignore_hash = 0;
|
||||||
int line_error;
|
int line_error;
|
||||||
int product_uuid_found = 0;
|
int product_uuid_found = 0;
|
||||||
int hostname_found = 0;
|
int hostname_found = 0;
|
||||||
@ -1197,6 +1202,24 @@ int device_ids_read(struct cmd_context *cmd)
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (fgets(line, sizeof(line), fp)) {
|
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] == '#')
|
if (line[0] == '#')
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -1204,6 +1227,8 @@ int device_ids_read(struct cmd_context *cmd)
|
|||||||
if (!strncmp(line, "SYSTEMID", 8))
|
if (!strncmp(line, "SYSTEMID", 8))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
hash = calc_crc(hash, (uint8_t *)line, strlen(line));
|
||||||
|
|
||||||
if (!strncmp(line, "HOSTNAME", 8)) {
|
if (!strncmp(line, "HOSTNAME", 8)) {
|
||||||
_copy_idline_str(line, check_id, sizeof(check_id));
|
_copy_idline_str(line, check_id, sizeof(check_id));
|
||||||
log_debug("read devices file hostname %s", check_id);
|
log_debug("read devices file hostname %s", check_id);
|
||||||
@ -1318,6 +1343,13 @@ int device_ids_read(struct cmd_context *cmd)
|
|||||||
if (fclose(fp))
|
if (fclose(fp))
|
||||||
stack;
|
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) {
|
if (!product_uuid_found && cmd->device_ids_check_product_uuid) {
|
||||||
cmd->device_ids_refresh_trigger = 1;
|
cmd->device_ids_refresh_trigger = 1;
|
||||||
log_debug("Devices file refresh: missing product_uuid");
|
log_debug("Devices file refresh: missing product_uuid");
|
||||||
@ -1326,24 +1358,197 @@ int device_ids_read(struct cmd_context *cmd)
|
|||||||
cmd->device_ids_refresh_trigger = 1;
|
cmd->device_ids_refresh_trigger = 1;
|
||||||
log_debug("Devices file refresh: missing product_uuid and hostname");
|
log_debug("Devices file refresh: missing product_uuid and hostname");
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
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;
|
||||||
|
DIR *dir;
|
||||||
|
FILE *fp;
|
||||||
|
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];
|
||||||
|
uint32_t low_date, low_time, low_count;
|
||||||
|
uint32_t de_date, de_time, de_count;
|
||||||
|
unsigned int backup_limit, backup_count, 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)
|
||||||
|
return;
|
||||||
|
if (!dm_create_dir(dirpath))
|
||||||
|
return;
|
||||||
|
if ((dir = opendir(dirpath)) < 0)
|
||||||
|
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)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!(fp = fopen(path, "w+"))) {
|
||||||
|
log_error("Failed to create backup file %s", path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fputs(fc, fp) < 0) {
|
||||||
|
log_error("Failed to write backup file %s", path);
|
||||||
|
fclose(fp);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (fputs(fb, fp) < 0) {
|
||||||
|
log_error("Failed to write backup file %s", path);
|
||||||
|
fclose(fp);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (fflush(fp) < 0) {
|
||||||
|
log_error("Failed to write backup file %s", path);
|
||||||
|
fclose(fp);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (fsync(fileno(fp)) < 0) {
|
||||||
|
log_error("Failed to sync backup file %s", path);
|
||||||
|
fclose(fp);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (fclose(fp))
|
||||||
|
stack;
|
||||||
|
|
||||||
|
log_debug("Wrote backup %s", path);
|
||||||
|
|
||||||
|
/* Possibly remove old backup files per configurable limit on backup files. */
|
||||||
|
|
||||||
|
backup_count = 0;
|
||||||
|
low_date = 0;
|
||||||
|
low_time = 0;
|
||||||
|
low_count = 0;
|
||||||
|
memset(low_name, 0, sizeof(low_name)); /* oldest backup file name */
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
dir_fd = dirfd(dir);
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
unlinkat(dir_fd, low_name, 0);
|
||||||
|
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_error("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; i < sort_count; i++) {
|
||||||
|
if (remove_count) {
|
||||||
|
log_debug("Remove backup %s", namelist[i]->d_name);
|
||||||
|
unlinkat(dir_fd, namelist[i]->d_name, 0);
|
||||||
|
remove_count--;
|
||||||
|
}
|
||||||
|
free(namelist[i]);
|
||||||
|
}
|
||||||
|
free(namelist);
|
||||||
|
out:
|
||||||
|
closedir(dir);
|
||||||
|
}
|
||||||
|
|
||||||
int device_ids_write(struct cmd_context *cmd)
|
int device_ids_write(struct cmd_context *cmd)
|
||||||
{
|
{
|
||||||
char dirpath[PATH_MAX];
|
char dirpath[PATH_MAX];
|
||||||
char tmppath[PATH_MAX];
|
char tmppath[PATH_MAX];
|
||||||
char version_buf[VERSION_LINE_MAX] = {0};
|
char version_buf[VERSION_LINE_MAX] = {0};
|
||||||
FILE *fp;
|
char fc[1024]; /* devices file comments (buf of commented lines) */
|
||||||
int dir_fd;
|
char *fb = NULL; /* devices file contents (buf of uncommented lines) */
|
||||||
|
FILE *fp = NULL;
|
||||||
|
int dir_fd = -1;
|
||||||
time_t t;
|
time_t t;
|
||||||
struct dev_use *du;
|
struct dev_use *du;
|
||||||
const char *devname;
|
const char *devname;
|
||||||
const char *pvid;
|
const char *pvid;
|
||||||
uint32_t df_major = 0, df_minor = 0, df_counter = 0;
|
uint32_t df_major = 0, df_minor = 0, df_counter = 0;
|
||||||
|
uint32_t hash = 0;
|
||||||
|
int names_len = 0;
|
||||||
|
int len, num, pos;
|
||||||
|
int fb_size, fb_bytes, fc_bytes;
|
||||||
int file_exists;
|
int file_exists;
|
||||||
int ret = 1;
|
int ret = 0;
|
||||||
|
|
||||||
if (!cmd->enable_devices_file && !cmd->pending_devices_file)
|
if (!cmd->enable_devices_file && !cmd->pending_devices_file)
|
||||||
return 1;
|
return 1;
|
||||||
@ -1385,6 +1590,14 @@ int device_ids_write(struct cmd_context *cmd)
|
|||||||
cmd->enable_devices_file = 1;
|
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())
|
if (test_mode())
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
@ -1401,46 +1614,59 @@ int device_ids_write(struct cmd_context *cmd)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dm_snprintf(dirpath, sizeof(dirpath), "%s/devices", cmd->system_dir) < 0) {
|
if (dm_snprintf(dirpath, sizeof(dirpath), "%s/devices", cmd->system_dir) < 0)
|
||||||
ret = 0;
|
goto_out;
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dm_snprintf(tmppath, sizeof(tmppath), "%s_new", cmd->devices_file_path) < 0) {
|
if (dm_snprintf(tmppath, sizeof(tmppath), "%s_new", cmd->devices_file_path) < 0)
|
||||||
ret = 0;
|
goto_out;
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
(void) unlink(tmppath); /* in case a previous file was left */
|
(void) unlink(tmppath); /* in case a previous file was left */
|
||||||
|
|
||||||
if (!(fp = fopen(tmppath, "w+"))) {
|
if (!(fp = fopen(tmppath, "w+"))) {
|
||||||
log_warn("Cannot open tmp devices_file to write.");
|
log_warn("Cannot open to write %s.", tmppath);
|
||||||
ret = 0;
|
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((dir_fd = open(dirpath, O_RDONLY)) < 0) {
|
if ((dir_fd = open(dirpath, O_RDONLY)) < 0)
|
||||||
if (fclose(fp))
|
goto_out;
|
||||||
log_sys_debug("fclose", tmppath);
|
|
||||||
ret = 0;
|
/*
|
||||||
|
* 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;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
t = time(NULL);
|
len = fb_size;
|
||||||
|
pos = 0;
|
||||||
fprintf(fp, "# LVM uses devices listed in this file.\n");
|
|
||||||
fprintf(fp, "# Created by LVM command %s pid %d at %s", cmd->name, getpid(), ctime(&t));
|
|
||||||
|
|
||||||
/* if product_uuid is included, then hostname is unnecessary */
|
/* if product_uuid is included, then hostname is unnecessary */
|
||||||
if (cmd->product_uuid && cmd->device_ids_check_product_uuid)
|
if (cmd->product_uuid && cmd->device_ids_check_product_uuid)
|
||||||
fprintf(fp, "PRODUCT_UUID=%s\n", cmd->product_uuid);
|
num = snprintf(fb + pos, len - pos, "PRODUCT_UUID=%s\n", cmd->product_uuid);
|
||||||
else if (cmd->hostname && cmd->device_ids_check_hostname)
|
else if (cmd->hostname && cmd->device_ids_check_hostname)
|
||||||
fprintf(fp, "HOSTNAME=%s\n", cmd->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)
|
if (dm_snprintf(version_buf, VERSION_LINE_MAX, "VERSION=%u.%u.%u", DEVICES_FILE_MAJOR, DEVICES_FILE_MINOR, df_counter+1) < 0)
|
||||||
stack;
|
goto_out;
|
||||||
else
|
|
||||||
fprintf(fp, "%s\n", version_buf);
|
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 */
|
/* as if we had read this version in case we want to write again */
|
||||||
memset(_devices_file_version, 0, sizeof(_devices_file_version));
|
memset(_devices_file_version, 0, sizeof(_devices_file_version));
|
||||||
@ -1457,33 +1683,83 @@ int device_ids_write(struct cmd_context *cmd)
|
|||||||
pvid = du->pvid;
|
pvid = du->pvid;
|
||||||
|
|
||||||
if (du->part) {
|
if (du->part) {
|
||||||
fprintf(fp, "IDTYPE=%s IDNAME=%s DEVNAME=%s PVID=%s PART=%d\n",
|
num = snprintf(fb + pos, len - pos, "IDTYPE=%s IDNAME=%s DEVNAME=%s PVID=%s PART=%d\n",
|
||||||
idtype_to_str(du->idtype) ?: ".",
|
idtype_to_str(du->idtype) ?: ".",
|
||||||
du->idname ?: ".", devname, pvid, du->part);
|
du->idname ?: ".", devname, pvid, du->part);
|
||||||
} else {
|
} else {
|
||||||
fprintf(fp, "IDTYPE=%s IDNAME=%s DEVNAME=%s PVID=%s\n",
|
num = snprintf(fb + pos, len - pos, "IDTYPE=%s IDNAME=%s DEVNAME=%s PVID=%s\n",
|
||||||
idtype_to_str(du->idtype) ?: ".",
|
idtype_to_str(du->idtype) ?: ".",
|
||||||
du->idname ?: ".", devname, pvid);
|
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 (fflush(fp))
|
if (!cmd->devices_file_hash_ignore)
|
||||||
stack;
|
hash = calc_crc(INITIAL_CRC, (const uint8_t *)fb, fb_bytes);
|
||||||
if (fclose(fp))
|
|
||||||
stack;
|
t = time(NULL);
|
||||||
|
|
||||||
|
num = snprintf(fc, sizeof(fc),
|
||||||
|
"# LVM uses devices listed in this file.\n" \
|
||||||
|
"# Created by LVM command %s pid %d at %s" \
|
||||||
|
"# HASH=%u\n",
|
||||||
|
cmd->name, getpid(), ctime(&t), hash);
|
||||||
|
|
||||||
|
if (num >= sizeof(fc)) {
|
||||||
|
log_error("Failed to write buffer for devices file content.");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
fc_bytes = num;
|
||||||
|
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)
|
||||||
|
goto_out;
|
||||||
|
|
||||||
|
fp = NULL;
|
||||||
|
|
||||||
if (rename(tmppath, cmd->devices_file_path) < 0) {
|
if (rename(tmppath, cmd->devices_file_path) < 0) {
|
||||||
log_error("Failed to replace devices file errno %d", errno);
|
log_error("Failed to replace devices file.");
|
||||||
ret = 0;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fsync(dir_fd) < 0)
|
if (fsync(dir_fd) < 0)
|
||||||
stack;
|
stack;
|
||||||
if (close(dir_fd) < 0)
|
if (close(dir_fd) < 0)
|
||||||
stack;
|
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);
|
||||||
|
|
||||||
log_debug("Wrote devices file %s", version_buf);
|
|
||||||
out:
|
out:
|
||||||
|
if (fb)
|
||||||
|
free(fb);
|
||||||
|
if (fp)
|
||||||
|
fclose(fp);
|
||||||
|
if (dir_fd > 0)
|
||||||
|
close(dir_fd);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3005,14 +3281,29 @@ void device_ids_validate(struct cmd_context *cmd, struct dm_list *scanned_devs,
|
|||||||
/* FIXME: for wrong devname cases, wait to write new until device_ids_search? */
|
/* FIXME: for wrong devname cases, wait to write new until device_ids_search? */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* try lock and device_ids_write(), the update is not required and will
|
* If an update is needed and allowed, then try lock and
|
||||||
* be done by a subsequent command if it's not done here.
|
* 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 && noupdate) {
|
|
||||||
log_debug("Validated device ids: invalid=%d, update disabled.", cmd->device_ids_invalid);
|
if (update_file) {
|
||||||
} else if (update_file) {
|
if (noupdate)
|
||||||
log_debug("Validated device ids: invalid=%d, trying to update devices file.", cmd->device_ids_invalid);
|
log_debug("Validated device ids: invalid=%d, update disabled.", cmd->device_ids_invalid);
|
||||||
_device_ids_update_try(cmd);
|
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 {
|
} else {
|
||||||
log_debug("Validated device ids: invalid=%d, no update needed.", cmd->device_ids_invalid);
|
log_debug("Validated device ids: invalid=%d, no update needed.", cmd->device_ids_invalid);
|
||||||
}
|
}
|
||||||
|
109
test/shell/devicesfile-backup.sh
Normal file
109
test/shell/devicesfile-backup.sh
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Copyright (C) 2020 Red Hat, Inc. All rights reserved.
|
||||||
|
#
|
||||||
|
# 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 General Public License v.2.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU 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
|
||||||
|
|
||||||
|
test_description='devices file backups'
|
||||||
|
|
||||||
|
SKIP_WITH_LVMPOLLD=1
|
||||||
|
|
||||||
|
. lib/inittest
|
||||||
|
|
||||||
|
aux prepare_devs 3
|
||||||
|
|
||||||
|
aux lvmconf 'devices/use_devicesfile = 1'
|
||||||
|
|
||||||
|
# Stupid tests use plain /etc/ rather than /etc/lvm/
|
||||||
|
DFDIR="$LVM_SYSTEM_DIR/devices"
|
||||||
|
BKDIR="$LVM_SYSTEM_DIR/devices/backup"
|
||||||
|
mkdir -p "$DFDIR" || true
|
||||||
|
mkdir -p "$BKDIR" || true
|
||||||
|
DF="$DFDIR/system.devices"
|
||||||
|
|
||||||
|
vgcreate $vg "$dev1"
|
||||||
|
diff $DF $BKDIR/*.0001
|
||||||
|
|
||||||
|
pvcreate "$dev2"
|
||||||
|
diff $DF $BKDIR/*.0002
|
||||||
|
|
||||||
|
lvmdevices --deldev "$dev2"
|
||||||
|
diff $DF $BKDIR/*.0003
|
||||||
|
|
||||||
|
lvmdevices --adddev "$dev2"
|
||||||
|
diff $DF $BKDIR/*.0004
|
||||||
|
|
||||||
|
# DF update and backup when an entry is manually removed
|
||||||
|
cat $DF | grep -v "$dev2" > tmp1
|
||||||
|
cp tmp1 $DF
|
||||||
|
pvs
|
||||||
|
diff $DF $BKDIR/*.0005
|
||||||
|
|
||||||
|
lvmdevices --adddev "$dev2"
|
||||||
|
diff $DF $BKDIR/*.0006
|
||||||
|
|
||||||
|
# DF update and abckup when HASH value changes
|
||||||
|
sed -e "s|HASH=.......|HASH=1111111|" $DF > tmp1
|
||||||
|
cp tmp1 $DF
|
||||||
|
pvs
|
||||||
|
not grep "HASH=1111111" $DF
|
||||||
|
diff $DF $BKDIR/*.0007
|
||||||
|
|
||||||
|
# DF update and backup when old DF has no HASH value
|
||||||
|
cat $DF | grep -v HASH > tmp1
|
||||||
|
cp tmp1 $DF
|
||||||
|
pvs
|
||||||
|
grep HASH $DF
|
||||||
|
diff $DF $BKDIR/*.0008
|
||||||
|
|
||||||
|
# DF update and backup when dev names change
|
||||||
|
pvcreate "$dev3"
|
||||||
|
diff $DF $BKDIR/*.0009
|
||||||
|
grep "$dev2" $DF
|
||||||
|
grep "$dev3" $DF
|
||||||
|
dd if="$dev2" of=dev2_header bs=1M count=1
|
||||||
|
dd if="$dev3" of=dev3_header bs=1M count=1
|
||||||
|
dd if=dev2_header of="$dev3"
|
||||||
|
dd if=dev3_header of="$dev2"
|
||||||
|
pvs
|
||||||
|
diff $DF $BKDIR/*.0010
|
||||||
|
|
||||||
|
# backup limit, remove 1
|
||||||
|
aux lvmconf 'devices/devicesfile_backup_limit = 10'
|
||||||
|
lvmdevices --deldev "$dev2"
|
||||||
|
diff $DF $BKDIR/*.0011
|
||||||
|
not ls $BKDIR/*.0001
|
||||||
|
|
||||||
|
# backup limit, remove N
|
||||||
|
aux lvmconf 'devices/devicesfile_backup_limit = 5'
|
||||||
|
lvmdevices --adddev "$dev2"
|
||||||
|
diff $DF $BKDIR/*.0012
|
||||||
|
not ls $BKDIR/*.0002
|
||||||
|
not ls $BKDIR/*.0003
|
||||||
|
not ls $BKDIR/*.0004
|
||||||
|
not ls $BKDIR/*.0005
|
||||||
|
not ls $BKDIR/*.0006
|
||||||
|
not ls $BKDIR/*.0007
|
||||||
|
ls $BKDIR/*.0008
|
||||||
|
ls $BKDIR/*.0009
|
||||||
|
ls $BKDIR/*.0010
|
||||||
|
ls $BKDIR/*.0011
|
||||||
|
|
||||||
|
# backup disabled
|
||||||
|
aux lvmconf 'devices/devicesfile_backup_limit = 0'
|
||||||
|
lvmdevices --deldev "$dev2"
|
||||||
|
not ls $BKDIR/*.0013
|
||||||
|
|
||||||
|
# backup re-enabled
|
||||||
|
aux lvmconf 'devices/devicesfile_backup_limit = 5'
|
||||||
|
lvmdevices --adddev "$dev2"
|
||||||
|
ls $BKDIR/*.0014
|
||||||
|
not ls $BKDIR/*.0013
|
||||||
|
|
||||||
|
vgremove -ff $vg
|
@ -749,7 +749,7 @@ int lvmdevices(struct cmd_context *cmd, int argc, char **argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (arg_is_set(cmd, update_ARG)) {
|
if (arg_is_set(cmd, update_ARG)) {
|
||||||
if (update_needed || !dm_list_empty(&found_devs)) {
|
if (update_needed || !dm_list_empty(&found_devs) || cmd->devices_file_hash_mismatch) {
|
||||||
if (!device_ids_write(cmd))
|
if (!device_ids_write(cmd))
|
||||||
goto_bad;
|
goto_bad;
|
||||||
log_print("Updated devices file to version %s", devices_file_version());
|
log_print("Updated devices file to version %s", devices_file_version());
|
||||||
@ -760,12 +760,22 @@ int lvmdevices(struct cmd_context *cmd, int argc, char **argv)
|
|||||||
/*
|
/*
|
||||||
* --check exits with an error if the devices file
|
* --check exits with an error if the devices file
|
||||||
* needs updates, i.e. running --update would make
|
* needs updates, i.e. running --update would make
|
||||||
* changes.
|
* changes to the devices entries.
|
||||||
*/
|
*/
|
||||||
if (update_needed) {
|
if (update_needed) {
|
||||||
log_error("Updates needed for devices file.");
|
log_error("Updates needed for devices file.");
|
||||||
goto bad;
|
goto bad;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If only the hash comment would be updated, it isn't
|
||||||
|
* considered a "real" update for purposes of the
|
||||||
|
* --check exit code, since no device entries would be
|
||||||
|
* changed (although --update would lead to a new
|
||||||
|
* file version with the updated hash comment.)
|
||||||
|
*/
|
||||||
|
if (cmd->devices_file_hash_mismatch)
|
||||||
|
log_print("Hash update needed for devices file.");
|
||||||
}
|
}
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user