2 Bacula® - The Network Backup Solution
4 Copyright (C) 2002-2008 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. Enter \"done\" to exit.\n"));
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. Enter \"done\" to exit.\n"));
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]);
202 * - The first time we see a file (node->inserted==true), we accept it.
203 * - In the same JobId, we accept only the first copy of a
204 * hard linked file (the others are simply pointers).
205 * - In the same JobId, we accept the last copy of any other
206 * file -- in particular directories.
208 * All the code to set ok could be condensed to a single
209 * line, but it would be even harder to read.
212 if (!node->inserted && JobId == node->JobId) {
213 if ((hard_link && FileIndex > node->FileIndex) ||
214 (!hard_link && FileIndex < node->FileIndex)) {
219 node->hard_link = hard_link;
220 node->FileIndex = FileIndex;
223 node->soft_link = S_ISLNK(statp.st_mode) != 0;
225 node->extract = true; /* extract all by default */
226 if (type == TN_DIR || type == TN_DIR_NLS) {
227 node->extract_dir = true; /* if dir, extract it */
231 if (node->inserted) {
233 if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) {
234 tree->ua->send_msg("+");
235 tree->LastCount = tree->FileCount;
244 * Set extract to value passed. We recursively walk
245 * down the tree setting all children if the
246 * node is a directory.
248 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
255 node->extract = extract;
256 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
257 node->extract_dir = extract; /* set/clear dir too */
259 if (node->type != TN_NEWDIR) {
262 /* For a non-file (i.e. directory), we see all the children */
263 if (node->type != TN_FILE || (node->soft_link && tree_node_has_child(node))) {
264 /* Recursive set children within directory */
265 foreach_child(n, node) {
266 count += set_extract(ua, n, tree, extract);
269 * Walk up tree marking any unextracted parent to be
273 while (node->parent && !node->parent->extract_dir) {
275 node->extract_dir = true;
278 } else if (extract) {
281 * Ordinary file, we get the full path, look up the
282 * attributes, decode them, and if we are hard linked to
283 * a file that was saved, we must load that file too.
285 tree_getpath(node, cwd, sizeof(cwd));
287 fdbr.JobId = node->JobId;
288 if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
290 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
292 * If we point to a hard linked file, traverse the tree to
293 * find that file, and mark it to be restored as well. It
294 * must have the Link we just obtained and the same JobId.
297 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
298 if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
300 if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
301 n->extract_dir = true;
312 static void strip_trailing_slash(char *arg)
314 int len = strlen(arg);
319 if (arg[len] == '/') { /* strip any trailing slash */
325 * Recursively mark the current directory to be restored as
326 * well as all directories and files below it.
328 static int markcmd(UAContext *ua, TREE_CTX *tree)
334 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
335 ua->send_msg(_("No files marked.\n"));
338 for (int i=1; i < ua->argc; i++) {
339 strip_trailing_slash(ua->argk[i]);
340 foreach_child(node, tree->node) {
341 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
342 count += set_extract(ua, node, tree, true);
347 ua->send_msg(_("No files marked.\n"));
348 } else if (count == 1) {
349 ua->send_msg(_("1 file marked.\n"));
351 ua->send_msg(_("%s files marked.\n"),
352 edit_uint64_with_commas(count, ec1));
357 static int markdircmd(UAContext *ua, TREE_CTX *tree)
363 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
364 ua->send_msg(_("No files marked.\n"));
367 for (int i=1; i < ua->argc; i++) {
368 strip_trailing_slash(ua->argk[i]);
369 foreach_child(node, tree->node) {
370 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
371 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
372 node->extract_dir = true;
379 ua->send_msg(_("No directories marked.\n"));
380 } else if (count == 1) {
381 ua->send_msg(_("1 directory marked.\n"));
383 ua->send_msg(_("%s directories marked.\n"),
384 edit_uint64_with_commas(count, ec1));
390 static int countcmd(UAContext *ua, TREE_CTX *tree)
392 int total, num_extract;
393 char ec1[50], ec2[50];
395 total = num_extract = 0;
396 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
397 if (node->type != TN_NEWDIR) {
399 if (node->extract || node->extract_dir) {
404 ua->send_msg(_("%s total files/dirs. %s marked to be restored.\n"),
405 edit_uint64_with_commas(total, ec1),
406 edit_uint64_with_commas(num_extract, ec2));
410 static int findcmd(UAContext *ua, TREE_CTX *tree)
415 ua->send_msg(_("No file specification given.\n"));
416 return 1; /* make it non-fatal */
419 for (int i=1; i < ua->argc; i++) {
420 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
421 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
423 tree_getpath(node, cwd, sizeof(cwd));
426 } else if (node->extract_dir) {
431 ua->send_msg("%s%s\n", tag, cwd);
440 static int lscmd(UAContext *ua, TREE_CTX *tree)
444 if (!tree_node_has_child(tree->node)) {
447 foreach_child(node, tree->node) {
448 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
452 } else if (node->extract_dir) {
457 ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
464 * Ls command that lists only the marked files
466 static void rlsmark(UAContext *ua, TREE_NODE *tnode)
469 if (!tree_node_has_child(tnode)) {
472 foreach_child(node, tnode) {
473 if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
474 (node->extract || node->extract_dir)) {
478 } else if (node->extract_dir) {
483 ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
484 if (tree_node_has_child(node)) {
491 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
493 rlsmark(ua, tree->node);
498 * This is actually the long form used for "dir"
500 static void ls_output(guid_list *guid, char *buf, const char *fname, const char *tag,
501 struct stat *statp, bool dot_cmd)
506 char en1[30], en2[30];
510 p = encode_mode(statp->st_mode, buf);
513 n = sprintf(p, "%d,", (uint32_t)statp->st_nlink);
515 n = sprintf(p, "%s,%s,",
516 guid->uid_to_name(statp->st_uid, en1, sizeof(en1)),
517 guid->gid_to_name(statp->st_gid, en2, sizeof(en2)));
519 n = sprintf(p, "%s,", edit_int64(statp->st_size, ec1));
521 p = encode_time(statp->st_mtime, p);
526 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
528 n = sprintf(p, "%-8.8s %-8.8s",
529 guid->uid_to_name(statp->st_uid, en1, sizeof(en1)),
530 guid->gid_to_name(statp->st_gid, en2, sizeof(en2)));
532 n = sprintf(p, "%10.10s ", edit_int64(statp->st_size, ec1));
534 if (statp->st_ctime > statp->st_mtime) {
535 time = statp->st_ctime;
537 time = statp->st_mtime;
539 /* Display most recent time */
540 p = encode_time(time, p);
544 for (f=fname; *f; ) {
551 * Like ls command, but give more detail on each file
553 static int do_dircmd(UAContext *ua, TREE_CTX *tree, bool dot_cmd)
559 char cwd[1100], *pcwd;
562 if (!tree_node_has_child(tree->node)) {
563 ua->send_msg(_("Node %s has no children.\n"), tree->node->fname);
567 guid = new_guid_list();
568 foreach_child(node, tree->node) {
570 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
573 } else if (node->extract_dir) {
578 tree_getpath(node, cwd, sizeof(cwd));
580 fdbr.JobId = node->JobId;
582 * Strip / from soft links to directories.
583 * This is because soft links to files have a trailing slash
584 * when returned from tree_getpath, but db_get_file_attr...
585 * treats soft links as files, so they do not have a trailing
586 * slash like directory names.
588 if (node->type == TN_FILE && tree_node_has_child(node)) {
589 bstrncpy(buf, cwd, sizeof(buf));
591 int len = strlen(buf);
593 buf[len-1] = 0; /* strip trailing / */
598 if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
600 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
602 /* Something went wrong getting attributes -- print name */
603 memset(&statp, 0, sizeof(statp));
605 ls_output(guid, buf, cwd, tag, &statp, dot_cmd);
606 ua->send_msg("%s\n", buf);
609 free_guid_list(guid);
613 int dot_dircmd(UAContext *ua, TREE_CTX *tree)
615 return do_dircmd(ua, tree, true/*dot command*/);
618 static int dircmd(UAContext *ua, TREE_CTX *tree)
620 return do_dircmd(ua, tree, false/*not dot command*/);
624 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
626 int total, num_extract;
627 uint64_t total_bytes = 0;
633 total = num_extract = 0;
634 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
635 if (node->type != TN_NEWDIR) {
637 /* If regular file, get size */
638 if (node->extract && node->type == TN_FILE) {
640 tree_getpath(node, cwd, sizeof(cwd));
642 fdbr.JobId = node->JobId;
643 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
645 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
646 if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
647 total_bytes += statp.st_size;
650 /* Directory, count only */
651 } else if (node->extract || node->extract_dir) {
656 ua->send_msg(_("%d total files; %d marked to be restored; %s bytes.\n"),
657 total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
663 static int helpcmd(UAContext *ua, TREE_CTX *tree)
667 ua->send_msg(_(" Command Description\n ======= ===========\n"));
668 for (i=0; i<comsize; i++) {
669 /* List only non-dot commands */
670 if (commands[i].key[0] != '.') {
671 ua->send_msg(" %-10s %s\n", _(commands[i].key), _(commands[i].help));
679 * Change directories. Note, if the user specifies x: and it fails,
680 * we assume it is a Win32 absolute cd rather than relative and
681 * try a second time with /x: ... Win32 kludge.
683 static int cdcmd(UAContext *ua, TREE_CTX *tree)
690 ua->error_msg(_("Too few or too many arguments. Try using double quotes.\n"));
693 node = tree_cwd(ua->argk[1], tree->root, tree->node);
695 /* Try once more if Win32 drive -- make absolute */
696 if (ua->argk[1][1] == ':') { /* win32 drive */
697 bstrncpy(cwd, "/", sizeof(cwd));
698 bstrncat(cwd, ua->argk[1], sizeof(cwd));
699 node = tree_cwd(cwd, tree->root, tree->node);
702 ua->warning_msg(_("Invalid path given.\n"));
709 return pwdcmd(ua, tree);
712 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
715 tree_getpath(tree->node, cwd, sizeof(cwd));
717 ua->send_msg("%s", cwd);
719 ua->send_msg(_("cwd is: %s\n"), cwd);
724 static int dot_pwdcmd(UAContext *ua, TREE_CTX *tree)
727 tree_getpath(tree->node, cwd, sizeof(cwd));
728 ua->send_msg("%s", cwd);
732 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
737 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
738 ua->send_msg(_("No files unmarked.\n"));
741 for (int i=1; i < ua->argc; i++) {
742 strip_trailing_slash(ua->argk[i]);
743 foreach_child(node, tree->node) {
744 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
745 count += set_extract(ua, node, tree, false);
750 ua->send_msg(_("No files unmarked.\n"));
751 } else if (count == 1) {
752 ua->send_msg(_("1 file unmarked.\n"));
755 ua->send_msg(_("%s files unmarked.\n"), edit_uint64_with_commas(count, ed1));
760 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
765 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
766 ua->send_msg(_("No directories unmarked.\n"));
770 for (int i=1; i < ua->argc; i++) {
771 strip_trailing_slash(ua->argk[i]);
772 foreach_child(node, tree->node) {
773 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
774 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
775 node->extract_dir = false;
783 ua->send_msg(_("No directories unmarked.\n"));
784 } else if (count == 1) {
785 ua->send_msg(_("1 directory unmarked.\n"));
787 ua->send_msg(_("%d directories unmarked.\n"), count);
793 static int donecmd(UAContext *ua, TREE_CTX *tree)
798 static int quitcmd(UAContext *ua, TREE_CTX *tree)