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