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 three of the GNU Affero 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 Affero 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);
68 static int dot_lsdircmd(UAContext *ua, TREE_CTX *tree);
69 static int dot_lscmd(UAContext *ua, TREE_CTX *tree);
70 static int dot_helpcmd(UAContext *ua, TREE_CTX *tree);
71 static int dot_lsmarkcmd(UAContext *ua, TREE_CTX *tree);
73 struct cmdstruct { const char *key; int (*func)(UAContext *ua, TREE_CTX *tree); const char *help; };
74 static struct cmdstruct commands[] = {
75 { NT_("add"), markcmd, _("add dir/file to be restored recursively, wildcards allowed")},
76 { NT_("cd"), cdcmd, _("change current directory")},
77 { NT_("count"), countcmd, _("count marked files in and below the cd")},
78 { NT_("delete"), unmarkcmd, _("delete dir/file to be restored recursively in dir")},
79 { NT_("dir"), dircmd, _("long list current directory, wildcards allowed")},
80 { NT_(".dir"), dot_dircmd, _("long list current directory, wildcards allowed")},
81 { NT_("done"), donecmd, _("leave file selection mode")},
82 { NT_("estimate"), estimatecmd, _("estimate restore size")},
83 { NT_("exit"), donecmd, _("same as done command")},
84 { NT_("find"), findcmd, _("find files, wildcards allowed")},
85 { NT_("help"), helpcmd, _("print help")},
86 { NT_("ls"), lscmd, _("list current directory, wildcards allowed")},
87 { NT_(".ls"), dot_lscmd, _("list current directory, wildcards allowed")},
88 { NT_(".lsdir"), dot_lsdircmd, _("list subdir in current directory, wildcards allowed")},
89 { NT_("lsmark"), lsmarkcmd, _("list the marked files in and below the cd")},
90 { NT_(".lsmark"), dot_lsmarkcmd,_("list the marked files in")},
91 { NT_("mark"), markcmd, _("mark dir/file to be restored recursively, wildcards allowed")},
92 { NT_("markdir"), markdircmd, _("mark directory name to be restored (no files)")},
93 { NT_("pwd"), pwdcmd, _("print current working directory")},
94 { NT_(".pwd"), dot_pwdcmd, _("print current working directory")},
95 { NT_("unmark"), unmarkcmd, _("unmark dir/file to be restored recursively in dir")},
96 { NT_("unmarkdir"), unmarkdircmd, _("unmark directory name only no recursion")},
97 { NT_("quit"), quitcmd, _("quit and do not do restore")},
98 { NT_(".help"), dot_helpcmd, _("print help")},
99 { NT_("?"), helpcmd, _("print help")},
101 #define comsize ((int)(sizeof(commands)/sizeof(struct cmdstruct)))
104 * Enter a prompt mode where the user can select/deselect
105 * files to be restored. This is sort of like a mini-shell
106 * that allows "cd", "pwd", "add", "rm", ...
108 bool user_select_files_from_tree(TREE_CTX *tree)
112 /* Get a new context so we don't destroy restore command args */
113 UAContext *ua = new_ua_context(tree->ua->jcr);
114 ua->UA_sock = tree->ua->UA_sock; /* patch in UA socket */
115 ua->api = tree->ua->api; /* keep API flag too */
116 BSOCK *user = ua->UA_sock;
119 "\nYou are now entering file selection mode where you add (mark) and\n"
120 "remove (unmark) files to be restored. No files are initially added, unless\n"
121 "you used the \"all\" keyword on the command line.\n"
122 "Enter \"done\" to leave this mode.\n\n"));
123 if (ua->api) user->signal(BNET_START_RTREE);
125 * Enter interactive command handler allowing selection
126 * of individual files.
128 tree->node = (TREE_NODE *)tree->root;
129 tree_getpath(tree->node, cwd, sizeof(cwd));
130 ua->send_msg(_("cwd is: %s\n"), cwd);
133 if (!get_cmd(ua, "$ ")) {
136 if (ua->api) user->signal(BNET_CMD_BEGIN);
137 parse_args_only(ua->cmd, &ua->args, &ua->argc, ua->argk, ua->argv, MAX_CMD_ARGS);
139 ua->warning_msg(_("Invalid command \"%s\". Enter \"done\" to exit.\n"), ua->cmd);
140 if (ua->api) user->signal(BNET_CMD_FAILED);
144 len = strlen(ua->argk[0]);
147 for (i=0; i<comsize; i++) /* search for command */
148 if (strncasecmp(ua->argk[0], commands[i].key, len) == 0) {
149 stat = (*commands[i].func)(ua, tree); /* go execute command */
154 ua->warning_msg(_("Invalid command \"%s\". Enter \"done\" to exit.\n"), ua->cmd);
155 if (ua->api) user->signal(BNET_CMD_FAILED);
158 if (ua->api) user->signal(BNET_CMD_OK);
163 if (ua->api) user->signal(BNET_END_RTREE);
164 ua->UA_sock = NULL; /* don't release restore socket */
167 free_ua_context(ua); /* get rid of temp UA context */
173 * This callback routine is responsible for inserting the
174 * items it gets into the directory tree. For each JobId selected
175 * this routine is called once for each file. We do not allow
176 * duplicate filenames, but instead keep the info from the most
177 * recent file entered (i.e. the JobIds are assumed to be sorted)
179 * See uar_sel_files in sql_cmds.c for query that calls us.
180 * row[0]=Path, row[1]=Filename, row[2]=FileIndex
181 * row[3]=JobId row[4]=LStat
183 int insert_tree_handler(void *ctx, int num_fields, char **row)
186 TREE_CTX *tree = (TREE_CTX *)ctx;
193 Dmsg4(400, "Path=%s%s FI=%s JobId=%s\n", row[0], row[1],
195 if (*row[1] == 0) { /* no filename => directory */
196 if (!IsPathSeparator(*row[0])) { /* Must be Win32 directory */
204 hard_link = (decode_LinkFI(row[4], &statp) != 0);
205 node = insert_tree_node(row[0], row[1], type, tree->root, NULL);
206 JobId = str_to_int64(row[3]);
207 FileIndex = str_to_int64(row[2]);
208 Dmsg2(400, "JobId=%s FileIndex=%s\n", row[3], row[2]);
210 * - The first time we see a file (node->inserted==true), we accept it.
211 * - In the same JobId, we accept only the first copy of a
212 * hard linked file (the others are simply pointers).
213 * - In the same JobId, we accept the last copy of any other
214 * file -- in particular directories.
216 * All the code to set ok could be condensed to a single
217 * line, but it would be even harder to read.
220 if (!node->inserted && JobId == node->JobId) {
221 if ((hard_link && FileIndex > node->FileIndex) ||
222 (!hard_link && FileIndex < node->FileIndex)) {
227 node->hard_link = hard_link;
228 node->FileIndex = FileIndex;
231 node->soft_link = S_ISLNK(statp.st_mode) != 0;
233 node->extract = true; /* extract all by default */
234 if (type == TN_DIR || type == TN_DIR_NLS) {
235 node->extract_dir = true; /* if dir, extract it */
239 if (node->inserted) {
241 if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) {
242 tree->ua->send_msg("+");
243 tree->LastCount = tree->FileCount;
252 * Set extract to value passed. We recursively walk
253 * down the tree setting all children if the
254 * node is a directory.
256 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
263 node->extract = extract;
264 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
265 node->extract_dir = extract; /* set/clear dir too */
267 if (node->type != TN_NEWDIR) {
270 /* For a non-file (i.e. directory), we see all the children */
271 if (node->type != TN_FILE || (node->soft_link && tree_node_has_child(node))) {
272 /* Recursive set children within directory */
273 foreach_child(n, node) {
274 count += set_extract(ua, n, tree, extract);
277 * Walk up tree marking any unextracted parent to be
281 while (node->parent && !node->parent->extract_dir) {
283 node->extract_dir = true;
286 } else if (extract) {
289 * Ordinary file, we get the full path, look up the
290 * attributes, decode them, and if we are hard linked to
291 * a file that was saved, we must load that file too.
293 tree_getpath(node, cwd, sizeof(cwd));
295 fdbr.JobId = node->JobId;
296 if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
298 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
300 * If we point to a hard linked file, traverse the tree to
301 * find that file, and mark it to be restored as well. It
302 * must have the Link we just obtained and the same JobId.
305 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
306 if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
308 if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
309 n->extract_dir = true;
320 static void strip_trailing_slash(char *arg)
322 int len = strlen(arg);
327 if (arg[len] == '/') { /* strip any trailing slash */
333 * Recursively mark the current directory to be restored as
334 * well as all directories and files below it.
336 static int markcmd(UAContext *ua, TREE_CTX *tree)
342 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
343 ua->send_msg(_("No files marked.\n"));
346 for (int i=1; i < ua->argc; i++) {
347 strip_trailing_slash(ua->argk[i]);
348 foreach_child(node, tree->node) {
349 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
350 count += set_extract(ua, node, tree, true);
355 ua->send_msg(_("No files marked.\n"));
356 } else if (count == 1) {
357 ua->send_msg(_("1 file marked.\n"));
359 ua->send_msg(_("%s files marked.\n"),
360 edit_uint64_with_commas(count, ec1));
365 static int markdircmd(UAContext *ua, TREE_CTX *tree)
371 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
372 ua->send_msg(_("No files marked.\n"));
375 for (int i=1; i < ua->argc; i++) {
376 strip_trailing_slash(ua->argk[i]);
377 foreach_child(node, tree->node) {
378 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
379 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
380 node->extract_dir = true;
387 ua->send_msg(_("No directories marked.\n"));
388 } else if (count == 1) {
389 ua->send_msg(_("1 directory marked.\n"));
391 ua->send_msg(_("%s directories marked.\n"),
392 edit_uint64_with_commas(count, ec1));
398 static int countcmd(UAContext *ua, TREE_CTX *tree)
400 int total, num_extract;
401 char ec1[50], ec2[50];
403 total = num_extract = 0;
404 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
405 if (node->type != TN_NEWDIR) {
407 if (node->extract || node->extract_dir) {
412 ua->send_msg(_("%s total files/dirs. %s marked to be restored.\n"),
413 edit_uint64_with_commas(total, ec1),
414 edit_uint64_with_commas(num_extract, ec2));
418 static int findcmd(UAContext *ua, TREE_CTX *tree)
423 ua->send_msg(_("No file specification given.\n"));
424 return 1; /* make it non-fatal */
427 for (int i=1; i < ua->argc; i++) {
428 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
429 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
431 tree_getpath(node, cwd, sizeof(cwd));
434 } else if (node->extract_dir) {
439 ua->send_msg("%s%s\n", tag, cwd);
446 static int dot_lsdircmd(UAContext *ua, TREE_CTX *tree)
450 if (!tree_node_has_child(tree->node)) {
454 foreach_child(node, tree->node) {
455 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
456 if (tree_node_has_child(node)) {
457 ua->send_msg("%s/\n", node->fname);
465 static int dot_helpcmd(UAContext *ua, TREE_CTX *tree)
467 for (int i=0; i<comsize; i++) {
468 /* List only non-dot commands */
469 if (commands[i].key[0] != '.') {
470 ua->send_msg("%s\n", commands[i].key);
476 static int dot_lscmd(UAContext *ua, TREE_CTX *tree)
480 if (!tree_node_has_child(tree->node)) {
484 foreach_child(node, tree->node) {
485 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
486 ua->send_msg("%s%s\n", node->fname, tree_node_has_child(node)?"/":"");
493 static int lscmd(UAContext *ua, TREE_CTX *tree)
497 if (!tree_node_has_child(tree->node)) {
500 foreach_child(node, tree->node) {
501 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
505 } else if (node->extract_dir) {
510 ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
517 * Ls command that lists only the marked files
519 static int dot_lsmarkcmd(UAContext *ua, TREE_CTX *tree)
522 if (!tree_node_has_child(tree->node)) {
525 foreach_child(node, tree->node) {
526 if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
527 (node->extract || node->extract_dir)) {
528 ua->send_msg("%s%s\n", node->fname, tree_node_has_child(node)?"/":"");
535 * Ls command that lists only the marked files
537 static void rlsmark(UAContext *ua, TREE_NODE *tnode)
540 if (!tree_node_has_child(tnode)) {
543 foreach_child(node, tnode) {
544 if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
545 (node->extract || node->extract_dir)) {
549 } else if (node->extract_dir) {
554 ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
555 if (tree_node_has_child(node)) {
562 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
564 rlsmark(ua, tree->node);
569 * This is actually the long form used for "dir"
571 static void ls_output(guid_list *guid, char *buf, const char *fname, const char *tag,
572 struct stat *statp, bool dot_cmd)
577 char en1[30], en2[30];
581 p = encode_mode(statp->st_mode, buf);
584 n = sprintf(p, "%d,", (uint32_t)statp->st_nlink);
586 n = sprintf(p, "%s,%s,",
587 guid->uid_to_name(statp->st_uid, en1, sizeof(en1)),
588 guid->gid_to_name(statp->st_gid, en2, sizeof(en2)));
590 n = sprintf(p, "%s,", edit_int64(statp->st_size, ec1));
592 p = encode_time(statp->st_mtime, p);
597 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
599 n = sprintf(p, "%-8.8s %-8.8s",
600 guid->uid_to_name(statp->st_uid, en1, sizeof(en1)),
601 guid->gid_to_name(statp->st_gid, en2, sizeof(en2)));
603 n = sprintf(p, "%12.12s ", edit_int64(statp->st_size, ec1));
605 if (statp->st_ctime > statp->st_mtime) {
606 time = statp->st_ctime;
608 time = statp->st_mtime;
610 /* Display most recent time */
611 p = encode_time(time, p);
615 for (f=fname; *f; ) {
622 * Like ls command, but give more detail on each file
624 static int do_dircmd(UAContext *ua, TREE_CTX *tree, bool dot_cmd)
630 char cwd[1100], *pcwd;
633 if (!tree_node_has_child(tree->node)) {
634 ua->send_msg(_("Node %s has no children.\n"), tree->node->fname);
638 guid = new_guid_list();
639 foreach_child(node, tree->node) {
641 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
644 } else if (node->extract_dir) {
649 tree_getpath(node, cwd, sizeof(cwd));
651 fdbr.JobId = node->JobId;
653 * Strip / from soft links to directories.
654 * This is because soft links to files have a trailing slash
655 * when returned from tree_getpath, but db_get_file_attr...
656 * treats soft links as files, so they do not have a trailing
657 * slash like directory names.
659 if (node->type == TN_FILE && tree_node_has_child(node)) {
660 bstrncpy(buf, cwd, sizeof(buf));
662 int len = strlen(buf);
664 buf[len-1] = 0; /* strip trailing / */
669 if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
671 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
673 /* Something went wrong getting attributes -- print name */
674 memset(&statp, 0, sizeof(statp));
676 ls_output(guid, buf, cwd, tag, &statp, dot_cmd);
677 ua->send_msg("%s\n", buf);
680 free_guid_list(guid);
684 int dot_dircmd(UAContext *ua, TREE_CTX *tree)
686 return do_dircmd(ua, tree, true/*dot command*/);
689 static int dircmd(UAContext *ua, TREE_CTX *tree)
691 return do_dircmd(ua, tree, false/*not dot command*/);
695 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
697 int total, num_extract;
698 uint64_t total_bytes = 0;
704 total = num_extract = 0;
705 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
706 if (node->type != TN_NEWDIR) {
708 /* If regular file, get size */
709 if (node->extract && node->type == TN_FILE) {
711 tree_getpath(node, cwd, sizeof(cwd));
713 fdbr.JobId = node->JobId;
714 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
716 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
717 if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
718 total_bytes += statp.st_size;
721 /* Directory, count only */
722 } else if (node->extract || node->extract_dir) {
727 ua->send_msg(_("%d total files; %d marked to be restored; %s bytes.\n"),
728 total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
734 static int helpcmd(UAContext *ua, TREE_CTX *tree)
738 ua->send_msg(_(" Command Description\n ======= ===========\n"));
739 for (i=0; i<comsize; i++) {
740 /* List only non-dot commands */
741 if (commands[i].key[0] != '.') {
742 ua->send_msg(" %-10s %s\n", _(commands[i].key), _(commands[i].help));
750 * Change directories. Note, if the user specifies x: and it fails,
751 * we assume it is a Win32 absolute cd rather than relative and
752 * try a second time with /x: ... Win32 kludge.
754 static int cdcmd(UAContext *ua, TREE_CTX *tree)
761 ua->error_msg(_("Too few or too many arguments. Try using double quotes.\n"));
764 node = tree_cwd(ua->argk[1], tree->root, tree->node);
766 /* Try once more if Win32 drive -- make absolute */
767 if (ua->argk[1][1] == ':') { /* win32 drive */
768 bstrncpy(cwd, "/", sizeof(cwd));
769 bstrncat(cwd, ua->argk[1], sizeof(cwd));
770 node = tree_cwd(cwd, tree->root, tree->node);
773 ua->warning_msg(_("Invalid path given.\n"));
780 return pwdcmd(ua, tree);
783 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
786 tree_getpath(tree->node, cwd, sizeof(cwd));
788 ua->send_msg("%s", cwd);
790 ua->send_msg(_("cwd is: %s\n"), cwd);
795 static int dot_pwdcmd(UAContext *ua, TREE_CTX *tree)
798 tree_getpath(tree->node, cwd, sizeof(cwd));
799 ua->send_msg("%s", cwd);
803 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
808 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
809 ua->send_msg(_("No files unmarked.\n"));
812 for (int i=1; i < ua->argc; i++) {
813 strip_trailing_slash(ua->argk[i]);
814 foreach_child(node, tree->node) {
815 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
816 count += set_extract(ua, node, tree, false);
821 ua->send_msg(_("No files unmarked.\n"));
822 } else if (count == 1) {
823 ua->send_msg(_("1 file unmarked.\n"));
826 ua->send_msg(_("%s files unmarked.\n"), edit_uint64_with_commas(count, ed1));
831 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
836 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
837 ua->send_msg(_("No directories unmarked.\n"));
841 for (int i=1; i < ua->argc; i++) {
842 strip_trailing_slash(ua->argk[i]);
843 foreach_child(node, tree->node) {
844 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
845 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
846 node->extract_dir = false;
854 ua->send_msg(_("No directories unmarked.\n"));
855 } else if (count == 1) {
856 ua->send_msg(_("1 directory unmarked.\n"));
858 ua->send_msg(_("%d directories unmarked.\n"), count);
864 static int donecmd(UAContext *ua, TREE_CTX *tree)
869 static int quitcmd(UAContext *ua, TREE_CTX *tree)