Imported Upstream version 3.1.0
[debian/amanda] / oldrecover-src / amrecover.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1998, 2000 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: amrecover.c,v 1.7 2006/07/25 18:27:57 martinea Exp $
28  *
29  * an interactive program for recovering backed-up files
30  */
31
32 #include "amanda.h"
33 #include "stream.h"
34 #include "amfeatures.h"
35 #include "amrecover.h"
36 #include "getfsent.h"
37 #include "dgram.h"
38 #include "util.h"
39 #include "conffile.h"
40
41 extern int process_line(char *line);
42 int guess_disk(char *cwd, size_t cwd_len, char **dn_guess, char **mpt_guess);
43 int get_line(void);
44 int grab_reply(int show);
45 void sigint_handler(int signum);
46 int main(int argc, char **argv);
47
48 #define USAGE _("Usage: amoldrecover [[-C] <config>] [-s <index-server>] [-t <tape-server>] [-d <tape-device>]\n")
49
50 char *config = NULL;
51 char *server_name = NULL;
52 int server_socket;
53 char *server_line = NULL;
54 char *dump_datestamp = NULL;            /* date we are restoring */
55 char *dump_hostname;                    /* which machine we are restoring */
56 char *disk_name = NULL;                 /* disk we are restoring */
57 char *mount_point = NULL;               /* where disk was mounted */
58 char *disk_path = NULL;                 /* path relative to mount point */
59 char dump_date[STR_SIZE];               /* date on which we are restoring */
60 int quit_prog;                          /* set when time to exit parser */
61 char *tape_server_name = NULL;
62 int tape_server_socket;
63 char *tape_device_name = NULL;
64 am_feature_t *our_features = NULL;
65 am_feature_t *indexsrv_features = NULL;
66 am_feature_t *tapesrv_features = NULL;
67
68 /* gets a "line" from server and put in server_line */
69 /* server_line is terminated with \0, \r\n is striped */
70 /* returns -1 if error */
71
72 int
73 get_line(void)
74 {
75     char *line = NULL;
76     char *part = NULL;
77     size_t len;
78
79     while(1) {
80         if((part = areads(server_socket)) == NULL) {
81             int save_errno = errno;
82
83             if(server_line) {
84                 fputs(server_line, stderr);     /* show the last line read */
85                 fputc('\n', stderr);
86             }
87             if(save_errno != 0) {
88                 g_fprintf(stderr, _("%s: Error reading line from server: %s\n"),
89                                 get_pname(),
90                                 strerror(save_errno));
91             } else {
92                 g_fprintf(stderr, _("%s: Unexpected end of file, check amindexd*debug on server %s\n"),
93                         get_pname(),
94                         server_name);
95             }
96             errno = save_errno;
97             break;      /* exit while loop */
98         }
99         if(line) {
100             strappend(line, part);
101             amfree(part);
102         } else {
103             line = part;
104             part = NULL;
105         }
106         if((len = strlen(line)) > 0 && line[len-1] == '\r') {
107             line[len-1] = '\0';
108             server_line = newstralloc(server_line, line);
109             amfree(line);
110             return 0;
111         }
112         /*
113          * Hmmm.  We got a "line" from areads(), which means it saw
114          * a '\n' (or EOF, etc), but there was not a '\r' before it.
115          * Put a '\n' back in the buffer and loop for more.
116          */
117         strappend(line, "\n");
118     }
119     amfree(line);
120     amfree(server_line);
121     return -1;
122 }
123
124
125 /* get reply from server and print to screen */
126 /* handle multi-line reply */
127 /* return -1 if error */
128 /* return code returned by server always occupies first 3 bytes of global
129    variable server_line */
130 int
131 grab_reply(
132     int         show)
133 {
134     do {
135         if (get_line() == -1) {
136             return -1;
137         }
138         if(show) puts(server_line);
139     } while (server_line[3] == '-');
140     if(show) fflush(stdout);
141
142     return 0;
143 }
144
145
146 /* get 1 line of reply */
147 /* returns -1 if error, 0 if last (or only) line, 1 if more to follow */
148 int
149 get_reply_line(void)
150 {
151     if (get_line() == -1)
152         return -1;
153     return server_line[3] == '-';
154 }
155
156
157 /* returns pointer to returned line */
158 char *
159 reply_line(void)
160 {
161     return server_line;
162 }
163
164
165
166 /* returns 0 if server returned an error code (ie code starting with 5)
167    and non-zero otherwise */
168 int
169 server_happy(void)
170 {
171     return server_line[0] != '5';
172 }
173
174
175 int
176 send_command(
177     char *      cmd)
178 {
179     /*
180      * NOTE: this routine is called from sigint_handler, so we must be
181      * **very** careful about what we do since there is no way to know
182      * our state at the time the interrupt happened.  For instance,
183      * do not use any stdio or malloc routines here.
184      */
185     struct iovec msg[2];
186     ssize_t bytes;
187
188     memset(msg, 0, sizeof(msg));
189     msg[0].iov_base = cmd;
190     msg[0].iov_len = strlen(msg[0].iov_base);
191     msg[1].iov_base = "\r\n";
192     msg[1].iov_len = strlen(msg[1].iov_base);
193     bytes = (ssize_t)(msg[0].iov_len + msg[1].iov_len);
194
195     if (writev(server_socket, msg, 2) < bytes) {
196         return -1;
197     }
198     return (0);
199 }
200
201
202 /* send a command to the server, get reply and print to screen */
203 int
204 converse(
205     char *      cmd)
206 {
207     if (send_command(cmd) == -1) return -1;
208     if (grab_reply(1) == -1) return -1;
209     return 0;
210 }
211
212
213 /* same as converse() but reply not echoed to stdout */
214 int
215 exchange(
216     char *      cmd)
217 {
218     if (send_command(cmd) == -1) return -1;
219     if (grab_reply(0) == -1) return -1;
220     return 0;
221 }
222
223
224 /* basic interrupt handler for when user presses ^C */
225 /* Bale out, letting server know before doing so */
226 void
227 sigint_handler(
228     int signum)
229 {
230     /*
231      * NOTE: we must be **very** careful about what we do here since there
232      * is no way to know our state at the time the interrupt happened.
233      * For instance, do not use any stdio routines here or in any called
234      * routines.  Also, use _exit() instead of exit() to make sure stdio
235      * buffer flushing is not attempted.
236      */
237     (void)signum;       /* Quiet unused parameter warning */
238
239     if (extract_restore_child_pid != -1)
240         (void)kill(extract_restore_child_pid, SIGKILL);
241     extract_restore_child_pid = -1;
242
243     (void)send_command("QUIT");
244     _exit(1);
245 }
246
247
248 void
249 clean_pathname(
250     char *      s)
251 {
252     size_t length;
253     length = strlen(s);
254
255     /* remove "/" at end of path */
256     if(length>1 && s[length-1]=='/')
257         s[length-1]='\0';
258
259     /* change "/." to "/" */
260     if(strcmp(s,"/.")==0)
261         s[1]='\0';
262
263     /* remove "/." at end of path */
264     if(strcmp(&(s[length-2]),"/.")==0)
265         s[length-2]='\0';
266 }
267
268
269 /* try and guess the disk the user is currently on.
270    Return -1 if error, 0 if disk not local, 1 if disk local,
271    2 if disk local but can't guess name */
272 /* do this by looking for the longest mount point which matches the
273    current directory */
274 int
275 guess_disk (
276     char *      cwd,
277     size_t      cwd_len,
278     char **     dn_guess,
279     char **     mpt_guess)
280 {
281     size_t longest_match = 0;
282     size_t current_length;
283     size_t cwd_length;
284     int local_disk = 0;
285     generic_fsent_t fsent;
286     char *fsname = NULL;
287     char *disk_try = NULL;
288
289     *dn_guess = NULL;
290     *mpt_guess = NULL;
291
292     if (getcwd(cwd, cwd_len) == NULL) {
293         return -1;
294         /*NOTREACHED*/
295     }
296     cwd_length = strlen(cwd);
297     dbprintf(_("guess_disk: %zu: \"%s\"\n"), cwd_length, cwd);
298
299     if (open_fstab() == 0) {
300         return -1;
301         /*NOTREACHED*/
302     }
303
304     while (get_fstab_nextentry(&fsent))
305     {
306         current_length = fsent.mntdir ? strlen(fsent.mntdir) : (size_t)0;
307         dbprintf(_("guess_disk: %zu: %zu: \"%s\": \"%s\"\n"),
308                   longest_match,
309                   current_length,
310                   fsent.mntdir ? fsent.mntdir : _("(mntdir null)"),
311                   fsent.fsname ? fsent.fsname : _("(fsname null)"));
312         if ((current_length > longest_match)
313             && (current_length <= cwd_length)
314             && (strncmp(fsent.mntdir, cwd, current_length) == 0))
315         {
316             longest_match = current_length;
317             *mpt_guess = newstralloc(*mpt_guess, fsent.mntdir);
318             if(strncmp(fsent.fsname,DEV_PREFIX,(strlen(DEV_PREFIX))))
319             {
320                 fsname = newstralloc(fsname, fsent.fsname);
321             }
322             else
323             {
324                 fsname = newstralloc(fsname,fsent.fsname+strlen(DEV_PREFIX));
325             }
326             local_disk = is_local_fstype(&fsent);
327             dbprintf(_("guess_disk: local_disk = %d, fsname = \"%s\"\n"),
328                       local_disk,
329                       fsname);
330         }
331     }
332     close_fstab();
333
334     if (longest_match == 0) {
335         amfree(*mpt_guess);
336         amfree(fsname);
337         return -1;                      /* ? at least / should match */
338     }
339
340     if (!local_disk) {
341         amfree(*mpt_guess);
342         amfree(fsname);
343         return 0;
344     }
345
346     /* have mount point now */
347     /* disk name may be specified by mount point (logical name) or
348        device name, have to determine */
349     g_printf(_("Trying disk %s ...\n"), *mpt_guess);
350     disk_try = stralloc2("DISK ", *mpt_guess);          /* try logical name */
351     if (exchange(disk_try) == -1)
352         exit(1);
353     amfree(disk_try);
354     if (server_happy())
355     {
356         *dn_guess = stralloc(*mpt_guess);               /* logical is okay */
357         amfree(fsname);
358         return 1;
359     }
360     g_printf(_("Trying disk %s ...\n"), fsname);
361     disk_try = stralloc2("DISK ", fsname);              /* try device name */
362     if (exchange(disk_try) == -1)
363         exit(1);
364     amfree(disk_try);
365     if (server_happy())
366     {
367         *dn_guess = stralloc(fsname);                   /* dev name is okay */
368         amfree(fsname);
369         return 1;
370     }
371
372     /* neither is okay */
373     amfree(*mpt_guess);
374     amfree(fsname);
375     return 2;
376 }
377
378
379 void
380 quit(void)
381 {
382     quit_prog = 1;
383     (void)converse("QUIT");
384 }
385
386 char *localhost = NULL;
387
388 #ifdef DEFAULT_TAPE_SERVER
389 # define DEFAULT_TAPE_SERVER_FAILOVER (DEFAULT_TAPE_SERVER)
390 #else
391 # define DEFAULT_TAPE_SERVER_FAILOVER (NULL)
392 #endif
393
394 int
395 main(
396     int         argc,
397     char **     argv)
398 {
399     in_port_t my_port;
400     struct servent *sp;
401     int i;
402     time_t timer;
403     char *lineread = NULL;
404     struct sigaction act, oact;
405     extern char *optarg;
406     extern int optind;
407     char cwd[STR_SIZE], *dn_guess = NULL, *mpt_guess = NULL;
408     char *service_name;
409     char *line = NULL;
410     struct tm *tm;
411
412     /*
413      * Configure program for internationalization:
414      *   1) Only set the message locale for now.
415      *   2) Set textdomain for all amanda related programs to "amanda"
416      *      We don't want to be forced to support dozens of message catalogs.
417      */  
418     setlocale(LC_MESSAGES, "C");
419     textdomain("amanda"); 
420
421     safe_fd(-1, 0);
422
423     set_pname("amoldrecover");
424
425     /* Don't die when child closes pipe */
426     signal(SIGPIPE, SIG_IGN);
427
428     dbopen(DBG_SUBDIR_CLIENT);
429
430     localhost = alloc(MAX_HOSTNAME_LENGTH+1);
431     if (gethostname(localhost, MAX_HOSTNAME_LENGTH) != 0) {
432         error(_("cannot determine local host name\n"));
433         /*NOTREACHED*/
434     }
435     localhost[MAX_HOSTNAME_LENGTH] = '\0';
436
437     config = newstralloc(config, DEFAULT_CONFIG);
438
439     dbrename(config, DBG_SUBDIR_CLIENT);
440
441     check_running_as(RUNNING_AS_ROOT);
442
443     amfree(server_name);
444     server_name = getenv("AMANDA_SERVER");
445     if(!server_name) server_name = DEFAULT_SERVER;
446     server_name = stralloc(server_name);
447
448     amfree(tape_server_name);
449     tape_server_name = getenv("AMANDA_TAPESERVER");
450     if(!tape_server_name) tape_server_name = DEFAULT_TAPE_SERVER;
451     tape_server_name = stralloc(tape_server_name);
452
453     config_init(CONFIG_INIT_CLIENT, NULL);
454
455     if (config_errors(NULL) >= CFGERR_WARNINGS) {
456         config_print_errors();
457         if (config_errors(NULL) >= CFGERR_ERRORS) {
458             g_critical(_("errors processing config file"));
459         }
460     }
461
462     if (argc > 1 && argv[1][0] != '-')
463     {
464         /*
465          * If the first argument is not an option flag, then we assume
466          * it is a configuration name to match the syntax of the other
467          * Amanda utilities.
468          */
469         char **new_argv;
470
471         new_argv = (char **) alloc((size_t)((argc + 1 + 1) * sizeof(*new_argv)));
472         new_argv[0] = argv[0];
473         new_argv[1] = "-C";
474         for (i = 1; i < argc; i++)
475         {
476             new_argv[i + 1] = argv[i];
477         }
478         new_argv[i + 1] = NULL;
479         argc++;
480         argv = new_argv;
481     }
482     while ((i = getopt(argc, argv, "C:s:t:d:U")) != EOF)
483     {
484         switch (i)
485         {
486             case 'C':
487                 config = newstralloc(config, optarg);
488                 break;
489
490             case 's':
491                 server_name = newstralloc(server_name, optarg);
492                 break;
493
494             case 't':
495                 tape_server_name = newstralloc(tape_server_name, optarg);
496                 break;
497
498             case 'd':
499                 tape_device_name = newstralloc(tape_device_name, optarg);
500                 break;
501
502             case 'U':
503             case '?':
504                 (void)g_printf(USAGE);
505                 return 0;
506         }
507     }
508     if (optind != argc)
509     {
510         (void)g_fprintf(stderr, USAGE);
511         exit(1);
512     }
513
514     amfree(disk_name);
515     amfree(mount_point);
516     amfree(disk_path);
517     dump_date[0] = '\0';
518
519     /* Don't die when child closes pipe */
520     signal(SIGPIPE, SIG_IGN);
521
522     /* set up signal handler */
523     act.sa_handler = sigint_handler;
524     sigemptyset(&act.sa_mask);
525     act.sa_flags = 0;
526     if (sigaction(SIGINT, &act, &oact) != 0) {
527         error(_("error setting signal handler: %s"), strerror(errno));
528         /*NOTREACHED*/
529     }
530
531     service_name = stralloc2("amandaidx", SERVICE_SUFFIX);
532
533     g_printf(_("AMRECOVER Version %s. Contacting server on %s ...\n"),
534            VERSION, server_name);  
535     if ((sp = getservbyname(service_name, "tcp")) == NULL) {
536         error(_("%s/tcp unknown protocol"), service_name);
537         /*NOTREACHED*/
538     }
539     amfree(service_name);
540     server_socket = stream_client_privileged(server_name,
541                                              (in_port_t)ntohs((in_port_t)sp->s_port),
542                                              0,
543                                              0,
544                                              &my_port,
545                                              0);
546     if (server_socket < 0) {
547         error(_("cannot connect to %s: %s"), server_name, strerror(errno));
548         /*NOTREACHED*/
549     }
550     if (my_port >= IPPORT_RESERVED) {
551         aclose(server_socket);
552         error(_("did not get a reserved port: %d"), my_port);
553         /*NOTREACHED*/
554     }
555
556     /* get server's banner */
557     if (grab_reply(1) == -1) {
558         aclose(server_socket);
559         exit(1);
560     }
561     if (!server_happy())
562     {
563         dbclose();
564         aclose(server_socket);
565         exit(1);
566     }
567
568     /* do the security thing */
569     line = get_security();
570     if (converse(line) == -1) {
571         aclose(server_socket);
572         exit(1);
573     }
574     if (!server_happy()) {
575         aclose(server_socket);
576         exit(1);
577     }
578     memset(line, '\0', strlen(line));
579     amfree(line);
580
581     /* try to get the features from the server */
582     {
583         char *our_feature_string = NULL;
584         char *their_feature_string = NULL;
585
586         our_features = am_init_feature_set();
587         our_feature_string = am_feature_to_string(our_features);
588         line = stralloc2("FEATURES ", our_feature_string);
589         if(exchange(line) == 0) {
590             their_feature_string = stralloc(server_line+13);
591             indexsrv_features = am_string_to_feature(their_feature_string);
592         }
593         else {
594             indexsrv_features = am_set_default_feature_set();
595         }
596         amfree(our_feature_string);
597         amfree(their_feature_string);
598         amfree(line);
599     }
600
601     /* set the date of extraction to be today */
602     (void)time(&timer);
603     tm = localtime(&timer);
604     if (tm)
605         strftime(dump_date, sizeof(dump_date), "%Y-%m-%d", tm);
606     else
607         error(_("BAD DATE"));
608
609     g_printf(_("Setting restore date to today (%s)\n"), dump_date);
610     line = stralloc2("DATE ", dump_date);
611     if (converse(line) == -1) {
612         aclose(server_socket);
613         exit(1);
614     }
615     amfree(line);
616
617     line = stralloc2("SCNF ", config);
618     if (converse(line) == -1) {
619         aclose(server_socket);
620         exit(1);
621     }
622     amfree(line);
623
624     if (server_happy())
625     {
626         /* set host we are restoring to this host by default */
627         amfree(dump_hostname);
628         set_host(localhost);
629         if (dump_hostname)
630         {
631             /* get a starting disk and directory based on where
632                we currently are */
633             switch (guess_disk(cwd, sizeof(cwd), &dn_guess, &mpt_guess))
634             {
635                 case 1:
636                     /* okay, got a guess. Set disk accordingly */
637                     g_printf(_("$CWD '%s' is on disk '%s' mounted at '%s'.\n"),
638                            cwd, dn_guess, mpt_guess);
639                     set_disk(dn_guess, mpt_guess);
640                     set_directory(cwd);
641                     if (server_happy() && strcmp(cwd, mpt_guess) != 0)
642                         g_printf(_("WARNING: not on root of selected filesystem, check man-page!\n"));
643                     amfree(dn_guess);
644                     amfree(mpt_guess);
645                     break;
646
647                 case 0:
648                     g_printf(_("$CWD '%s' is on a network mounted disk\n"),
649                            cwd);
650                     g_printf(_("so you must 'sethost' to the server\n"));
651                     /* fake an unhappy server */
652                     server_line[0] = '5';
653                     break;
654
655                 case 2:
656                 case -1:
657                 default:
658                     g_printf(_("Use the setdisk command to choose dump disk to recover\n"));
659                     /* fake an unhappy server */
660                     server_line[0] = '5';
661                     break;
662             }
663         }
664     }
665
666     quit_prog = 0;
667     do
668     {
669         if ((lineread = readline("amrecover> ")) == NULL) {
670             clearerr(stdin);
671             putchar('\n');
672             break;
673         }
674         if (lineread[0] != '\0') 
675         {
676             add_history(lineread);
677             process_line(lineread);     /* act on line's content */
678         }
679         amfree(lineread);
680     } while (!quit_prog);
681
682     dbclose();
683
684     aclose(server_socket);
685     return 0;
686 }
687
688 char *
689 get_security(void)
690 {
691     struct passwd *pwptr;
692
693     if((pwptr = getpwuid(getuid())) == NULL) {
694         error(_("can't get login name for my uid %ld"), (long)getuid());
695         /*NOTREACHED*/
696     }
697     return stralloc2("SECURITY USER ", pwptr->pw_name);
698 }