3 * Bacula Director -- User Agent Database File tree for Restore
4 * command. This file interacts with the user implementing the
7 * Kern Sibbald, July MMII
12 Bacula® - The Network Backup Solution
14 Copyright (C) 2002-2006 Free Software Foundation Europe e.V.
16 The main author of Bacula is Kern Sibbald, with contributions from
17 many others, a complete list can be found in the file AUTHORS.
18 This program is Free Software; you can redistribute it and/or
19 modify it under the terms of version two of the GNU General Public
20 License as published by the Free Software Foundation plus additions
21 that are listed in the file LICENSE.
23 This program is distributed in the hope that it will be useful, but
24 WITHOUT ANY WARRANTY; without even the implied warranty of
25 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26 General Public License for more details.
28 You should have received a copy of the GNU General Public License
29 along with this program; if not, write to the Free Software
30 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
33 Bacula® is a registered trademark of John Walker.
34 The licensor of Bacula is the Free Software Foundation Europe
35 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
36 Switzerland, email:ftf@fsfeurope.org.
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 unmarkcmd(UAContext *ua, TREE_CTX *tree);
64 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree);
65 static int quitcmd(UAContext *ua, TREE_CTX *tree);
66 static int donecmd(UAContext *ua, TREE_CTX *tree);
69 struct cmdstruct { const char *key; int (*func)(UAContext *ua, TREE_CTX *tree); const char *help; };
70 static struct cmdstruct commands[] = {
71 { NT_("cd"), cdcmd, _("change current directory")},
72 { NT_("count"), countcmd, _("count marked files in and below the cd")},
73 { NT_("dir"), dircmd, _("long list current directory, wildcards allowed")},
74 { NT_(".dir"), dot_dircmd, _("long list current directory, wildcards allowed")},
75 { NT_("done"), donecmd, _("leave file selection mode")},
76 { NT_("estimate"), estimatecmd, _("estimate restore size")},
77 { NT_("exit"), donecmd, _("same as done command")},
78 { NT_("find"), findcmd, _("find files, wildcards allowed")},
79 { NT_("help"), helpcmd, _("print help")},
80 { NT_("ls"), lscmd, _("list current directory, wildcards allowed")},
81 { NT_("lsmark"), lsmarkcmd, _("list the marked files in and below the cd")},
82 { NT_("mark"), markcmd, _("mark dir/file to be restored recursively, wildcards allowed")},
83 { NT_("markdir"), markdircmd, _("mark directory name to be restored (no files)")},
84 { NT_("pwd"), pwdcmd, _("print current working directory")},
85 { NT_("unmark"), unmarkcmd, _("unmark dir/file to be restored recursively in dir")},
86 { NT_("unmarkdir"), unmarkdircmd, _("unmark directory name only no recursion")},
87 { NT_("quit"), quitcmd, _("quit and do not do restore")},
88 { NT_("?"), helpcmd, _("print help")},
90 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
94 * Enter a prompt mode where the user can select/deselect
95 * files to be restored. This is sort of like a mini-shell
96 * that allows "cd", "pwd", "add", "rm", ...
98 bool user_select_files_from_tree(TREE_CTX *tree)
102 /* Get a new context so we don't destroy restore command args */
103 UAContext *ua = new_ua_context(tree->ua->jcr);
104 ua->UA_sock = tree->ua->UA_sock; /* patch in UA socket */
106 bsendmsg(tree->ua, _(
107 "\nYou are now entering file selection mode where you add (mark) and\n"
108 "remove (unmark) files to be restored. No files are initially added, unless\n"
109 "you used the \"all\" keyword on the command line.\n"
110 "Enter \"done\" to leave this mode.\n\n"));
112 * Enter interactive command handler allowing selection
113 * of individual files.
115 tree->node = (TREE_NODE *)tree->root;
116 tree_getpath(tree->node, cwd, sizeof(cwd));
117 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
120 if (!get_cmd(ua, "$ ")) {
123 parse_args_only(ua->cmd, &ua->args, &ua->argc, ua->argk, ua->argv, MAX_CMD_ARGS);
125 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
129 len = strlen(ua->argk[0]);
132 for (i=0; i<(int)comsize; i++) /* search for command */
133 if (strncasecmp(ua->argk[0], _(commands[i].key), len) == 0) {
134 stat = (*commands[i].func)(ua, tree); /* go execute command */
139 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
146 ua->UA_sock = NULL; /* don't release restore socket */
149 free_ua_context(ua); /* get rid of temp UA context */
155 * This callback routine is responsible for inserting the
156 * items it gets into the directory tree. For each JobId selected
157 * this routine is called once for each file. We do not allow
158 * duplicate filenames, but instead keep the info from the most
159 * recent file entered (i.e. the JobIds are assumed to be sorted)
161 * See uar_sel_files in sql_cmds.c for query that calls us.
162 * row[0]=Path, row[1]=Filename, row[2]=FileIndex
163 * row[3]=JobId row[4]=LStat
165 int insert_tree_handler(void *ctx, int num_fields, char **row)
168 TREE_CTX *tree = (TREE_CTX *)ctx;
175 // Dmsg4(000, "Path=%s%s FI=%s JobId=%s\n", row[0], row[1],
177 if (*row[1] == 0) { /* no filename => directory */
178 if (!IsPathSeparator(*row[0])) { /* Must be Win32 directory */
186 hard_link = (decode_LinkFI(row[4], &statp) != 0);
187 node = insert_tree_node(row[0], row[1], type, tree->root, NULL);
188 JobId = str_to_int64(row[3]);
189 FileIndex = str_to_int64(row[2]);
191 * - The first time we see a file (node->inserted==true), we accept it.
192 * - In the same JobId, we accept only the first copy of a
193 * hard linked file (the others are simply pointers).
194 * - In the same JobId, we accept the last copy of any other
195 * file -- in particular directories.
197 * All the code to set ok could be condensed to a single
198 * line, but it would be even harder to read.
201 if (!node->inserted && JobId == node->JobId) {
202 if ((hard_link && FileIndex > node->FileIndex) ||
203 (!hard_link && FileIndex < node->FileIndex)) {
208 node->hard_link = hard_link;
209 node->FileIndex = FileIndex;
212 node->soft_link = S_ISLNK(statp.st_mode) != 0;
214 node->extract = true; /* extract all by default */
215 if (type == TN_DIR || type == TN_DIR_NLS) {
216 node->extract_dir = true; /* if dir, extract it */
220 if (node->inserted) {
222 if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) {
223 bsendmsg(tree->ua, "+");
224 tree->LastCount = tree->FileCount;
233 * Set extract to value passed. We recursively walk
234 * down the tree setting all children if the
235 * node is a directory.
237 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
244 node->extract = extract;
245 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
246 node->extract_dir = extract; /* set/clear dir too */
248 if (node->type != TN_NEWDIR) {
251 /* For a non-file (i.e. directory), we see all the children */
252 if (node->type != TN_FILE || (node->soft_link && tree_node_has_child(node))) {
253 /* Recursive set children within directory */
254 foreach_child(n, node) {
255 count += set_extract(ua, n, tree, extract);
258 * Walk up tree marking any unextracted parent to be
262 while (node->parent && !node->parent->extract_dir) {
264 node->extract_dir = true;
267 } else if (extract) {
270 * Ordinary file, we get the full path, look up the
271 * attributes, decode them, and if we are hard linked to
272 * a file that was saved, we must load that file too.
274 tree_getpath(node, cwd, sizeof(cwd));
276 fdbr.JobId = node->JobId;
277 if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
279 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
281 * If we point to a hard linked file, traverse the tree to
282 * find that file, and mark it to be restored as well. It
283 * must have the Link we just obtained and the same JobId.
286 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
287 if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
289 if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
290 n->extract_dir = true;
302 * Recursively mark the current directory to be restored as
303 * well as all directories and files below it.
305 static int markcmd(UAContext *ua, TREE_CTX *tree)
311 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
312 bsendmsg(ua, _("No files marked.\n"));
315 for (int i=1; i < ua->argc; i++) {
316 foreach_child(node, tree->node) {
317 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
318 count += set_extract(ua, node, tree, true);
323 bsendmsg(ua, _("No files marked.\n"));
324 } else if (count == 1) {
325 bsendmsg(ua, _("1 file marked.\n"));
327 bsendmsg(ua, _("%s files marked.\n"),
328 edit_uint64_with_commas(count, ec1));
333 static int markdircmd(UAContext *ua, TREE_CTX *tree)
339 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
340 bsendmsg(ua, _("No files marked.\n"));
343 for (int i=1; i < ua->argc; i++) {
344 foreach_child(node, tree->node) {
345 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
346 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
347 node->extract_dir = true;
354 bsendmsg(ua, _("No directories marked.\n"));
355 } else if (count == 1) {
356 bsendmsg(ua, _("1 directory marked.\n"));
358 bsendmsg(ua, _("%s directories marked.\n"),
359 edit_uint64_with_commas(count, ec1));
365 static int countcmd(UAContext *ua, TREE_CTX *tree)
367 int total, num_extract;
368 char ec1[50], ec2[50];
370 total = num_extract = 0;
371 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
372 if (node->type != TN_NEWDIR) {
374 if (node->extract || node->extract_dir) {
379 bsendmsg(ua, _("%s total files/dirs. %s marked to be restored.\n"),
380 edit_uint64_with_commas(total, ec1),
381 edit_uint64_with_commas(num_extract, ec2));
385 static int findcmd(UAContext *ua, TREE_CTX *tree)
390 bsendmsg(ua, _("No file specification given.\n"));
391 return 1; /* make it non-fatal */
394 for (int i=1; i < ua->argc; i++) {
395 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
396 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
398 tree_getpath(node, cwd, sizeof(cwd));
401 } else if (node->extract_dir) {
406 bsendmsg(ua, "%s%s\n", tag, cwd);
415 static int lscmd(UAContext *ua, TREE_CTX *tree)
419 if (!tree_node_has_child(tree->node)) {
422 foreach_child(node, tree->node) {
423 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
427 } else if (node->extract_dir) {
432 bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
439 * Ls command that lists only the marked files
441 static void rlsmark(UAContext *ua, TREE_NODE *tnode)
444 if (!tree_node_has_child(tnode)) {
447 foreach_child(node, tnode) {
448 if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
449 (node->extract || node->extract_dir)) {
453 } else if (node->extract_dir) {
458 bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
459 if (tree_node_has_child(node)) {
466 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
468 rlsmark(ua, tree->node);
474 extern char *getuser(uid_t uid, char *name, int len);
475 extern char *getgroup(gid_t gid, char *name, int len);
478 * This is actually the long form used for "dir"
480 static void ls_output(char *buf, const char *fname, const char *tag,
481 struct stat *statp, bool dot_cmd)
487 char en1[30], en2[30];
491 p = encode_mode(statp->st_mode, buf);
494 n = sprintf(p, "%d,", (uint32_t)statp->st_nlink);
496 n = sprintf(p, "%s,%s,", getuser(statp->st_uid, en1, sizeof(en1)),
497 getgroup(statp->st_gid, en2, sizeof(en2)));
499 n = sprintf(p, "%s,", edit_uint64(statp->st_size, ec1));
501 p = encode_time(statp->st_mtime, p);
506 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
508 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid, en1, sizeof(en1)),
509 getgroup(statp->st_gid, en2, sizeof(en2)));
511 n = sprintf(p, "%10.10s ", edit_uint64(statp->st_size, ec1));
513 if (statp->st_ctime > statp->st_mtime) {
514 time = statp->st_ctime;
516 time = statp->st_mtime;
518 /* Display most recent time */
519 p = encode_time(time, p);
523 for (f=fname; *f; ) {
530 * Like ls command, but give more detail on each file
532 static int do_dircmd(UAContext *ua, TREE_CTX *tree, bool dot_cmd)
538 char cwd[1100], *pcwd;
540 if (!tree_node_has_child(tree->node)) {
541 bsendmsg(ua, _("Node %s has no children.\n"), tree->node->fname);
545 foreach_child(node, tree->node) {
547 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
550 } else if (node->extract_dir) {
555 tree_getpath(node, cwd, sizeof(cwd));
557 fdbr.JobId = node->JobId;
559 * Strip / from soft links to directories.
560 * This is because soft links to files have a trailing slash
561 * when returned from tree_getpath, but db_get_file_attr...
562 * treats soft links as files, so they do not have a trailing
563 * slash like directory names.
565 if (node->type == TN_FILE && tree_node_has_child(node)) {
566 bstrncpy(buf, cwd, sizeof(buf));
568 int len = strlen(buf);
570 buf[len-1] = 0; /* strip trailing / */
575 if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
577 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
579 /* Something went wrong getting attributes -- print name */
580 memset(&statp, 0, sizeof(statp));
582 ls_output(buf, cwd, tag, &statp, dot_cmd);
583 bsendmsg(ua, "%s\n", buf);
589 int dot_dircmd(UAContext *ua, TREE_CTX *tree)
591 return do_dircmd(ua, tree, true/*dot command*/);
594 static int dircmd(UAContext *ua, TREE_CTX *tree)
596 return do_dircmd(ua, tree, false/*not dot command*/);
600 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
602 int total, num_extract;
603 uint64_t total_bytes = 0;
609 total = num_extract = 0;
610 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
611 if (node->type != TN_NEWDIR) {
613 /* If regular file, get size */
614 if (node->extract && node->type == TN_FILE) {
616 tree_getpath(node, cwd, sizeof(cwd));
618 fdbr.JobId = node->JobId;
619 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
621 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
622 if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
623 total_bytes += statp.st_size;
626 /* Directory, count only */
627 } else if (node->extract || node->extract_dir) {
632 bsendmsg(ua, _("%d total files; %d marked to be restored; %s bytes.\n"),
633 total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
639 static int helpcmd(UAContext *ua, TREE_CTX *tree)
643 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
644 for (i=0; i<comsize; i++) {
645 /* List only non-dot commands */
646 if (commands[i].key[0] != '.') {
647 bsendmsg(ua, " %-10s %s\n", _(commands[i].key), _(commands[i].help));
655 * Change directories. Note, if the user specifies x: and it fails,
656 * we assume it is a Win32 absolute cd rather than relative and
657 * try a second time with /x: ... Win32 kludge.
659 static int cdcmd(UAContext *ua, TREE_CTX *tree)
666 bsendmsg(ua, _("Too many arguments. Try using double quotes.\n"));
669 node = tree_cwd(ua->argk[1], tree->root, tree->node);
671 /* Try once more if Win32 drive -- make absolute */
672 if (ua->argk[1][1] == ':') { /* win32 drive */
673 bstrncpy(cwd, "/", sizeof(cwd));
674 bstrncat(cwd, ua->argk[1], sizeof(cwd));
675 node = tree_cwd(cwd, tree->root, tree->node);
678 bsendmsg(ua, _("Invalid path given.\n"));
685 tree_getpath(tree->node, cwd, sizeof(cwd));
686 bsendmsg(ua, _("cwd is: %s\n"), cwd);
690 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
693 tree_getpath(tree->node, cwd, sizeof(cwd));
694 bsendmsg(ua, _("cwd is: %s\n"), cwd);
699 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
704 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
705 bsendmsg(ua, _("No files unmarked.\n"));
708 for (int i=1; i < ua->argc; i++) {
709 foreach_child(node, tree->node) {
710 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
711 count += set_extract(ua, node, tree, false);
716 bsendmsg(ua, _("No files unmarked.\n"));
717 } else if (count == 1) {
718 bsendmsg(ua, _("1 file unmarked.\n"));
721 bsendmsg(ua, _("%s files unmarked.\n"), edit_uint64_with_commas(count, ed1));
726 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
731 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
732 bsendmsg(ua, _("No directories unmarked.\n"));
736 for (int i=1; i < ua->argc; i++) {
737 foreach_child(node, tree->node) {
738 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
739 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
740 node->extract_dir = false;
748 bsendmsg(ua, _("No directories unmarked.\n"));
749 } else if (count == 1) {
750 bsendmsg(ua, _("1 directory unmarked.\n"));
752 bsendmsg(ua, _("%d directories unmarked.\n"), count);
758 static int donecmd(UAContext *ua, TREE_CTX *tree)
763 static int quitcmd(UAContext *ua, TREE_CTX *tree)