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 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, "$ ")) {
115 len = strlen(ua->argk[0]);
118 for (i=0; i<(int)comsize; i++) /* search for command */
119 if (strncasecmp(ua->argk[0], _(commands[i].key), len) == 0) {
120 stat = (*commands[i].func)(ua, tree); /* go execute command */
125 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
132 ua->UA_sock = NULL; /* don't release restore socket */
135 free_ua_context(ua); /* get rid of temp UA context */
141 * This callback routine is responsible for inserting the
142 * items it gets into the directory tree. For each JobId selected
143 * this routine is called once for each file. We do not allow
144 * duplicate filenames, but instead keep the info from the most
145 * recent file entered (i.e. the JobIds are assumed to be sorted)
147 * See uar_sel_files in sql_cmds.c for query that calls us.
148 * row[0]=Path, row[1]=Filename, row[2]=FileIndex
149 * row[3]=JobId row[4]=LStat
151 int insert_tree_handler(void *ctx, int num_fields, char **row)
154 TREE_CTX *tree = (TREE_CTX *)ctx;
161 // Dmsg4(000, "Path=%s%s FI=%s JobId=%s\n", row[0], row[1],
163 if (*row[1] == 0) { /* no filename => directory */
164 if (*row[0] != '/') { /* Must be Win32 directory */
172 hard_link = (decode_LinkFI(row[4], &statp) != 0);
173 node = insert_tree_node(row[0], row[1], type, tree->root, NULL);
174 JobId = str_to_int64(row[3]);
175 FileIndex = str_to_int64(row[2]);
177 * - The first time we see a file (node->inserted==true), we accept it.
178 * - In the same JobId, we accept only the first copy of a
179 * hard linked file (the others are simply pointers).
180 * - In the same JobId, we accept the last copy of any other
181 * file -- in particular directories.
183 * All the code to set ok could be condensed to a single
184 * line, but it would be even harder to read.
187 if (!node->inserted && JobId == node->JobId) {
188 if ((hard_link && FileIndex > node->FileIndex) ||
189 (!hard_link && FileIndex < node->FileIndex)) {
194 node->hard_link = hard_link;
195 node->FileIndex = FileIndex;
198 node->soft_link = S_ISLNK(statp.st_mode) != 0;
200 node->extract = true; /* extract all by default */
201 if (type == TN_DIR || type == TN_DIR_NLS) {
202 node->extract_dir = true; /* if dir, extract it */
206 if (node->inserted) {
208 if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) {
209 bsendmsg(tree->ua, "+");
210 tree->LastCount = tree->FileCount;
219 * Set extract to value passed. We recursively walk
220 * down the tree setting all children if the
221 * node is a directory.
223 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
230 node->extract = extract;
231 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
232 node->extract_dir = extract; /* set/clear dir too */
234 if (node->type != TN_NEWDIR) {
237 /* For a non-file (i.e. directory), we see all the children */
238 if (node->type != TN_FILE || (node->soft_link && tree_node_has_child(node))) {
239 /* Recursive set children within directory */
240 foreach_child(n, node) {
241 count += set_extract(ua, n, tree, extract);
244 * Walk up tree marking any unextracted parent to be
248 while (node->parent && !node->parent->extract_dir) {
250 node->extract_dir = true;
253 } else if (extract) {
256 * Ordinary file, we get the full path, look up the
257 * attributes, decode them, and if we are hard linked to
258 * a file that was saved, we must load that file too.
260 tree_getpath(node, cwd, sizeof(cwd));
262 fdbr.JobId = node->JobId;
263 if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
265 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
267 * If we point to a hard linked file, traverse the tree to
268 * find that file, and mark it to be restored as well. It
269 * must have the Link we just obtained and the same JobId.
272 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
273 if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
275 if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
276 n->extract_dir = true;
288 * Recursively mark the current directory to be restored as
289 * well as all directories and files below it.
291 static int markcmd(UAContext *ua, TREE_CTX *tree)
297 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
298 bsendmsg(ua, _("No files marked.\n"));
301 for (int i=1; i < ua->argc; i++) {
302 foreach_child(node, tree->node) {
303 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
304 count += set_extract(ua, node, tree, true);
309 bsendmsg(ua, _("No files marked.\n"));
310 } else if (count == 1) {
311 bsendmsg(ua, _("1 file marked.\n"));
313 bsendmsg(ua, _("%s files marked.\n"),
314 edit_uint64_with_commas(count, ec1));
319 static int markdircmd(UAContext *ua, TREE_CTX *tree)
325 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
326 bsendmsg(ua, _("No files marked.\n"));
329 for (int i=1; i < ua->argc; i++) {
330 foreach_child(node, tree->node) {
331 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
332 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
333 node->extract_dir = true;
340 bsendmsg(ua, _("No directories marked.\n"));
341 } else if (count == 1) {
342 bsendmsg(ua, _("1 directory marked.\n"));
344 bsendmsg(ua, _("%s directories marked.\n"),
345 edit_uint64_with_commas(count, ec1));
351 static int countcmd(UAContext *ua, TREE_CTX *tree)
353 int total, num_extract;
354 char ec1[50], ec2[50];
356 total = num_extract = 0;
357 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
358 if (node->type != TN_NEWDIR) {
360 if (node->extract || node->extract_dir) {
365 bsendmsg(ua, _("%s total files/dirs. %s marked to be restored.\n"),
366 edit_uint64_with_commas(total, ec1),
367 edit_uint64_with_commas(num_extract, ec2));
371 static int findcmd(UAContext *ua, TREE_CTX *tree)
376 bsendmsg(ua, _("No file specification given.\n"));
380 for (int i=1; i < ua->argc; i++) {
381 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
382 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
384 tree_getpath(node, cwd, sizeof(cwd));
387 } else if (node->extract_dir) {
392 bsendmsg(ua, "%s%s\n", tag, cwd);
401 static int lscmd(UAContext *ua, TREE_CTX *tree)
405 if (!tree_node_has_child(tree->node)) {
408 foreach_child(node, tree->node) {
409 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
413 } else if (node->extract_dir) {
418 bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
425 * Ls command that lists only the marked files
427 static void rlsmark(UAContext *ua, TREE_NODE *tnode)
430 if (!tree_node_has_child(tnode)) {
433 foreach_child(node, tnode) {
434 if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
435 (node->extract || node->extract_dir)) {
439 } else if (node->extract_dir) {
444 bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
445 if (tree_node_has_child(node)) {
452 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
454 rlsmark(ua, tree->node);
460 extern char *getuser(uid_t uid, char *name, int len);
461 extern char *getgroup(gid_t gid, char *name, int len);
464 * This is actually the long form used for "dir"
466 static void ls_output(char *buf, const char *fname, const char *tag,
467 struct stat *statp, bool dot_cmd)
473 char en1[30], en2[30];
477 p = encode_mode(statp->st_mode, buf);
480 n = sprintf(p, "%d,", (uint32_t)statp->st_nlink);
482 n = sprintf(p, "%s,%s,", getuser(statp->st_uid, en1, sizeof(en1)),
483 getgroup(statp->st_gid, en2, sizeof(en2)));
485 n = sprintf(p, "%s,", edit_uint64(statp->st_size, ec1));
487 p = encode_time(statp->st_mtime, p);
492 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
494 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid, en1, sizeof(en1)),
495 getgroup(statp->st_gid, en2, sizeof(en2)));
497 n = sprintf(p, "%10.10s ", edit_uint64(statp->st_size, ec1));
499 if (statp->st_ctime > statp->st_mtime) {
500 time = statp->st_ctime;
502 time = statp->st_mtime;
504 /* Display most recent time */
505 p = encode_time(time, p);
509 for (f=fname; *f; ) {
516 * Like ls command, but give more detail on each file
518 static int do_dircmd(UAContext *ua, TREE_CTX *tree, bool dot_cmd)
524 char cwd[1100], *pcwd;
526 if (!tree_node_has_child(tree->node)) {
527 bsendmsg(ua, _("Node %s has no children.\n"), tree->node->fname);
531 foreach_child(node, tree->node) {
533 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
536 } else if (node->extract_dir) {
541 tree_getpath(node, cwd, sizeof(cwd));
543 fdbr.JobId = node->JobId;
545 * Strip / from soft links to directories.
546 * This is because soft links to files have a trailing slash
547 * when returned from tree_getpath, but db_get_file_attr...
548 * treats soft links as files, so they do not have a trailing
549 * slash like directory names.
551 if (node->type == TN_FILE && tree_node_has_child(node)) {
552 bstrncpy(buf, cwd, sizeof(buf));
554 int len = strlen(buf);
556 buf[len-1] = 0; /* strip trailing / */
561 if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
563 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
565 /* Something went wrong getting attributes -- print name */
566 memset(&statp, 0, sizeof(statp));
568 ls_output(buf, cwd, tag, &statp, dot_cmd);
569 bsendmsg(ua, "%s\n", buf);
575 int dot_dircmd(UAContext *ua, TREE_CTX *tree)
577 return do_dircmd(ua, tree, true/*dot command*/);
580 static int dircmd(UAContext *ua, TREE_CTX *tree)
582 return do_dircmd(ua, tree, false/*not dot command*/);
586 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
588 int total, num_extract;
589 uint64_t total_bytes = 0;
595 total = num_extract = 0;
596 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
597 if (node->type != TN_NEWDIR) {
599 /* If regular file, get size */
600 if (node->extract && node->type == TN_FILE) {
602 tree_getpath(node, cwd, sizeof(cwd));
604 fdbr.JobId = node->JobId;
605 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
607 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
608 if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
609 total_bytes += statp.st_size;
612 /* Directory, count only */
613 } else if (node->extract || node->extract_dir) {
618 bsendmsg(ua, _("%d total files; %d marked to be restored; %s bytes.\n"),
619 total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
625 static int helpcmd(UAContext *ua, TREE_CTX *tree)
629 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
630 for (i=0; i<comsize; i++) {
631 /* List only non-dot commands */
632 if (commands[i].key[0] != '.') {
633 bsendmsg(ua, " %-10s %s\n", _(commands[i].key), _(commands[i].help));
641 * Change directories. Note, if the user specifies x: and it fails,
642 * we assume it is a Win32 absolute cd rather than relative and
643 * try a second time with /x: ... Win32 kludge.
645 static int cdcmd(UAContext *ua, TREE_CTX *tree)
653 strip_leading_space(ua->argk[1]);
654 node = tree_cwd(ua->argk[1], tree->root, tree->node);
656 /* Try once more if Win32 drive -- make absolute */
657 if (ua->argk[1][1] == ':') { /* win32 drive */
658 bstrncpy(cwd, "/", sizeof(cwd));
659 bstrncat(cwd, ua->argk[1], sizeof(cwd));
660 node = tree_cwd(cwd, tree->root, tree->node);
663 bsendmsg(ua, _("Invalid path given.\n"));
670 tree_getpath(tree->node, cwd, sizeof(cwd));
671 bsendmsg(ua, _("cwd is: %s\n"), cwd);
675 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
678 tree_getpath(tree->node, cwd, sizeof(cwd));
679 bsendmsg(ua, _("cwd is: %s\n"), cwd);
684 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
689 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
690 bsendmsg(ua, _("No files unmarked.\n"));
693 for (int i=1; i < ua->argc; i++) {
694 foreach_child(node, tree->node) {
695 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
696 count += set_extract(ua, node, tree, false);
701 bsendmsg(ua, _("No files unmarked.\n"));
702 } else if (count == 1) {
703 bsendmsg(ua, _("1 file unmarked.\n"));
705 bsendmsg(ua, _("%d files unmarked.\n"), count);
710 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
715 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
716 bsendmsg(ua, _("No directories unmarked.\n"));
720 for (int i=1; i < ua->argc; i++) {
721 foreach_child(node, tree->node) {
722 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
723 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
724 node->extract_dir = false;
732 bsendmsg(ua, _("No directories unmarked.\n"));
733 } else if (count == 1) {
734 bsendmsg(ua, _("1 directory unmarked.\n"));
736 bsendmsg(ua, _("%d directories unmarked.\n"), count);
742 static int donecmd(UAContext *ua, TREE_CTX *tree)
747 static int quitcmd(UAContext *ua, TREE_CTX *tree)