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