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