2 Bacula® - The Network Backup Solution
4 Copyright (C) 2002-2009 Free Software Foundation Europe e.V.
6 The main author of Bacula is Kern Sibbald, with contributions from
7 many others, a complete list can be found in the file AUTHORS.
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version two of the GNU General Public
10 License as published by the Free Software Foundation and included
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 Bacula® is a registered trademark of Kern Sibbald.
24 The licensor of Bacula is the Free Software Foundation Europe
25 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26 Switzerland, email:ftf@fsfeurope.org.
30 * Bacula Director -- User Agent Database File tree for Restore
31 * command. This file interacts with the user implementing the
34 * Kern Sibbald, July MMII
44 #include "lib/fnmatch.h"
46 #include "findlib/find.h"
49 /* Forward referenced commands */
51 static int markcmd(UAContext *ua, TREE_CTX *tree);
52 static int markdircmd(UAContext *ua, TREE_CTX *tree);
53 static int countcmd(UAContext *ua, TREE_CTX *tree);
54 static int findcmd(UAContext *ua, TREE_CTX *tree);
55 static int lscmd(UAContext *ua, TREE_CTX *tree);
56 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree);
57 static int dircmd(UAContext *ua, TREE_CTX *tree);
58 static int dot_dircmd(UAContext *ua, TREE_CTX *tree);
59 static int estimatecmd(UAContext *ua, TREE_CTX *tree);
60 static int helpcmd(UAContext *ua, TREE_CTX *tree);
61 static int cdcmd(UAContext *ua, TREE_CTX *tree);
62 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
63 static int dot_pwdcmd(UAContext *ua, TREE_CTX *tree);
64 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
65 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree);
66 static int quitcmd(UAContext *ua, TREE_CTX *tree);
67 static int donecmd(UAContext *ua, TREE_CTX *tree);
70 struct cmdstruct { const char *key; int (*func)(UAContext *ua, TREE_CTX *tree); const char *help; };
71 static struct cmdstruct commands[] = {
72 { NT_("add"), markcmd, _("add dir/file to be restored recursively, wildcards allowed")},
73 { NT_("cd"), cdcmd, _("change current directory")},
74 { NT_("count"), countcmd, _("count marked files in and below the cd")},
75 { NT_("delete"), unmarkcmd, _("delete dir/file to be restored recursively in dir")},
76 { NT_("dir"), dircmd, _("long list current directory, wildcards allowed")},
77 { NT_(".dir"), dot_dircmd, _("long list current directory, wildcards allowed")},
78 { NT_("done"), donecmd, _("leave file selection mode")},
79 { NT_("estimate"), estimatecmd, _("estimate restore size")},
80 { NT_("exit"), donecmd, _("same as done command")},
81 { NT_("find"), findcmd, _("find files, wildcards allowed")},
82 { NT_("help"), helpcmd, _("print help")},
83 { NT_("ls"), lscmd, _("list current directory, wildcards allowed")},
84 { NT_("lsmark"), lsmarkcmd, _("list the marked files in and below the cd")},
85 { NT_("mark"), markcmd, _("mark dir/file to be restored recursively, wildcards allowed")},
86 { NT_("markdir"), markdircmd, _("mark directory name to be restored (no files)")},
87 { NT_("pwd"), pwdcmd, _("print current working directory")},
88 { NT_(".pwd"), dot_pwdcmd, _("print current working directory")},
89 { NT_("unmark"), unmarkcmd, _("unmark dir/file to be restored recursively in dir")},
90 { NT_("unmarkdir"), unmarkdircmd, _("unmark directory name only no recursion")},
91 { NT_("quit"), quitcmd, _("quit and do not do restore")},
92 { NT_("?"), helpcmd, _("print help")},
94 #define comsize ((int)(sizeof(commands)/sizeof(struct cmdstruct)))
97 * Enter a prompt mode where the user can select/deselect
98 * files to be restored. This is sort of like a mini-shell
99 * that allows "cd", "pwd", "add", "rm", ...
101 bool user_select_files_from_tree(TREE_CTX *tree)
105 /* Get a new context so we don't destroy restore command args */
106 UAContext *ua = new_ua_context(tree->ua->jcr);
107 ua->UA_sock = tree->ua->UA_sock; /* patch in UA socket */
108 ua->api = tree->ua->api; /* keep API flag too */
109 BSOCK *user = ua->UA_sock;
112 "\nYou are now entering file selection mode where you add (mark) and\n"
113 "remove (unmark) files to be restored. No files are initially added, unless\n"
114 "you used the \"all\" keyword on the command line.\n"
115 "Enter \"done\" to leave this mode.\n\n"));
116 if (ua->api) user->signal(BNET_START_RTREE);
118 * Enter interactive command handler allowing selection
119 * of individual files.
121 tree->node = (TREE_NODE *)tree->root;
122 tree_getpath(tree->node, cwd, sizeof(cwd));
123 ua->send_msg(_("cwd is: %s\n"), cwd);
126 if (!get_cmd(ua, "$ ")) {
129 if (ua->api) user->signal(BNET_CMD_BEGIN);
130 parse_args_only(ua->cmd, &ua->args, &ua->argc, ua->argk, ua->argv, MAX_CMD_ARGS);
132 ua->warning_msg(_("Invalid command \"%s\". Enter \"done\" to exit.\n"), ua->cmd);
133 if (ua->api) user->signal(BNET_CMD_FAILED);
137 len = strlen(ua->argk[0]);
140 for (i=0; i<comsize; i++) /* search for command */
141 if (strncasecmp(ua->argk[0], _(commands[i].key), len) == 0) {
142 stat = (*commands[i].func)(ua, tree); /* go execute command */
147 ua->warning_msg(_("Invalid command \"%s\". Enter \"done\" to exit.\n"), ua->cmd);
148 if (ua->api) user->signal(BNET_CMD_FAILED);
151 if (ua->api) user->signal(BNET_CMD_OK);
156 if (ua->api) user->signal(BNET_END_RTREE);
157 ua->UA_sock = NULL; /* don't release restore socket */
160 free_ua_context(ua); /* get rid of temp UA context */
166 * This callback routine is responsible for inserting the
167 * items it gets into the directory tree. For each JobId selected
168 * this routine is called once for each file. We do not allow
169 * duplicate filenames, but instead keep the info from the most
170 * recent file entered (i.e. the JobIds are assumed to be sorted)
172 * See uar_sel_files in sql_cmds.c for query that calls us.
173 * row[0]=Path, row[1]=Filename, row[2]=FileIndex
174 * row[3]=JobId row[4]=LStat
176 int insert_tree_handler(void *ctx, int num_fields, char **row)
179 TREE_CTX *tree = (TREE_CTX *)ctx;
186 Dmsg4(400, "Path=%s%s FI=%s JobId=%s\n", row[0], row[1],
188 if (*row[1] == 0) { /* no filename => directory */
189 if (!IsPathSeparator(*row[0])) { /* Must be Win32 directory */
197 hard_link = (decode_LinkFI(row[4], &statp) != 0);
198 node = insert_tree_node(row[0], row[1], type, tree->root, NULL);
199 JobId = str_to_int64(row[3]);
200 FileIndex = str_to_int64(row[2]);
201 Dmsg2(400, "JobId=%s FileIndex=%s\n", row[3], row[2]);
203 * - The first time we see a file (node->inserted==true), we accept it.
204 * - In the same JobId, we accept only the first copy of a
205 * hard linked file (the others are simply pointers).
206 * - In the same JobId, we accept the last copy of any other
207 * file -- in particular directories.
209 * All the code to set ok could be condensed to a single
210 * line, but it would be even harder to read.
213 if (!node->inserted && JobId == node->JobId) {
214 if ((hard_link && FileIndex > node->FileIndex) ||
215 (!hard_link && FileIndex < node->FileIndex)) {
220 node->hard_link = hard_link;
221 node->FileIndex = FileIndex;
224 node->soft_link = S_ISLNK(statp.st_mode) != 0;
226 node->extract = true; /* extract all by default */
227 if (type == TN_DIR || type == TN_DIR_NLS) {
228 node->extract_dir = true; /* if dir, extract it */
232 if (node->inserted) {
234 if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) {
235 tree->ua->send_msg("+");
236 tree->LastCount = tree->FileCount;
245 * Set extract to value passed. We recursively walk
246 * down the tree setting all children if the
247 * node is a directory.
249 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
256 node->extract = extract;
257 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
258 node->extract_dir = extract; /* set/clear dir too */
260 if (node->type != TN_NEWDIR) {
263 /* For a non-file (i.e. directory), we see all the children */
264 if (node->type != TN_FILE || (node->soft_link && tree_node_has_child(node))) {
265 /* Recursive set children within directory */
266 foreach_child(n, node) {
267 count += set_extract(ua, n, tree, extract);
270 * Walk up tree marking any unextracted parent to be
274 while (node->parent && !node->parent->extract_dir) {
276 node->extract_dir = true;
279 } else if (extract) {
282 * Ordinary file, we get the full path, look up the
283 * attributes, decode them, and if we are hard linked to
284 * a file that was saved, we must load that file too.
286 tree_getpath(node, cwd, sizeof(cwd));
288 fdbr.JobId = node->JobId;
289 if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
291 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
293 * If we point to a hard linked file, traverse the tree to
294 * find that file, and mark it to be restored as well. It
295 * must have the Link we just obtained and the same JobId.
298 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
299 if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
301 if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
302 n->extract_dir = true;
313 static void strip_trailing_slash(char *arg)
315 int len = strlen(arg);
320 if (arg[len] == '/') { /* strip any trailing slash */
326 * Recursively mark the current directory to be restored as
327 * well as all directories and files below it.
329 static int markcmd(UAContext *ua, TREE_CTX *tree)
335 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
336 ua->send_msg(_("No files marked.\n"));
339 for (int i=1; i < ua->argc; i++) {
340 strip_trailing_slash(ua->argk[i]);
341 foreach_child(node, tree->node) {
342 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
343 count += set_extract(ua, node, tree, true);
348 ua->send_msg(_("No files marked.\n"));
349 } else if (count == 1) {
350 ua->send_msg(_("1 file marked.\n"));
352 ua->send_msg(_("%s files marked.\n"),
353 edit_uint64_with_commas(count, ec1));
358 static int markdircmd(UAContext *ua, TREE_CTX *tree)
364 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
365 ua->send_msg(_("No files marked.\n"));
368 for (int i=1; i < ua->argc; i++) {
369 strip_trailing_slash(ua->argk[i]);
370 foreach_child(node, tree->node) {
371 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
372 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
373 node->extract_dir = true;
380 ua->send_msg(_("No directories marked.\n"));
381 } else if (count == 1) {
382 ua->send_msg(_("1 directory marked.\n"));
384 ua->send_msg(_("%s directories marked.\n"),
385 edit_uint64_with_commas(count, ec1));
391 static int countcmd(UAContext *ua, TREE_CTX *tree)
393 int total, num_extract;
394 char ec1[50], ec2[50];
396 total = num_extract = 0;
397 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
398 if (node->type != TN_NEWDIR) {
400 if (node->extract || node->extract_dir) {
405 ua->send_msg(_("%s total files/dirs. %s marked to be restored.\n"),
406 edit_uint64_with_commas(total, ec1),
407 edit_uint64_with_commas(num_extract, ec2));
411 static int findcmd(UAContext *ua, TREE_CTX *tree)
416 ua->send_msg(_("No file specification given.\n"));
417 return 1; /* make it non-fatal */
420 for (int i=1; i < ua->argc; i++) {
421 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
422 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
424 tree_getpath(node, cwd, sizeof(cwd));
427 } else if (node->extract_dir) {
432 ua->send_msg("%s%s\n", tag, cwd);
441 static int lscmd(UAContext *ua, TREE_CTX *tree)
445 if (!tree_node_has_child(tree->node)) {
448 foreach_child(node, tree->node) {
449 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
453 } else if (node->extract_dir) {
458 ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
465 * Ls command that lists only the marked files
467 static void rlsmark(UAContext *ua, TREE_NODE *tnode)
470 if (!tree_node_has_child(tnode)) {
473 foreach_child(node, tnode) {
474 if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
475 (node->extract || node->extract_dir)) {
479 } else if (node->extract_dir) {
484 ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
485 if (tree_node_has_child(node)) {
492 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
494 rlsmark(ua, tree->node);
499 * This is actually the long form used for "dir"
501 static void ls_output(guid_list *guid, char *buf, const char *fname, const char *tag,
502 struct stat *statp, bool dot_cmd)
507 char en1[30], en2[30];
511 p = encode_mode(statp->st_mode, buf);
514 n = sprintf(p, "%d,", (uint32_t)statp->st_nlink);
516 n = sprintf(p, "%s,%s,",
517 guid->uid_to_name(statp->st_uid, en1, sizeof(en1)),
518 guid->gid_to_name(statp->st_gid, en2, sizeof(en2)));
520 n = sprintf(p, "%s,", edit_int64(statp->st_size, ec1));
522 p = encode_time(statp->st_mtime, p);
527 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
529 n = sprintf(p, "%-8.8s %-8.8s",
530 guid->uid_to_name(statp->st_uid, en1, sizeof(en1)),
531 guid->gid_to_name(statp->st_gid, en2, sizeof(en2)));
533 n = sprintf(p, "%10.10s ", edit_int64(statp->st_size, ec1));
535 if (statp->st_ctime > statp->st_mtime) {
536 time = statp->st_ctime;
538 time = statp->st_mtime;
540 /* Display most recent time */
541 p = encode_time(time, p);
545 for (f=fname; *f; ) {
552 * Like ls command, but give more detail on each file
554 static int do_dircmd(UAContext *ua, TREE_CTX *tree, bool dot_cmd)
560 char cwd[1100], *pcwd;
563 if (!tree_node_has_child(tree->node)) {
564 ua->send_msg(_("Node %s has no children.\n"), tree->node->fname);
568 guid = new_guid_list();
569 foreach_child(node, tree->node) {
571 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
574 } else if (node->extract_dir) {
579 tree_getpath(node, cwd, sizeof(cwd));
581 fdbr.JobId = node->JobId;
583 * Strip / from soft links to directories.
584 * This is because soft links to files have a trailing slash
585 * when returned from tree_getpath, but db_get_file_attr...
586 * treats soft links as files, so they do not have a trailing
587 * slash like directory names.
589 if (node->type == TN_FILE && tree_node_has_child(node)) {
590 bstrncpy(buf, cwd, sizeof(buf));
592 int len = strlen(buf);
594 buf[len-1] = 0; /* strip trailing / */
599 if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
601 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
603 /* Something went wrong getting attributes -- print name */
604 memset(&statp, 0, sizeof(statp));
606 ls_output(guid, buf, cwd, tag, &statp, dot_cmd);
607 ua->send_msg("%s\n", buf);
610 free_guid_list(guid);
614 int dot_dircmd(UAContext *ua, TREE_CTX *tree)
616 return do_dircmd(ua, tree, true/*dot command*/);
619 static int dircmd(UAContext *ua, TREE_CTX *tree)
621 return do_dircmd(ua, tree, false/*not dot command*/);
625 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
627 int total, num_extract;
628 uint64_t total_bytes = 0;
634 total = num_extract = 0;
635 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
636 if (node->type != TN_NEWDIR) {
638 /* If regular file, get size */
639 if (node->extract && node->type == TN_FILE) {
641 tree_getpath(node, cwd, sizeof(cwd));
643 fdbr.JobId = node->JobId;
644 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
646 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
647 if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
648 total_bytes += statp.st_size;
651 /* Directory, count only */
652 } else if (node->extract || node->extract_dir) {
657 ua->send_msg(_("%d total files; %d marked to be restored; %s bytes.\n"),
658 total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
664 static int helpcmd(UAContext *ua, TREE_CTX *tree)
668 ua->send_msg(_(" Command Description\n ======= ===========\n"));
669 for (i=0; i<comsize; i++) {
670 /* List only non-dot commands */
671 if (commands[i].key[0] != '.') {
672 ua->send_msg(" %-10s %s\n", _(commands[i].key), _(commands[i].help));
680 * Change directories. Note, if the user specifies x: and it fails,
681 * we assume it is a Win32 absolute cd rather than relative and
682 * try a second time with /x: ... Win32 kludge.
684 static int cdcmd(UAContext *ua, TREE_CTX *tree)
691 ua->error_msg(_("Too few or too many arguments. Try using double quotes.\n"));
694 node = tree_cwd(ua->argk[1], tree->root, tree->node);
696 /* Try once more if Win32 drive -- make absolute */
697 if (ua->argk[1][1] == ':') { /* win32 drive */
698 bstrncpy(cwd, "/", sizeof(cwd));
699 bstrncat(cwd, ua->argk[1], sizeof(cwd));
700 node = tree_cwd(cwd, tree->root, tree->node);
703 ua->warning_msg(_("Invalid path given.\n"));
710 return pwdcmd(ua, tree);
713 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
716 tree_getpath(tree->node, cwd, sizeof(cwd));
718 ua->send_msg("%s", cwd);
720 ua->send_msg(_("cwd is: %s\n"), cwd);
725 static int dot_pwdcmd(UAContext *ua, TREE_CTX *tree)
728 tree_getpath(tree->node, cwd, sizeof(cwd));
729 ua->send_msg("%s", cwd);
733 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
738 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
739 ua->send_msg(_("No files unmarked.\n"));
742 for (int i=1; i < ua->argc; i++) {
743 strip_trailing_slash(ua->argk[i]);
744 foreach_child(node, tree->node) {
745 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
746 count += set_extract(ua, node, tree, false);
751 ua->send_msg(_("No files unmarked.\n"));
752 } else if (count == 1) {
753 ua->send_msg(_("1 file unmarked.\n"));
756 ua->send_msg(_("%s files unmarked.\n"), edit_uint64_with_commas(count, ed1));
761 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
766 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
767 ua->send_msg(_("No directories unmarked.\n"));
771 for (int i=1; i < ua->argc; i++) {
772 strip_trailing_slash(ua->argk[i]);
773 foreach_child(node, tree->node) {
774 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
775 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
776 node->extract_dir = false;
784 ua->send_msg(_("No directories unmarked.\n"));
785 } else if (count == 1) {
786 ua->send_msg(_("1 directory unmarked.\n"));
788 ua->send_msg(_("%d directories unmarked.\n"), count);
794 static int donecmd(UAContext *ua, TREE_CTX *tree)
799 static int quitcmd(UAContext *ua, TREE_CTX *tree)