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 */ 52 struct 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 */ 62 static 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 92 static 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 112 static 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 */ 127 static 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 191 static 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 227 int 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 298 static 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 335 int 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 354 int 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 370 int 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