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