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 { const char *key; int (*func)(UAContext *ua, TREE_CTX *tree); const 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 dir/file to be restored -- recursively in dirs")},
70 { N_("markdir"), markdircmd, _("mark directory name to be restored (no files)")},
71 { N_("pwd"), pwdcmd, _("print current working directory")},
72 { N_("unmark"), unmarkcmd, _("unmark dir/file to be restored -- recursively in dir")},
73 { N_("unmarkdir"), unmarkdircmd, _("unmark directory name only -- 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 (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 strip_trailing_junk(row[1]);
162 if (*row[1] == 0) { /* no filename => directory */
163 if (*row[0] != '/') { /* Must be Win32 directory */
171 hard_link = (decode_LinkFI(row[4], &statp) != 0);
172 node = insert_tree_node(row[0], row[1], type, tree->root, NULL);
173 JobId = (JobId_t)str_to_int64(row[3]);
174 FileIndex = atoi(row[2]);
176 * - The first time we see a file (node->inserted==true), we accept it.
177 * - In the same JobId, we accept only the first copy of a
178 * hard linked file (the others are simply pointers).
179 * - In the same JobId, we accept the last copy of any other
180 * file -- in particular directories.
182 * All the code to set ok could be condensed to a single
183 * line, but it would be even harder to read.
186 if (!node->inserted && JobId == node->JobId) {
187 if ((hard_link && FileIndex > node->FileIndex) ||
188 (!hard_link && FileIndex < node->FileIndex)) {
193 node->hard_link = hard_link;
194 node->FileIndex = FileIndex;
197 node->soft_link = S_ISLNK(statp.st_mode) != 0;
199 node->extract = true; /* extract all by default */
200 if (type == TN_DIR || type == TN_DIR_NLS) {
201 node->extract_dir = true; /* if dir, extract it */
205 if (node->inserted) {
207 if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) {
208 bsendmsg(tree->ua, "+");
209 tree->LastCount = tree->FileCount;
218 * Set extract to value passed. We recursively walk
219 * down the tree setting all children if the
220 * node is a directory.
222 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
229 node->extract = extract;
230 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
231 node->extract_dir = extract; /* set/clear dir too */
233 if (node->type != TN_NEWDIR) {
236 /* For a non-file (i.e. directory), we see all the children */
237 if (node->type != TN_FILE || (node->soft_link && tree_node_has_child(node))) {
238 /* Recursive set children within directory */
239 foreach_child(n, node) {
240 count += set_extract(ua, n, tree, extract);
243 * Walk up tree marking any unextracted parent to be
247 while (node->parent && !node->parent->extract_dir) {
249 node->extract_dir = true;
252 } else if (extract) {
255 * Ordinary file, we get the full path, look up the
256 * attributes, decode them, and if we are hard linked to
257 * a file that was saved, we must load that file too.
259 tree_getpath(node, cwd, sizeof(cwd));
261 fdbr.JobId = node->JobId;
262 if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
264 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
266 * If we point to a hard linked file, traverse the tree to
267 * find that file, and mark it to be restored as well. It
268 * must have the Link we just obtained and the same JobId.
271 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
272 if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
274 if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
275 n->extract_dir = true;
287 * Recursively mark the current directory to be restored as
288 * well as all directories and files below it.
290 static int markcmd(UAContext *ua, TREE_CTX *tree)
296 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
297 bsendmsg(ua, _("No files marked.\n"));
300 for (int i=1; i < ua->argc; i++) {
301 foreach_child(node, tree->node) {
302 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
303 count += set_extract(ua, node, tree, true);
308 bsendmsg(ua, _("No files marked.\n"));
310 bsendmsg(ua, _("%s file%s marked.\n"),
311 edit_uint64_with_commas(count, ec1), count==0?"":"s");
316 static int markdircmd(UAContext *ua, TREE_CTX *tree)
322 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
323 bsendmsg(ua, _("No files marked.\n"));
326 for (int i=1; i < ua->argc; i++) {
327 foreach_child(node, tree->node) {
328 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
329 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
330 node->extract_dir = true;
337 bsendmsg(ua, _("No directories marked.\n"));
339 bsendmsg(ua, _("%s director%s marked.\n"),
340 edit_uint64_with_commas(count, ec1), count==1?"y":"ies");
346 static int countcmd(UAContext *ua, TREE_CTX *tree)
348 int total, num_extract;
351 total = num_extract = 0;
352 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
353 if (node->type != TN_NEWDIR) {
355 if (node->extract || node->extract_dir) {
360 bsendmsg(ua, "%s total files/dirs. %d marked to be restored.\n", total,
361 edit_uint64_with_commas(num_extract, ec1));
365 static int findcmd(UAContext *ua, TREE_CTX *tree)
370 bsendmsg(ua, _("No file specification given.\n"));
374 for (int i=1; i < ua->argc; i++) {
375 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
376 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
378 tree_getpath(node, cwd, sizeof(cwd));
381 } else if (node->extract_dir) {
386 bsendmsg(ua, "%s%s\n", tag, cwd);
395 static int lscmd(UAContext *ua, TREE_CTX *tree)
399 if (!tree_node_has_child(tree->node)) {
402 foreach_child(node, tree->node) {
403 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
407 } else if (node->extract_dir) {
412 bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
419 * Ls command that lists only the marked files
421 static void rlsmark(UAContext *ua, TREE_NODE *node)
423 if (!tree_node_has_child(node)) {
426 foreach_child(node, node) {
427 if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
428 (node->extract || node->extract_dir)) {
432 } else if (node->extract_dir) {
437 bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
438 if (tree_node_has_child(node)) {
445 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
447 rlsmark(ua, tree->node);
453 extern char *getuser(uid_t uid);
454 extern char *getgroup(gid_t gid);
457 * This is actually the long form used for "dir"
459 static void ls_output(char *buf, const char *fname, const char *tag, struct stat *statp)
466 p = encode_mode(statp->st_mode, buf);
467 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
469 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
471 n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
473 p = encode_time(statp->st_ctime, p);
476 for (f=fname; *f; ) {
484 * Like ls command, but give more detail on each file
486 static int dircmd(UAContext *ua, TREE_CTX *tree)
492 char cwd[1100], *pcwd;
494 if (!tree_node_has_child(tree->node)) {
495 bsendmsg(ua, "Node %s has no children.\n", tree->node->fname);
499 foreach_child(node, tree->node) {
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 && tree_node_has_child(node)) {
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_has_child(tree->node)) {
645 bsendmsg(ua, _("No files unmarked.\n"));
648 for (int i=1; i < ua->argc; i++) {
649 foreach_child(node, tree->node) {
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_has_child(tree->node)) {
669 bsendmsg(ua, _("No directories unmarked.\n"));
673 for (int i=1; i < ua->argc; i++) {
674 foreach_child(node, tree->node) {
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)