From 952d12a5f51d9693a177f1bd547cb23c79017728 Mon Sep 17 00:00:00 2001 From: Alasdair Kergon Date: Wed, 9 Jan 2002 19:16:48 +0000 Subject: [PATCH] o Rename many occurrences of 'backup' to 'archive' to reduce confusion. o Extract file creation/renaming code into a library and change backup code to use it too. o Support umask. o Bring lvm.conf man page up-to-date. --- include/.symlinks | 1 + lib/Makefile.in | 3 +- lib/format_text/archive.c | 421 ++++++++++++++++++++++++++++++++++ lib/format_text/format-text.c | 42 +++- lib/format_text/format-text.h | 13 +- man/lvm.conf.5 | 42 +++- tools/archive.c | 3 +- tools/archive.h | 1 + tools/commands.h | 3 +- tools/defaults.h | 2 + tools/lvm.c | 31 ++- tools/toollib.c | 2 +- tools/vgremove.c | 2 +- 13 files changed, 532 insertions(+), 34 deletions(-) create mode 100644 lib/format_text/archive.c diff --git a/include/.symlinks b/include/.symlinks index 4ec61b862..9be795da2 100644 --- a/include/.symlinks +++ b/include/.symlinks @@ -20,6 +20,7 @@ ../lib/mm/dbg_malloc.h ../lib/mm/pool.h ../lib/mm/xlate.h +../lib/misc/lvm-file.h ../lib/misc/lvm-string.h ../lib/regex/matcher.h ../lib/uuid/uuid.h diff --git a/lib/Makefile.in b/lib/Makefile.in index c6aa882f5..14039c89d 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -29,7 +29,7 @@ SOURCES=\ format1/import-extents.c \ format1/layout.c \ format1/vg_number.c \ - format_text/backup.c \ + format_text/archive.c \ format_text/export.c \ format_text/flags.c \ format_text/format-text.c \ @@ -40,6 +40,7 @@ SOURCES=\ metadata/merge.c \ metadata/metadata.c \ metadata/pv_map.c \ + misc/lvm-file.c \ mm/dbg_malloc.c \ mm/pool.c \ regex/matcher.c \ diff --git a/lib/format_text/archive.c b/lib/format_text/archive.c new file mode 100644 index 000000000..d401205a2 --- /dev/null +++ b/lib/format_text/archive.c @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2001 Sistina Software (UK) Limited. + * + * This file is released under the LGPL. + */ + +#include "format-text.h" + +#include "log.h" +#include "pool.h" +#include "config.h" +#include "hash.h" +#include "import-export.h" +#include "lvm-string.h" +#include "lvm-file.h" + +#include +#include +#include +#include +#include +#include + + +/* + * The format instance is given a directory path upon creation. + * Each file in this directory whose name is of the form + * '(.*)_[0-9]*.vg' is a config file (see lib/config.[hc]), which + * contains a description of a single volume group. + * + * The prefix ($1 from the above regex) of the config file gives + * the volume group name. + * + * Backup files that have expired will be removed. + */ + +struct archive_c { + uint32_t retain_days; + uint32_t min_retains; + + char *dir; + + /* + * An ordered list of previous archives. Each list + * entered against the vg name. Most recent first. + */ + struct hash_table *vg_archives; + + /* + * Scratch pool. Contents of vg_archives come from here. + */ + struct pool *mem; +}; + +/* + * A list of these is built up for each volume group. Ordered + * with the least recent at the head. + */ +struct archive_file { + struct list list; + + char *path; + char *vg; + int index; +}; + +/* + * This format is write only. + */ +static void _unsupported(const char *cmd) +{ + log_err("The archive format doesn't support '%s'", cmd); +} + +static struct list *_get_vgs(struct format_instance *fi) +{ + _unsupported("get_vgs"); + return NULL; +} + +static struct list *_get_pvs(struct format_instance *fi) +{ + _unsupported("get_pvs"); + return NULL; +} + +static struct physical_volume *_pv_read(struct format_instance *fi, + const char *pv_name) +{ + _unsupported("pv_read"); + return NULL; +} + +static int _pv_setup(struct format_instance *fi, struct physical_volume *pv, + struct volume_group *vg) +{ + _unsupported("pv_setup"); + return 0; +} + +static int _pv_write(struct format_instance *fi, struct physical_volume *pv) +{ + _unsupported("pv_write"); + return 0; +} + +static int _vg_setup(struct format_instance *fi, struct volume_group *vg) +{ + _unsupported("vg_setup"); + return 0; +} + +static struct volume_group *_vg_read(struct format_instance *fi, + const char *vg_name) +{ + _unsupported("vg_read"); + return NULL; +} + +static void _destroy(struct format_instance *fi) +{ + struct archive_c *bc = (struct archive_c *) fi->private; + if (bc->vg_archives) + hash_destroy(bc->vg_archives); + pool_destroy(bc->mem); +} + + +/* + * Extract vg name and version number from a filename. + */ +static int _split_vg(const char *filename, char *vg, size_t vg_size, + uint32_t *index) +{ + int len, vg_len; + char *dot, *underscore; + + len = strlen(filename); + if (len < 7) + return 0; + + dot = (char *) (filename + len - 3); + if (strcmp(".vg", dot)) + return 0; + + if (!(underscore = rindex(filename, '_'))) + return 0; + + if (sscanf(underscore + 1, "%u", index) != 1) + return 0; + + vg_len = underscore - filename; + if (vg_len + 1 > vg_size) + return 0; + + strncpy(vg, filename, vg_len); + vg[vg_len] = '\0'; + + return 1; +} + +static void _insert_file(struct list *head, struct archive_file *b) +{ + struct list *bh; + struct archive_file *bf; + + if (list_empty(head)) { + list_add(head, &b->list); + return; + } + + /* index increases through list */ + list_iterate (bh, head) { + bf = list_item(bh, struct archive_file); + + if (bf->index > b->index) { + list_add(&bf->list, &b->list); + return; + } + } + + list_add_h(&bf->list, &b->list); +} + +static int _scan_vg(struct archive_c *bc, const char *file, + const char *vg_name, int index) +{ + struct archive_file *b; + struct list *files; + + /* + * Do we need to create a new list of archive files for + * this vg ? + */ + if (!(files = hash_lookup(bc->vg_archives, vg_name))) { + if (!(files = pool_alloc(bc->mem, sizeof(*files)))) { + stack; + return 0; + } + + list_init(files); + if (!hash_insert(bc->vg_archives, vg_name, files)) { + log_err("Couldn't insert archive file " + "into hash table."); + return 0; + } + } + + /* + * Create a new archive file. + */ + if (!(b = pool_alloc(bc->mem, sizeof(*b)))) { + log_err("Couldn't create new archive file."); + return 0; + } + + b->index = index; + b->path = (char *)file; + b->vg = (char *)vg_name; + + /* + * Insert it to the correct part of the list. + */ + _insert_file(files, b); + + return 1; +} + +static char *_join(struct pool *mem, const char *dir, const char *name) +{ + if (!pool_begin_object(mem, 32) || + !pool_grow_object(mem, dir, strlen(dir)) || + !pool_grow_object(mem, "/", 1) || + !pool_grow_object(mem, name, strlen(name)) || + !pool_grow_object(mem, "\0", 1)) { + stack; + return NULL; + } + + return pool_end_object(mem); +} + +static int _scan_dir(struct archive_c *bc) +{ + int r = 0, i, count, index; + char vg_name[64], *path; + struct dirent **dirent; + + if ((count = scandir(bc->dir, &dirent, NULL, alphasort)) < 0) { + log_err("Couldn't scan archive directory."); + return 0; + } + + for (i = 0; i < count; i++) { + if ((dirent[i]->d_name[0] == '.') || + !_split_vg(dirent[i]->d_name, vg_name, + sizeof(vg_name), &index)) + continue; + + if (!(path = _join(bc->mem, bc->dir, dirent[i]->d_name))) { + stack; + goto out; + } + + _scan_vg(bc, path, vg_name, index); + } + r = 1; + + out: + for (i = 0; i < count; i++) + free(dirent[i]); + free(dirent); + + return r; +} + +static int _scan_archives(struct archive_c *bc) +{ + pool_empty(bc->mem); + + if (bc->vg_archives) + hash_destroy(bc->vg_archives); + + if (!(bc->vg_archives = hash_create(128))) { + log_err("Couldn't create hash table for scanning archives."); + return 0; + } + + if (!_scan_dir(bc)) { + stack; + return 0; + } + + return 1; +} + +static int _vg_write(struct format_instance *fi, struct volume_group *vg) +{ + int r = 0, i, fd; + unsigned int index = 0; + struct archive_c *bc = (struct archive_c *) fi->private; + struct archive_file *last; + FILE *fp = NULL; + char temp_file[PATH_MAX], archive_name[PATH_MAX]; + + if (!create_temp_name(bc->dir, temp_file, sizeof(temp_file), &fd)) { + log_err("Couldn't create temporary archive name."); + return 0; + } + + if (!(fp = fdopen(fd, "w"))) { + log_err("Couldn't create FILE object for archive."); + close(fd); + return 0; + } + + if (!text_vg_export(fp, vg)) { + stack; + fclose(fp); + return 0; + } + + fclose(fp); + + /* + * Now we want to rename this file to _index.vg. + */ + if (!_scan_archives(bc)) { + log_err("Couldn't scan the archive directory (%s).", bc->dir); + goto out; + } + + if ((last = (struct archive_file *) hash_lookup(bc->vg_archives, + vg->name))) { + /* move to the last in the list */ + last = list_item(last->list.p, struct archive_file); + index = last->index + 1; + } + + for (i = 0; i < 10; i++) { + if (lvm_snprintf(archive_name, sizeof(archive_name), + "%s/%s_%05d.vg", + bc->dir, vg->name, index) < 0) { + log_err("archive file name too long."); + goto out; + } + + if (lvm_rename(temp_file, archive_name)) { + r = 1; + break; + } + + index++; + } + + out: + return r; +} + +void archive_expire(struct format_instance *fi) +{ + /* FIXME: finish */ +} + +static struct format_handler _archive_handler = { + get_vgs: _get_vgs, + get_pvs: _get_pvs, + pv_read: _pv_read, + pv_setup: _pv_setup, + pv_write: _pv_write, + vg_setup: _vg_setup, + vg_read: _vg_read, + vg_write: _vg_write, + destroy: _destroy +}; + +struct format_instance *archive_format_create(struct cmd_context *cmd, + const char *dir, + uint32_t retain_days, + uint32_t min_retains) +{ + struct format_instance *fi; + struct archive_c *bc = NULL; + struct pool *mem = cmd->mem; + + if (!(bc = pool_zalloc(mem, sizeof(*bc)))) { + stack; + return NULL; + } + + if (!(bc->mem = pool_create(1024))) { + stack; + goto bad; + } + + if (!(bc->dir = pool_strdup(mem, dir))) { + stack; + goto bad; + } + + bc->retain_days = retain_days; + bc->min_retains = min_retains; + + if (!(fi = pool_alloc(mem, sizeof(*fi)))) { + stack; + goto bad; + } + + fi->cmd = cmd; + fi->ops = &_archive_handler; + fi->private = bc; + + return fi; + + bad: + if (bc->mem) + pool_destroy(bc->mem); + + pool_free(mem, bc); + return NULL; +} diff --git a/lib/format_text/format-text.c b/lib/format_text/format-text.c index 296b2037b..2b0c3bc06 100644 --- a/lib/format_text/format-text.c +++ b/lib/format_text/format-text.c @@ -7,11 +7,17 @@ #include "format-text.h" #include "import-export.h" +#include "lvm-file.h" #include "log.h" #include "pool.h" #include "config.h" #include "hash.h" +#include +#include +#include +#include + /* * NOTE: Currently there can be only one vg per file. */ @@ -69,25 +75,51 @@ static struct volume_group *_vg_read(struct format_instance *fi, static int _vg_write(struct format_instance *fi, struct volume_group *vg) { FILE *fp; + int fd; + char *slash; char *file = (char *) fi->private; + char temp_file[PATH_MAX], temp_dir[PATH_MAX]; + + slash = rindex(file, '/'); - /* FIXME: should be opened exclusively */ - if (!(fp = fopen(file, "w"))) { - log_sys_error("fopen", file); + if (slash == 0) + strcpy(temp_dir, "."); + else if (slash - file < PATH_MAX) { + strncpy(temp_dir, file, slash - file); + temp_dir[slash - file] = '\0'; + } else { + log_error("Text format failed to determine directory."); + return 0; + } + + if (!create_temp_name(temp_dir, temp_file, sizeof(temp_file), &fd)) { + log_err("Couldn't create temporary text file name."); + return 0; + } + + if (!(fp = fdopen(fd, "w"))) { + log_sys_error("fdopen", temp_file); + close(fd); return 0; } if (!text_vg_export(fp, vg)) { - log_error("Failed to write metadata to %s.", file); + log_error("Failed to write metadata to %s.", temp_file); fclose(fp); return 0; } - if (!fclose(fp)) { + if (fclose(fp)) { log_sys_error("fclose", file); return 0; } + if (rename(temp_file, file)) { + log_error("%s: rename to %s failed: %s", temp_file, file, + strerror(errno)); + return 0; + } + return 1; } diff --git a/lib/format_text/format-text.h b/lib/format_text/format-text.h index 4f5b408a1..d6338b22c 100644 --- a/lib/format_text/format-text.h +++ b/lib/format_text/format-text.h @@ -11,17 +11,18 @@ #include "metadata.h" /* - * The backup format is used to maintain a set of backup files. - * 'retain_days' gives the minimum number of days that a backup must + * The archive format is used to maintain a set of metadata backup files + * in an archive directory. + * 'retain_days' is the minimum number of days that an archive file must * be held for. * - * 'min_backups' is the minimum number of backups required for each volume - * group. + * 'min_archives' is the minimum number of archives required to be kept + * for each volume group. */ -struct format_instance *backup_format_create(struct cmd_context *cmd, +struct format_instance *archive_format_create(struct cmd_context *cmd, const char *dir, uint32_t retain_days, - uint32_t min_backups); + uint32_t min_archives); void backup_expire(struct format_instance *fi); diff --git a/man/lvm.conf.5 b/man/lvm.conf.5 index dfb3c1e7f..1579c5376 100644 --- a/man/lvm.conf.5 +++ b/man/lvm.conf.5 @@ -42,7 +42,7 @@ e.g. backup { .br An assignment associates a type with an identifier. .br -e.g. max_backups = 42 +e.g. max_archives = 42 .br .TP \fBarray = '[' (type ',')* type ']' | '[' ']'\fP @@ -109,26 +109,50 @@ is invoked. By default tools append messages to the log file. \fBtest\fP \(em If set to 1, run tools in test mode i.e. no metadata gets updated. .TP -\fBbackup\fP \(em Configuration for metadata backups +\fBbackup\fP \(em Configuration for metadata backups. .IP -\fBdir\fP \(em Directory used for automatic metadata backups. +\fBarchive_dir\fP \(em Directory used for automatic metadata archives. +Backup copies of former metadata for each volume group are archived here. +Defaults to "/etc/lvm/archive". +.IP +\fBbackup_dir\fP \(em Directory used for automatic metadata backups. +A single backup copy of the current metadata for each volume group +is stored here. Defaults to "/etc/lvm/backup". .IP -\fBkeep\fP \(em Minimum number of backups to keep. -Defaults to 10. +\fBarchive\fP \(em Whether or not tools automatically archive existing +metadata into \fBarchive_dir\fP before making changes to it. +Default is 1 (automatic archives enabled). +Set to 0 to disable. +Disabling this might make metadata recovery difficult or impossible +if something goes wrong. +.IP +\fBbackup\fP \(em Whether or not tools make an automatic backup +into \fBbackup_dir\fP after changing metadata. +Default is 1 (automatic backups enabled). Set to 0 to disable. +Disabling this might make metadata recovery difficult or impossible +if something goes wrong. .IP -\fBdays\fP \(em Minimum number of days to keep backup files. -Defaults to 14. +\fBretain_min\fP \(em Minimum number of archives to keep. +Defaults to 10. .IP -\fBauto\fP \(em Whether or not tools make automatic backups after changing -metadata. Default is 1 (automatic backups enabled). Set to 0 to disable. +\fBretain_days\fP \(em Minimum number of days to keep archive files. +Defaults to 30. .TP \fBshell\fP \(em LVM2 built-in readline shell settings .IP \fBhistory_size\fP \(em Maximum number of lines of shell history to retain (default 100) in $HOME/.lvm_history +.TP +\fBglobal\fP \(em Global settings +.IP +\fBumask\fP \(em File creation mask for any files and directories created. +Interpreted as octal if the first digit is zero. +Defaults to 077. +Use 022 to allow other users to read the files by default. .SH FILES .I /etc/lvm/lvm.conf .br .I $HOME/.lvm_history .SH SEE ALSO .BR lvm (8) +.BR umask (2) diff --git a/tools/archive.c b/tools/archive.c index 3ec55501e..2f40c1bb6 100644 --- a/tools/archive.c +++ b/tools/archive.c @@ -11,6 +11,7 @@ #include "lvm-string.h" #include "toollib.h" +#include #include static struct { @@ -60,7 +61,7 @@ static int __archive(struct volume_group *vg) int r; struct format_instance *archiver; - if (!(archiver = backup_format_create(vg->cmd, + if (!(archiver = archive_format_create(vg->cmd, _archive_params.dir, _archive_params.keep_days, _archive_params.keep_number))) { diff --git a/tools/archive.h b/tools/archive.h index 78cad1d29..505d045ac 100644 --- a/tools/archive.h +++ b/tools/archive.h @@ -37,5 +37,6 @@ void backup_exit(void); void backup_enable(int flag); int backup(struct volume_group *vg); +int backup_remove(const char *vg_name); #endif diff --git a/tools/commands.h b/tools/commands.h index 257539e46..568d09a3f 100644 --- a/tools/commands.h +++ b/tools/commands.h @@ -333,6 +333,7 @@ xx(vgcfgbackup, "Backup volume group configuration(s)", "vgcfgbackup " "\n" "\t[-d|--debug] " "\n" + "\t[-f|--file filename] " "\n" "\t[-h|--help] " "\n" "\t[-v|--verbose]" "\n" "\t[-V|--version] " "\n" @@ -343,7 +344,7 @@ xx(vgcfgrestore, "Restore volume group configuration", "vgcfgrestore " "\n" "\t[-d|--debug] " "\n" - "\t[-f|--file VGConfPath] " "\n" + "\t[-f|--file filename] " "\n" "\t[-l[l]|--list [--list]]" "\n" "\t[-n|--name VolumeGroupName] " "\n" "\t[-h|--help]" "\n" diff --git a/tools/defaults.h b/tools/defaults.h index c6e7db26f..4ae0051a3 100644 --- a/tools/defaults.h +++ b/tools/defaults.h @@ -21,6 +21,8 @@ #define DEFAULT_DEV_DIR "/dev" +#define DEFAULT_UMASK 0077 + #ifdef READLINE_SUPPORT #define DEFAULT_MAX_HISTORY 100 #endif diff --git a/tools/lvm.c b/tools/lvm.c index ad705610e..cd9655b47 100644 --- a/tools/lvm.c +++ b/tools/lvm.c @@ -65,6 +65,8 @@ struct config_info { int archive; /* should we archive ? */ int backup; /* should we backup ? */ + + mode_t umask; }; static struct config_info _default_settings; @@ -661,6 +663,15 @@ static void __init_log(struct config_file *cf) const char *log_file = find_config_str(cf->root, "log/file", '/', 0); + _default_settings.debug = + find_config_int(cf->root, "log/level", '/', 0); + _default_settings.verbose = + find_config_int(cf->root, "log/verbose", '/', 0); + _default_settings.test = find_config_int(cf->root, "log/test", '/', 0); + + init_debug(_default_settings.debug); + init_verbose(_default_settings.verbose); + if (find_config_int(cf->root, "log/overwrite", '/', 0)) open_mode = "w"; @@ -672,13 +683,6 @@ static void __init_log(struct config_file *cf) init_log(_log); } - _default_settings.debug = - find_config_int(cf->root, "log/level", '/', 0); - - _default_settings.verbose = - find_config_int(cf->root, "log/verbose", '/', 0); - - _default_settings.test = find_config_int(cf->root, "log/test", '/', 0); } static int _init_backup(struct config_file *cf) @@ -869,6 +873,7 @@ static int init(void) { struct stat info; char config_file[PATH_MAX] = ""; + mode_t old_umask; if (!_get_env_vars()) return 0; @@ -890,11 +895,11 @@ static int init(void) /* Use LOG_USER for syslog messages by default */ init_syslog(LOG_USER); - _init_rand(); - /* send log messages to stderr for now */ init_log(stderr); + _init_rand(); + if (*_sys_dir && lvm_snprintf(config_file, sizeof(config_file), "%s/lvm.conf", _sys_dir) < 0) { log_error("lvm_sys_dir was too long"); @@ -912,6 +917,14 @@ static int init(void) __init_log(cmd->cf); } + _default_settings.umask = find_config_int(cmd->cf->root, + "global/umask", '/', + DEFAULT_UMASK); + + if ((old_umask = umask((mode_t)_default_settings.umask)) != + (mode_t)_default_settings.umask) + log_verbose("Set umask to %04o", _default_settings.umask); + if (lvm_snprintf(_dev_dir, sizeof(_dev_dir), "%s/", find_config_str(cmd->cf->root, "devices/dir", '/', DEFAULT_DEV_DIR)) < 0) { diff --git a/tools/toollib.c b/tools/toollib.c index 4b2345d90..2098f6bb0 100644 --- a/tools/toollib.c +++ b/tools/toollib.c @@ -17,7 +17,7 @@ int create_dir(const char *dir) if (stat(dir, &info) < 0 ) { log_verbose("Creating directory %s", dir); - if (!mkdir(dir, S_IRWXU)) + if (!mkdir(dir, 0777)); return 1; log_sys_error("mkdir", dir); return 0; diff --git a/tools/vgremove.c b/tools/vgremove.c index 1988fb038..d5dd8b481 100644 --- a/tools/vgremove.c +++ b/tools/vgremove.c @@ -77,7 +77,7 @@ static int vgremove_single(const char *vg_name) } } - backup(vg); + backup_remove(vg_name); if (!ret) log_print("Volume group %s successfully removed", vg_name); -- 2.43.5