2 Bacula® - The Network Backup Solution
4 Copyright (C) 2002-2007 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 John Walker.
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_("cd"), cdcmd, _("change current directory")},
73 { NT_("count"), countcmd, _("count marked files in and below the cd")},
74 { NT_("dir"), dircmd, _("long list current directory, wildcards allowed")},
75 { NT_(".dir"), dot_dircmd, _("long list current directory, wildcards allowed")},
76 { NT_("done"), donecmd, _("leave file selection mode")},
77 { NT_("estimate"), estimatecmd, _("estimate restore size")},
78 { NT_("exit"), donecmd, _("same as done command")},
79 { NT_("find"), findcmd, _("find files, wildcards allowed")},
80 { NT_("help"), helpcmd, _("print help")},
81 { NT_("ls"), lscmd, _("list current directory, wildcards allowed")},
82 { NT_("lsmark"), lsmarkcmd, _("list the marked files in and below the cd")},
83 { NT_("mark"), markcmd, _("mark dir/file to be restored recursively, wildcards allowed")},
84 { NT_("markdir"), markdircmd, _("mark directory name to be restored (no files)")},
85 { NT_("pwd"), pwdcmd, _("print current working directory")},
86 { NT_(".pwd"), dot_pwdcmd, _("print current working directory")},
87 { NT_("unmark"), unmarkcmd, _("unmark dir/file to be restored recursively in dir")},
88 { NT_("unmarkdir"), unmarkdircmd, _("unmark directory name only no recursion")},
89 { NT_("quit"), quitcmd, _("quit and do not do restore")},
90 { NT_("?"), helpcmd, _("print help")},
92 #define comsize ((int)(sizeof(commands)/sizeof(struct cmdstruct)))
95 * Enter a prompt mode where the user can select/deselect
96 * files to be restored. This is sort of like a mini-shell
97 * that allows "cd", "pwd", "add", "rm", ...
99 bool user_select_files_from_tree(TREE_CTX *tree)
103 /* Get a new context so we don't destroy restore command args */
104 UAContext *ua = new_ua_context(tree->ua->jcr);
105 ua->UA_sock = tree->ua->UA_sock; /* patch in UA socket */
106 ua->api = tree->ua->api; /* keep API flag too */
107 BSOCK *user = ua->UA_sock;
110 "\nYou are now entering file selection mode where you add (mark) and\n"
111 "remove (unmark) files to be restored. No files are initially added, unless\n"
112 "you used the \"all\" keyword on the command line.\n"
113 "Enter \"done\" to leave this mode.\n\n"));
115 * Enter interactive command handler allowing selection
116 * of individual files.
118 tree->node = (TREE_NODE *)tree->root;
119 tree_getpath(tree->node, cwd, sizeof(cwd));
120 ua->send_msg(_("cwd is: %s\n"), cwd);
123 if (!get_cmd(ua, "$ ")) {
126 if (ua->api) user->signal(BNET_CMD_BEGIN);
127 parse_args_only(ua->cmd, &ua->args, &ua->argc, ua->argk, ua->argv, MAX_CMD_ARGS);
129 ua->warning_msg(_("Invalid command. Enter \"done\" to exit.\n"));
130 if (ua->api) user->signal(BNET_CMD_FAILED);
134 len = strlen(ua->argk[0]);
137 for (i=0; i<comsize; i++) /* search for command */
138 if (strncasecmp(ua->argk[0], _(commands[i].key), len) == 0) {
139 stat = (*commands[i].func)(ua, tree); /* go execute command */
144 ua->warning_msg(_("Invalid command. Enter \"done\" to exit.\n"));
145 if (ua->api) user->signal(BNET_CMD_FAILED);
148 if (ua->api) user->signal(BNET_CMD_OK);
153 ua->UA_sock = NULL; /* don't release restore socket */
156 free_ua_context(ua); /* get rid of temp UA context */
162 * This callback routine is responsible for inserting the
163 * items it gets into the directory tree. For each JobId selected
164 * this routine is called once for each file. We do not allow
165 * duplicate filenames, but instead keep the info from the most
166 * recent file entered (i.e. the JobIds are assumed to be sorted)
168 * See uar_sel_files in sql_cmds.c for query that calls us.
169 * row[0]=Path, row[1]=Filename, row[2]=FileIndex
170 * row[3]=JobId row[4]=LStat
172 int insert_tree_handler(void *ctx, int num_fields, char **row)
175 TREE_CTX *tree = (TREE_CTX *)ctx;
182 // Dmsg4(000, "Path=%s%s FI=%s JobId=%s\n", row[0], row[1],
184 if (*row[1] == 0) { /* no filename => directory */
185 if (!IsPathSeparator(*row[0])) { /* Must be Win32 directory */
193 hard_link = (decode_LinkFI(row[4], &statp) != 0);
194 node = insert_tree_node(row[0], row[1], type, tree->root, NULL);
195 JobId = str_to_int64(row[3]);
196 FileIndex = str_to_int64(row[2]);
198 * - The first time we see a file (node->inserted==true), we accept it.
199 * - In the same JobId, we accept only the first copy of a
200 * hard linked file (the others are simply pointers).
201 * - In the same JobId, we accept the last copy of any other
202 * file -- in particular directories.
204 * All the code to set ok could be condensed to a single
205 * line, but it would be even harder to read.
208 if (!node->inserted && JobId == node->JobId) {
209 if ((hard_link && FileIndex > node->FileIndex) ||
210 (!hard_link && FileIndex < node->FileIndex)) {
215 node->hard_link = hard_link;
216 node->FileIndex = FileIndex;
219 node->soft_link = S_ISLNK(statp.st_mode) != 0;
221 node->extract = true; /* extract all by default */
222 if (type == TN_DIR || type == TN_DIR_NLS) {
223 node->extract_dir = true; /* if dir, extract it */
227 if (node->inserted) {
229 if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) {
230 tree->ua->send_msg("+");
231 tree->LastCount = tree->FileCount;
240 * Set extract to value passed. We recursively walk
241 * down the tree setting all children if the
242 * node is a directory.
244 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
251 node->extract = extract;
252 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
253 node->extract_dir = extract; /* set/clear dir too */
255 if (node->type != TN_NEWDIR) {
258 /* For a non-file (i.e. directory), we see all the children */
259 if (node->type != TN_FILE || (node->soft_link && tree_node_has_child(node))) {
260 /* Recursive set children within directory */
261 foreach_child(n, node) {
262 count += set_extract(ua, n, tree, extract);
265 * Walk up tree marking any unextracted parent to be
269 while (node->parent && !node->parent->extract_dir) {
271 node->extract_dir = true;
274 } else if (extract) {
277 * Ordinary file, we get the full path, look up the
278 * attributes, decode them, and if we are hard linked to
279 * a file that was saved, we must load that file too.
281 tree_getpath(node, cwd, sizeof(cwd));
283 fdbr.JobId = node->JobId;
284 if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
286 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
288 * If we point to a hard linked file, traverse the tree to
289 * find that file, and mark it to be restored as well. It
290 * must have the Link we just obtained and the same JobId.
293 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
294 if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
296 if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
297 n->extract_dir = true;
308 static void strip_trailing_slash(char *arg)
310 int len = strlen(arg);
315 if (arg[len] == '/') { /* strip any trailing slash */
321 * Recursively mark the current directory to be restored as
322 * well as all directories and files below it.
324 static int markcmd(UAContext *ua, TREE_CTX *tree)
330 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
331 ua->send_msg(_("No files marked.\n"));
334 for (int i=1; i < ua->argc; i++) {
335 strip_trailing_slash(ua->argk[i]);
336 foreach_child(node, tree->node) {
337 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
338 count += set_extract(ua, node, tree, true);
343 ua->send_msg(_("No files marked.\n"));
344 } else if (count == 1) {
345 ua->send_msg(_("1 file marked.\n"));
347 ua->send_msg(_("%s files marked.\n"),
348 edit_uint64_with_commas(count, ec1));
353 static int markdircmd(UAContext *ua, TREE_CTX *tree)
359 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
360 ua->send_msg(_("No files marked.\n"));
363 for (int i=1; i < ua->argc; i++) {
364 strip_trailing_slash(ua->argk[i]);
365 foreach_child(node, tree->node) {
366 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
367 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
368 node->extract_dir = true;
375 ua->send_msg(_("No directories marked.\n"));
376 } else if (count == 1) {
377 ua->send_msg(_("1 directory marked.\n"));
379 ua->send_msg(_("%s directories marked.\n"),
380 edit_uint64_with_commas(count, ec1));
386 static int countcmd(UAContext *ua, TREE_CTX *tree)
388 int total, num_extract;
389 char ec1[50], ec2[50];
391 total = num_extract = 0;
392 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
393 if (node->type != TN_NEWDIR) {
395 if (node->extract || node->extract_dir) {
400 ua->send_msg(_("%s total files/dirs. %s marked to be restored.\n"),
401 edit_uint64_with_commas(total, ec1),
402 edit_uint64_with_commas(num_extract, ec2));
406 static int findcmd(UAContext *ua, TREE_CTX *tree)
411 ua->send_msg(_("No file specification given.\n"));
412 return 1; /* make it non-fatal */
415 for (int i=1; i < ua->argc; i++) {
416 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
417 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
419 tree_getpath(node, cwd, sizeof(cwd));
422 } else if (node->extract_dir) {
427 ua->send_msg("%s%s\n", tag, cwd);
436 static int lscmd(UAContext *ua, TREE_CTX *tree)
440 if (!tree_node_has_child(tree->node)) {
443 foreach_child(node, tree->node) {
444 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
448 } else if (node->extract_dir) {
453 ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
460 * Ls command that lists only the marked files
462 static void rlsmark(UAContext *ua, TREE_NODE *tnode)
465 if (!tree_node_has_child(tnode)) {
468 foreach_child(node, tnode) {
469 if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
470 (node->extract || node->extract_dir)) {
474 } else if (node->extract_dir) {
479 ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
480 if (tree_node_has_child(node)) {
487 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
489 rlsmark(ua, tree->node);
495 extern char *getuser(uid_t uid, char *name, int len);
496 extern char *getgroup(gid_t gid, char *name, int len);
499 * This is actually the long form used for "dir"
501 static void ls_output(char *buf, const char *fname, const char *tag,
502 struct stat *statp, bool dot_cmd)
508 char en1[30], en2[30];
512 p = encode_mode(statp->st_mode, buf);
515 n = sprintf(p, "%d,", (uint32_t)statp->st_nlink);
517 n = sprintf(p, "%s,%s,", getuser(statp->st_uid, en1, sizeof(en1)),
518 getgroup(statp->st_gid, en2, sizeof(en2)));
520 n = sprintf(p, "%s,", edit_uint64(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", getuser(statp->st_uid, en1, sizeof(en1)),
530 getgroup(statp->st_gid, en2, sizeof(en2)));
532 n = sprintf(p, "%10.10s ", edit_uint64(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;
561 if (!tree_node_has_child(tree->node)) {
562 ua->send_msg(_("Node %s has no children.\n"), tree->node->fname);
566 foreach_child(node, tree->node) {
568 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
571 } else if (node->extract_dir) {
576 tree_getpath(node, cwd, sizeof(cwd));
578 fdbr.JobId = node->JobId;
580 * Strip / from soft links to directories.
581 * This is because soft links to files have a trailing slash
582 * when returned from tree_getpath, but db_get_file_attr...
583 * treats soft links as files, so they do not have a trailing
584 * slash like directory names.
586 if (node->type == TN_FILE && tree_node_has_child(node)) {
587 bstrncpy(buf, cwd, sizeof(buf));
589 int len = strlen(buf);
591 buf[len-1] = 0; /* strip trailing / */
596 if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
598 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
600 /* Something went wrong getting attributes -- print name */
601 memset(&statp, 0, sizeof(statp));
603 ls_output(buf, cwd, tag, &statp, dot_cmd);
604 ua->send_msg("%s\n", buf);
610 int dot_dircmd(UAContext *ua, TREE_CTX *tree)
612 return do_dircmd(ua, tree, true/*dot command*/);
615 static int dircmd(UAContext *ua, TREE_CTX *tree)
617 return do_dircmd(ua, tree, false/*not dot command*/);
621 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
623 int total, num_extract;
624 uint64_t total_bytes = 0;
630 total = num_extract = 0;
631 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
632 if (node->type != TN_NEWDIR) {
634 /* If regular file, get size */
635 if (node->extract && node->type == TN_FILE) {
637 tree_getpath(node, cwd, sizeof(cwd));
639 fdbr.JobId = node->JobId;
640 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
642 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
643 if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
644 total_bytes += statp.st_size;
647 /* Directory, count only */
648 } else if (node->extract || node->extract_dir) {
653 ua->send_msg(_("%d total files; %d marked to be restored; %s bytes.\n"),
654 total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
660 static int helpcmd(UAContext *ua, TREE_CTX *tree)
664 ua->send_msg(_(" Command Description\n ======= ===========\n"));
665 for (i=0; i<comsize; i++) {
666 /* List only non-dot commands */
667 if (commands[i].key[0] != '.') {
668 ua->send_msg(" %-10s %s\n", _(commands[i].key), _(commands[i].help));
676 * Change directories. Note, if the user specifies x: and it fails,
677 * we assume it is a Win32 absolute cd rather than relative and
678 * try a second time with /x: ... Win32 kludge.
680 static int cdcmd(UAContext *ua, TREE_CTX *tree)
687 ua->error_msg(_("Too few or too many arguments. Try using double quotes.\n"));
690 node = tree_cwd(ua->argk[1], tree->root, tree->node);
692 /* Try once more if Win32 drive -- make absolute */
693 if (ua->argk[1][1] == ':') { /* win32 drive */
694 bstrncpy(cwd, "/", sizeof(cwd));
695 bstrncat(cwd, ua->argk[1], sizeof(cwd));
696 node = tree_cwd(cwd, tree->root, tree->node);
699 ua->warning_msg(_("Invalid path given.\n"));
706 return pwdcmd(ua, tree);
709 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
712 tree_getpath(tree->node, cwd, sizeof(cwd));
714 ua->send_msg("%s", cwd);
716 ua->send_msg(_("cwd is: %s\n"), cwd);
721 static int dot_pwdcmd(UAContext *ua, TREE_CTX *tree)
724 tree_getpath(tree->node, cwd, sizeof(cwd));
725 ua->send_msg("%s", cwd);
729 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
734 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
735 ua->send_msg(_("No files unmarked.\n"));
738 for (int i=1; i < ua->argc; i++) {
739 strip_trailing_slash(ua->argk[i]);
740 foreach_child(node, tree->node) {
741 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
742 count += set_extract(ua, node, tree, false);
747 ua->send_msg(_("No files unmarked.\n"));
748 } else if (count == 1) {
749 ua->send_msg(_("1 file unmarked.\n"));
752 ua->send_msg(_("%s files unmarked.\n"), edit_uint64_with_commas(count, ed1));
757 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
762 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
763 ua->send_msg(_("No directories unmarked.\n"));
767 for (int i=1; i < ua->argc; i++) {
768 strip_trailing_slash(ua->argk[i]);
769 foreach_child(node, tree->node) {
770 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
771 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
772 node->extract_dir = false;
780 ua->send_msg(_("No directories unmarked.\n"));
781 } else if (count == 1) {
782 ua->send_msg(_("1 directory unmarked.\n"));
784 ua->send_msg(_("%d directories unmarked.\n"), count);
790 static int donecmd(UAContext *ua, TREE_CTX *tree)
795 static int quitcmd(UAContext *ua, TREE_CTX *tree)