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
13 Copyright (C) 2002-2004 Kern Sibbald and John Walker
15 This program is free software; you can redistribute it and/or
16 modify it under the terms of the GNU General Public License as
17 published by the Free Software Foundation; either version 2 of
18 the License, or (at your option) any later version.
20 This program is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 General Public License for more details.
25 You should have received a copy of the GNU General Public
26 License along with this program; if not, write to the Free
27 Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
35 #include "findlib/find.h"
38 /* Forward referenced commands */
40 static int markcmd(UAContext *ua, TREE_CTX *tree);
41 static int markdircmd(UAContext *ua, TREE_CTX *tree);
42 static int countcmd(UAContext *ua, TREE_CTX *tree);
43 static int findcmd(UAContext *ua, TREE_CTX *tree);
44 static int lscmd(UAContext *ua, TREE_CTX *tree);
45 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree);
46 static int dircmd(UAContext *ua, TREE_CTX *tree);
47 static int estimatecmd(UAContext *ua, TREE_CTX *tree);
48 static int helpcmd(UAContext *ua, TREE_CTX *tree);
49 static int cdcmd(UAContext *ua, TREE_CTX *tree);
50 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
51 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
52 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree);
53 static int quitcmd(UAContext *ua, TREE_CTX *tree);
54 static int donecmd(UAContext *ua, TREE_CTX *tree);
57 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; };
58 static struct cmdstruct commands[] = {
59 { N_("cd"), cdcmd, _("change current directory")},
60 { N_("count"), countcmd, _("count marked files in and below the cd")},
61 { N_("dir"), dircmd, _("list current directory")},
62 { N_("done"), donecmd, _("leave file selection mode")},
63 { N_("estimate"), estimatecmd, _("estimate restore size")},
64 { N_("exit"), donecmd, _("exit = done")},
65 { N_("find"), findcmd, _("find files -- wildcards allowed")},
66 { N_("help"), helpcmd, _("print help")},
67 { N_("ls"), lscmd, _("list current directory -- wildcards allowed")},
68 { N_("lsmark"), lsmarkcmd, _("list the marked files in and below the cd")},
69 { N_("mark"), markcmd, _("mark file to be restored")},
70 { N_("markdir"), markdircmd, _("mark directory entry to be restored -- nonrecursive")},
71 { N_("pwd"), pwdcmd, _("print current working directory")},
72 { N_("unmark"), unmarkcmd, _("unmark file to be restored")},
73 { N_("unmarkdir"), unmarkdircmd, _("unmark directory -- no recursion")},
74 { N_("quit"), quitcmd, _("quit")},
75 { N_("?"), 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 and\n"
95 "remove files to be restored. All files are initially added.\n"
96 "Enter \"done\" to leave this mode.\n\n"));
98 * Enter interactive command handler allowing selection
99 * of individual files.
101 tree->node = (TREE_NODE *)tree->root;
102 tree_getpath(tree->node, cwd, sizeof(cwd));
103 bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
106 if (!get_cmd(ua, "$ ")) {
114 len = strlen(ua->argk[0]);
117 for (i=0; i<(int)comsize; i++) /* search for command */
118 if (strncasecmp(ua->argk[0], _(commands[i].key), len) == 0) {
119 stat = (*commands[i].func)(ua, tree); /* go execute command */
124 bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
131 ua->UA_sock = NULL; /* don't release restore socket */
134 free_ua_context(ua); /* get rid of temp UA context */
140 * This callback routine is responsible for inserting the
141 * items it gets into the directory tree. For each JobId selected
142 * this routine is called once for each file. We do not allow
143 * duplicate filenames, but instead keep the info from the most
144 * recent file entered (i.e. the JobIds are assumed to be sorted)
146 * See uar_sel_files in sql_cmds.c for query that calls us.
147 * row[0]=Path, row[1]=Filename, row[2]=FileIndex
148 * row[3]=JobId row[4]=LStat
150 int insert_tree_handler(void *ctx, int num_fields, char **row)
153 TREE_CTX *tree = (TREE_CTX *)ctx;
155 TREE_NODE *node, *new_node;
157 bool hard_link, first_time, ok;
161 strip_trailing_junk(row[1]);
162 if (*row[1] == 0) { /* no filename => directory */
163 if (*row[0] != '/') { /* Must be Win32 directory */
171 if (tree->avail_node) {
172 node = tree->avail_node; /* if prev node avail use it */
174 node = new_tree_node(tree->root, type); /* get new node */
175 tree->avail_node = node;
177 hard_link = (decode_LinkFI(row[4], &statp) != 0);
178 bsnprintf(fname, sizeof(fname), "%s%s%s", row[0], row[1], "");
179 Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
180 new_node = insert_tree_node(fname, node, tree->root, NULL);
181 /* Note, if node already exists, save new one for next time */
182 if (new_node != node) {
183 first_time = false; /* we saw this file before */
184 tree->avail_node = node; /* node already exists */
186 first_time = true; /* first time we saw this file */
187 tree->avail_node = NULL; /* added node to tree */
189 JobId = (JobId_t)str_to_int64(row[3]);
190 FileIndex = atoi(row[2]);
192 * - The first time we see a file, 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 (!first_time && JobId == new_node->JobId) {
203 if ((hard_link && FileIndex > new_node->FileIndex) ||
204 (!hard_link && FileIndex < new_node->FileIndex)) {
209 new_node->hard_link = hard_link;
210 new_node->FileIndex = FileIndex;
211 new_node->JobId = JobId;
212 new_node->type = type;
213 new_node->soft_link = S_ISLNK(statp.st_mode) != 0;
215 new_node->extract = true; /* extract all by default */
216 if (type == TN_DIR || type == TN_DIR_NLS) {
217 new_node->extract_dir = true; /* if dir, extract it */
227 * Set extract to value passed. We recursively walk
228 * down the tree setting all children if the
229 * node is a directory.
231 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
238 node->extract = extract;
239 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
240 node->extract_dir = extract; /* set/clear dir too */
242 if (node->type != TN_NEWDIR) {
245 /* For a non-file (i.e. directory), we see all the children */
246 if (node->type != TN_FILE || (node->soft_link && node->child)) {
247 /* Recursive set children within directory */
248 for (n=node->child; n; n=n->sibling) {
249 count += set_extract(ua, n, tree, extract);
252 * Walk up tree marking any unextracted parent to be
256 while (node->parent && !node->parent->extract_dir) {
258 node->extract_dir = true;
261 } else if (extract) {
264 * Ordinary file, we get the full path, look up the
265 * attributes, decode them, and if we are hard linked to
266 * a file that was saved, we must load that file too.
268 tree_getpath(node, cwd, sizeof(cwd));
270 fdbr.JobId = node->JobId;
271 if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
273 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
275 * If we point to a hard linked file, traverse the tree to
276 * find that file, and mark it to be restored as well. It
277 * must have the Link we just obtained and the same JobId.
280 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
281 if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
283 if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
284 n->extract_dir = true;
296 * Recursively mark the current directory to be restored as
297 * well as all directories and files below it.
299 static int markcmd(UAContext *ua, TREE_CTX *tree)
304 if (ua->argc < 2 || !tree->node->child) {
305 bsendmsg(ua, _("No files marked.\n"));
308 for (int i=1; i < ua->argc; i++) {
309 for (node = tree->node->child; node; node=node->sibling) {
310 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
311 count += set_extract(ua, node, tree, true);
316 bsendmsg(ua, _("No files marked.\n"));
318 bsendmsg(ua, _("%d file%s marked.\n"), count, count==0?"":"s");
323 static int markdircmd(UAContext *ua, TREE_CTX *tree)
328 if (ua->argc < 2 || !tree->node->child) {
329 bsendmsg(ua, _("No files marked.\n"));
332 for (int i=1; i < ua->argc; i++) {
333 for (node = tree->node->child; node; node=node->sibling) {
334 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
335 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
336 node->extract_dir = true;
343 bsendmsg(ua, _("No directories marked.\n"));
345 bsendmsg(ua, _("%d director%s marked.\n"), count, count==1?"y":"ies");
351 static int countcmd(UAContext *ua, TREE_CTX *tree)
353 int total, num_extract;
355 total = num_extract = 0;
356 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
357 if (node->type != TN_NEWDIR) {
359 if (node->extract || node->extract_dir) {
364 bsendmsg(ua, "%d total files/dirs. %d marked to be restored.\n", total, num_extract);
368 static int findcmd(UAContext *ua, TREE_CTX *tree)
373 bsendmsg(ua, _("No file specification given.\n"));
377 for (int i=1; i < ua->argc; i++) {
378 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
379 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
381 tree_getpath(node, cwd, sizeof(cwd));
384 } else if (node->extract_dir) {
389 bsendmsg(ua, "%s%s\n", tag, cwd);
398 static int lscmd(UAContext *ua, TREE_CTX *tree)
402 if (!tree->node->child) {
405 for (node = tree->node->child; node; node=node->sibling) {
406 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
410 } else if (node->extract_dir) {
415 bsendmsg(ua, "%s%s%s\n", tag, node->fname, node->child?"/":"");
422 * Ls command that lists only the marked files
424 static void rlsmark(UAContext *ua, TREE_NODE *node)
429 for (node = node->child; node; node=node->sibling) {
430 if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
431 (node->extract || node->extract_dir)) {
435 } else if (node->extract_dir) {
440 bsendmsg(ua, "%s%s%s\n", tag, node->fname, node->child?"/":"");
448 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
450 rlsmark(ua, tree->node);
456 extern char *getuser(uid_t uid);
457 extern char *getgroup(gid_t gid);
460 * This is actually the long form used for "dir"
462 static void ls_output(char *buf, char *fname, char *tag, struct stat *statp)
468 p = encode_mode(statp->st_mode, buf);
469 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
471 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
473 n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
475 p = encode_time(statp->st_ctime, p);
478 for (f=fname; *f; ) {
486 * Like ls command, but give more detail on each file
488 static int dircmd(UAContext *ua, TREE_CTX *tree)
494 char cwd[1100], *pcwd;
496 if (!tree->node->child) {
499 for (node = tree->node->child; node; node=node->sibling) {
501 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
504 } else if (node->extract_dir) {
509 tree_getpath(node, cwd, sizeof(cwd));
511 fdbr.JobId = node->JobId;
513 * Strip / from soft links to directories.
514 * This is because soft links to files have a trailing slash
515 * when returned from tree_getpath, but db_get_file_attr...
516 * treats soft links as files, so they do not have a trailing
517 * slash like directory names.
519 if (node->type == TN_FILE && node->child) {
520 bstrncpy(buf, cwd, sizeof(buf));
522 int len = strlen(buf);
524 buf[len-1] = 0; /* strip trailing / */
529 if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
531 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
533 /* Something went wrong getting attributes -- print name */
534 memset(&statp, 0, sizeof(statp));
536 ls_output(buf, cwd, tag, &statp);
537 bsendmsg(ua, "%s\n", buf);
544 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
546 int total, num_extract;
547 uint64_t total_bytes = 0;
553 total = num_extract = 0;
554 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
555 if (node->type != TN_NEWDIR) {
557 /* If regular file, get size */
558 if (node->extract && node->type == TN_FILE) {
560 tree_getpath(node, cwd, sizeof(cwd));
562 fdbr.JobId = node->JobId;
563 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
565 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
566 if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
567 total_bytes += statp.st_size;
570 /* Directory, count only */
571 } else if (node->extract || node->extract_dir) {
576 bsendmsg(ua, "%d total files; %d marked to be restored; %s bytes.\n",
577 total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
583 static int helpcmd(UAContext *ua, TREE_CTX *tree)
588 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
589 for (i=0; i<comsize; i++) {
590 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
597 * Change directories. Note, if the user specifies x: and it fails,
598 * we assume it is a Win32 absolute cd rather than relative and
599 * try a second time with /x: ... Win32 kludge.
601 static int cdcmd(UAContext *ua, TREE_CTX *tree)
609 node = tree_cwd(ua->argk[1], tree->root, tree->node);
611 /* Try once more if Win32 drive -- make absolute */
612 if (ua->argk[1][1] == ':') { /* win32 drive */
613 bstrncpy(cwd, "/", sizeof(cwd));
614 bstrncat(cwd, ua->argk[1], sizeof(cwd));
615 node = tree_cwd(cwd, tree->root, tree->node);
618 bsendmsg(ua, _("Invalid path given.\n"));
625 tree_getpath(tree->node, cwd, sizeof(cwd));
626 bsendmsg(ua, _("cwd is: %s\n"), cwd);
630 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
633 tree_getpath(tree->node, cwd, sizeof(cwd));
634 bsendmsg(ua, _("cwd is: %s\n"), cwd);
639 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
644 if (ua->argc < 2 || !tree->node->child) {
645 bsendmsg(ua, _("No files unmarked.\n"));
648 for (int i=1; i < ua->argc; i++) {
649 for (node = tree->node->child; node; node=node->sibling) {
650 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
651 count += set_extract(ua, node, tree, false);
656 bsendmsg(ua, _("No files unmarked.\n"));
658 bsendmsg(ua, _("%d file%s unmarked.\n"), count, count==0?"":"s");
663 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
668 if (ua->argc < 2 || !tree->node->child) {
669 bsendmsg(ua, _("No directories unmarked.\n"));
673 for (int i=1; i < ua->argc; i++) {
674 for (node = tree->node->child; node; node=node->sibling) {
675 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
676 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
677 node->extract_dir = false;
685 bsendmsg(ua, _("No directories unmarked.\n"));
687 bsendmsg(ua, _("%d director%s unmarked.\n"), count, count==1?"y":"ies");
693 static int donecmd(UAContext *ua, TREE_CTX *tree)
698 static int quitcmd(UAContext *ua, TREE_CTX *tree)