1 /*
2 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3 * Copyright (c) 1991-1998, 2000 University of Maryland at College Park
4 * Copyright (c) 2007-2013 Zmanda, Inc. All Rights Reserved.
5 * All Rights Reserved.
6 *
7 * Permission to use, copy, modify, distribute, and sell this software and its
8 * documentation for any purpose is hereby granted without fee, provided that
9 * the above copyright notice appear in all copies and that both that
10 * copyright notice and this permission notice appear in supporting
11 * documentation, and that the name of U.M. not be used in advertising or
12 * publicity pertaining to distribution of the software without specific,
13 * written prior permission. U.M. makes no representations about the
14 * suitability of this software for any purpose. It is provided "as is"
15 * without express or implied warranty.
16 *
17 * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
19 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
21 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
22 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 *
24 * Authors: the Amanda Development Team. Its members are listed in a
25 * file named AUTHORS, in the root directory of this distribution.
26 */
27 /*
28 * $Id: amrecover.c,v 1.7 2006/07/25 18:27:57 martinea Exp $
29 *
30 * an interactive program for recovering backed-up files
31 */
32
33 #include "amanda.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
get_line(void)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 g_fprintf(stderr, _("%s: Error reading line from server: %s\n"),
90 get_pname(),
91 strerror(save_errno));
92 } else {
93 g_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
grab_reply(int show)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
get_reply_line(void)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 *
reply_line(void)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
server_happy(void)170 server_happy(void)
171 {
172 return server_line[0] != '5';
173 }
174
175
176 int
send_command(char * cmd)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
converse(char * cmd)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
exchange(char * cmd)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
sigint_handler(int signum)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
clean_pathname(char * s)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
guess_disk(char * cwd,size_t cwd_len,char ** dn_guess,char ** mpt_guess)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: %zu: \"%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: %zu: %zu: \"%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 g_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 g_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
quit(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
main(int argc,char ** argv)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
413 /*
414 * Configure program for internationalization:
415 * 1) Only set the message locale for now.
416 * 2) Set textdomain for all amanda related programs to "amanda"
417 * We don't want to be forced to support dozens of message catalogs.
418 */
419 setlocale(LC_MESSAGES, "C");
420 textdomain("amanda");
421
422 safe_fd(-1, 0);
423
424 set_pname("amoldrecover");
425
426 /* Don't die when child closes pipe */
427 signal(SIGPIPE, SIG_IGN);
428
429 dbopen(DBG_SUBDIR_CLIENT);
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 check_running_as(RUNNING_AS_ROOT);
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 config_init(CONFIG_INIT_CLIENT, NULL);
453
454 if (config_errors(NULL) >= CFGERR_WARNINGS) {
455 config_print_errors();
456 if (config_errors(NULL) >= CFGERR_ERRORS) {
457 g_critical(_("errors processing config file"));
458 }
459 }
460
461 if (argc > 1 && argv[1][0] != '-')
462 {
463 /*
464 * If the first argument is not an option flag, then we assume
465 * it is a configuration name to match the syntax of the other
466 * Amanda utilities.
467 */
468 char **new_argv;
469
470 new_argv = (char **) alloc((size_t)((argc + 1 + 1) * sizeof(*new_argv)));
471 new_argv[0] = argv[0];
472 new_argv[1] = "-C";
473 for (i = 1; i < argc; i++)
474 {
475 new_argv[i + 1] = argv[i];
476 }
477 new_argv[i + 1] = NULL;
478 argc++;
479 argv = new_argv;
480 }
481 while ((i = getopt(argc, argv, "C:s:t:d:U")) != EOF)
482 {
483 switch (i)
484 {
485 case 'C':
486 config = newstralloc(config, optarg);
487 break;
488
489 case 's':
490 server_name = newstralloc(server_name, optarg);
491 break;
492
493 case 't':
494 tape_server_name = newstralloc(tape_server_name, optarg);
495 break;
496
497 case 'd':
498 tape_device_name = newstralloc(tape_device_name, optarg);
499 break;
500
501 case 'U':
502 case '?':
503 (void)g_printf(USAGE);
504 return 0;
505 }
506 }
507 if (optind != argc)
508 {
509 (void)g_fprintf(stderr, USAGE);
510 exit(1);
511 }
512
513 dbrename(config, DBG_SUBDIR_CLIENT);
514
515 amfree(disk_name);
516 amfree(mount_point);
517 amfree(disk_path);
518 dump_date[0] = '\0';
519
520 /* Don't die when child closes pipe */
521 signal(SIGPIPE, SIG_IGN);
522
523 /* set up signal handler */
524 act.sa_handler = sigint_handler;
525 sigemptyset(&act.sa_mask);
526 act.sa_flags = 0;
527 #ifdef SA_RESTORER
528 act.sa_restorer = NULL;
529 #endif
530 if (sigaction(SIGINT, &act, &oact) != 0) {
531 error(_("error setting signal handler: %s"), strerror(errno));
532 /*NOTREACHED*/
533 }
534
535 service_name = stralloc2("amandaidx", SERVICE_SUFFIX);
536
537 g_printf(_("AMRECOVER Version %s. Contacting server on %s ...\n"),
538 VERSION, server_name);
539 if ((sp = getservbyname(service_name, "tcp")) == NULL) {
540 error(_("%s/tcp unknown protocol"), service_name);
541 /*NOTREACHED*/
542 }
543 amfree(service_name);
544 server_socket = stream_client_privileged(server_name,
545 (in_port_t)ntohs((in_port_t)sp->s_port),
546 0,
547 0,
548 &my_port,
549 0);
550 if (server_socket < 0) {
551 error(_("cannot connect to %s: %s"), server_name, strerror(errno));
552 /*NOTREACHED*/
553 }
554 if (my_port >= IPPORT_RESERVED) {
555 aclose(server_socket);
556 error(_("did not get a reserved port: %d"), my_port);
557 /*NOTREACHED*/
558 }
559
560 /* get server's banner */
561 if (grab_reply(1) == -1) {
562 aclose(server_socket);
563 exit(1);
564 }
565 if (!server_happy())
566 {
567 dbclose();
568 aclose(server_socket);
569 exit(1);
570 }
571
572 /* do the security thing */
573 line = get_security();
574 if (converse(line) == -1) {
575 aclose(server_socket);
576 exit(1);
577 }
578 if (!server_happy()) {
579 aclose(server_socket);
580 exit(1);
581 }
582 memset(line, '\0', strlen(line));
583 amfree(line);
584
585 /* try to get the features from the server */
586 {
587 char *our_feature_string = NULL;
588 char *their_feature_string = NULL;
589
590 our_features = am_init_feature_set();
591 our_feature_string = am_feature_to_string(our_features);
592 line = stralloc2("FEATURES ", our_feature_string);
593 if(exchange(line) == 0) {
594 their_feature_string = stralloc(server_line+13);
595 indexsrv_features = am_string_to_feature(their_feature_string);
596 }
597 else {
598 indexsrv_features = am_set_default_feature_set();
599 }
600 amfree(our_feature_string);
601 amfree(their_feature_string);
602 amfree(line);
603 }
604
605 /* set the date of extraction to be today */
606 (void)time(&timer);
607 tm = localtime(&timer);
608 if (tm)
609 strftime(dump_date, sizeof(dump_date), "%Y-%m-%d", tm);
610 else
611 error(_("BAD DATE"));
612
613 g_printf(_("Setting restore date to today (%s)\n"), dump_date);
614 line = stralloc2("DATE ", dump_date);
615 if (converse(line) == -1) {
616 aclose(server_socket);
617 exit(1);
618 }
619 amfree(line);
620
621 line = stralloc2("SCNF ", config);
622 if (converse(line) == -1) {
623 aclose(server_socket);
624 exit(1);
625 }
626 amfree(line);
627
628 if (server_happy())
629 {
630 /* set host we are restoring to this host by default */
631 amfree(dump_hostname);
632 set_host(localhost);
633 if (dump_hostname)
634 {
635 /* get a starting disk and directory based on where
636 we currently are */
637 switch (guess_disk(cwd, sizeof(cwd), &dn_guess, &mpt_guess))
638 {
639 case 1:
640 /* okay, got a guess. Set disk accordingly */
641 g_printf(_("$CWD '%s' is on disk '%s' mounted at '%s'.\n"),
642 cwd, dn_guess, mpt_guess);
643 set_disk(dn_guess, mpt_guess);
644 set_directory(cwd);
645 if (server_happy() && strcmp(cwd, mpt_guess) != 0)
646 g_printf(_("WARNING: not on root of selected filesystem, check man-page!\n"));
647 amfree(dn_guess);
648 amfree(mpt_guess);
649 break;
650
651 case 0:
652 g_printf(_("$CWD '%s' is on a network mounted disk\n"),
653 cwd);
654 g_printf(_("so you must 'sethost' to the server\n"));
655 /* fake an unhappy server */
656 server_line[0] = '5';
657 break;
658
659 case 2:
660 case -1:
661 default:
662 g_printf(_("Use the setdisk command to choose dump disk to recover\n"));
663 /* fake an unhappy server */
664 server_line[0] = '5';
665 break;
666 }
667 }
668 }
669
670 quit_prog = 0;
671 do
672 {
673 if ((lineread = readline("amrecover> ")) == NULL) {
674 clearerr(stdin);
675 putchar('\n');
676 break;
677 }
678 if (lineread[0] != '\0')
679 {
680 add_history(lineread);
681 process_line(lineread); /* act on line's content */
682 }
683 amfree(lineread);
684 } while (!quit_prog);
685
686 dbclose();
687
688 aclose(server_socket);
689 return 0;
690 }
691
692 char *
get_security(void)693 get_security(void)
694 {
695 struct passwd *pwptr;
696
697 if((pwptr = getpwuid(getuid())) == NULL) {
698 error(_("can't get login name for my uid %ld"), (long)getuid());
699 /*NOTREACHED*/
700 }
701 return stralloc2("SECURITY USER ", pwptr->pw_name);
702 }
703