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.2.1 2006/09/27 12:04:09 martinea Exp $
29 * retrieves specific dumps from a set of amanda tapes
34 #include "fileheader.h"
43 #define CREAT_MODE 0640
45 extern char *rst_conf_logfile;
46 extern char *config_dir;
49 typedef struct needed_tapes_s {
53 struct needed_tapes_s *next;
54 struct needed_tapes_s *prev;
60 tapelist_t *list_needed_tapes(match_list_t *match_list);
62 int main(int argc, char **argv);
65 static pid_t parent_pid = -1;
66 static void cleanup(void);
70 * Do exit(2) after an error, rather than exit(1).
81 * Print usage message and terminate.
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");
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
113 match_list_t * match_list)
115 needed_tape_t *needed_tapes = NULL, *curtape = NULL;
117 match_list_t *me = NULL;
118 find_result_t *alldumps = NULL;
119 tapelist_t *tapes = NULL;
121 char *conf_diskfile, *conf_tapelist;
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);
129 conf_diskfile = stralloc2(config_dir, conf_diskfile);
131 if(read_diskfile(conf_diskfile, &diskqp) != 0) {
132 error("could not load disklist \"%s\"", conf_diskfile);
135 if (*conf_tapelist == '/') {
136 conf_tapelist = stralloc(conf_tapelist);
138 conf_tapelist = stralloc2(config_dir, conf_tapelist);
140 if(read_tapelist(conf_tapelist)) {
141 error("could not load tapelist \"%s\"", conf_tapelist);
144 amfree(conf_diskfile);
145 amfree(conf_tapelist);
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");
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;
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){
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,
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));
178 memcpy(rstfile, curmatch, SIZEOF(find_result_t));
182 for(rsttemp = curtape->files;
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",
189 (OFF_T_FMT_TYPE)rstfile->filenum);
197 rstfile->next = curtape->files;
199 if(curmatch->filenum < 1) curtape->isafile = 1;
200 else curtape->isafile = 0;
201 curtape->files = rstfile;
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;
216 needed_tapes->prev->next = newtape;
217 newtape->prev = needed_tapes->prev;
218 needed_tapes->prev = newtape;
221 needed_tapes = newtape;
222 needed_tapes->prev = needed_tapes;
224 newtape->next = NULL;
227 // free_find_result(rstfile);
229 } /* if(!havetape) */
231 } /* for(curmatch = matches ... */
232 } /* for(me = match_list ... */
235 fprintf(stderr, "No matching dumps found\n");
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);
249 fprintf(stderr, "%d tape(s) needed for restoration\n", numtapes);
255 * Parses command line, then loops through all files on tape, restoring
256 * files that match the command line criteria.
267 match_list_t *match_list = NULL;
268 match_list_t *me = NULL;
270 char *config_name = NULL;
271 char *conffile = NULL;
272 tapelist_t *needed_tapes = NULL;
275 rst_flags_t *rst_flags;
276 struct passwd *pwent;
277 int new_argc, my_argc;
278 char **new_argv, **my_argv;
280 for(fd = 3; fd < (int)FD_SETSIZE; fd++) {
282 * Make sure nobody spoofs us with a lot of extra open files
283 * that would cause an open we do to get a very high file
284 * descriptor, which in turn might be used as an index into
285 * an array (e.g. an fd_set).
290 set_pname("amfetchdump");
292 dbopen(DBG_SUBDIR_SERVER);
296 /* we'd rather not run as root */
298 if(client_uid == (uid_t) -1 && (pwent = getpwnam(CLIENT_LOGIN)) != NULL) {
299 client_uid = pwent->pw_uid;
300 client_gid = pwent->pw_gid;
304 if(client_uid == (uid_t) -1) {
305 error("error [cannot find user %s in passwd file]\n", CLIENT_LOGIN);
310 initgroups(CLIENT_LOGIN, client_gid);
316 #endif /* FORCE_USERID */
318 /* Don't die when child closes pipe */
319 signal(SIGPIPE, SIG_IGN);
321 erroutput_type = ERR_INTERACTIVE;
325 parse_server_conf(argc, argv, &new_argc, &new_argv);
334 rst_flags = new_rst_flags();
335 rst_flags->wait_tape_prompt = 1;
338 while( (opt = getopt(my_argc, my_argv, "alht:scCpb:nwi:d: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 = fileno(stdout); 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;
376 /* Check some flags that affect inventorying */
377 if(rst_flags->inventory_log){
378 if(rst_flags->inline_assemble) rst_flags->delay_assemble = 1;
379 rst_flags->inline_assemble = 0;
380 rst_flags->leave_comp = 1;
381 if(rst_flags->compress){
382 error("Cannot force compression when doing inventory/search");
385 fprintf(stderr, "Doing inventory/search, dumps will not be uncompressed or assembled on-the-fly.\n");
388 if(rst_flags->delay_assemble){
389 fprintf(stderr, "Using -w, split dumpfiles will *not* be automatically uncompressed.\n");
393 /* make sure our options all make sense otherwise */
394 if(check_rst_flags(rst_flags) == -1) {
399 config_name = my_argv[optind++];
400 config_dir = vstralloc(CONFIG_DIR, "/", config_name, "/", NULL);
401 conffile = stralloc2(config_dir, CONFFILE_NAME);
402 if (read_conffile(conffile)) {
403 error("errors processing config file \"%s\"", conffile);
408 dbrename(config_name, DBG_SUBDIR_SERVER);
410 if((my_argc - optind) < 1 && !rst_flags->inventory_log){
411 fprintf(stderr, "Not enough arguments\n\n");
416 #define ARG_GET_HOST 0
417 #define ARG_GET_DISK 1
418 #define ARG_GET_DATE 2
419 #define ARG_GET_LEVL 3
421 arg_state = ARG_GET_HOST;
422 while(optind < my_argc) {
426 * New host/disk/date/level set, so allocate a match_list.
428 me = alloc(SIZEOF(*me));
429 me->hostname = my_argv[optind++];
433 me->next = match_list;
435 if(me->hostname[0] != '\0'
436 && (errstr=validate_regexp(me->hostname)) != NULL) {
437 fprintf(stderr, "%s: bad hostname regex \"%s\": %s\n",
438 get_pname(), me->hostname, errstr);
442 arg_state = ARG_GET_DISK;
445 me->diskname = my_argv[optind++];
446 if(me->diskname[0] != '\0'
447 && (errstr=validate_regexp(me->diskname)) != NULL) {
448 fprintf(stderr, "%s: bad diskname regex \"%s\": %s\n",
449 get_pname(), me->diskname, errstr);
453 arg_state = ARG_GET_DATE;
456 me->datestamp = my_argv[optind++];
457 if(me->datestamp[0] != '\0'
458 && (errstr=validate_regexp(me->datestamp)) != NULL) {
459 fprintf(stderr, "%s: bad datestamp regex \"%s\": %s\n",
460 get_pname(), me->datestamp, errstr);
464 arg_state = ARG_GET_LEVL;
467 me->level = my_argv[optind++];
468 if(me->level[0] != '\0'
469 && (errstr=validate_regexp(me->level)) != NULL) {
470 fprintf(stderr, "%s: bad level regex \"%s\": %s\n",
471 get_pname(), me->level, errstr);
478 /* XXX I don't think this can happen */
479 if(match_list == NULL && !rst_flags->inventory_log) {
480 match_list = alloc(SIZEOF(*match_list));
481 match_list->hostname = "";
482 match_list->diskname = "";
483 match_list->datestamp = "";
484 match_list->level = "";
485 match_list->next = NULL;
489 * We've been told explicitly to go and search through the tapes the hard
492 if(rst_flags->inventory_log){
493 fprintf(stderr, "Beginning tape-by-tape search.\n");
494 search_tapes(stderr, stdin, 1, NULL, match_list, rst_flags, NULL);
499 /* Decide what tapes we'll need */
500 needed_tapes = list_needed_tapes(match_list);
502 parent_pid = getpid();
504 get_lock = lock_logfile(); /* config is loaded, should be ok here */
506 error("%s exists: amdump or amflush is already running, or you must run amcleanup", rst_conf_logfile);
508 search_tapes(NULL, stdin, 1, needed_tapes, match_list, rst_flags, NULL);
511 free_match_list(match_list);
513 if(rst_flags->inline_assemble || rst_flags->delay_assemble)
514 flush_open_outputs(1, NULL);
515 else flush_open_outputs(0, NULL);
517 free_rst_flags(rst_flags);
518 free_new_argv(new_argc, new_argv);
526 if(parent_pid == getpid()) {
527 if(get_lock) unlink(rst_conf_logfile);