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-2005 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 estimatecmd(UAContext *ua, TREE_CTX *tree);
46 static int helpcmd(UAContext *ua, TREE_CTX *tree);
47 static int cdcmd(UAContext *ua, TREE_CTX *tree);
48 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
49 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
50 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree);
51 static int quitcmd(UAContext *ua, TREE_CTX *tree);
52 static int donecmd(UAContext *ua, TREE_CTX *tree);
55 struct cmdstruct { const char *key; int (*func)(UAContext *ua, TREE_CTX *tree); const char *help; };
56 static struct cmdstruct commands[] = {
57 { N_("cd"), cdcmd, _("change current directory")},
58 { N_("count"), countcmd, _("count marked files in and below the cd")},
59 { N_("dir"), dircmd, _("long list current directory, wildcards allowed")},
60 { N_("done"), donecmd, _("leave file selection mode")},
61 { N_("estimate"), estimatecmd, _("estimate restore size")},
62 { N_("exit"), donecmd, _("same as done command")},
63 { N_("find"), findcmd, _("find files, wildcards allowed")},
64 { N_("help"), helpcmd, _("print help")},
65 { N_("ls"), lscmd, _("list current directory, wildcards allowed")},
66 { N_("lsmark"), lsmarkcmd, _("list the marked files in and below the cd")},
67 { N_("mark"), markcmd, _("mark dir/file to be restored recursively in dirs")},
68 { N_("markdir"), markdircmd, _("mark directory name to be restored (no files)")},
69 { N_("pwd"), pwdcmd, _("print current working directory")},
70 { N_("unmark"), unmarkcmd, _("unmark dir/file to be restored recursively in dir")},
71 { N_("unmarkdir"), unmarkdircmd, _("unmark directory name only no recursion")},
72 { N_("quit"), quitcmd, _("quit and do not do restore")},
73 { N_("?"), helpcmd, _("print help")},
75 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
79 * Enter a prompt mode where the user can select/deselect
80 * files to be restored. This is sort of like a mini-shell
81 * that allows "cd", "pwd", "add", "rm", ...
83 bool user_select_files_from_tree(TREE_CTX *tree)
87 /* Get a new context so we don't destroy restore command args */
88 UAContext *ua = new_ua_context(tree->ua->jcr);
89 ua->UA_sock = tree->ua->UA_sock; /* patch in UA socket */
92 "\nYou are now entering file selection mode where you add (mark) and\n"
93 "remove (unmark) files to be restored. No files are initially added, unless\n"
94 "you used the \"all\" keyword on the command line.\n"
95 "Enter \"done\" to leave this mode.\n\n"));
97 * Enter interactive command handler allowing selection
98 * of individual files.
100 tree->node = (TREE_NODE *)tree->root;
101 tree_getpath(tree->node, cwd, sizeof(cwd));
102 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
105 if (!get_cmd(ua, "$ ")) {
113 len = strlen(ua->argk[0]);
116 for (i=0; i<(int)comsize; i++) /* search for command */
117 if (strncasecmp(ua->argk[0], _(commands[i].key), len) == 0) {
118 stat = (*commands[i].func)(ua, tree); /* go execute command */
123 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
130 ua->UA_sock = NULL; /* don't release restore socket */
133 free_ua_context(ua); /* get rid of temp UA context */
139 * This callback routine is responsible for inserting the
140 * items it gets into the directory tree. For each JobId selected
141 * this routine is called once for each file. We do not allow
142 * duplicate filenames, but instead keep the info from the most
143 * recent file entered (i.e. the JobIds are assumed to be sorted)
145 * See uar_sel_files in sql_cmds.c for query that calls us.
146 * row[0]=Path, row[1]=Filename, row[2]=FileIndex
147 * row[3]=JobId row[4]=LStat
149 int insert_tree_handler(void *ctx, int num_fields, char **row)
152 TREE_CTX *tree = (TREE_CTX *)ctx;
159 // Dmsg4(000, "Path=%s%s FI=%s JobId=%s\n", row[0], row[1],
161 if (*row[1] == 0) { /* no filename => directory */
162 if (*row[0] != '/') { /* Must be Win32 directory */
170 hard_link = (decode_LinkFI(row[4], &statp) != 0);
171 node = insert_tree_node(row[0], row[1], type, tree->root, NULL);
172 JobId = str_to_int64(row[3]);
173 FileIndex = str_to_int64(row[2]);
175 * - The first time we see a file (node->inserted==true), we accept it.
176 * - In the same JobId, we accept only the first copy of a
177 * hard linked file (the others are simply pointers).
178 * - In the same JobId, we accept the last copy of any other
179 * file -- in particular directories.
181 * All the code to set ok could be condensed to a single
182 * line, but it would be even harder to read.
185 if (!node->inserted && JobId == node->JobId) {
186 if ((hard_link && FileIndex > node->FileIndex) ||
187 (!hard_link && FileIndex < node->FileIndex)) {
192 node->hard_link = hard_link;
193 node->FileIndex = FileIndex;
196 node->soft_link = S_ISLNK(statp.st_mode) != 0;
198 node->extract = true; /* extract all by default */
199 if (type == TN_DIR || type == TN_DIR_NLS) {
200 node->extract_dir = true; /* if dir, extract it */
204 if (node->inserted) {
206 if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) {
207 bsendmsg(tree->ua, "+");
208 tree->LastCount = tree->FileCount;
217 * Set extract to value passed. We recursively walk
218 * down the tree setting all children if the
219 * node is a directory.
221 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
228 node->extract = extract;
229 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
230 node->extract_dir = extract; /* set/clear dir too */
232 if (node->type != TN_NEWDIR) {
235 /* For a non-file (i.e. directory), we see all the children */
236 if (node->type != TN_FILE || (node->soft_link && tree_node_has_child(node))) {
237 /* Recursive set children within directory */
238 foreach_child(n, node) {
239 count += set_extract(ua, n, tree, extract);
242 * Walk up tree marking any unextracted parent to be
246 while (node->parent && !node->parent->extract_dir) {
248 node->extract_dir = true;
251 } else if (extract) {
254 * Ordinary file, we get the full path, look up the
255 * attributes, decode them, and if we are hard linked to
256 * a file that was saved, we must load that file too.
258 tree_getpath(node, cwd, sizeof(cwd));
260 fdbr.JobId = node->JobId;
261 if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
263 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
265 * If we point to a hard linked file, traverse the tree to
266 * find that file, and mark it to be restored as well. It
267 * must have the Link we just obtained and the same JobId.
270 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
271 if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
273 if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
274 n->extract_dir = true;
286 * Recursively mark the current directory to be restored as
287 * well as all directories and files below it.
289 static int markcmd(UAContext *ua, TREE_CTX *tree)
295 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
296 bsendmsg(ua, _("No files marked.\n"));
299 for (int i=1; i < ua->argc; i++) {
300 foreach_child(node, tree->node) {
301 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
302 count += set_extract(ua, node, tree, true);
307 bsendmsg(ua, _("No files marked.\n"));
308 } else if (count == 1) {
309 bsendmsg(ua, _("1 file marked.\n"));
311 bsendmsg(ua, _("%s files marked.\n"),
312 edit_uint64_with_commas(count, ec1));
317 static int markdircmd(UAContext *ua, TREE_CTX *tree)
323 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
324 bsendmsg(ua, _("No files marked.\n"));
327 for (int i=1; i < ua->argc; i++) {
328 foreach_child(node, tree->node) {
329 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
330 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
331 node->extract_dir = true;
338 bsendmsg(ua, _("No directories marked.\n"));
339 } else if (count == 1) {
340 bsendmsg(ua, _("1 directory marked.\n"));
342 bsendmsg(ua, _("%s directories marked.\n"),
343 edit_uint64_with_commas(count, ec1));
349 static int countcmd(UAContext *ua, TREE_CTX *tree)
351 int total, num_extract;
352 char ec1[50], ec2[50];
354 total = num_extract = 0;
355 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
356 if (node->type != TN_NEWDIR) {
358 if (node->extract || node->extract_dir) {
363 bsendmsg(ua, _("%s total files/dirs. %s marked to be restored.\n"),
364 edit_uint64_with_commas(total, ec1),
365 edit_uint64_with_commas(num_extract, ec2));
369 static int findcmd(UAContext *ua, TREE_CTX *tree)
374 bsendmsg(ua, _("No file specification given.\n"));
378 for (int i=1; i < ua->argc; i++) {
379 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
380 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
382 tree_getpath(node, cwd, sizeof(cwd));
385 } else if (node->extract_dir) {
390 bsendmsg(ua, "%s%s\n", tag, cwd);
399 static int lscmd(UAContext *ua, TREE_CTX *tree)
403 if (!tree_node_has_child(tree->node)) {
406 foreach_child(node, tree->node) {
407 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
411 } else if (node->extract_dir) {
416 bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
423 * Ls command that lists only the marked files
425 static void rlsmark(UAContext *ua, TREE_NODE *tnode)
428 if (!tree_node_has_child(tnode)) {
431 foreach_child(node, tnode) {
432 if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
433 (node->extract || node->extract_dir)) {
437 } else if (node->extract_dir) {
442 bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
443 if (tree_node_has_child(node)) {
450 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
452 rlsmark(ua, tree->node);
458 extern char *getuser(uid_t uid, char *name, int len);
459 extern char *getgroup(gid_t gid, char *name, int len);
462 * This is actually the long form used for "dir"
464 static void ls_output(char *buf, const char *fname, const char *tag, struct stat *statp)
469 char en1[30], en2[30];
472 p = encode_mode(statp->st_mode, buf);
473 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
475 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid, en1, sizeof(en1)),
476 getgroup(statp->st_gid, en2, sizeof(en2)));
478 n = sprintf(p, "%10.10s ", edit_uint64(statp->st_size, ec1));
480 p = encode_time(statp->st_ctime, p);
483 for (f=fname; *f; ) {
491 * Like ls command, but give more detail on each file
493 static int dircmd(UAContext *ua, TREE_CTX *tree)
499 char cwd[1100], *pcwd;
501 if (!tree_node_has_child(tree->node)) {
502 bsendmsg(ua, _("Node %s has no children.\n"), tree->node->fname);
506 foreach_child(node, tree->node) {
508 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
511 } else if (node->extract_dir) {
516 tree_getpath(node, cwd, sizeof(cwd));
518 fdbr.JobId = node->JobId;
520 * Strip / from soft links to directories.
521 * This is because soft links to files have a trailing slash
522 * when returned from tree_getpath, but db_get_file_attr...
523 * treats soft links as files, so they do not have a trailing
524 * slash like directory names.
526 if (node->type == TN_FILE && tree_node_has_child(node)) {
527 bstrncpy(buf, cwd, sizeof(buf));
529 int len = strlen(buf);
531 buf[len-1] = 0; /* strip trailing / */
536 if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
538 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
540 /* Something went wrong getting attributes -- print name */
541 memset(&statp, 0, sizeof(statp));
543 ls_output(buf, cwd, tag, &statp);
544 bsendmsg(ua, "%s\n", buf);
551 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
553 int total, num_extract;
554 uint64_t total_bytes = 0;
560 total = num_extract = 0;
561 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
562 if (node->type != TN_NEWDIR) {
564 /* If regular file, get size */
565 if (node->extract && node->type == TN_FILE) {
567 tree_getpath(node, cwd, sizeof(cwd));
569 fdbr.JobId = node->JobId;
570 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
572 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
573 if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
574 total_bytes += statp.st_size;
577 /* Directory, count only */
578 } else if (node->extract || node->extract_dir) {
583 bsendmsg(ua, _("%d total files; %d marked to be restored; %s bytes.\n"),
584 total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
590 static int helpcmd(UAContext *ua, TREE_CTX *tree)
594 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
595 for (i=0; i<comsize; i++) {
596 bsendmsg(ua, " %-10s %s\n", _(commands[i].key), _(commands[i].help));
603 * Change directories. Note, if the user specifies x: and it fails,
604 * we assume it is a Win32 absolute cd rather than relative and
605 * try a second time with /x: ... Win32 kludge.
607 static int cdcmd(UAContext *ua, TREE_CTX *tree)
615 strip_leading_space(ua->argk[1]);
616 node = tree_cwd(ua->argk[1], tree->root, tree->node);
618 /* Try once more if Win32 drive -- make absolute */
619 if (ua->argk[1][1] == ':') { /* win32 drive */
620 bstrncpy(cwd, "/", sizeof(cwd));
621 bstrncat(cwd, ua->argk[1], sizeof(cwd));
622 node = tree_cwd(cwd, tree->root, tree->node);
625 bsendmsg(ua, _("Invalid path given.\n"));
632 tree_getpath(tree->node, cwd, sizeof(cwd));
633 bsendmsg(ua, _("cwd is: %s\n"), cwd);
637 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
640 tree_getpath(tree->node, cwd, sizeof(cwd));
641 bsendmsg(ua, _("cwd is: %s\n"), cwd);
646 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
651 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
652 bsendmsg(ua, _("No files unmarked.\n"));
655 for (int i=1; i < ua->argc; i++) {
656 foreach_child(node, tree->node) {
657 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
658 count += set_extract(ua, node, tree, false);
663 bsendmsg(ua, _("No files unmarked.\n"));
664 } else if (count == 1) {
665 bsendmsg(ua, _("1 file unmarked.\n"));
667 bsendmsg(ua, _("%d files unmarked.\n"), count);
672 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
677 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
678 bsendmsg(ua, _("No directories unmarked.\n"));
682 for (int i=1; i < ua->argc; i++) {
683 foreach_child(node, tree->node) {
684 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
685 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
686 node->extract_dir = false;
694 bsendmsg(ua, _("No directories unmarked.\n"));
695 } else if (count == 1) {
696 bsendmsg(ua, _("1 directory unmarked.\n"));
698 bsendmsg(ua, _("%d directories unmarked.\n"), count);
704 static int donecmd(UAContext *ua, TREE_CTX *tree)
709 static int quitcmd(UAContext *ua, TREE_CTX *tree)