xref: /netbsd-src/external/gpl2/lvm2/dist/lib/format_text/archive.c (revision 7c604eea85b4f330dc75ffe65e947f4d73758aa0)
1*7c604eeaShaad /*	$NetBSD: archive.c,v 1.1.1.3 2009/12/02 00:26:28 haad Exp $	*/
256a34939Shaad 
356a34939Shaad /*
456a34939Shaad  * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
556a34939Shaad  * Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved.
656a34939Shaad  *
756a34939Shaad  * This file is part of LVM2.
856a34939Shaad  *
956a34939Shaad  * This copyrighted material is made available to anyone wishing to use,
1056a34939Shaad  * modify, copy, or redistribute it subject to the terms and conditions
1156a34939Shaad  * of the GNU Lesser General Public License v.2.1.
1256a34939Shaad  *
1356a34939Shaad  * You should have received a copy of the GNU Lesser General Public License
1456a34939Shaad  * along with this program; if not, write to the Free Software Foundation,
1556a34939Shaad  * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
1656a34939Shaad  */
1756a34939Shaad 
1856a34939Shaad #include "lib.h"
1956a34939Shaad #include "format-text.h"
2056a34939Shaad 
2156a34939Shaad #include "config.h"
2256a34939Shaad #include "import-export.h"
2356a34939Shaad #include "lvm-string.h"
2456a34939Shaad #include "lvm-file.h"
2556a34939Shaad #include "toolcontext.h"
2656a34939Shaad 
2756a34939Shaad #include <dirent.h>
2856a34939Shaad #include <unistd.h>
2956a34939Shaad #include <sys/stat.h>
3056a34939Shaad #include <sys/file.h>
3156a34939Shaad #include <fcntl.h>
3256a34939Shaad #include <time.h>
3356a34939Shaad 
3456a34939Shaad #define SECS_PER_DAY 86400	/* 24*60*60 */
3556a34939Shaad 
3656a34939Shaad /*
3756a34939Shaad  * The format instance is given a directory path upon creation.
3856a34939Shaad  * Each file in this directory whose name is of the form
3956a34939Shaad  * '(.*)_[0-9]*.vg' is a config file (see lib/config.[hc]), which
4056a34939Shaad  * contains a description of a single volume group.
4156a34939Shaad  *
4256a34939Shaad  * The prefix ($1 from the above regex) of the config file gives
4356a34939Shaad  * the volume group name.
4456a34939Shaad  *
4556a34939Shaad  * Backup files that have expired will be removed.
4656a34939Shaad  */
4756a34939Shaad 
4856a34939Shaad /*
4956a34939Shaad  * A list of these is built up for our volume group.  Ordered
5056a34939Shaad  * with the least recent at the head.
5156a34939Shaad  */
5256a34939Shaad struct archive_file {
5356a34939Shaad 	struct dm_list list;
5456a34939Shaad 
5556a34939Shaad 	char *path;
5656a34939Shaad 	uint32_t index;
5756a34939Shaad };
5856a34939Shaad 
5956a34939Shaad /*
6056a34939Shaad  * Extract vg name and version number from a filename.
6156a34939Shaad  */
_split_vg(const char * filename,char * vgname,size_t vgsize,uint32_t * ix)62*7c604eeaShaad static int _split_vg(const char *filename, char *vgname, size_t vgsize,
6356a34939Shaad 		     uint32_t *ix)
6456a34939Shaad {
6556a34939Shaad 	size_t len, vg_len;
6656a34939Shaad 	const char *dot, *underscore;
6756a34939Shaad 
6856a34939Shaad 	len = strlen(filename);
6956a34939Shaad 	if (len < 7)
7056a34939Shaad 		return 0;
7156a34939Shaad 
7256a34939Shaad 	dot = (filename + len - 3);
7356a34939Shaad 	if (strcmp(".vg", dot))
7456a34939Shaad 		return 0;
7556a34939Shaad 
7656a34939Shaad 	if (!(underscore = strrchr(filename, '_')))
7756a34939Shaad 		return 0;
7856a34939Shaad 
7956a34939Shaad 	if (sscanf(underscore + 1, "%u", ix) != 1)
8056a34939Shaad 		return 0;
8156a34939Shaad 
8256a34939Shaad 	vg_len = underscore - filename;
83*7c604eeaShaad 	if (vg_len + 1 > vgsize)
8456a34939Shaad 		return 0;
8556a34939Shaad 
8656a34939Shaad 	strncpy(vgname, filename, vg_len);
8756a34939Shaad 	vgname[vg_len] = '\0';
8856a34939Shaad 
8956a34939Shaad 	return 1;
9056a34939Shaad }
9156a34939Shaad 
_insert_archive_file(struct dm_list * head,struct archive_file * b)9256a34939Shaad static void _insert_archive_file(struct dm_list *head, struct archive_file *b)
9356a34939Shaad {
9456a34939Shaad 	struct archive_file *bf = NULL;
9556a34939Shaad 
9656a34939Shaad 	if (dm_list_empty(head)) {
9756a34939Shaad 		dm_list_add(head, &b->list);
9856a34939Shaad 		return;
9956a34939Shaad 	}
10056a34939Shaad 
10156a34939Shaad 	/* index reduces through list */
10256a34939Shaad 	dm_list_iterate_items(bf, head) {
10356a34939Shaad 		if (b->index > bf->index) {
10456a34939Shaad 			dm_list_add(&bf->list, &b->list);
10556a34939Shaad 			return;
10656a34939Shaad 		}
10756a34939Shaad 	}
10856a34939Shaad 
10956a34939Shaad 	dm_list_add_h(&bf->list, &b->list);
11056a34939Shaad }
11156a34939Shaad 
_join_file_to_dir(struct dm_pool * mem,const char * dir,const char * name)11256a34939Shaad static char *_join_file_to_dir(struct dm_pool *mem, const char *dir, const char *name)
11356a34939Shaad {
11456a34939Shaad 	if (!dm_pool_begin_object(mem, 32) ||
11556a34939Shaad 	    !dm_pool_grow_object(mem, dir, strlen(dir)) ||
11656a34939Shaad 	    !dm_pool_grow_object(mem, "/", 1) ||
11756a34939Shaad 	    !dm_pool_grow_object(mem, name, strlen(name)) ||
11856a34939Shaad 	    !dm_pool_grow_object(mem, "\0", 1))
11956a34939Shaad 		return_NULL;
12056a34939Shaad 
12156a34939Shaad 	return dm_pool_end_object(mem);
12256a34939Shaad }
12356a34939Shaad 
12456a34939Shaad /*
12556a34939Shaad  * Returns a list of archive_files.
12656a34939Shaad  */
_scan_archive(struct dm_pool * mem,const char * vgname,const char * dir)12756a34939Shaad static struct dm_list *_scan_archive(struct dm_pool *mem,
12856a34939Shaad 				  const char *vgname, const char *dir)
12956a34939Shaad {
13056a34939Shaad 	int i, count;
13156a34939Shaad 	uint32_t ix;
13256a34939Shaad 	char vgname_found[64], *path;
13356a34939Shaad 	struct dirent **dirent;
13456a34939Shaad 	struct archive_file *af;
13556a34939Shaad 	struct dm_list *results;
13656a34939Shaad 
13756a34939Shaad 	if (!(results = dm_pool_alloc(mem, sizeof(*results))))
13856a34939Shaad 		return_NULL;
13956a34939Shaad 
14056a34939Shaad 	dm_list_init(results);
14156a34939Shaad 
14256a34939Shaad 	/* Sort fails beyond 5-digit indexes */
14356a34939Shaad 	if ((count = scandir(dir, &dirent, NULL, alphasort)) < 0) {
144*7c604eeaShaad 		log_error("Couldn't scan the archive directory (%s).", dir);
14556a34939Shaad 		return 0;
14656a34939Shaad 	}
14756a34939Shaad 
14856a34939Shaad 	for (i = 0; i < count; i++) {
14956a34939Shaad 		if (!strcmp(dirent[i]->d_name, ".") ||
15056a34939Shaad 		    !strcmp(dirent[i]->d_name, ".."))
15156a34939Shaad 			continue;
15256a34939Shaad 
15356a34939Shaad 		/* check the name is the correct format */
15456a34939Shaad 		if (!_split_vg(dirent[i]->d_name, vgname_found,
15556a34939Shaad 			       sizeof(vgname_found), &ix))
15656a34939Shaad 			continue;
15756a34939Shaad 
15856a34939Shaad 		/* is it the vg we're interested in ? */
15956a34939Shaad 		if (strcmp(vgname, vgname_found))
16056a34939Shaad 			continue;
16156a34939Shaad 
16256a34939Shaad 		if (!(path = _join_file_to_dir(mem, dir, dirent[i]->d_name)))
16356a34939Shaad 			goto_out;
16456a34939Shaad 
16556a34939Shaad 		/*
16656a34939Shaad 		 * Create a new archive_file.
16756a34939Shaad 		 */
16856a34939Shaad 		if (!(af = dm_pool_alloc(mem, sizeof(*af)))) {
169*7c604eeaShaad 			log_error("Couldn't create new archive file.");
17056a34939Shaad 			results = NULL;
17156a34939Shaad 			goto out;
17256a34939Shaad 		}
17356a34939Shaad 
17456a34939Shaad 		af->index = ix;
17556a34939Shaad 		af->path = path;
17656a34939Shaad 
17756a34939Shaad 		/*
17856a34939Shaad 		 * Insert it to the correct part of the list.
17956a34939Shaad 		 */
18056a34939Shaad 		_insert_archive_file(results, af);
18156a34939Shaad 	}
18256a34939Shaad 
18356a34939Shaad       out:
18456a34939Shaad 	for (i = 0; i < count; i++)
18556a34939Shaad 		free(dirent[i]);
18656a34939Shaad 	free(dirent);
18756a34939Shaad 
18856a34939Shaad 	return results;
18956a34939Shaad }
19056a34939Shaad 
_remove_expired(struct dm_list * archives,uint32_t archives_size,uint32_t retain_days,uint32_t min_archive)19156a34939Shaad static void _remove_expired(struct dm_list *archives, uint32_t archives_size,
19256a34939Shaad 			    uint32_t retain_days, uint32_t min_archive)
19356a34939Shaad {
19456a34939Shaad 	struct archive_file *bf;
19556a34939Shaad 	struct stat sb;
19656a34939Shaad 	time_t retain_time;
19756a34939Shaad 
19856a34939Shaad 	/* Make sure there are enough archives to even bother looking for
19956a34939Shaad 	 * expired ones... */
20056a34939Shaad 	if (archives_size <= min_archive)
20156a34939Shaad 		return;
20256a34939Shaad 
20356a34939Shaad 	/* Convert retain_days into the time after which we must retain */
20456a34939Shaad 	retain_time = time(NULL) - (time_t) retain_days *SECS_PER_DAY;
20556a34939Shaad 
20656a34939Shaad 	/* Assume list is ordered newest first (by index) */
20756a34939Shaad 	dm_list_iterate_back_items(bf, archives) {
20856a34939Shaad 		/* Get the mtime of the file and unlink if too old */
20956a34939Shaad 		if (stat(bf->path, &sb)) {
21056a34939Shaad 			log_sys_error("stat", bf->path);
21156a34939Shaad 			continue;
21256a34939Shaad 		}
21356a34939Shaad 
21456a34939Shaad 		if (sb.st_mtime > retain_time)
21556a34939Shaad 			return;
21656a34939Shaad 
21756a34939Shaad 		log_very_verbose("Expiring archive %s", bf->path);
21856a34939Shaad 		if (unlink(bf->path))
21956a34939Shaad 			log_sys_error("unlink", bf->path);
22056a34939Shaad 
22156a34939Shaad 		/* Don't delete any more if we've reached the minimum */
22256a34939Shaad 		if (--archives_size <= min_archive)
22356a34939Shaad 			return;
22456a34939Shaad 	}
22556a34939Shaad }
22656a34939Shaad 
archive_vg(struct volume_group * vg,const char * dir,const char * desc,uint32_t retain_days,uint32_t min_archive)22756a34939Shaad int archive_vg(struct volume_group *vg,
22856a34939Shaad 	       const char *dir, const char *desc,
22956a34939Shaad 	       uint32_t retain_days, uint32_t min_archive)
23056a34939Shaad {
23156a34939Shaad 	int i, fd, renamed = 0;
23256a34939Shaad 	uint32_t ix = 0;
23356a34939Shaad 	struct archive_file *last;
23456a34939Shaad 	FILE *fp = NULL;
23556a34939Shaad 	char temp_file[PATH_MAX], archive_name[PATH_MAX];
23656a34939Shaad 	struct dm_list *archives;
23756a34939Shaad 
23856a34939Shaad 	/*
23956a34939Shaad 	 * Write the vg out to a temporary file.
24056a34939Shaad 	 */
241bec4d750Shaad 	if (!create_temp_name(dir, temp_file, sizeof(temp_file), &fd,
242bec4d750Shaad 			      &vg->cmd->rand_seed)) {
243*7c604eeaShaad 		log_error("Couldn't create temporary archive name.");
24456a34939Shaad 		return 0;
24556a34939Shaad 	}
24656a34939Shaad 
24756a34939Shaad 	if (!(fp = fdopen(fd, "w"))) {
248*7c604eeaShaad 		log_error("Couldn't create FILE object for archive.");
24956a34939Shaad 		if (close(fd))
25056a34939Shaad 			log_sys_error("close", temp_file);
25156a34939Shaad 		return 0;
25256a34939Shaad 	}
25356a34939Shaad 
25456a34939Shaad 	if (!text_vg_export_file(vg, desc, fp)) {
25556a34939Shaad 		if (fclose(fp))
25656a34939Shaad 			log_sys_error("fclose", temp_file);
25756a34939Shaad 		return_0;
25856a34939Shaad 	}
25956a34939Shaad 
26056a34939Shaad 	if (lvm_fclose(fp, temp_file))
26156a34939Shaad 		return_0; /* Leave file behind as evidence of failure */
26256a34939Shaad 
26356a34939Shaad 	/*
26456a34939Shaad 	 * Now we want to rename this file to <vg>_index.vg.
26556a34939Shaad 	 */
26656a34939Shaad 	if (!(archives = _scan_archive(vg->cmd->mem, vg->name, dir)))
26756a34939Shaad 		return_0;
26856a34939Shaad 
26956a34939Shaad 	if (dm_list_empty(archives))
27056a34939Shaad 		ix = 0;
27156a34939Shaad 	else {
27256a34939Shaad 		last = dm_list_item(dm_list_first(archives), struct archive_file);
27356a34939Shaad 		ix = last->index + 1;
27456a34939Shaad 	}
27556a34939Shaad 
27656a34939Shaad 	for (i = 0; i < 10; i++) {
27756a34939Shaad 		if (dm_snprintf(archive_name, sizeof(archive_name),
27856a34939Shaad 				 "%s/%s_%05u.vg", dir, vg->name, ix) < 0) {
27956a34939Shaad 			log_error("Archive file name too long.");
28056a34939Shaad 			return 0;
28156a34939Shaad 		}
28256a34939Shaad 
28356a34939Shaad 		if ((renamed = lvm_rename(temp_file, archive_name)))
28456a34939Shaad 			break;
28556a34939Shaad 
28656a34939Shaad 		ix++;
28756a34939Shaad 	}
28856a34939Shaad 
28956a34939Shaad 	if (!renamed)
29056a34939Shaad 		log_error("Archive rename failed for %s", temp_file);
29156a34939Shaad 
29256a34939Shaad 	_remove_expired(archives, dm_list_size(archives) + renamed, retain_days,
29356a34939Shaad 			min_archive);
29456a34939Shaad 
29556a34939Shaad 	return 1;
29656a34939Shaad }
29756a34939Shaad 
_display_archive(struct cmd_context * cmd,struct archive_file * af)29856a34939Shaad static void _display_archive(struct cmd_context *cmd, struct archive_file *af)
29956a34939Shaad {
30056a34939Shaad 	struct volume_group *vg = NULL;
30156a34939Shaad 	struct format_instance *tf;
30256a34939Shaad 	time_t when;
30356a34939Shaad 	char *desc;
30456a34939Shaad 	void *context;
30556a34939Shaad 
30656a34939Shaad 	log_print(" ");
30756a34939Shaad 	log_print("File:\t\t%s", af->path);
30856a34939Shaad 
30956a34939Shaad 	if (!(context = create_text_context(cmd, af->path, NULL)) ||
31056a34939Shaad 	    !(tf = cmd->fmt_backup->ops->create_instance(cmd->fmt_backup, NULL,
31156a34939Shaad 							 NULL, context))) {
31256a34939Shaad 		log_error("Couldn't create text instance object.");
31356a34939Shaad 		return;
31456a34939Shaad 	}
31556a34939Shaad 
31656a34939Shaad 	/*
31756a34939Shaad 	 * Read the archive file to ensure that it is valid, and
31856a34939Shaad 	 * retrieve the archive time and description.
31956a34939Shaad 	 */
32056a34939Shaad 	/* FIXME Use variation on _vg_read */
32156a34939Shaad 	if (!(vg = text_vg_import_file(tf, af->path, &when, &desc))) {
32256a34939Shaad 		log_print("Unable to read archive file.");
32356a34939Shaad 		tf->fmt->ops->destroy_instance(tf);
32456a34939Shaad 		return;
32556a34939Shaad 	}
32656a34939Shaad 
32756a34939Shaad 	log_print("VG name:    \t%s", vg->name);
32856a34939Shaad 	log_print("Description:\t%s", desc ? : "<No description>");
32956a34939Shaad 	log_print("Backup Time:\t%s", ctime(&when));
33056a34939Shaad 
331*7c604eeaShaad 	vg_release(vg);
33256a34939Shaad 	tf->fmt->ops->destroy_instance(tf);
33356a34939Shaad }
33456a34939Shaad 
archive_list(struct cmd_context * cmd,const char * dir,const char * vgname)33556a34939Shaad int archive_list(struct cmd_context *cmd, const char *dir, const char *vgname)
33656a34939Shaad {
33756a34939Shaad 	struct dm_list *archives;
33856a34939Shaad 	struct archive_file *af;
33956a34939Shaad 
34056a34939Shaad 	if (!(archives = _scan_archive(cmd->mem, vgname, dir)))
34156a34939Shaad 		return_0;
34256a34939Shaad 
34356a34939Shaad 	if (dm_list_empty(archives))
34456a34939Shaad 		log_print("No archives found in %s.", dir);
34556a34939Shaad 
34656a34939Shaad 	dm_list_iterate_back_items(af, archives)
34756a34939Shaad 		_display_archive(cmd, af);
34856a34939Shaad 
34956a34939Shaad 	dm_pool_free(cmd->mem, archives);
35056a34939Shaad 
35156a34939Shaad 	return 1;
35256a34939Shaad }
35356a34939Shaad 
archive_list_file(struct cmd_context * cmd,const char * file)35456a34939Shaad int archive_list_file(struct cmd_context *cmd, const char *file)
35556a34939Shaad {
35656a34939Shaad 	struct archive_file af;
35756a34939Shaad 
35856a34939Shaad 	af.path = (char *)file;
35956a34939Shaad 
36056a34939Shaad 	if (!path_exists(af.path)) {
361*7c604eeaShaad 		log_error("Archive file %s not found.", af.path);
36256a34939Shaad 		return 0;
36356a34939Shaad 	}
36456a34939Shaad 
36556a34939Shaad 	_display_archive(cmd, &af);
36656a34939Shaad 
36756a34939Shaad 	return 1;
36856a34939Shaad }
36956a34939Shaad 
backup_list(struct cmd_context * cmd,const char * dir,const char * vgname)37056a34939Shaad int backup_list(struct cmd_context *cmd, const char *dir, const char *vgname)
37156a34939Shaad {
37256a34939Shaad 	struct archive_file af;
37356a34939Shaad 
37456a34939Shaad 	if (!(af.path = _join_file_to_dir(cmd->mem, dir, vgname)))
37556a34939Shaad 		return_0;
37656a34939Shaad 
37756a34939Shaad 	if (path_exists(af.path))
37856a34939Shaad 		_display_archive(cmd, &af);
37956a34939Shaad 
38056a34939Shaad 	return 1;
38156a34939Shaad }
382