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 if (*row[1] == 0) { /* no filename => directory */
160 if (*row[0] != '/') { /* Must be Win32 directory */
168 hard_link = (decode_LinkFI(row[4], &statp) != 0);
169 node = insert_tree_node(row[0], row[1], type, tree->root, NULL);
170 JobId = str_to_int64(row[3]);
171 FileIndex = str_to_int64(row[2]);
173 * - The first time we see a file (node->inserted==true), we accept it.
174 * - In the same JobId, we accept only the first copy of a
175 * hard linked file (the others are simply pointers).
176 * - In the same JobId, we accept the last copy of any other
177 * file -- in particular directories.
179 * All the code to set ok could be condensed to a single
180 * line, but it would be even harder to read.
183 if (!node->inserted && JobId == node->JobId) {
184 if ((hard_link && FileIndex > node->FileIndex) ||
185 (!hard_link && FileIndex < node->FileIndex)) {
190 node->hard_link = hard_link;
191 node->FileIndex = FileIndex;
194 node->soft_link = S_ISLNK(statp.st_mode) != 0;
196 node->extract = true; /* extract all by default */
197 if (type == TN_DIR || type == TN_DIR_NLS) {
198 node->extract_dir = true; /* if dir, extract it */
202 if (node->inserted) {
204 if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) {
205 bsendmsg(tree->ua, "+");
206 tree->LastCount = tree->FileCount;
215 * Set extract to value passed. We recursively walk
216 * down the tree setting all children if the
217 * node is a directory.
219 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
226 node->extract = extract;
227 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
228 node->extract_dir = extract; /* set/clear dir too */
230 if (node->type != TN_NEWDIR) {
233 /* For a non-file (i.e. directory), we see all the children */
234 if (node->type != TN_FILE || (node->soft_link && tree_node_has_child(node))) {
235 /* Recursive set children within directory */
236 foreach_child(n, node) {
237 count += set_extract(ua, n, tree, extract);
240 * Walk up tree marking any unextracted parent to be
244 while (node->parent && !node->parent->extract_dir) {
246 node->extract_dir = true;
249 } else if (extract) {
252 * Ordinary file, we get the full path, look up the
253 * attributes, decode them, and if we are hard linked to
254 * a file that was saved, we must load that file too.
256 tree_getpath(node, cwd, sizeof(cwd));
258 fdbr.JobId = node->JobId;
259 if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
261 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
263 * If we point to a hard linked file, traverse the tree to
264 * find that file, and mark it to be restored as well. It
265 * must have the Link we just obtained and the same JobId.
268 for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
269 if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
271 if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
272 n->extract_dir = true;
284 * Recursively mark the current directory to be restored as
285 * well as all directories and files below it.
287 static int markcmd(UAContext *ua, TREE_CTX *tree)
293 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
294 bsendmsg(ua, _("No files marked.\n"));
297 for (int i=1; i < ua->argc; i++) {
298 foreach_child(node, tree->node) {
299 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
300 count += set_extract(ua, node, tree, true);
305 bsendmsg(ua, _("No files marked.\n"));
307 bsendmsg(ua, _("%s file%s marked.\n"),
308 edit_uint64_with_commas(count, ec1), count==0?"":"s");
313 static int markdircmd(UAContext *ua, TREE_CTX *tree)
319 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
320 bsendmsg(ua, _("No files marked.\n"));
323 for (int i=1; i < ua->argc; i++) {
324 foreach_child(node, tree->node) {
325 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
326 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
327 node->extract_dir = true;
334 bsendmsg(ua, _("No directories marked.\n"));
336 bsendmsg(ua, _("%s director%s marked.\n"),
337 edit_uint64_with_commas(count, ec1), count==1?"y":"ies");
343 static int countcmd(UAContext *ua, TREE_CTX *tree)
345 int total, num_extract;
346 char ec1[50], ec2[50];
348 total = num_extract = 0;
349 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
350 if (node->type != TN_NEWDIR) {
352 if (node->extract || node->extract_dir) {
357 bsendmsg(ua, "%s total files/dirs. %s marked to be restored.\n",
358 edit_uint64_with_commas(total, ec1),
359 edit_uint64_with_commas(num_extract, ec2));
363 static int findcmd(UAContext *ua, TREE_CTX *tree)
368 bsendmsg(ua, _("No file specification given.\n"));
372 for (int i=1; i < ua->argc; i++) {
373 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
374 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
376 tree_getpath(node, cwd, sizeof(cwd));
379 } else if (node->extract_dir) {
384 bsendmsg(ua, "%s%s\n", tag, cwd);
393 static int lscmd(UAContext *ua, TREE_CTX *tree)
397 if (!tree_node_has_child(tree->node)) {
400 foreach_child(node, tree->node) {
401 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
405 } else if (node->extract_dir) {
410 bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
417 * Ls command that lists only the marked files
419 static void rlsmark(UAContext *ua, TREE_NODE *tnode)
422 if (!tree_node_has_child(tnode)) {
425 foreach_child(node, tnode) {
426 if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
427 (node->extract || node->extract_dir)) {
431 } else if (node->extract_dir) {
436 bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
437 if (tree_node_has_child(node)) {
444 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
446 rlsmark(ua, tree->node);
452 extern char *getuser(uid_t uid, char *name, int len);
453 extern char *getgroup(gid_t gid, char *name, int len);
456 * This is actually the long form used for "dir"
458 static void ls_output(char *buf, const char *fname, const char *tag, struct stat *statp)
463 char en1[30], en2[30];
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, en1, sizeof(en1)),
470 getgroup(statp->st_gid, en2, sizeof(en2)));
472 n = sprintf(p, "%10.10s ", edit_uint64(statp->st_size, ec1));
474 p = encode_time(statp->st_ctime, p);
477 for (f=fname; *f; ) {
485 * Like ls command, but give more detail on each file
487 static int dircmd(UAContext *ua, TREE_CTX *tree)
493 char cwd[1100], *pcwd;
495 if (!tree_node_has_child(tree->node)) {
496 bsendmsg(ua, "Node %s has no children.\n", tree->node->fname);
500 foreach_child(node, tree->node) {
502 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
505 } else if (node->extract_dir) {
510 tree_getpath(node, cwd, sizeof(cwd));
512 fdbr.JobId = node->JobId;
514 * Strip / from soft links to directories.
515 * This is because soft links to files have a trailing slash
516 * when returned from tree_getpath, but db_get_file_attr...
517 * treats soft links as files, so they do not have a trailing
518 * slash like directory names.
520 if (node->type == TN_FILE && tree_node_has_child(node)) {
521 bstrncpy(buf, cwd, sizeof(buf));
523 int len = strlen(buf);
525 buf[len-1] = 0; /* strip trailing / */
530 if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
532 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
534 /* Something went wrong getting attributes -- print name */
535 memset(&statp, 0, sizeof(statp));
537 ls_output(buf, cwd, tag, &statp);
538 bsendmsg(ua, "%s\n", buf);
545 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
547 int total, num_extract;
548 uint64_t total_bytes = 0;
554 total = num_extract = 0;
555 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
556 if (node->type != TN_NEWDIR) {
558 /* If regular file, get size */
559 if (node->extract && node->type == TN_FILE) {
561 tree_getpath(node, cwd, sizeof(cwd));
563 fdbr.JobId = node->JobId;
564 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
566 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
567 if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
568 total_bytes += statp.st_size;
571 /* Directory, count only */
572 } else if (node->extract || node->extract_dir) {
577 bsendmsg(ua, "%d total files; %d marked to be restored; %s bytes.\n",
578 total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
584 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)