2 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3 * Copyright (c) 1991-1998, 2000 University of Maryland at College Park
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.
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.
23 * Authors: the Amanda Development Team. Its members are listed in a
24 * file named AUTHORS, in the root directory of this distribution.
27 * $Id: amrecover.c,v 1.73 2006/07/25 18:27:57 martinea Exp $
29 * an interactive program for recovering backed-up files
34 #include "amfeatures.h"
35 #include "amrecover.h"
46 #define amrecover_debug(i, ...) do { \
47 if ((i) <= debug_amrecover) { \
48 dbprintf(__VA_ARGS__); \
52 static struct option long_options[] = {
53 {"version" , 0, NULL, 1},
57 extern int process_line(char *line);
59 int grab_reply(int show);
60 void sigint_handler(int signum);
61 int main(int argc, char **argv);
63 #define USAGE _("Usage: amrecover [--version] [[-C] <config>] [-s <index-server>] [-t <tape-server>] [-d <tape-device>] [-o <clientconfigoption>]*\n")
65 char *server_name = NULL;
67 char *server_line = NULL;
68 char *dump_datestamp = NULL; /* date we are restoring */
69 char *dump_hostname; /* which machine we are restoring */
70 char *disk_name = NULL; /* disk we are restoring */
71 dle_t *dump_dle = NULL;
72 char *mount_point = NULL; /* where disk was mounted */
73 char *disk_path = NULL; /* path relative to mount point */
74 char *disk_tpath = NULL; /* translated path relative to mount point */
75 char dump_date[STR_SIZE]; /* date on which we are restoring */
76 int quit_prog; /* set when time to exit parser */
77 char *tape_server_name = NULL;
78 int tape_server_socket;
79 char *tape_device_name = NULL;
80 am_feature_t *our_features = NULL;
81 char *our_features_string = NULL;
82 am_feature_t *indexsrv_features = NULL;
83 am_feature_t *tapesrv_features = NULL;
84 proplist_t proplist = NULL;
85 static char *errstr = NULL;
87 int amindexd_alive = 0;
91 security_stream_t *fd;
96 #define NSTREAMS (int)(sizeof(streams) / sizeof(streams[0]))
98 static void amindexd_response(void *, pkt_t *, security_handle_t *);
99 void stop_amindexd(void);
101 static char* mesg_buffer = NULL;
102 /* gets a "line" from server and put in server_line */
103 /* server_line is terminated with \0, \r\n is striped */
104 /* returns -1 if error */
114 mesg_buffer = stralloc("");
116 while (!strstr(mesg_buffer,"\r\n")) {
118 size = security_stream_read_sync(streams[MESGFD].fd, &buf);
120 amrecover_debug(1, "amrecover: get_line size < 0 (%zd)\n", size);
124 amrecover_debug(1, "amrecover: get_line size == 0 (%zd)\n", size);
127 else if (buf == NULL) {
128 amrecover_debug(1, "amrecover: get_line buf == NULL\n");
131 amrecover_debug(1, "amrecover: get_line size = %zd\n", size);
132 newbuf = alloc(strlen(mesg_buffer)+size+1);
133 strncpy(newbuf, mesg_buffer, (size_t)(strlen(mesg_buffer) + size));
134 memcpy(newbuf+strlen(mesg_buffer), buf, (size_t)size);
135 newbuf[strlen(mesg_buffer)+size] = '\0';
137 mesg_buffer = newbuf;
141 s = strstr(mesg_buffer,"\r\n");
143 newbuf = stralloc(s+2);
144 server_line = newstralloc(server_line, mesg_buffer);
146 mesg_buffer = newbuf;
147 amrecover_debug(1, "server_line: %s\n", server_line);
148 amrecover_debug(1, "get: %s\n", mesg_buffer);
153 /* get reply from server and print to screen */
154 /* handle multi-line reply */
155 /* return -1 if error */
156 /* return code returned by server always occupies first 3 bytes of global
157 variable server_line */
158 /* show == 0: Print the reply if it is an error */
159 /* show == 1: Always print the reply */
165 if (get_line() == -1) {
168 if (show || server_line[0] == '5') {
171 } while (server_line[3] == '-');
172 if(show) fflush(stdout);
178 /* get 1 line of reply */
179 /* returns -1 if error, 0 if last (or only) line, 1 if more to follow */
183 if (get_line() == -1)
185 return server_line[3] == '-';
189 /* returns pointer to returned line */
198 /* returns 0 if server returned an error code (ie code starting with 5)
199 and non-zero otherwise */
203 return server_line[0] != '5';
212 * NOTE: this routine is called from sigint_handler, so we must be
213 * **very** careful about what we do since there is no way to know
214 * our state at the time the interrupt happened. For instance,
215 * do not use any stdio or malloc routines here.
219 buffer = alloc(strlen(cmd)+3);
220 strncpy(buffer, cmd, strlen(cmd));
221 buffer[strlen(cmd)] = '\r';
222 buffer[strlen(cmd)+1] = '\n';
223 buffer[strlen(cmd)+2] = '\0';
225 g_debug("sending: %s\n", buffer);
226 if(security_stream_write(streams[MESGFD].fd, buffer, strlen(buffer)) < 0) {
234 /* send a command to the server, get reply and print to screen */
239 if (send_command(cmd) == -1) return -1;
240 if (grab_reply(1) == -1) return -1;
245 /* same as converse() but reply not echoed to stdout */
250 if (send_command(cmd) == -1) return -1;
251 if (grab_reply(0) == -1) return -1;
256 /* basic interrupt handler for when user presses ^C */
257 /* Bale out, letting server know before doing so */
263 * NOTE: we must be **very** careful about what we do here since there
264 * is no way to know our state at the time the interrupt happened.
265 * For instance, do not use any stdio routines here or in any called
266 * routines. Also, use _exit() instead of exit() to make sure stdio
267 * buffer flushing is not attempted.
269 (void)signum; /* Quiet unused parameter warning */
271 if (extract_restore_child_pid != -1)
272 (void)kill(extract_restore_child_pid, SIGKILL);
273 extract_restore_child_pid = -1;
276 (void)send_command("QUIT");
289 /* remove "/" at end of path */
290 if(length>1 && s[length-1]=='/')
293 /* change "/." to "/" */
294 if(strcmp(s,"/.")==0)
297 /* remove "/." at end of path */
298 if(strcmp(&(s[length-2]),"/.")==0)
309 (void)converse("QUIT");
313 #ifdef DEFAULT_TAPE_SERVER
314 # define DEFAULT_TAPE_SERVER_FAILOVER (DEFAULT_TAPE_SERVER)
316 # define DEFAULT_TAPE_SERVER_FAILOVER (NULL)
326 char *lineread = NULL;
327 struct sigaction act, oact;
331 const security_driver_t *secdrv;
335 config_overrides_t *cfg_ovr;
336 char *starting_hostname = NULL;
339 * Configure program for internationalization:
340 * 1) Only set the message locale for now.
341 * 2) Set textdomain for all amanda related programs to "amanda"
342 * We don't want to be forced to support dozens of message catalogs.
344 setlocale(LC_MESSAGES, "C");
345 textdomain("amanda");
349 set_pname("amrecover");
351 /* Don't die when child closes pipe */
352 signal(SIGPIPE, SIG_IGN);
354 dbopen(DBG_SUBDIR_CLIENT);
356 /* treat amrecover-specific command line options as the equivalent
357 * -o command-line options to set configuration values */
358 cfg_ovr = new_config_overrides(argc/2);
360 /* If the first argument is not an option flag, then we assume
361 * it is a configuration name to match the syntax of the other
362 * Amanda utilities. */
363 if (argc > 1 && argv[1][0] != '-') {
364 add_config_override(cfg_ovr, "conf", argv[1]);
366 /* remove that option from the command line */
371 /* now parse regular command-line '-' options */
372 while ((i = getopt_long(argc, argv, "o:C:s:t:d:Uh:", long_options, NULL)) != EOF) {
375 printf("amrecover-%s\n", VERSION);
379 add_config_override(cfg_ovr, "conf", optarg);
383 add_config_override(cfg_ovr, "index_server", optarg);
387 add_config_override(cfg_ovr, "tape_server", optarg);
391 add_config_override(cfg_ovr, "tapedev", optarg);
395 add_config_override_opt(cfg_ovr, optarg);
399 starting_hostname = g_strdup(optarg);
404 (void)g_printf(USAGE);
408 if (optind != argc) {
409 (void)g_fprintf(stderr, USAGE);
413 /* load the base client configuration */
414 set_config_overrides(cfg_ovr);
415 config_init(CONFIG_INIT_CLIENT, NULL);
417 if (config_errors(NULL) >= CFGERR_WARNINGS) {
418 config_print_errors();
419 if (config_errors(NULL) >= CFGERR_ERRORS) {
420 g_critical(_("errors processing config file"));
424 /* and now try to load the configuration named in that file */
425 config_init(CONFIG_INIT_CLIENT | CONFIG_INIT_EXPLICIT_NAME | CONFIG_INIT_OVERLAY,
426 getconf_str(CNF_CONF));
428 check_running_as(RUNNING_AS_ROOT);
430 dbrename(get_config_name(), DBG_SUBDIR_CLIENT);
432 our_features = am_init_feature_set();
433 our_features_string = am_feature_to_string(our_features);
435 if (!starting_hostname) {
436 starting_hostname = alloc(MAX_HOSTNAME_LENGTH+1);
437 if (gethostname(starting_hostname, MAX_HOSTNAME_LENGTH) != 0) {
438 error(_("cannot determine local host name\n"));
441 starting_hostname[MAX_HOSTNAME_LENGTH] = '\0';
445 if (getconf_seen(CNF_INDEX_SERVER) == -2) { /* command line argument */
446 server_name = getconf_str(CNF_INDEX_SERVER);
449 server_name = getenv("AMANDA_SERVER");
451 g_printf(_("Using index server from environment AMANDA_SERVER (%s)\n"), server_name);
455 server_name = getconf_str(CNF_INDEX_SERVER);
458 error(_("No index server set"));
461 server_name = stralloc(server_name);
463 tape_server_name = NULL;
464 if (getconf_seen(CNF_TAPE_SERVER) == -2) { /* command line argument */
465 tape_server_name = getconf_str(CNF_TAPE_SERVER);
467 if (!tape_server_name) {
468 tape_server_name = getenv("AMANDA_TAPE_SERVER");
469 if (!tape_server_name) {
470 tape_server_name = getenv("AMANDA_TAPESERVER");
471 if (tape_server_name) {
472 g_printf(_("Using tape server from environment AMANDA_TAPESERVER (%s)\n"), tape_server_name);
475 g_printf(_("Using tape server from environment AMANDA_TAPE_SERVER (%s)\n"), tape_server_name);
478 if (!tape_server_name) {
479 tape_server_name = getconf_str(CNF_TAPE_SERVER);
481 if (!tape_server_name) {
482 error(_("No tape server set"));
485 tape_server_name = stralloc(tape_server_name);
487 amfree(tape_device_name);
488 tape_device_name = getconf_str(CNF_TAPEDEV);
489 if (!tape_device_name ||
490 strlen(tape_device_name) == 0 ||
491 !getconf_seen(CNF_TAPEDEV)) {
492 tape_device_name = NULL;
494 tape_device_name = stralloc(tape_device_name);
497 authopt = stralloc(getconf_str(CNF_AUTH));
506 /* Don't die when child closes pipe */
507 signal(SIGPIPE, SIG_IGN);
509 /* set up signal handler */
510 act.sa_handler = sigint_handler;
511 sigemptyset(&act.sa_mask);
513 if (sigaction(SIGINT, &act, &oact) != 0) {
514 error(_("error setting signal handler: %s"), strerror(errno));
518 proplist = g_hash_table_new_full(g_str_hash, g_str_equal, &g_free, &free_property_t);
522 /* We assume that amindexd support fe_amindexd_options_features */
523 /* and fe_amindexd_options_auth */
524 /* We should send a noop to really know */
525 req = vstrallocf("SERVICE amindexd\n"
526 "OPTIONS features=%s;auth=%s;\n",
527 our_features_string, authopt);
529 secdrv = security_getdriver(authopt);
530 if (secdrv == NULL) {
531 error(_("no '%s' security driver available for host '%s'"),
532 authopt, server_name);
536 protocol_sendreq(server_name, secdrv, generic_client_get_security_conf,
537 req, STARTUP_TIMEOUT, amindexd_response, &response_error);
542 g_printf(_("AMRECOVER Version %s. Contacting server on %s ...\n"),
543 VERSION, server_name);
545 if(response_error != 0) {
546 g_fprintf(stderr,"%s\n",errstr);
550 /* get server's banner */
551 if (grab_reply(1) == -1) {
552 aclose(server_socket);
555 if (!server_happy()) {
557 aclose(server_socket);
561 /* try to get the features from the server */
563 char *their_feature_string = NULL;
565 indexsrv_features = NULL;
567 line = vstrallocf("FEATURES %s", our_features_string);
568 if(exchange(line) == 0) {
569 their_feature_string = stralloc(server_line+13);
570 indexsrv_features = am_string_to_feature(their_feature_string);
571 if (!indexsrv_features)
572 g_printf(_("Bad feature string from server: %s"), their_feature_string);
574 if (!indexsrv_features)
575 indexsrv_features = am_set_default_feature_set();
577 amfree(their_feature_string);
581 /* set the date of extraction to be today */
583 tm = localtime(&timer);
585 strftime(dump_date, sizeof(dump_date), "%Y-%m-%d", tm);
587 error(_("BAD DATE"));
589 g_printf(_("Setting restore date to today (%s)\n"), dump_date);
590 line = vstrallocf("DATE %s", dump_date);
591 if (converse(line) == -1) {
592 aclose(server_socket);
597 line = vstrallocf("SCNF %s", get_config_name());
598 if (converse(line) == -1) {
599 aclose(server_socket);
604 if (server_happy()) {
605 /* set host we are restoring to this host by default */
606 amfree(dump_hostname);
607 set_host(starting_hostname);
609 g_printf(_("Use the setdisk command to choose dump disk to recover\n"));
611 g_printf(_("Use the sethost command to choose a host to recover\n"));
617 if ((lineread = readline("amrecover> ")) == NULL) {
622 if (lineread[0] != '\0')
624 add_history(lineread);
625 dbprintf(_("user command: '%s'\n"), lineread);
626 process_line(lineread); /* act on line's content */
629 } while (!quit_prog);
633 aclose(server_socket);
641 security_handle_t *sech)
643 int ports[NSTREAMS], *response_error = datap, i;
648 assert(response_error != NULL);
649 assert(sech != NULL);
652 errstr = newvstrallocf(errstr, _("[request failed: %s]"),
653 security_geterror(sech));
658 if (pkt->type == P_NAK) {
659 #if defined(PACKET_DEBUG)
660 dbprintf(_("got nak response:\n----\n%s\n----\n\n"), pkt->body);
663 tok = strtok(pkt->body, " ");
664 if (tok == NULL || strcmp(tok, "ERROR") != 0)
667 tok = strtok(NULL, "\n");
669 errstr = newvstrallocf(errstr, "NAK: %s", tok);
673 errstr = newvstrallocf(errstr, _("request NAK"));
679 if (pkt->type != P_REP) {
680 errstr = newvstrallocf(errstr, _("received strange packet type %s: %s"),
681 pkt_type2str(pkt->type), pkt->body);
686 #if defined(PACKET_DEBUG)
687 g_fprintf(stderr, _("got response:\n----\n%s\n----\n\n"), pkt->body);
690 for(i = 0; i < NSTREAMS; i++) {
692 streams[i].fd = NULL;
696 while((tok = strtok(p, " \n")) != NULL) {
700 * Error response packets have "ERROR" followed by the error message
701 * followed by a newline.
703 if (strcmp(tok, "ERROR") == 0) {
704 tok = strtok(NULL, "\n");
706 errstr = newvstrallocf(errstr, _("[bogus error packet]"));
708 errstr = newvstrallocf(errstr, "%s", tok);
716 * Regular packets have CONNECT followed by three streams
718 if (strcmp(tok, "CONNECT") == 0) {
721 * Parse the three stream specifiers out of the packet.
723 for (i = 0; i < NSTREAMS; i++) {
724 tok = strtok(NULL, " ");
725 if (tok == NULL || strcmp(tok, streams[i].name) != 0) {
727 _("CONNECT token is \"%s\": expected \"%s\""),
728 tok ? tok : _("(null)"), streams[i].name);
731 tok = strtok(NULL, " \n");
732 if (tok == NULL || sscanf(tok, "%d", &ports[i]) != 1) {
734 _("CONNECT %s token is \"%s\" expected a port number"),
735 streams[i].name, tok ? tok : _("(null)"));
743 * OPTIONS [options string] '\n'
745 if (strcmp(tok, "OPTIONS") == 0) {
746 tok = strtok(NULL, "\n");
748 extra = vstrallocf(_("OPTIONS token is missing"));
752 tok_end = tok + strlen(tok);
753 while((p = strchr(tok, ';')) != NULL) {
755 if(strncmp_const(tok, "features=") == 0) {
756 tok += SIZEOF("features=") - 1;
757 am_release_feature_set(their_features);
758 if((their_features = am_string_to_feature(tok)) == NULL) {
759 errstr = newvstrallocf(errstr,
760 _("OPTIONS: bad features value: %s"),
771 extra = vstrallocf(_("next token is \"%s\": expected \"CONNECT\", \"ERROR\" or \"OPTIONS\""), tok ? tok : _("(null)"));
777 * Connect the streams to their remote ports
779 for (i = 0; i < NSTREAMS; i++) {
780 /*@i@*/ if (ports[i] == -1)
782 streams[i].fd = security_stream_client(sech, ports[i]);
783 if (streams[i].fd == NULL) {
784 errstr = newvstrallocf(errstr,
785 _("[could not connect %s stream: %s]"),
786 streams[i].name, security_geterror(sech));
791 * Authenticate the streams
793 for (i = 0; i < NSTREAMS; i++) {
794 if (streams[i].fd == NULL)
796 if (security_stream_auth(streams[i].fd) < 0) {
797 errstr = newvstrallocf(errstr,
798 _("[could not authenticate %s stream: %s]"),
799 streams[i].name, security_stream_geterror(streams[i].fd));
805 * The MESGFD and DATAFD streams are mandatory. If we didn't get
808 if (streams[MESGFD].fd == NULL) {
809 errstr = newvstrallocf(errstr, _("[couldn't open MESG streams]"));
813 /* everything worked */
819 errstr = newvstrallocf(errstr,
820 _("[parse of reply message failed: %s]"),
821 extra ? extra : _("(no additional information)"));
832 * This is called when everything needs to shut down so event_loop()
841 for (i = 0; i < NSTREAMS; i++) {
842 if (streams[i].fd != NULL) {
843 security_stream_close(streams[i].fd);
844 streams[i].fd = NULL;
854 char *s = line, *s1, *s2;
862 if ((s == line || *(s-1) != '\\') && *s == '\\') {
866 if (g_ascii_isdigit(*s) && *s1 != '\0' &&
867 g_ascii_isdigit(*s1) &&
868 g_ascii_isdigit(*s2)) {
869 i = ((*s)-'0')*64 + ((*s1)-'0')*8 + ((*s2)-'0');