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 plus additions
11 that are listed in the file LICENSE.
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 */
107 bsendmsg(tree->ua, _(
108 "\nYou are now entering file selection mode where you add (mark) and\n"
109 "remove (unmark) files to be restored. No files are initially added, unless\n"
110 "you used the \"all\" keyword on the command line.\n"
111 "Enter \"done\" to leave this mode.\n\n"));
113 * Enter interactive command handler allowing selection
114 * of individual files.
116 tree->node = (TREE_NODE *)tree->root;
117 tree_getpath(tree->node, cwd, sizeof(cwd));
118 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
121 if (!get_cmd(ua, "$ ")) {
124 parse_args_only(ua->cmd, &ua->args, &ua->argc, ua->argk, ua->argv, MAX_CMD_ARGS);
126 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
130 len = strlen(ua->argk[0]);
133 for (i=0; i<comsize; i++) /* search for command */
134 if (strncasecmp(ua->argk[0], _(commands[i].key), len) == 0) {
135 stat = (*commands[i].func)(ua, tree); /* go execute command */
140 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
147 ua->UA_sock = NULL; /* don't release restore socket */
150 free_ua_context(ua); /* get rid of temp UA context */
156 * This callback routine is responsible for inserting the
157 * items it gets into the directory tree. For each JobId selected
158 * this routine is called once for each file. We do not allow
159 * duplicate filenames, but instead keep the info from the most
160 * recent file entered (i.e. the JobIds are assumed to be sorted)
162 * See uar_sel_files in sql_cmds.c for query that calls us.
163 * row[0]=Path, row[1]=Filename, row[2]=FileIndex
164 * row[3]=JobId row[4]=LStat
166 int insert_tree_handler(void *ctx, int num_fields, char **row)
169 TREE_CTX *tree = (TREE_CTX *)ctx;
176 // Dmsg4(000, "Path=%s%s FI=%s JobId=%s\n", row[0], row[1],
178 if (*row[1] == 0) { /* no filename => directory */
179 if (!IsPathSeparator(*row[0])) { /* Must be Win32 directory */
187 hard_link = (decode_LinkFI(row[4], &statp) != 0);
188 node = insert_tree_node(row[0], row[1], type, tree->root, NULL);
189 JobId = str_to_int64(row[3]);
190 FileIndex = str_to_int64(row[2]);
192 * - The first time we see a file (node->inserted==true), we accept it.
193 * - In the same JobId, we accept only the first copy of a
194 * hard linked file (the others are simply pointers).
195 * - In the same JobId, we accept the last copy of any other
196 * file -- in particular directories.
198 * All the code to set ok could be condensed to a single
199 * line, but it would be even harder to read.
202 if (!node->inserted && JobId == node->JobId) {
203 if ((hard_link && FileIndex > node->FileIndex) ||
204 (!hard_link && FileIndex < node->FileIndex)) {
209 node->hard_link = hard_link;
210 node->FileIndex = FileIndex;
213 node->soft_link = S_ISLNK(statp.st_mode) != 0;
215 node->extract = true; /* extract all by default */
216 if (type == TN_DIR || type == TN_DIR_NLS) {
217 node->extract_dir = true; /* if dir, extract it */
221 if (node->inserted) {
223 if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) {
224 bsendmsg(tree->ua, "+");
225 tree->LastCount = tree->FileCount;
234 * Set extract to value passed. We recursively walk
235 * down the tree setting all children if the
236 * node is a directory.
238 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
245 node->extract = extract;
246 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
247 node->extract_dir = extract; /* set/clear dir too */
249 if (node->type != TN_NEWDIR) {
252 /* For a non-file (i.e. directory), we see all the children */
253 if (node->type != TN_FILE || (node->soft_link && tree_node_has_child(node))) {
254 /* Recursive set children within directory */
255 foreach_child(n, node) {
256 count += set_extract(ua, n, tree, extract);
259 * Walk up tree marking any unextracted parent to be
263 while (node->parent && !node->parent->extract_dir) {
265 node->extract_dir = true;
268 } else if (extract) {
271 * Ordinary file, we get the full path, look up the
272 * attributes, decode them, and if we are hard linked to
273 * a file that was saved, we must load that file too.
275 tree_getpath(node, cwd, sizeof(cwd));
277 fdbr.JobId = node->JobId;
278 if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
280 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
282 * If we point to a hard linked file, traverse the tree to
283 * find that file, and mark it to be restored as well. It
284 * must have the Link we just obtained and the same JobId.
287 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
288 if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
290 if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
291 n->extract_dir = true;
302 static void strip_trailing_slash(char *arg)
304 int len = strlen(arg);
309 if (arg[len] == '/') { /* strip any trailing slash */
315 * Recursively mark the current directory to be restored as
316 * well as all directories and files below it.
318 static int markcmd(UAContext *ua, TREE_CTX *tree)
324 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
325 bsendmsg(ua, _("No files marked.\n"));
328 for (int i=1; i < ua->argc; i++) {
329 strip_trailing_slash(ua->argk[i]);
330 foreach_child(node, tree->node) {
331 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
332 count += set_extract(ua, node, tree, true);
337 bsendmsg(ua, _("No files marked.\n"));
338 } else if (count == 1) {
339 bsendmsg(ua, _("1 file marked.\n"));
341 bsendmsg(ua, _("%s files marked.\n"),
342 edit_uint64_with_commas(count, ec1));
347 static int markdircmd(UAContext *ua, TREE_CTX *tree)
353 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
354 bsendmsg(ua, _("No files marked.\n"));
357 for (int i=1; i < ua->argc; i++) {
358 strip_trailing_slash(ua->argk[i]);
359 foreach_child(node, tree->node) {
360 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
361 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
362 node->extract_dir = true;
369 bsendmsg(ua, _("No directories marked.\n"));
370 } else if (count == 1) {
371 bsendmsg(ua, _("1 directory marked.\n"));
373 bsendmsg(ua, _("%s directories marked.\n"),
374 edit_uint64_with_commas(count, ec1));
380 static int countcmd(UAContext *ua, TREE_CTX *tree)
382 int total, num_extract;
383 char ec1[50], ec2[50];
385 total = num_extract = 0;
386 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
387 if (node->type != TN_NEWDIR) {
389 if (node->extract || node->extract_dir) {
394 bsendmsg(ua, _("%s total files/dirs. %s marked to be restored.\n"),
395 edit_uint64_with_commas(total, ec1),
396 edit_uint64_with_commas(num_extract, ec2));
400 static int findcmd(UAContext *ua, TREE_CTX *tree)
405 bsendmsg(ua, _("No file specification given.\n"));
406 return 1; /* make it non-fatal */
409 for (int i=1; i < ua->argc; i++) {
410 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
411 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
413 tree_getpath(node, cwd, sizeof(cwd));
416 } else if (node->extract_dir) {
421 bsendmsg(ua, "%s%s\n", tag, cwd);
430 static int lscmd(UAContext *ua, TREE_CTX *tree)
434 if (!tree_node_has_child(tree->node)) {
437 foreach_child(node, tree->node) {
438 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
442 } else if (node->extract_dir) {
447 bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
454 * Ls command that lists only the marked files
456 static void rlsmark(UAContext *ua, TREE_NODE *tnode)
459 if (!tree_node_has_child(tnode)) {
462 foreach_child(node, tnode) {
463 if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
464 (node->extract || node->extract_dir)) {
468 } else if (node->extract_dir) {
473 bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
474 if (tree_node_has_child(node)) {
481 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
483 rlsmark(ua, tree->node);
489 extern char *getuser(uid_t uid, char *name, int len);
490 extern char *getgroup(gid_t gid, char *name, int len);
493 * This is actually the long form used for "dir"
495 static void ls_output(char *buf, const char *fname, const char *tag,
496 struct stat *statp, bool dot_cmd)
502 char en1[30], en2[30];
506 p = encode_mode(statp->st_mode, buf);
509 n = sprintf(p, "%d,", (uint32_t)statp->st_nlink);
511 n = sprintf(p, "%s,%s,", getuser(statp->st_uid, en1, sizeof(en1)),
512 getgroup(statp->st_gid, en2, sizeof(en2)));
514 n = sprintf(p, "%s,", edit_uint64(statp->st_size, ec1));
516 p = encode_time(statp->st_mtime, p);
521 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
523 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid, en1, sizeof(en1)),
524 getgroup(statp->st_gid, en2, sizeof(en2)));
526 n = sprintf(p, "%10.10s ", edit_uint64(statp->st_size, ec1));
528 if (statp->st_ctime > statp->st_mtime) {
529 time = statp->st_ctime;
531 time = statp->st_mtime;
533 /* Display most recent time */
534 p = encode_time(time, p);
538 for (f=fname; *f; ) {
545 * Like ls command, but give more detail on each file
547 static int do_dircmd(UAContext *ua, TREE_CTX *tree, bool dot_cmd)
553 char cwd[1100], *pcwd;
555 if (!tree_node_has_child(tree->node)) {
556 bsendmsg(ua, _("Node %s has no children.\n"), tree->node->fname);
560 foreach_child(node, tree->node) {
562 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
565 } else if (node->extract_dir) {
570 tree_getpath(node, cwd, sizeof(cwd));
572 fdbr.JobId = node->JobId;
574 * Strip / from soft links to directories.
575 * This is because soft links to files have a trailing slash
576 * when returned from tree_getpath, but db_get_file_attr...
577 * treats soft links as files, so they do not have a trailing
578 * slash like directory names.
580 if (node->type == TN_FILE && tree_node_has_child(node)) {
581 bstrncpy(buf, cwd, sizeof(buf));
583 int len = strlen(buf);
585 buf[len-1] = 0; /* strip trailing / */
590 if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
592 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
594 /* Something went wrong getting attributes -- print name */
595 memset(&statp, 0, sizeof(statp));
597 ls_output(buf, cwd, tag, &statp, dot_cmd);
598 bsendmsg(ua, "%s\n", buf);
604 int dot_dircmd(UAContext *ua, TREE_CTX *tree)
606 return do_dircmd(ua, tree, true/*dot command*/);
609 static int dircmd(UAContext *ua, TREE_CTX *tree)
611 return do_dircmd(ua, tree, false/*not dot command*/);
615 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
617 int total, num_extract;
618 uint64_t total_bytes = 0;
624 total = num_extract = 0;
625 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
626 if (node->type != TN_NEWDIR) {
628 /* If regular file, get size */
629 if (node->extract && node->type == TN_FILE) {
631 tree_getpath(node, cwd, sizeof(cwd));
633 fdbr.JobId = node->JobId;
634 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
636 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
637 if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
638 total_bytes += statp.st_size;
641 /* Directory, count only */
642 } else if (node->extract || node->extract_dir) {
647 bsendmsg(ua, _("%d total files; %d marked to be restored; %s bytes.\n"),
648 total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
654 static int helpcmd(UAContext *ua, TREE_CTX *tree)
658 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
659 for (i=0; i<comsize; i++) {
660 /* List only non-dot commands */
661 if (commands[i].key[0] != '.') {
662 bsendmsg(ua, " %-10s %s\n", _(commands[i].key), _(commands[i].help));
670 * Change directories. Note, if the user specifies x: and it fails,
671 * we assume it is a Win32 absolute cd rather than relative and
672 * try a second time with /x: ... Win32 kludge.
674 static int cdcmd(UAContext *ua, TREE_CTX *tree)
681 bsendmsg(ua, _("Too many arguments. Try using double quotes.\n"));
684 node = tree_cwd(ua->argk[1], tree->root, tree->node);
686 /* Try once more if Win32 drive -- make absolute */
687 if (ua->argk[1][1] == ':') { /* win32 drive */
688 bstrncpy(cwd, "/", sizeof(cwd));
689 bstrncat(cwd, ua->argk[1], sizeof(cwd));
690 node = tree_cwd(cwd, tree->root, tree->node);
693 bsendmsg(ua, _("Invalid path given.\n"));
700 tree_getpath(tree->node, cwd, sizeof(cwd));
701 bsendmsg(ua, _("cwd is: %s\n"), cwd);
705 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
708 tree_getpath(tree->node, cwd, sizeof(cwd));
709 bsendmsg(ua, _("cwd is: %s\n"), cwd);
713 static int dot_pwdcmd(UAContext *ua, TREE_CTX *tree)
716 tree_getpath(tree->node, cwd, sizeof(cwd));
717 bsendmsg(ua, _("%s"), cwd);
721 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
726 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
727 bsendmsg(ua, _("No files unmarked.\n"));
730 for (int i=1; i < ua->argc; i++) {
731 strip_trailing_slash(ua->argk[i]);
732 foreach_child(node, tree->node) {
733 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
734 count += set_extract(ua, node, tree, false);
739 bsendmsg(ua, _("No files unmarked.\n"));
740 } else if (count == 1) {
741 bsendmsg(ua, _("1 file unmarked.\n"));
744 bsendmsg(ua, _("%s files unmarked.\n"), edit_uint64_with_commas(count, ed1));
749 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
754 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
755 bsendmsg(ua, _("No directories unmarked.\n"));
759 for (int i=1; i < ua->argc; i++) {
760 strip_trailing_slash(ua->argk[i]);
761 foreach_child(node, tree->node) {
762 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
763 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
764 node->extract_dir = false;
772 bsendmsg(ua, _("No directories unmarked.\n"));
773 } else if (count == 1) {
774 bsendmsg(ua, _("1 directory unmarked.\n"));
776 bsendmsg(ua, _("%d directories unmarked.\n"), count);
782 static int donecmd(UAContext *ua, TREE_CTX *tree)
787 static int quitcmd(UAContext *ua, TREE_CTX *tree)