2 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3 * Copyright (c) 1991-1998 University of Maryland at College Park
6 * Permission to use, copy, modify, distribute, and sell this software and its
7 * documentation for any purpose is hereby granted without fee, provided that
8 * the above copyright notice appear in all copies and that both that
9 * copyright notice and this permission notice appear in supporting
10 * documentation, and that the name of U.M. not be used in advertising or
11 * publicity pertaining to distribution of the software without specific,
12 * written prior permission. U.M. makes no representations about the
13 * suitability of this software for any purpose. It is provided "as is"
14 * without express or implied warranty.
16 * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
18 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 * Authors: the Amanda Development Team. Its members are listed in a
24 * file named AUTHORS, in the root directory of this distribution.
27 * $Id: amfetchdump.c,v 1.16 2006/08/24 01:57:15 paddy_s Exp $
29 * retrieves specific dumps from a set of amanda tapes
33 #include "fileheader.h"
42 #include "server_util.h"
44 #define CREAT_MODE 0640
46 extern char *rst_conf_logfile;
47 extern char *config_dir;
50 typedef struct needed_tapes_s {
55 /* usage_order helps to determine the order in which multiple tapes were
56 * used in a single run; it is set as the tapes are added, as sorted by
57 * dumpfile part number */
63 tapelist_t *list_needed_tapes(GSList *dumpspecs, int only_one, disklist_t *diskqp);
65 int main(int argc, char **argv);
68 static pid_t parent_pid = -1;
69 static void cleanup(void);
73 * Print usage message and terminate.
79 g_fprintf(stderr, _("Usage: amfetchdump [options] config hostname [diskname [datestamp [level [hostname [diskname [datestamp [level ... ]]]]]]] [-o configoption]*\n\n"));
80 g_fprintf(stderr, _("Goes and grabs a dump from tape, moving tapes around and assembling parts as\n"));
81 g_fprintf(stderr, _("necessary. Files are restored to the current directory, unless otherwise\nspecified.\n\n"));
82 g_fprintf(stderr, _(" -p Pipe exactly *one* complete dumpfile to stdout, instead of to disk.\n"));
83 g_fprintf(stderr, _(" -O <output dir> Restore files to this directory.\n"));
84 g_fprintf(stderr, _(" -d <device> Force restoration from a particular tape device.\n"));
85 g_fprintf(stderr, _(" -c Compress output, fastest method available.\n"));
86 g_fprintf(stderr, _(" -C Compress output, best filesize method available.\n"));
87 g_fprintf(stderr, _(" -l Leave dumps (un)compressed, whichever way they were originally on tape.\n"));
88 g_fprintf(stderr, _(" -a Assume all tapes are available via changer, do not prompt for initial load.\n"));
89 g_fprintf(stderr, _(" -i <dst_file> Search through tapes and write out an inventory while we\n restore. Useful only if normal logs are unavailable.\n"));
90 g_fprintf(stderr, _(" -w Wait to put split dumps together until all chunks have been restored.\n"));
91 g_fprintf(stderr, _(" -n Do not reassemble split dumpfiles.\n"));
92 g_fprintf(stderr, _(" -k Skip the rewind/label read when reading a new tape.\n"));
93 g_fprintf(stderr, _(" -s Do not use fast forward to skip files we won't restore. Use only if fsf\n causes your tapes to skip too far.\n"));
94 g_fprintf(stderr, _(" -b <blocksize> Force a particular block size (default is 32kb).\n"));
99 sort_needed_tapes_by_write_timestamp(
103 needed_tape_t *a_nt = (needed_tape_t *)a;
104 needed_tape_t *b_nt = (needed_tape_t *)b;
105 tape_t *a_t = a_nt->isafile? NULL : lookup_tapelabel(a_nt->label);
106 tape_t *b_t = b_nt->isafile? NULL : lookup_tapelabel(b_nt->label);
107 char *a_ds = a_t? a_t->datestamp : "none";
108 char *b_ds = b_t? b_t->datestamp : "none";
110 /* if the tape timestamps match, sort them by usage_order, which is derived
111 * from the order the tapes were written in a single run */
112 int r = strcmp(a_ds, b_ds);
115 return (a_nt->usage_order > b_nt->usage_order)? 1 : -1;
119 * Build the list of tapes we'll be wanting, and include data about the
120 * files we want from said tapes while we're at it (the whole find_result
129 GSList *needed_tapes = NULL;
130 GSList *seen_dumps = NULL;
131 GSList *iter, *iter2;
132 find_result_t *alldumps = NULL;
133 find_result_t *curmatch = NULL;
134 find_result_t *matches = NULL;
135 tapelist_t *tapes = NULL;
136 int usage_order_counter = 0;
139 /* Load the tape list */
140 conf_tapelist = config_dir_relative(getconf_str(CNF_TAPELIST));
141 if(read_tapelist(conf_tapelist)) {
142 error(_("could not load tapelist \"%s\""), conf_tapelist);
145 amfree(conf_tapelist);
147 /* Grab a find_output_t of all logged dumps */
148 alldumps = find_dump(diskqp);
149 if(alldumps == NULL){
150 g_fprintf(stderr, _("No dump records found\n"));
154 /* Compare all known dumps to our match list, note what we'll need */
155 matches = dumps_match_dumpspecs(alldumps, dumpspecs, 1);
157 /* D = dump_timestamp, newest first
162 * w = write_timestamp */
163 sort_find_result("Dhklpw", &matches);
165 for(curmatch = matches; curmatch; curmatch = curmatch->next) {
168 /* keep only first dump if only_one */
170 curmatch != matches &&
171 (strcmp(curmatch->hostname, matches->hostname) ||
172 strcmp(curmatch->diskname, matches->diskname) ||
173 strcmp(curmatch->timestamp, matches->timestamp) ||
174 curmatch->level != matches->level)) {
177 if(strcmp("OK", curmatch->status)){
178 g_fprintf(stderr,_("Dump %s %s %s %d had status '%s', skipping\n"),
179 curmatch->timestamp, curmatch->hostname,
180 curmatch->diskname, curmatch->level,
185 for(iter = needed_tapes; iter; iter = iter->next) {
186 needed_tape_t *curtape = iter->data;
187 if (!strcmp(curtape->label, curmatch->label)) {
192 for(iter2 = curtape->files; iter2; iter2 = iter2->next){
193 find_result_t *rsttemp = iter2->data;
194 if(curmatch->filenum == rsttemp->filenum){
195 g_fprintf(stderr, _("Seeing multiple entries for tape "
196 "%s file %lld, using most recent\n"),
198 (long long)curmatch->filenum);
206 curtape->isafile = (curmatch->filenum < 1);
207 curtape->files = g_slist_prepend(curtape->files, curmatch);
212 needed_tape_t *newtape = g_new0(needed_tape_t, 1);
213 newtape->usage_order = usage_order_counter++;
214 newtape->files = g_slist_prepend(newtape->files, curmatch);
215 newtape->isafile = (curmatch->filenum < 1);
216 newtape->label = curmatch->label;
217 needed_tapes = g_slist_prepend(needed_tapes, newtape);
218 } /* if(!havetape) */
220 } /* for(curmatch = matches ... */
222 if(g_slist_length(needed_tapes) == 0){
223 g_fprintf(stderr, _("No matching dumps found\n"));
228 /* sort the tapelist by tape write_timestamp */
229 needed_tapes = g_slist_sort(needed_tapes, sort_needed_tapes_by_write_timestamp);
231 /* stick that list in a structure that librestore will understand, removing
232 * files we have already seen in the process; this prefers the earliest written
233 * copy of any dumps which are available on multiple tapes */
235 for(iter = needed_tapes; iter; iter = iter->next) {
236 needed_tape_t *curtape = iter->data;
237 for(iter2 = curtape->files; iter2; iter2 = iter2->next) {
238 find_result_t *curfind = iter2->data;
243 /* have we already seen this? */
245 for (iter = seen_dumps; iter; iter = iter->next) {
248 if (!strcmp(prev->partnum, curfind->partnum) &&
249 !strcmp(prev->hostname, curfind->hostname) &&
250 !strcmp(prev->diskname, curfind->diskname) &&
251 !strcmp(prev->timestamp, curfind->timestamp) &&
252 prev->level == curfind->level) {
259 seen_dumps = g_slist_prepend(seen_dumps, curfind);
260 tapes = append_to_tapelist(tapes, curtape->label,
261 curfind->filenum, -1, curtape->isafile);
266 /* free our resources */
267 for (iter = needed_tapes; iter; iter = iter->next) {
268 needed_tape_t *curtape = iter->data;
269 g_slist_free(curtape->files);
272 g_slist_free(seen_dumps);
273 g_slist_free(needed_tapes);
274 free_find_result(&matches);
277 g_fprintf(stderr, _("%d tape(s) needed for restoration\n"), num_entries(tapes));
283 * Parses command line, then loops through all files on tape, restoring
284 * files that match the command line criteria.
294 GSList *dumpspecs = NULL;
296 tapelist_t *needed_tapes = NULL;
298 rst_flags_t *rst_flags;
299 int minimum_arguments;
300 config_overwrites_t *cfg_ovr = NULL;
302 char * conf_diskfile = NULL;
305 * Configure program for internationalization:
306 * 1) Only set the message locale for now.
307 * 2) Set textdomain for all amanda related programs to "amanda"
308 * We don't want to be forced to support dozens of message catalogs.
310 setlocale(LC_MESSAGES, "C");
311 textdomain("amanda");
313 for(fd = 3; fd < (int)FD_SETSIZE; fd++) {
315 * Make sure nobody spoofs us with a lot of extra open files
316 * that would cause a successful open to get a very high file
317 * descriptor, which in turn might be used as an index into
318 * an array (e.g. an fd_set).
323 set_pname("amfetchdump");
325 /* Don't die when child closes pipe */
326 signal(SIGPIPE, SIG_IGN);
328 dbopen(DBG_SUBDIR_SERVER);
330 erroutput_type = ERR_INTERACTIVE;
331 error_exit_status = 2;
333 rst_flags = new_rst_flags();
334 rst_flags->wait_tape_prompt = 1;
337 cfg_ovr = new_config_overwrites(argc/2);
338 while( (opt = getopt(argc, argv, "alht:scCpb:nwi:d:O:o:")) != -1) {
341 rst_flags->blocksize = (ssize_t)strtol(optarg, &e, 10);
342 if(*e == 'k' || *e == 'K') {
343 rst_flags->blocksize *= 1024;
344 } else if(*e == 'm' || *e == 'M') {
345 rst_flags->blocksize *= 1024 * 1024;
346 } else if(*e != '\0') {
347 error(_("invalid blocksize value \"%s\""), optarg);
350 if(rst_flags->blocksize < DISK_BLOCK_BYTES) {
351 error(_("minimum block size is %dk"), DISK_BLOCK_BYTES / 1024);
355 case 'c': rst_flags->compress = 1; break;
356 case 'O': rst_flags->restore_dir = stralloc(optarg) ; break;
357 case 'd': rst_flags->alt_tapedev = stralloc(optarg) ; break;
359 rst_flags->compress = 1;
360 rst_flags->comp_type = COMPRESS_BEST_OPT;
362 case 'p': rst_flags->pipe_to_fd = STDOUT_FILENO; break;
363 case 's': rst_flags->fsf = (off_t)0; break;
364 case 'l': rst_flags->leave_comp = 1; break;
365 case 'i': rst_flags->inventory_log = stralloc(optarg); break;
366 case 'n': rst_flags->inline_assemble = 0; break;
367 case 'w': rst_flags->delay_assemble = 1; break;
368 case 'a': rst_flags->wait_tape_prompt = 0; break;
369 case 'h': rst_flags->headers = 1; break;
370 case 'o': add_config_overwrite_opt(cfg_ovr, optarg); break;
377 /* Check some flags that affect inventorying */
378 if(rst_flags->inventory_log){
379 if(rst_flags->inline_assemble) rst_flags->delay_assemble = 1;
380 rst_flags->inline_assemble = 0;
381 rst_flags->leave_comp = 1;
382 if(rst_flags->compress){
383 error(_("Cannot force compression when doing inventory/search"));
386 g_fprintf(stderr, _("Doing inventory/search, dumps will not be uncompressed or assembled on-the-fly.\n"));
389 if(rst_flags->delay_assemble){
390 g_fprintf(stderr, _("Using -w, split dumpfiles will *not* be automatically uncompressed.\n"));
394 /* make sure our options all make sense otherwise */
395 if(check_rst_flags(rst_flags) == -1) {
400 if (rst_flags->inventory_log) {
401 minimum_arguments = 1;
403 minimum_arguments = 2;
406 if(argc - optind < minimum_arguments) {
411 config_init(CONFIG_INIT_EXPLICIT_NAME, argv[optind++]);
412 apply_config_overwrites(cfg_ovr);
414 conf_diskfile = config_dir_relative(getconf_str(CNF_DISKFILE));
415 read_diskfile(conf_diskfile, &diskq);
416 amfree(conf_diskfile);
418 if (config_errors(NULL) >= CFGERR_WARNINGS) {
419 config_print_errors();
420 if (config_errors(NULL) >= CFGERR_ERRORS) {
421 g_critical(_("errors processing config file"));
425 check_running_as(RUNNING_AS_DUMPUSER);
427 dbrename(get_config_name(), DBG_SUBDIR_SERVER);
429 dumpspecs = cmdline_parse_dumpspecs(argc - optind, argv + optind,
430 CMDLINE_PARSE_DATESTAMP |
431 CMDLINE_PARSE_LEVEL |
432 CMDLINE_EMPTY_TO_WILDCARD);
435 * We've been told explicitly to go and search through the tapes the hard
438 if(rst_flags->inventory_log){
439 g_fprintf(stderr, _("Beginning tape-by-tape search.\n"));
440 search_tapes(stderr, stdin, rst_flags->alt_tapedev == NULL,
441 NULL, dumpspecs, rst_flags, NULL);
446 /* Decide what tapes we'll need */
447 needed_tapes = list_needed_tapes(dumpspecs,
448 rst_flags->pipe_to_fd == STDOUT_FILENO,
451 parent_pid = getpid();
453 get_lock = lock_logfile(); /* config is loaded, should be ok here */
455 char *process_name = get_master_process(rst_conf_logfile);
456 error(_("%s exists: %s is already running, or you must run amcleanup"), rst_conf_logfile, process_name);
458 log_add(L_INFO, "%s pid %ld", get_pname(), (long)getpid());
459 search_tapes(NULL, stdin, rst_flags->alt_tapedev == NULL,
460 needed_tapes, dumpspecs, rst_flags, NULL);
463 dumpspec_list_free(dumpspecs);
465 if(rst_flags->inline_assemble || rst_flags->delay_assemble)
466 flush_open_outputs(1, NULL);
467 else flush_open_outputs(0, NULL);
469 free_disklist(&diskq);
470 free_rst_flags(rst_flags);
478 if (parent_pid == getpid()) {
480 log_add(L_INFO, "pid-done %ld\n", (long)getpid());
481 unlink(rst_conf_logfile);