e0764d79d22b6b162a3e0cd131b15dfc5f811b9e
[debian/amanda] / restore-src / amfetchdump.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1998 University of Maryland at College Park
4  * All Rights Reserved.
5  *
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.
15  *
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.
22  *
23  * Authors: the Amanda Development Team.  Its members are listed in a
24  * file named AUTHORS, in the root directory of this distribution.
25  */
26 /*
27  * $Id: amfetchdump.c,v 1.16 2006/08/24 01:57:15 paddy_s Exp $
28  *
29  * retrieves specific dumps from a set of amanda tapes
30  */
31
32 #include "amanda.h"
33 #include "fileheader.h"
34 #include "util.h"
35 #include "restore.h"
36 #include "diskfile.h"
37 #include "tapefile.h"
38 #include "find.h"
39 #include "changer.h"
40 #include "logfile.h"
41 #include "cmdline.h"
42 #include "server_util.h"
43
44 #define CREAT_MODE      0640
45
46 extern char *rst_conf_logfile;
47 extern char *config_dir;
48 int get_lock = 0;
49
50 typedef struct needed_tapes_s {
51     char *label;
52     int isafile;
53     GSList *files;
54
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 */
58     int usage_order;
59 } needed_tape_t;
60
61 /* local functions */
62
63 tapelist_t *list_needed_tapes(GSList *dumpspecs, int only_one, disklist_t *diskqp);
64 void usage(void);
65 int main(int argc, char **argv);
66
67 /* exit routine */
68 static pid_t parent_pid = -1;
69 static void cleanup(void);
70
71
72 /*
73  * Print usage message and terminate.
74  */
75
76 void
77 usage(void)
78 {
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"));
95     exit(1);
96 }
97
98 static gint
99 sort_needed_tapes_by_write_timestamp(
100         gconstpointer a,
101         gconstpointer b)
102 {
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";
109
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);
113     if (r != 0)
114         return r;
115     return (a_nt->usage_order > b_nt->usage_order)? 1 : -1;
116 }
117
118 /*
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
121  * should do fine)
122  */
123 tapelist_t *
124 list_needed_tapes(
125     GSList *    dumpspecs,
126     int         only_one,
127     disklist_t  *diskqp)
128 {
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;
137     char *conf_tapelist;
138
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);
143         /*NOTREACHED*/
144     }
145     amfree(conf_tapelist);
146
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"));
151         exit(1);
152     }
153
154     /* Compare all known dumps to our match list, note what we'll need */
155     matches = dumps_match_dumpspecs(alldumps, dumpspecs, 1);
156
157     /* D = dump_timestamp, newest first
158      * h = hostname
159      * k = diskname
160      * l = level
161      * p = partnum
162      * w = write_timestamp */
163     sort_find_result("Dhklpw", &matches);
164
165     for(curmatch = matches; curmatch; curmatch = curmatch->next) {
166         int havetape = 0;
167
168         /* keep only first dump if only_one */
169         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)) {
175             continue;
176         }
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,
181                              curmatch->status);
182             continue;
183         }
184
185         for(iter = needed_tapes; iter; iter = iter->next) {
186             needed_tape_t *curtape = iter->data;
187             if (!strcmp(curtape->label, curmatch->label)) {
188                 int keep = 1;
189
190                 havetape = 1;
191
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"),
197                                     curtape->label,
198                                     (long long)curmatch->filenum);
199                         keep = 0;
200                     }
201                 }
202                 if(!keep){
203                     break;
204                 }
205
206                 curtape->isafile = (curmatch->filenum < 1);
207                 curtape->files = g_slist_prepend(curtape->files, curmatch);
208                 break;
209             }
210         }
211         if (!havetape) {
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) */
219
220     } /* for(curmatch = matches ... */
221
222     if(g_slist_length(needed_tapes) == 0){
223       g_fprintf(stderr, _("No matching dumps found\n"));
224       exit(1);
225       /* NOTREACHED */
226     }
227
228     /* sort the tapelist by tape write_timestamp */
229     needed_tapes = g_slist_sort(needed_tapes, sort_needed_tapes_by_write_timestamp);
230
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 */
234     seen_dumps = NULL;
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;
239             find_result_t *prev;
240             GSList *iter;
241             int have_part;
242
243             /* have we already seen this? */
244             have_part = 0;
245             for (iter = seen_dumps; iter; iter = iter->next) {
246                 prev = iter->data;
247
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) {
253                     have_part = 1;
254                     break;
255                 }
256             }
257
258             if (!have_part) {
259                 seen_dumps = g_slist_prepend(seen_dumps, curfind);
260                 tapes = append_to_tapelist(tapes, curtape->label,
261                                            curfind->filenum, -1, curtape->isafile);
262             }
263         }
264     }
265
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);
270         g_free(curtape);
271     }
272     g_slist_free(seen_dumps);
273     g_slist_free(needed_tapes);
274     free_find_result(&matches);
275
276     /* and we're done */
277     g_fprintf(stderr, _("%d tape(s) needed for restoration\n"), num_entries(tapes));
278     return(tapes);
279 }
280
281
282 /*
283  * Parses command line, then loops through all files on tape, restoring
284  * files that match the command line criteria.
285  */
286
287 int
288 main(
289     int         argc,
290     char **     argv)
291 {
292     extern int optind;
293     int opt;
294     GSList *dumpspecs = NULL;
295     int fd;
296     tapelist_t *needed_tapes = NULL;
297     char *e;
298     rst_flags_t *rst_flags;
299     int minimum_arguments;
300     config_overwrites_t *cfg_ovr = NULL;
301     disklist_t diskq;
302     char * conf_diskfile = NULL;
303
304     /*
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.
309      */  
310     setlocale(LC_MESSAGES, "C");
311     textdomain("amanda"); 
312
313     for(fd = 3; fd < (int)FD_SETSIZE; fd++) {
314         /*
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).
319          */
320         close(fd);
321     }
322
323     set_pname("amfetchdump");
324
325     /* Don't die when child closes pipe */
326     signal(SIGPIPE, SIG_IGN);
327
328     dbopen(DBG_SUBDIR_SERVER);
329
330     erroutput_type = ERR_INTERACTIVE;
331     error_exit_status = 2;
332
333     rst_flags = new_rst_flags();
334     rst_flags->wait_tape_prompt = 1;
335
336     /* handle options */
337     cfg_ovr = new_config_overwrites(argc/2);
338     while( (opt = getopt(argc, argv, "alht:scCpb:nwi:d:O:o:")) != -1) {
339         switch(opt) {
340         case 'b':
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);
348                 /*NOTREACHED*/
349             }
350             if(rst_flags->blocksize < DISK_BLOCK_BYTES) {
351                 error(_("minimum block size is %dk"), DISK_BLOCK_BYTES / 1024);
352                 /*NOTREACHED*/
353             }
354             break;
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;
358         case 'C':
359             rst_flags->compress = 1;
360             rst_flags->comp_type = COMPRESS_BEST_OPT;
361             break;
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;
371         default:
372             usage();
373             /*NOTREACHED*/
374         }
375     }
376
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"));
384             /*NOTREACHED*/
385         }
386         g_fprintf(stderr, _("Doing inventory/search, dumps will not be uncompressed or assembled on-the-fly.\n"));
387     }
388     else{
389         if(rst_flags->delay_assemble){
390             g_fprintf(stderr, _("Using -w, split dumpfiles will *not* be automatically uncompressed.\n"));
391         }
392     }
393
394     /* make sure our options all make sense otherwise */
395     if(check_rst_flags(rst_flags) == -1) {
396         usage();
397         /*NOTREACHED*/
398     }
399
400     if (rst_flags->inventory_log) {
401         minimum_arguments = 1;
402     } else {
403         minimum_arguments = 2;
404     }
405  
406     if(argc - optind < minimum_arguments) {
407         usage();
408         /*NOTREACHED*/
409     }
410
411     config_init(CONFIG_INIT_EXPLICIT_NAME, argv[optind++]);
412     apply_config_overwrites(cfg_ovr);
413
414     conf_diskfile = config_dir_relative(getconf_str(CNF_DISKFILE));
415     read_diskfile(conf_diskfile, &diskq);
416     amfree(conf_diskfile);
417
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"));
422         }
423     }
424
425     check_running_as(RUNNING_AS_DUMPUSER);
426
427     dbrename(get_config_name(), DBG_SUBDIR_SERVER);
428
429     dumpspecs = cmdline_parse_dumpspecs(argc - optind, argv + optind,
430                                         CMDLINE_PARSE_DATESTAMP |
431                                         CMDLINE_PARSE_LEVEL |
432                                         CMDLINE_EMPTY_TO_WILDCARD);
433
434     /*
435      * We've been told explicitly to go and search through the tapes the hard
436      * way.
437      */
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);
442         exit(0);
443     }
444
445
446     /* Decide what tapes we'll need */
447     needed_tapes = list_needed_tapes(dumpspecs,
448                                      rst_flags->pipe_to_fd == STDOUT_FILENO,
449                                      &diskq);
450
451     parent_pid = getpid();
452     atexit(cleanup);
453     get_lock = lock_logfile(); /* config is loaded, should be ok here */
454     if(get_lock == 0) {
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);
457     }
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);
461     cleanup();
462
463     dumpspec_list_free(dumpspecs);
464
465     if(rst_flags->inline_assemble || rst_flags->delay_assemble)
466         flush_open_outputs(1, NULL);
467     else flush_open_outputs(0, NULL);
468
469     free_disklist(&diskq);
470     free_rst_flags(rst_flags);
471
472     return(0);
473 }
474
475 static void
476 cleanup(void)
477 {
478     if (parent_pid == getpid()) {
479         if (get_lock) {
480             log_add(L_INFO, "pid-done %ld\n", (long)getpid());
481             unlink(rst_conf_logfile);
482         }
483     }
484 }