e94ce290f1c843b4965d8a296204552686b04fce
[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.7 2006/03/14 13:12:01 martinea Exp $
28  *
29  * retrieves specific dumps from a set of amanda tapes
30  */
31
32 #include "amanda.h"
33 #include "tapeio.h"
34 #include "fileheader.h"
35 #include "util.h"
36 #include "restore.h"
37 #include "diskfile.h"
38 #include "tapefile.h"
39 #include "find.h"
40 #include "changer.h"
41 #include "logfile.h"
42
43 #define CREAT_MODE      0640
44
45 extern char *rst_conf_logdir;
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     find_result_t *files;
54     struct needed_tapes_s *next;
55     struct needed_tapes_s *prev;
56 } needed_tape_t;
57
58 /* local functions */
59
60 void errexit P((void));
61 void handle_sigpipe P((int sig));
62 tapelist_t *list_needed_tapes P((match_list_t *match_list));
63 void usage P((void));
64 int main P((int argc, char **argv));
65
66 /* exit routine */
67 static int parent_pid = -1;
68 static void cleanup P((void));
69
70 void errexit()
71 /*
72  * Do exit(2) after an error, rather than exit(1).
73  */
74 {
75     exit(2);
76 }
77
78
79 void usage()
80 /*
81  * Print usage message and terminate.
82  */
83 {
84     fprintf(stderr, "Usage: amfetchdump [options] config hostname [diskname [datestamp [level [hostname [diskname [datestamp [level ... ]]]]]]]\n\n");
85     fprintf(stderr, "Goes and grabs a dump from tape, moving tapes around and assembling parts as\n");
86     fprintf(stderr, "necessary.  Files are restored to the current directory, unless otherwise\nspecified.\n\n");
87     fprintf(stderr, "  -p Pipe exactly *one* complete dumpfile to stdout, instead of to disk.\n");
88     fprintf(stderr, "  -o <output dir> Restore files to this directory.\n");
89     fprintf(stderr, "  -d <device> Force restoration from a particular tape device.\n");
90     fprintf(stderr, "  -c Compress output, fastest method available.\n");
91     fprintf(stderr, "  -C Compress output, best filesize method available.\n");
92     fprintf(stderr, "  -l Leave dumps (un)compressed, whichever way they were originally on tape.\n");
93     fprintf(stderr, "  -a Assume all tapes are available via changer, do not prompt for initial load.\n");
94     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");
95     fprintf(stderr, "  -w Wait to put split dumps together until all chunks have been restored.\n");
96     fprintf(stderr, "  -n Do not reassemble split dumpfiles.\n");
97     fprintf(stderr, "  -k Skip the rewind/label read when reading a new tape.\n");
98     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");
99     fprintf(stderr, "  -b <blocksize> Force a particular block size (default is 32kb).\n");
100     exit(1);
101 }
102
103 /*
104  * Build the list of tapes we'll be wanting, and include data about the
105  * files we want from said tapes while we're at it (the whole find_result
106  * should do fine)
107  */
108 tapelist_t *list_needed_tapes(match_list)
109 match_list_t *match_list;
110 {
111     needed_tape_t *needed_tapes = NULL, *curtape = NULL;
112     disklist_t diskqp;
113     match_list_t *me = NULL;
114     find_result_t *alldumps = NULL;
115     tapelist_t *tapes = NULL;
116     int numtapes = 0;
117     char *conf_diskfile, *conf_tapelist;
118
119     /* For disks and tape lists */
120     conf_diskfile = getconf_str(CNF_DISKFILE);
121     conf_tapelist = getconf_str(CNF_TAPELIST);
122     if (*conf_diskfile == '/') {
123         conf_diskfile = stralloc(conf_diskfile);
124     } else {
125         conf_diskfile = stralloc2(config_dir, conf_diskfile);
126     }
127     if(read_diskfile(conf_diskfile, &diskqp) != 0) {
128         error("could not load disklist \"%s\"", conf_diskfile);
129     }
130     if (*conf_tapelist == '/') {
131         conf_tapelist = stralloc(conf_tapelist);
132     } else {
133         conf_tapelist = stralloc2(config_dir, conf_tapelist);
134     }
135     if(read_tapelist(conf_tapelist)) {
136         error("could not load tapelist \"%s\"", conf_tapelist);
137     }
138     amfree(conf_diskfile);
139     amfree(conf_tapelist);
140
141     /* Grab a find_output_t of all logged dumps */
142     alldumps = find_dump(1, &diskqp); 
143     free_disklist(&diskqp);
144     if(alldumps == NULL){
145         fprintf(stderr, "No dump records found\n");
146         exit(1);
147     }
148  
149     /* Compare all known dumps to our match list, note what we'll need */
150     for(me = match_list; me; me = me->next) {
151         find_result_t *curmatch = NULL; 
152         find_result_t *matches = NULL;  
153
154         matches = dumps_match(alldumps, me->hostname, me->diskname,
155                                  me->datestamp, me->level, 1);
156         sort_find_result("Dhklp", &matches);
157         for(curmatch = matches; curmatch; curmatch = curmatch->next){
158             int havetape = 0;
159             if(strcmp("OK", curmatch->status)){
160                 fprintf(stderr,"Dump %d %s %s %d had status '%s', skipping\n",
161                                  curmatch->datestamp, curmatch->hostname,
162                                  curmatch->diskname, curmatch->level,
163                                  curmatch->status);
164                 continue;
165             }
166             for(curtape = needed_tapes; curtape; curtape = curtape->next) {
167                 if(!strcmp(curtape->label, curmatch->label)){
168                     find_result_t *rsttemp = NULL;
169                     find_result_t *rstfile = alloc(sizeof(find_result_t));
170                     int keep = 1;
171
172                     memcpy(rstfile, curmatch, sizeof(find_result_t));
173
174                     havetape = 1;
175
176                     for(rsttemp = curtape->files;
177                             rsttemp;
178                             rsttemp=rsttemp->next){
179                         if(rstfile->filenum == rsttemp->filenum){
180                             fprintf(stderr, "Seeing multiple entries for tape %s file %d, using most recent\n", curtape->label, rstfile->filenum);
181                             keep = 0;
182                         }
183                     }
184                     if(!keep){
185                         amfree(rstfile);
186                         break;
187                     }
188                     rstfile->next = curtape->files;
189
190                     if(curmatch->filenum < 1) curtape->isafile = 1;
191                     else curtape->isafile = 0;
192                     curtape->files = rstfile;
193                     break;
194                 }
195             }
196             if(!havetape){
197                 find_result_t *rstfile = alloc(sizeof(find_result_t));
198                 needed_tape_t *newtape =
199                                           alloc(sizeof(needed_tape_t));
200                 memcpy(rstfile, curmatch, sizeof(find_result_t));
201                 rstfile->next = NULL;
202                 newtape->files = rstfile;
203                 if(curmatch->filenum < 1) newtape->isafile = 1;
204                 else newtape->isafile = 0;
205                 newtape->label = curmatch->label;
206                 if(needed_tapes){
207                     needed_tapes->prev->next = newtape;
208                     newtape->prev = needed_tapes->prev;
209                     needed_tapes->prev = newtape;
210                 }
211                 else{
212                     needed_tapes = newtape;
213                     needed_tapes->prev = needed_tapes;
214                 }
215                 newtape->next = NULL;
216                 numtapes++;
217 #if 0
218 //              free_find_result(rstfile);
219 #endif
220             } /* if(!havetape) */
221
222         } /* for(curmatch = matches ... */
223     } /* for(me = match_list ... */
224
225     if(numtapes == 0){
226       fprintf(stderr, "No matching dumps found\n");
227       exit(1);
228     }
229
230     /* stick that list in a structure that librestore will understand */
231     for(curtape = needed_tapes; curtape; curtape = curtape->next) {
232         find_result_t *curfind = NULL;
233         for(curfind = curtape->files; curfind; curfind = curfind->next) {
234             tapes = append_to_tapelist(tapes, curtape->label,
235                                        curfind->filenum, curtape->isafile);
236         }
237     }
238
239     fprintf(stderr, "%d tape(s) needed for restoration\n", numtapes);
240     return(tapes);
241 }
242
243 int main(argc, argv)
244 int argc;
245 char **argv;
246 /*
247  * Parses command line, then loops through all files on tape, restoring
248  * files that match the command line criteria.
249  */
250 {
251     extern int optind;
252     int opt;
253     char *errstr;
254     match_list_t *match_list = NULL;
255     match_list_t *me = NULL;
256     int fd;
257     char *config_name = NULL;
258     char *conffile = NULL;
259     tapelist_t *needed_tapes = NULL;
260     char *e;
261     int arg_state;
262     rst_flags_t *rst_flags;
263     struct passwd *pwent;
264
265     for(fd = 3; fd < FD_SETSIZE; fd++) {
266         /*
267          * Make sure nobody spoofs us with a lot of extra open files
268          * that would cause an open we do to get a very high file
269          * descriptor, which in turn might be used as an index into
270          * an array (e.g. an fd_set).
271          */
272         close(fd);
273     }
274
275     set_pname("amfetchdump");
276
277 #ifdef FORCE_USERID
278
279     /* we'd rather not run as root */
280
281     if(client_uid == (uid_t) -1 && (pwent = getpwnam(CLIENT_LOGIN)) != NULL) {
282         client_uid = pwent->pw_uid;
283         client_gid = pwent->pw_gid;
284         endpwent();
285     }
286     if(geteuid() == 0) {
287         if(client_uid == (uid_t) -1) {
288             error("error [cannot find user %s in passwd file]\n", CLIENT_LOGIN);
289         }
290
291         initgroups(CLIENT_LOGIN, client_gid);
292         setgid(client_gid);
293         setuid(client_uid);
294     }
295
296 #endif  /* FORCE_USERID */
297
298     /* Don't die when child closes pipe */
299     signal(SIGPIPE, SIG_IGN);
300
301     erroutput_type = ERR_INTERACTIVE;
302
303     onerror(errexit);
304
305     if(argc <= 1) usage();
306
307     rst_flags = new_rst_flags();
308     rst_flags->wait_tape_prompt = 1;
309     
310     /* handle options */
311     while( (opt = getopt(argc, argv, "alht:scCpb:nwi:d:o:")) != -1) {
312         switch(opt) {
313         case 'b': rst_flags->compress = 1; break;
314             rst_flags->blocksize = strtol(optarg, &e, 10);
315             if(*e == 'k' || *e == 'K') {
316                 rst_flags->blocksize *= 1024;
317             } else if(*e == 'm' || *e == 'M') {
318                 rst_flags->blocksize *= 1024 * 1024;
319             } else if(*e != '\0') {
320                 error("invalid blocksize value \"%s\"", optarg);
321             }
322             if(rst_flags->blocksize < DISK_BLOCK_BYTES) {
323                 error("minimum block size is %dk", DISK_BLOCK_BYTES / 1024);
324             }
325             break;
326         case 'c': rst_flags->compress = 1; break;
327         case 'o': rst_flags->restore_dir = stralloc(optarg) ; break;
328         case 'd': rst_flags->alt_tapedev = stralloc(optarg) ; break;
329         case 'C':
330             rst_flags->compress = 1;
331             rst_flags->comp_type = COMPRESS_BEST_OPT;
332             break;
333         case 'p': rst_flags->pipe_to_fd = fileno(stdout); break;
334         case 's': rst_flags->fsf = 0; break;
335         case 'l': rst_flags->leave_comp = 1; break;
336         case 'i': rst_flags->inventory_log = stralloc(optarg); break;
337         case 'n': rst_flags->inline_assemble = 0; break;
338         case 'w': rst_flags->delay_assemble = 1; break;
339         case 'a': rst_flags->wait_tape_prompt = 0; break;
340         case 'h': rst_flags->headers = 1; break;
341         default:
342             usage();
343         }
344     }
345
346     /* Check some flags that affect inventorying */
347     if(rst_flags->inventory_log){
348         if(rst_flags->inline_assemble) rst_flags->delay_assemble = 1;
349         rst_flags->inline_assemble = 0;
350         rst_flags->leave_comp = 1;
351         if(rst_flags->compress){
352             error("Cannot force compression when doing inventory/search");
353         }
354         fprintf(stderr, "Doing inventory/search, dumps will not be uncompressed or assembled on-the-fly.\n");
355     }
356     else{
357         if(rst_flags->delay_assemble){
358             fprintf(stderr, "Using -w, split dumpfiles will *not* be automatically uncompressed.\n");
359         }
360     }
361
362     /* make sure our options all make sense otherwise */
363     if(check_rst_flags(rst_flags) == -1) usage();
364
365     config_name = argv[optind++];
366     config_dir = vstralloc(CONFIG_DIR, "/", config_name, "/", NULL);
367     conffile = stralloc2(config_dir, CONFFILE_NAME);
368     if (read_conffile(conffile)) {
369         error("errors processing config file \"%s\"", conffile);
370     }
371     amfree(conffile);
372
373
374     if((argc - optind) < 1 && !rst_flags->inventory_log){
375         fprintf(stderr, "Not enough arguments\n\n");
376         usage();
377     }
378
379 #define ARG_GET_HOST 0
380 #define ARG_GET_DISK 1
381 #define ARG_GET_DATE 2
382 #define ARG_GET_LEVL 3
383
384     arg_state = ARG_GET_HOST;
385     while(optind < argc) {
386         switch(arg_state) {
387         case ARG_GET_HOST:
388             /*
389              * New host/disk/date/level set, so allocate a match_list.
390              */
391             me = alloc(sizeof(*me));
392             me->hostname = argv[optind++];
393             me->diskname = "";
394             me->datestamp = "";
395             me->level = "";
396             me->next = match_list;
397             match_list = me;
398             if(me->hostname[0] != '\0'
399                && (errstr=validate_regexp(me->hostname)) != NULL) {
400                 fprintf(stderr, "%s: bad hostname regex \"%s\": %s\n",
401                         get_pname(), me->hostname, errstr);
402                 usage();
403             }
404             arg_state = ARG_GET_DISK;
405             break;
406         case ARG_GET_DISK:
407             me->diskname = argv[optind++];
408             if(me->diskname[0] != '\0'
409                && (errstr=validate_regexp(me->diskname)) != NULL) {
410                 fprintf(stderr, "%s: bad diskname regex \"%s\": %s\n",
411                         get_pname(), me->diskname, errstr);
412                 usage();
413             }
414             arg_state = ARG_GET_DATE;
415             break;
416         case ARG_GET_DATE:
417             me->datestamp = argv[optind++];
418             if(me->datestamp[0] != '\0'
419                && (errstr=validate_regexp(me->datestamp)) != NULL) {
420                 fprintf(stderr, "%s: bad datestamp regex \"%s\": %s\n",
421                         get_pname(), me->datestamp, errstr);
422                 usage();
423             }
424             arg_state = ARG_GET_LEVL;
425             break;
426         case ARG_GET_LEVL:
427             me->level = argv[optind++];
428             if(me->level[0] != '\0'
429                && (errstr=validate_regexp(me->level)) != NULL) {
430                 fprintf(stderr, "%s: bad level regex \"%s\": %s\n",
431                         get_pname(), me->level, errstr);
432                 usage();
433             }
434         }
435     }
436
437     /* XXX I don't think this can happen */
438     if(match_list == NULL && !rst_flags->inventory_log) {
439         match_list = alloc(sizeof(*match_list));
440         match_list->hostname = "";
441         match_list->diskname = "";
442         match_list->datestamp = "";
443         match_list->level = "";
444         match_list->next = NULL;
445     }
446
447     /*
448      * We've been told explicitly to go and search through the tapes the hard
449      * way.
450      */
451     if(rst_flags->inventory_log){
452         fprintf(stderr, "Beginning tape-by-tape search.\n");
453         search_tapes(stderr, 1, NULL, match_list, rst_flags, NULL);
454         exit(0);
455     }
456
457
458     /* Decide what tapes we'll need */
459     needed_tapes = list_needed_tapes(match_list);
460     
461     parent_pid = getpid();
462     atexit(cleanup);
463     get_lock = lock_logfile(); /* config is loaded, should be ok here */
464     search_tapes(NULL, 1, needed_tapes, match_list, rst_flags, NULL);
465     cleanup();
466
467     free_match_list(match_list);
468
469     if(rst_flags->inline_assemble || rst_flags->delay_assemble)
470         flush_open_outputs(1, NULL);
471     else flush_open_outputs(0, NULL);
472
473     free_rst_flags(rst_flags);
474
475     return(0);
476 }
477
478 static void
479 cleanup(void)
480 {
481     if(parent_pid == getpid()) {
482         if(get_lock) unlink(rst_conf_logfile);
483     }
484 }
485