archive.c revision 1.1.1.3
1/*	$NetBSD: archive.c,v 1.1.1.3 2009/12/02 00:26:28 haad Exp $	*/
2
3/*
4 * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
5 * Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved.
6 *
7 * This file is part of LVM2.
8 *
9 * This copyrighted material is made available to anyone wishing to use,
10 * modify, copy, or redistribute it subject to the terms and conditions
11 * of the GNU Lesser General Public License v.2.1.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, write to the Free Software Foundation,
15 * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16 */
17
18#include "lib.h"
19#include "format-text.h"
20
21#include "config.h"
22#include "import-export.h"
23#include "lvm-string.h"
24#include "lvm-file.h"
25#include "toolcontext.h"
26
27#include <dirent.h>
28#include <unistd.h>
29#include <sys/stat.h>
30#include <sys/file.h>
31#include <fcntl.h>
32#include <time.h>
33
34#define SECS_PER_DAY 86400	/* 24*60*60 */
35
36/*
37 * The format instance is given a directory path upon creation.
38 * Each file in this directory whose name is of the form
39 * '(.*)_[0-9]*.vg' is a config file (see lib/config.[hc]), which
40 * contains a description of a single volume group.
41 *
42 * The prefix ($1 from the above regex) of the config file gives
43 * the volume group name.
44 *
45 * Backup files that have expired will be removed.
46 */
47
48/*
49 * A list of these is built up for our volume group.  Ordered
50 * with the least recent at the head.
51 */
52struct archive_file {
53	struct dm_list list;
54
55	char *path;
56	uint32_t index;
57};
58
59/*
60 * Extract vg name and version number from a filename.
61 */
62static int _split_vg(const char *filename, char *vgname, size_t vgsize,
63		     uint32_t *ix)
64{
65	size_t len, vg_len;
66	const char *dot, *underscore;
67
68	len = strlen(filename);
69	if (len < 7)
70		return 0;
71
72	dot = (filename + len - 3);
73	if (strcmp(".vg", dot))
74		return 0;
75
76	if (!(underscore = strrchr(filename, '_')))
77		return 0;
78
79	if (sscanf(underscore + 1, "%u", ix) != 1)
80		return 0;
81
82	vg_len = underscore - filename;
83	if (vg_len + 1 > vgsize)
84		return 0;
85
86	strncpy(vgname, filename, vg_len);
87	vgname[vg_len] = '\0';
88
89	return 1;
90}
91
92static void _insert_archive_file(struct dm_list *head, struct archive_file *b)
93{
94	struct archive_file *bf = NULL;
95
96	if (dm_list_empty(head)) {
97		dm_list_add(head, &b->list);
98		return;
99	}
100
101	/* index reduces through list */
102	dm_list_iterate_items(bf, head) {
103		if (b->index > bf->index) {
104			dm_list_add(&bf->list, &b->list);
105			return;
106		}
107	}
108
109	dm_list_add_h(&bf->list, &b->list);
110}
111
112static char *_join_file_to_dir(struct dm_pool *mem, const char *dir, const char *name)
113{
114	if (!dm_pool_begin_object(mem, 32) ||
115	    !dm_pool_grow_object(mem, dir, strlen(dir)) ||
116	    !dm_pool_grow_object(mem, "/", 1) ||
117	    !dm_pool_grow_object(mem, name, strlen(name)) ||
118	    !dm_pool_grow_object(mem, "\0", 1))
119		return_NULL;
120
121	return dm_pool_end_object(mem);
122}
123
124/*
125 * Returns a list of archive_files.
126 */
127static struct dm_list *_scan_archive(struct dm_pool *mem,
128				  const char *vgname, const char *dir)
129{
130	int i, count;
131	uint32_t ix;
132	char vgname_found[64], *path;
133	struct dirent **dirent;
134	struct archive_file *af;
135	struct dm_list *results;
136
137	if (!(results = dm_pool_alloc(mem, sizeof(*results))))
138		return_NULL;
139
140	dm_list_init(results);
141
142	/* Sort fails beyond 5-digit indexes */
143	if ((count = scandir(dir, &dirent, NULL, alphasort)) < 0) {
144		log_error("Couldn't scan the archive directory (%s).", dir);
145		return 0;
146	}
147
148	for (i = 0; i < count; i++) {
149		if (!strcmp(dirent[i]->d_name, ".") ||
150		    !strcmp(dirent[i]->d_name, ".."))
151			continue;
152
153		/* check the name is the correct format */
154		if (!_split_vg(dirent[i]->d_name, vgname_found,
155			       sizeof(vgname_found), &ix))
156			continue;
157
158		/* is it the vg we're interested in ? */
159		if (strcmp(vgname, vgname_found))
160			continue;
161
162		if (!(path = _join_file_to_dir(mem, dir, dirent[i]->d_name)))
163			goto_out;
164
165		/*
166		 * Create a new archive_file.
167		 */
168		if (!(af = dm_pool_alloc(mem, sizeof(*af)))) {
169			log_error("Couldn't create new archive file.");
170			results = NULL;
171			goto out;
172		}
173
174		af->index = ix;
175		af->path = path;
176
177		/*
178		 * Insert it to the correct part of the list.
179		 */
180		_insert_archive_file(results, af);
181	}
182
183      out:
184	for (i = 0; i < count; i++)
185		free(dirent[i]);
186	free(dirent);
187
188	return results;
189}
190
191static void _remove_expired(struct dm_list *archives, uint32_t archives_size,
192			    uint32_t retain_days, uint32_t min_archive)
193{
194	struct archive_file *bf;
195	struct stat sb;
196	time_t retain_time;
197
198	/* Make sure there are enough archives to even bother looking for
199	 * expired ones... */
200	if (archives_size <= min_archive)
201		return;
202
203	/* Convert retain_days into the time after which we must retain */
204	retain_time = time(NULL) - (time_t) retain_days *SECS_PER_DAY;
205
206	/* Assume list is ordered newest first (by index) */
207	dm_list_iterate_back_items(bf, archives) {
208		/* Get the mtime of the file and unlink if too old */
209		if (stat(bf->path, &sb)) {
210			log_sys_error("stat", bf->path);
211			continue;
212		}
213
214		if (sb.st_mtime > retain_time)
215			return;
216
217		log_very_verbose("Expiring archive %s", bf->path);
218		if (unlink(bf->path))
219			log_sys_error("unlink", bf->path);
220
221		/* Don't delete any more if we've reached the minimum */
222		if (--archives_size <= min_archive)
223			return;
224	}
225}
226
227int archive_vg(struct volume_group *vg,
228	       const char *dir, const char *desc,
229	       uint32_t retain_days, uint32_t min_archive)
230{
231	int i, fd, renamed = 0;
232	uint32_t ix = 0;
233	struct archive_file *last;
234	FILE *fp = NULL;
235	char temp_file[PATH_MAX], archive_name[PATH_MAX];
236	struct dm_list *archives;
237
238	/*
239	 * Write the vg out to a temporary file.
240	 */
241	if (!create_temp_name(dir, temp_file, sizeof(temp_file), &fd,
242			      &vg->cmd->rand_seed)) {
243		log_error("Couldn't create temporary archive name.");
244		return 0;
245	}
246
247	if (!(fp = fdopen(fd, "w"))) {
248		log_error("Couldn't create FILE object for archive.");
249		if (close(fd))
250			log_sys_error("close", temp_file);
251		return 0;
252	}
253
254	if (!text_vg_export_file(vg, desc, fp)) {
255		if (fclose(fp))
256			log_sys_error("fclose", temp_file);
257		return_0;
258	}
259
260	if (lvm_fclose(fp, temp_file))
261		return_0; /* Leave file behind as evidence of failure */
262
263	/*
264	 * Now we want to rename this file to <vg>_index.vg.
265	 */
266	if (!(archives = _scan_archive(vg->cmd->mem, vg->name, dir)))
267		return_0;
268
269	if (dm_list_empty(archives))
270		ix = 0;
271	else {
272		last = dm_list_item(dm_list_first(archives), struct archive_file);
273		ix = last->index + 1;
274	}
275
276	for (i = 0; i < 10; i++) {
277		if (dm_snprintf(archive_name, sizeof(archive_name),
278				 "%s/%s_%05u.vg", dir, vg->name, ix) < 0) {
279			log_error("Archive file name too long.");
280			return 0;
281		}
282
283		if ((renamed = lvm_rename(temp_file, archive_name)))
284			break;
285
286		ix++;
287	}
288
289	if (!renamed)
290		log_error("Archive rename failed for %s", temp_file);
291
292	_remove_expired(archives, dm_list_size(archives) + renamed, retain_days,
293			min_archive);
294
295	return 1;
296}
297
298static void _display_archive(struct cmd_context *cmd, struct archive_file *af)
299{
300	struct volume_group *vg = NULL;
301	struct format_instance *tf;
302	time_t when;
303	char *desc;
304	void *context;
305
306	log_print(" ");
307	log_print("File:\t\t%s", af->path);
308
309	if (!(context = create_text_context(cmd, af->path, NULL)) ||
310	    !(tf = cmd->fmt_backup->ops->create_instance(cmd->fmt_backup, NULL,
311							 NULL, context))) {
312		log_error("Couldn't create text instance object.");
313		return;
314	}
315
316	/*
317	 * Read the archive file to ensure that it is valid, and
318	 * retrieve the archive time and description.
319	 */
320	/* FIXME Use variation on _vg_read */
321	if (!(vg = text_vg_import_file(tf, af->path, &when, &desc))) {
322		log_print("Unable to read archive file.");
323		tf->fmt->ops->destroy_instance(tf);
324		return;
325	}
326
327	log_print("VG name:    \t%s", vg->name);
328	log_print("Description:\t%s", desc ? : "<No description>");
329	log_print("Backup Time:\t%s", ctime(&when));
330
331	vg_release(vg);
332	tf->fmt->ops->destroy_instance(tf);
333}
334
335int archive_list(struct cmd_context *cmd, const char *dir, const char *vgname)
336{
337	struct dm_list *archives;
338	struct archive_file *af;
339
340	if (!(archives = _scan_archive(cmd->mem, vgname, dir)))
341		return_0;
342
343	if (dm_list_empty(archives))
344		log_print("No archives found in %s.", dir);
345
346	dm_list_iterate_back_items(af, archives)
347		_display_archive(cmd, af);
348
349	dm_pool_free(cmd->mem, archives);
350
351	return 1;
352}
353
354int archive_list_file(struct cmd_context *cmd, const char *file)
355{
356	struct archive_file af;
357
358	af.path = (char *)file;
359
360	if (!path_exists(af.path)) {
361		log_error("Archive file %s not found.", af.path);
362		return 0;
363	}
364
365	_display_archive(cmd, &af);
366
367	return 1;
368}
369
370int backup_list(struct cmd_context *cmd, const char *dir, const char *vgname)
371{
372	struct archive_file af;
373
374	if (!(af.path = _join_file_to_dir(cmd->mem, dir, vgname)))
375		return_0;
376
377	if (path_exists(af.path))
378		_display_archive(cmd, &af);
379
380	return 1;
381}
382