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