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.7 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"
41 extern int process_line(char *line);
42 int guess_disk(char *cwd, size_t cwd_len, char **dn_guess, char **mpt_guess);
44 int grab_reply(int show);
45 void sigint_handler(int signum);
46 int main(int argc, char **argv);
48 #define USAGE _("Usage: amoldrecover [[-C] <config>] [-s <index-server>] [-t <tape-server>] [-d <tape-device>]\n")
51 char *server_name = NULL;
53 char *server_line = NULL;
54 char *dump_datestamp = NULL; /* date we are restoring */
55 char *dump_hostname; /* which machine we are restoring */
56 char *disk_name = NULL; /* disk we are restoring */
57 char *mount_point = NULL; /* where disk was mounted */
58 char *disk_path = NULL; /* path relative to mount point */
59 char dump_date[STR_SIZE]; /* date on which we are restoring */
60 int quit_prog; /* set when time to exit parser */
61 char *tape_server_name = NULL;
62 int tape_server_socket;
63 char *tape_device_name = NULL;
64 am_feature_t *our_features = NULL;
65 am_feature_t *indexsrv_features = NULL;
66 am_feature_t *tapesrv_features = NULL;
68 /* gets a "line" from server and put in server_line */
69 /* server_line is terminated with \0, \r\n is striped */
70 /* returns -1 if error */
80 if((part = areads(server_socket)) == NULL) {
81 int save_errno = errno;
84 fputs(server_line, stderr); /* show the last line read */
88 g_fprintf(stderr, _("%s: Error reading line from server: %s\n"),
90 strerror(save_errno));
92 g_fprintf(stderr, _("%s: Unexpected end of file, check amindexd*debug on server %s\n"),
97 break; /* exit while loop */
100 strappend(line, part);
106 if((len = strlen(line)) > 0 && line[len-1] == '\r') {
108 server_line = newstralloc(server_line, line);
113 * Hmmm. We got a "line" from areads(), which means it saw
114 * a '\n' (or EOF, etc), but there was not a '\r' before it.
115 * Put a '\n' back in the buffer and loop for more.
117 strappend(line, "\n");
125 /* get reply from server and print to screen */
126 /* handle multi-line reply */
127 /* return -1 if error */
128 /* return code returned by server always occupies first 3 bytes of global
129 variable server_line */
135 if (get_line() == -1) {
138 if(show) puts(server_line);
139 } while (server_line[3] == '-');
140 if(show) fflush(stdout);
146 /* get 1 line of reply */
147 /* returns -1 if error, 0 if last (or only) line, 1 if more to follow */
151 if (get_line() == -1)
153 return server_line[3] == '-';
157 /* returns pointer to returned line */
166 /* returns 0 if server returned an error code (ie code starting with 5)
167 and non-zero otherwise */
171 return server_line[0] != '5';
180 * NOTE: this routine is called from sigint_handler, so we must be
181 * **very** careful about what we do since there is no way to know
182 * our state at the time the interrupt happened. For instance,
183 * do not use any stdio or malloc routines here.
188 memset(msg, 0, sizeof(msg));
189 msg[0].iov_base = cmd;
190 msg[0].iov_len = strlen(msg[0].iov_base);
191 msg[1].iov_base = "\r\n";
192 msg[1].iov_len = strlen(msg[1].iov_base);
193 bytes = (ssize_t)(msg[0].iov_len + msg[1].iov_len);
195 if (writev(server_socket, msg, 2) < bytes) {
202 /* send a command to the server, get reply and print to screen */
207 if (send_command(cmd) == -1) return -1;
208 if (grab_reply(1) == -1) return -1;
213 /* same as converse() but reply not echoed to stdout */
218 if (send_command(cmd) == -1) return -1;
219 if (grab_reply(0) == -1) return -1;
224 /* basic interrupt handler for when user presses ^C */
225 /* Bale out, letting server know before doing so */
231 * NOTE: we must be **very** careful about what we do here since there
232 * is no way to know our state at the time the interrupt happened.
233 * For instance, do not use any stdio routines here or in any called
234 * routines. Also, use _exit() instead of exit() to make sure stdio
235 * buffer flushing is not attempted.
237 (void)signum; /* Quiet unused parameter warning */
239 if (extract_restore_child_pid != -1)
240 (void)kill(extract_restore_child_pid, SIGKILL);
241 extract_restore_child_pid = -1;
243 (void)send_command("QUIT");
255 /* remove "/" at end of path */
256 if(length>1 && s[length-1]=='/')
259 /* change "/." to "/" */
260 if(strcmp(s,"/.")==0)
263 /* remove "/." at end of path */
264 if(strcmp(&(s[length-2]),"/.")==0)
269 /* try and guess the disk the user is currently on.
270 Return -1 if error, 0 if disk not local, 1 if disk local,
271 2 if disk local but can't guess name */
272 /* do this by looking for the longest mount point which matches the
281 size_t longest_match = 0;
282 size_t current_length;
285 generic_fsent_t fsent;
287 char *disk_try = NULL;
292 if (getcwd(cwd, cwd_len) == NULL) {
296 cwd_length = strlen(cwd);
297 dbprintf(_("guess_disk: %zu: \"%s\"\n"), cwd_length, cwd);
299 if (open_fstab() == 0) {
304 while (get_fstab_nextentry(&fsent))
306 current_length = fsent.mntdir ? strlen(fsent.mntdir) : (size_t)0;
307 dbprintf(_("guess_disk: %zu: %zu: \"%s\": \"%s\"\n"),
310 fsent.mntdir ? fsent.mntdir : _("(mntdir null)"),
311 fsent.fsname ? fsent.fsname : _("(fsname null)"));
312 if ((current_length > longest_match)
313 && (current_length <= cwd_length)
314 && (strncmp(fsent.mntdir, cwd, current_length) == 0))
316 longest_match = current_length;
317 *mpt_guess = newstralloc(*mpt_guess, fsent.mntdir);
318 if(strncmp(fsent.fsname,DEV_PREFIX,(strlen(DEV_PREFIX))))
320 fsname = newstralloc(fsname, fsent.fsname);
324 fsname = newstralloc(fsname,fsent.fsname+strlen(DEV_PREFIX));
326 local_disk = is_local_fstype(&fsent);
327 dbprintf(_("guess_disk: local_disk = %d, fsname = \"%s\"\n"),
334 if (longest_match == 0) {
337 return -1; /* ? at least / should match */
346 /* have mount point now */
347 /* disk name may be specified by mount point (logical name) or
348 device name, have to determine */
349 g_printf(_("Trying disk %s ...\n"), *mpt_guess);
350 disk_try = stralloc2("DISK ", *mpt_guess); /* try logical name */
351 if (exchange(disk_try) == -1)
356 *dn_guess = stralloc(*mpt_guess); /* logical is okay */
360 g_printf(_("Trying disk %s ...\n"), fsname);
361 disk_try = stralloc2("DISK ", fsname); /* try device name */
362 if (exchange(disk_try) == -1)
367 *dn_guess = stralloc(fsname); /* dev name is okay */
372 /* neither is okay */
383 (void)converse("QUIT");
386 char *localhost = NULL;
388 #ifdef DEFAULT_TAPE_SERVER
389 # define DEFAULT_TAPE_SERVER_FAILOVER (DEFAULT_TAPE_SERVER)
391 # define DEFAULT_TAPE_SERVER_FAILOVER (NULL)
403 char *lineread = NULL;
404 struct sigaction act, oact;
407 char cwd[STR_SIZE], *dn_guess = NULL, *mpt_guess = NULL;
413 * Configure program for internationalization:
414 * 1) Only set the message locale for now.
415 * 2) Set textdomain for all amanda related programs to "amanda"
416 * We don't want to be forced to support dozens of message catalogs.
418 setlocale(LC_MESSAGES, "C");
419 textdomain("amanda");
423 set_pname("amoldrecover");
425 /* Don't die when child closes pipe */
426 signal(SIGPIPE, SIG_IGN);
428 dbopen(DBG_SUBDIR_CLIENT);
430 localhost = alloc(MAX_HOSTNAME_LENGTH+1);
431 if (gethostname(localhost, MAX_HOSTNAME_LENGTH) != 0) {
432 error(_("cannot determine local host name\n"));
435 localhost[MAX_HOSTNAME_LENGTH] = '\0';
437 config = newstralloc(config, DEFAULT_CONFIG);
439 dbrename(config, DBG_SUBDIR_CLIENT);
441 check_running_as(RUNNING_AS_ROOT);
444 server_name = getenv("AMANDA_SERVER");
445 if(!server_name) server_name = DEFAULT_SERVER;
446 server_name = stralloc(server_name);
448 amfree(tape_server_name);
449 tape_server_name = getenv("AMANDA_TAPESERVER");
450 if(!tape_server_name) tape_server_name = DEFAULT_TAPE_SERVER;
451 tape_server_name = stralloc(tape_server_name);
453 config_init(CONFIG_INIT_CLIENT, NULL);
455 if (config_errors(NULL) >= CFGERR_WARNINGS) {
456 config_print_errors();
457 if (config_errors(NULL) >= CFGERR_ERRORS) {
458 g_critical(_("errors processing config file"));
462 if (argc > 1 && argv[1][0] != '-')
465 * If the first argument is not an option flag, then we assume
466 * it is a configuration name to match the syntax of the other
471 new_argv = (char **) alloc((size_t)((argc + 1 + 1) * sizeof(*new_argv)));
472 new_argv[0] = argv[0];
474 for (i = 1; i < argc; i++)
476 new_argv[i + 1] = argv[i];
478 new_argv[i + 1] = NULL;
482 while ((i = getopt(argc, argv, "C:s:t:d:U")) != EOF)
487 config = newstralloc(config, optarg);
491 server_name = newstralloc(server_name, optarg);
495 tape_server_name = newstralloc(tape_server_name, optarg);
499 tape_device_name = newstralloc(tape_device_name, optarg);
504 (void)g_printf(USAGE);
510 (void)g_fprintf(stderr, USAGE);
519 /* Don't die when child closes pipe */
520 signal(SIGPIPE, SIG_IGN);
522 /* set up signal handler */
523 act.sa_handler = sigint_handler;
524 sigemptyset(&act.sa_mask);
526 if (sigaction(SIGINT, &act, &oact) != 0) {
527 error(_("error setting signal handler: %s"), strerror(errno));
531 service_name = stralloc2("amandaidx", SERVICE_SUFFIX);
533 g_printf(_("AMRECOVER Version %s. Contacting server on %s ...\n"),
534 VERSION, server_name);
535 if ((sp = getservbyname(service_name, "tcp")) == NULL) {
536 error(_("%s/tcp unknown protocol"), service_name);
539 amfree(service_name);
540 server_socket = stream_client_privileged(server_name,
541 (in_port_t)ntohs((in_port_t)sp->s_port),
546 if (server_socket < 0) {
547 error(_("cannot connect to %s: %s"), server_name, strerror(errno));
550 if (my_port >= IPPORT_RESERVED) {
551 aclose(server_socket);
552 error(_("did not get a reserved port: %d"), my_port);
556 /* get server's banner */
557 if (grab_reply(1) == -1) {
558 aclose(server_socket);
564 aclose(server_socket);
568 /* do the security thing */
569 line = get_security();
570 if (converse(line) == -1) {
571 aclose(server_socket);
574 if (!server_happy()) {
575 aclose(server_socket);
578 memset(line, '\0', strlen(line));
581 /* try to get the features from the server */
583 char *our_feature_string = NULL;
584 char *their_feature_string = NULL;
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 indexsrv_features = am_string_to_feature(their_feature_string);
594 indexsrv_features = am_set_default_feature_set();
596 amfree(our_feature_string);
597 amfree(their_feature_string);
601 /* set the date of extraction to be today */
603 tm = localtime(&timer);
605 strftime(dump_date, sizeof(dump_date), "%Y-%m-%d", tm);
607 error(_("BAD DATE"));
609 g_printf(_("Setting restore date to today (%s)\n"), dump_date);
610 line = stralloc2("DATE ", dump_date);
611 if (converse(line) == -1) {
612 aclose(server_socket);
617 line = stralloc2("SCNF ", config);
618 if (converse(line) == -1) {
619 aclose(server_socket);
626 /* set host we are restoring to this host by default */
627 amfree(dump_hostname);
631 /* get a starting disk and directory based on where
633 switch (guess_disk(cwd, sizeof(cwd), &dn_guess, &mpt_guess))
636 /* okay, got a guess. Set disk accordingly */
637 g_printf(_("$CWD '%s' is on disk '%s' mounted at '%s'.\n"),
638 cwd, dn_guess, mpt_guess);
639 set_disk(dn_guess, mpt_guess);
641 if (server_happy() && strcmp(cwd, mpt_guess) != 0)
642 g_printf(_("WARNING: not on root of selected filesystem, check man-page!\n"));
648 g_printf(_("$CWD '%s' is on a network mounted disk\n"),
650 g_printf(_("so you must 'sethost' to the server\n"));
651 /* fake an unhappy server */
652 server_line[0] = '5';
658 g_printf(_("Use the setdisk command to choose dump disk to recover\n"));
659 /* fake an unhappy server */
660 server_line[0] = '5';
669 if ((lineread = readline("amrecover> ")) == NULL) {
674 if (lineread[0] != '\0')
676 add_history(lineread);
677 process_line(lineread); /* act on line's content */
680 } while (!quit_prog);
684 aclose(server_socket);
691 struct passwd *pwptr;
693 if((pwptr = getpwuid(getuid())) == NULL) {
694 error(_("can't get login name for my uid %ld"), (long)getuid());
697 return stralloc2("SECURITY USER ", pwptr->pw_name);