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 Copyright (C) 2002-2006 Kern Sibbald
14 This program is free software; you can redistribute it and/or
15 modify it under the terms of the GNU General Public License
16 version 2 as amended with additional clauses defined in the
17 file LICENSE in the main source directory.
19 This program is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 the file LICENSE for additional details.
31 #include "lib/fnmatch.h"
33 #include "findlib/find.h"
36 /* Forward referenced commands */
38 static int markcmd(UAContext *ua, TREE_CTX *tree);
39 static int markdircmd(UAContext *ua, TREE_CTX *tree);
40 static int countcmd(UAContext *ua, TREE_CTX *tree);
41 static int findcmd(UAContext *ua, TREE_CTX *tree);
42 static int lscmd(UAContext *ua, TREE_CTX *tree);
43 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree);
44 static int dircmd(UAContext *ua, TREE_CTX *tree);
45 static int dot_dircmd(UAContext *ua, TREE_CTX *tree);
46 static int estimatecmd(UAContext *ua, TREE_CTX *tree);
47 static int helpcmd(UAContext *ua, TREE_CTX *tree);
48 static int cdcmd(UAContext *ua, TREE_CTX *tree);
49 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
50 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
51 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree);
52 static int quitcmd(UAContext *ua, TREE_CTX *tree);
53 static int donecmd(UAContext *ua, TREE_CTX *tree);
56 struct cmdstruct { const char *key; int (*func)(UAContext *ua, TREE_CTX *tree); const char *help; };
57 static struct cmdstruct commands[] = {
58 { NT_("cd"), cdcmd, _("change current directory")},
59 { NT_("count"), countcmd, _("count marked files in and below the cd")},
60 { NT_("dir"), dircmd, _("long list current directory, wildcards allowed")},
61 { NT_(".dir"), dot_dircmd, _("long list current directory, wildcards allowed")},
62 { NT_("done"), donecmd, _("leave file selection mode")},
63 { NT_("estimate"), estimatecmd, _("estimate restore size")},
64 { NT_("exit"), donecmd, _("same as done command")},
65 { NT_("find"), findcmd, _("find files, wildcards allowed")},
66 { NT_("help"), helpcmd, _("print help")},
67 { NT_("ls"), lscmd, _("list current directory, wildcards allowed")},
68 { NT_("lsmark"), lsmarkcmd, _("list the marked files in and below the cd")},
69 { NT_("mark"), markcmd, _("mark dir/file to be restored recursively, wildcards allowed")},
70 { NT_("markdir"), markdircmd, _("mark directory name to be restored (no files)")},
71 { NT_("pwd"), pwdcmd, _("print current working directory")},
72 { NT_("unmark"), unmarkcmd, _("unmark dir/file to be restored recursively in dir")},
73 { NT_("unmarkdir"), unmarkdircmd, _("unmark directory name only no recursion")},
74 { NT_("quit"), quitcmd, _("quit and do not do restore")},
75 { NT_("?"), helpcmd, _("print help")},
77 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
81 * Enter a prompt mode where the user can select/deselect
82 * files to be restored. This is sort of like a mini-shell
83 * that allows "cd", "pwd", "add", "rm", ...
85 bool user_select_files_from_tree(TREE_CTX *tree)
89 /* Get a new context so we don't destroy restore command args */
90 UAContext *ua = new_ua_context(tree->ua->jcr);
91 ua->UA_sock = tree->ua->UA_sock; /* patch in UA socket */
94 "\nYou are now entering file selection mode where you add (mark) and\n"
95 "remove (unmark) files to be restored. No files are initially added, unless\n"
96 "you used the \"all\" keyword on the command line.\n"
97 "Enter \"done\" to leave this mode.\n\n"));
99 * Enter interactive command handler allowing selection
100 * of individual files.
102 tree->node = (TREE_NODE *)tree->root;
103 tree_getpath(tree->node, cwd, sizeof(cwd));
104 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
107 if (!get_cmd(ua, "$ ")) {
112 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
116 len = strlen(ua->argk[0]);
119 for (i=0; i<(int)comsize; i++) /* search for command */
120 if (strncasecmp(ua->argk[0], _(commands[i].key), len) == 0) {
121 stat = (*commands[i].func)(ua, tree); /* go execute command */
126 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
133 ua->UA_sock = NULL; /* don't release restore socket */
136 free_ua_context(ua); /* get rid of temp UA context */
142 * This callback routine is responsible for inserting the
143 * items it gets into the directory tree. For each JobId selected
144 * this routine is called once for each file. We do not allow
145 * duplicate filenames, but instead keep the info from the most
146 * recent file entered (i.e. the JobIds are assumed to be sorted)
148 * See uar_sel_files in sql_cmds.c for query that calls us.
149 * row[0]=Path, row[1]=Filename, row[2]=FileIndex
150 * row[3]=JobId row[4]=LStat
152 int insert_tree_handler(void *ctx, int num_fields, char **row)
155 TREE_CTX *tree = (TREE_CTX *)ctx;
162 // Dmsg4(000, "Path=%s%s FI=%s JobId=%s\n", row[0], row[1],
164 if (*row[1] == 0) { /* no filename => directory */
165 if (*row[0] != '/') { /* Must be Win32 directory */
173 hard_link = (decode_LinkFI(row[4], &statp) != 0);
174 node = insert_tree_node(row[0], row[1], type, tree->root, NULL);
175 JobId = str_to_int64(row[3]);
176 FileIndex = str_to_int64(row[2]);
178 * - The first time we see a file (node->inserted==true), we accept it.
179 * - In the same JobId, we accept only the first copy of a
180 * hard linked file (the others are simply pointers).
181 * - In the same JobId, we accept the last copy of any other
182 * file -- in particular directories.
184 * All the code to set ok could be condensed to a single
185 * line, but it would be even harder to read.
188 if (!node->inserted && JobId == node->JobId) {
189 if ((hard_link && FileIndex > node->FileIndex) ||
190 (!hard_link && FileIndex < node->FileIndex)) {
195 node->hard_link = hard_link;
196 node->FileIndex = FileIndex;
199 node->soft_link = S_ISLNK(statp.st_mode) != 0;
201 node->extract = true; /* extract all by default */
202 if (type == TN_DIR || type == TN_DIR_NLS) {
203 node->extract_dir = true; /* if dir, extract it */
207 if (node->inserted) {
209 if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) {
210 bsendmsg(tree->ua, "+");
211 tree->LastCount = tree->FileCount;
220 * Set extract to value passed. We recursively walk
221 * down the tree setting all children if the
222 * node is a directory.
224 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
231 node->extract = extract;
232 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
233 node->extract_dir = extract; /* set/clear dir too */
235 if (node->type != TN_NEWDIR) {
238 /* For a non-file (i.e. directory), we see all the children */
239 if (node->type != TN_FILE || (node->soft_link && tree_node_has_child(node))) {
240 /* Recursive set children within directory */
241 foreach_child(n, node) {
242 count += set_extract(ua, n, tree, extract);
245 * Walk up tree marking any unextracted parent to be
249 while (node->parent && !node->parent->extract_dir) {
251 node->extract_dir = true;
254 } else if (extract) {
257 * Ordinary file, we get the full path, look up the
258 * attributes, decode them, and if we are hard linked to
259 * a file that was saved, we must load that file too.
261 tree_getpath(node, cwd, sizeof(cwd));
263 fdbr.JobId = node->JobId;
264 if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
266 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
268 * If we point to a hard linked file, traverse the tree to
269 * find that file, and mark it to be restored as well. It
270 * must have the Link we just obtained and the same JobId.
273 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
274 if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
276 if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
277 n->extract_dir = true;
289 * Recursively mark the current directory to be restored as
290 * well as all directories and files below it.
292 static int markcmd(UAContext *ua, TREE_CTX *tree)
298 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
299 bsendmsg(ua, _("No files marked.\n"));
302 for (int i=1; i < ua->argc; i++) {
303 foreach_child(node, tree->node) {
304 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
305 count += set_extract(ua, node, tree, true);
310 bsendmsg(ua, _("No files marked.\n"));
311 } else if (count == 1) {
312 bsendmsg(ua, _("1 file marked.\n"));
314 bsendmsg(ua, _("%s files marked.\n"),
315 edit_uint64_with_commas(count, ec1));
320 static int markdircmd(UAContext *ua, TREE_CTX *tree)
326 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
327 bsendmsg(ua, _("No files marked.\n"));
330 for (int i=1; i < ua->argc; i++) {
331 foreach_child(node, tree->node) {
332 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
333 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
334 node->extract_dir = true;
341 bsendmsg(ua, _("No directories marked.\n"));
342 } else if (count == 1) {
343 bsendmsg(ua, _("1 directory marked.\n"));
345 bsendmsg(ua, _("%s directories marked.\n"),
346 edit_uint64_with_commas(count, ec1));
352 static int countcmd(UAContext *ua, TREE_CTX *tree)
354 int total, num_extract;
355 char ec1[50], ec2[50];
357 total = num_extract = 0;
358 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
359 if (node->type != TN_NEWDIR) {
361 if (node->extract || node->extract_dir) {
366 bsendmsg(ua, _("%s total files/dirs. %s marked to be restored.\n"),
367 edit_uint64_with_commas(total, ec1),
368 edit_uint64_with_commas(num_extract, ec2));
372 static int findcmd(UAContext *ua, TREE_CTX *tree)
377 bsendmsg(ua, _("No file specification given.\n"));
381 for (int i=1; i < ua->argc; i++) {
382 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
383 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
385 tree_getpath(node, cwd, sizeof(cwd));
388 } else if (node->extract_dir) {
393 bsendmsg(ua, "%s%s\n", tag, cwd);
402 static int lscmd(UAContext *ua, TREE_CTX *tree)
406 if (!tree_node_has_child(tree->node)) {
409 foreach_child(node, tree->node) {
410 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
414 } else if (node->extract_dir) {
419 bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
426 * Ls command that lists only the marked files
428 static void rlsmark(UAContext *ua, TREE_NODE *tnode)
431 if (!tree_node_has_child(tnode)) {
434 foreach_child(node, tnode) {
435 if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
436 (node->extract || node->extract_dir)) {
440 } else if (node->extract_dir) {
445 bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
446 if (tree_node_has_child(node)) {
453 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
455 rlsmark(ua, tree->node);
461 extern char *getuser(uid_t uid, char *name, int len);
462 extern char *getgroup(gid_t gid, char *name, int len);
465 * This is actually the long form used for "dir"
467 static void ls_output(char *buf, const char *fname, const char *tag,
468 struct stat *statp, bool dot_cmd)
474 char en1[30], en2[30];
478 p = encode_mode(statp->st_mode, buf);
481 n = sprintf(p, "%d,", (uint32_t)statp->st_nlink);
483 n = sprintf(p, "%s,%s,", getuser(statp->st_uid, en1, sizeof(en1)),
484 getgroup(statp->st_gid, en2, sizeof(en2)));
486 n = sprintf(p, "%s,", edit_uint64(statp->st_size, ec1));
488 p = encode_time(statp->st_mtime, p);
493 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
495 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid, en1, sizeof(en1)),
496 getgroup(statp->st_gid, en2, sizeof(en2)));
498 n = sprintf(p, "%10.10s ", edit_uint64(statp->st_size, ec1));
500 if (statp->st_ctime > statp->st_mtime) {
501 time = statp->st_ctime;
503 time = statp->st_mtime;
505 /* Display most recent time */
506 p = encode_time(time, p);
510 for (f=fname; *f; ) {
517 * Like ls command, but give more detail on each file
519 static int do_dircmd(UAContext *ua, TREE_CTX *tree, bool dot_cmd)
525 char cwd[1100], *pcwd;
527 if (!tree_node_has_child(tree->node)) {
528 bsendmsg(ua, _("Node %s has no children.\n"), tree->node->fname);
532 foreach_child(node, tree->node) {
534 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
537 } else if (node->extract_dir) {
542 tree_getpath(node, cwd, sizeof(cwd));
544 fdbr.JobId = node->JobId;
546 * Strip / from soft links to directories.
547 * This is because soft links to files have a trailing slash
548 * when returned from tree_getpath, but db_get_file_attr...
549 * treats soft links as files, so they do not have a trailing
550 * slash like directory names.
552 if (node->type == TN_FILE && tree_node_has_child(node)) {
553 bstrncpy(buf, cwd, sizeof(buf));
555 int len = strlen(buf);
557 buf[len-1] = 0; /* strip trailing / */
562 if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
564 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
566 /* Something went wrong getting attributes -- print name */
567 memset(&statp, 0, sizeof(statp));
569 ls_output(buf, cwd, tag, &statp, dot_cmd);
570 bsendmsg(ua, "%s\n", buf);
576 int dot_dircmd(UAContext *ua, TREE_CTX *tree)
578 return do_dircmd(ua, tree, true/*dot command*/);
581 static int dircmd(UAContext *ua, TREE_CTX *tree)
583 return do_dircmd(ua, tree, false/*not dot command*/);
587 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
589 int total, num_extract;
590 uint64_t total_bytes = 0;
596 total = num_extract = 0;
597 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
598 if (node->type != TN_NEWDIR) {
600 /* If regular file, get size */
601 if (node->extract && node->type == TN_FILE) {
603 tree_getpath(node, cwd, sizeof(cwd));
605 fdbr.JobId = node->JobId;
606 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
608 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
609 if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
610 total_bytes += statp.st_size;
613 /* Directory, count only */
614 } else if (node->extract || node->extract_dir) {
619 bsendmsg(ua, _("%d total files; %d marked to be restored; %s bytes.\n"),
620 total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
626 static int helpcmd(UAContext *ua, TREE_CTX *tree)
630 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
631 for (i=0; i<comsize; i++) {
632 /* List only non-dot commands */
633 if (commands[i].key[0] != '.') {
634 bsendmsg(ua, " %-10s %s\n", _(commands[i].key), _(commands[i].help));
642 * Change directories. Note, if the user specifies x: and it fails,
643 * we assume it is a Win32 absolute cd rather than relative and
644 * try a second time with /x: ... Win32 kludge.
646 static int cdcmd(UAContext *ua, TREE_CTX *tree)
654 strip_leading_space(ua->argk[1]);
655 node = tree_cwd(ua->argk[1], tree->root, tree->node);
657 /* Try once more if Win32 drive -- make absolute */
658 if (ua->argk[1][1] == ':') { /* win32 drive */
659 bstrncpy(cwd, "/", sizeof(cwd));
660 bstrncat(cwd, ua->argk[1], sizeof(cwd));
661 node = tree_cwd(cwd, tree->root, tree->node);
664 bsendmsg(ua, _("Invalid path given.\n"));
671 tree_getpath(tree->node, cwd, sizeof(cwd));
672 bsendmsg(ua, _("cwd is: %s\n"), cwd);
676 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
679 tree_getpath(tree->node, cwd, sizeof(cwd));
680 bsendmsg(ua, _("cwd is: %s\n"), cwd);
685 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
690 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
691 bsendmsg(ua, _("No files unmarked.\n"));
694 for (int i=1; i < ua->argc; i++) {
695 foreach_child(node, tree->node) {
696 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
697 count += set_extract(ua, node, tree, false);
702 bsendmsg(ua, _("No files unmarked.\n"));
703 } else if (count == 1) {
704 bsendmsg(ua, _("1 file unmarked.\n"));
706 bsendmsg(ua, _("%d files unmarked.\n"), count);
711 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
716 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
717 bsendmsg(ua, _("No directories unmarked.\n"));
721 for (int i=1; i < ua->argc; i++) {
722 foreach_child(node, tree->node) {
723 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
724 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
725 node->extract_dir = false;
733 bsendmsg(ua, _("No directories unmarked.\n"));
734 } else if (count == 1) {
735 bsendmsg(ua, _("1 directory unmarked.\n"));
737 bsendmsg(ua, _("%d directories unmarked.\n"), count);
743 static int donecmd(UAContext *ua, TREE_CTX *tree)
748 static int quitcmd(UAContext *ua, TREE_CTX *tree)