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