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 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 = (JobId_t)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"));
309 bsendmsg(ua, _("%s file%s marked.\n"),
310 edit_uint64_with_commas(count, ec1), count==0?"":"s");
315 static int markdircmd(UAContext *ua, TREE_CTX *tree)
321 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
322 bsendmsg(ua, _("No files marked.\n"));
325 for (int i=1; i < ua->argc; i++) {
326 foreach_child(node, tree->node) {
327 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
328 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
329 node->extract_dir = true;
336 bsendmsg(ua, _("No directories marked.\n"));
338 bsendmsg(ua, _("%s director%s marked.\n"),
339 edit_uint64_with_commas(count, ec1), count==1?"y":"ies");
345 static int countcmd(UAContext *ua, TREE_CTX *tree)
347 int total, num_extract;
348 char ec1[50], ec2[50];
350 total = num_extract = 0;
351 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
352 if (node->type != TN_NEWDIR) {
354 if (node->extract || node->extract_dir) {
359 bsendmsg(ua, "%s total files/dirs. %s marked to be restored.\n",
360 edit_uint64_with_commas(total, ec1),
361 edit_uint64_with_commas(num_extract, ec2));
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 *tnode)
424 if (!tree_node_has_child(tnode)) {
427 foreach_child(node, tnode) {
428 if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
429 (node->extract || node->extract_dir)) {
433 } else if (node->extract_dir) {
438 bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
439 if (tree_node_has_child(node)) {
446 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
448 rlsmark(ua, tree->node);
454 extern char *getuser(uid_t uid, char *name, int len);
455 extern char *getgroup(gid_t gid, char *name, int len);
458 * This is actually the long form used for "dir"
460 static void ls_output(char *buf, const char *fname, const char *tag, struct stat *statp)
465 char en1[30], en2[30];
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, en1, sizeof(en1)),
472 getgroup(statp->st_gid, en2, sizeof(en2)));
474 n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
476 p = encode_time(statp->st_ctime, p);
479 for (f=fname; *f; ) {
487 * Like ls command, but give more detail on each file
489 static int dircmd(UAContext *ua, TREE_CTX *tree)
495 char cwd[1100], *pcwd;
497 if (!tree_node_has_child(tree->node)) {
498 bsendmsg(ua, "Node %s has no children.\n", tree->node->fname);
502 foreach_child(node, tree->node) {
504 if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
507 } else if (node->extract_dir) {
512 tree_getpath(node, cwd, sizeof(cwd));
514 fdbr.JobId = node->JobId;
516 * Strip / from soft links to directories.
517 * This is because soft links to files have a trailing slash
518 * when returned from tree_getpath, but db_get_file_attr...
519 * treats soft links as files, so they do not have a trailing
520 * slash like directory names.
522 if (node->type == TN_FILE && tree_node_has_child(node)) {
523 bstrncpy(buf, cwd, sizeof(buf));
525 int len = strlen(buf);
527 buf[len-1] = 0; /* strip trailing / */
532 if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
534 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
536 /* Something went wrong getting attributes -- print name */
537 memset(&statp, 0, sizeof(statp));
539 ls_output(buf, cwd, tag, &statp);
540 bsendmsg(ua, "%s\n", buf);
547 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
549 int total, num_extract;
550 uint64_t total_bytes = 0;
556 total = num_extract = 0;
557 for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
558 if (node->type != TN_NEWDIR) {
560 /* If regular file, get size */
561 if (node->extract && node->type == TN_FILE) {
563 tree_getpath(node, cwd, sizeof(cwd));
565 fdbr.JobId = node->JobId;
566 if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
568 decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
569 if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
570 total_bytes += statp.st_size;
573 /* Directory, count only */
574 } else if (node->extract || node->extract_dir) {
579 bsendmsg(ua, "%d total files; %d marked to be restored; %s bytes.\n",
580 total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
586 static int helpcmd(UAContext *ua, TREE_CTX *tree)
591 bsendmsg(ua, _(" Command Description\n ======= ===========\n"));
592 for (i=0; i<comsize; i++) {
593 bsendmsg(ua, _(" %-10s %s\n"), _(commands[i].key), _(commands[i].help));
600 * Change directories. Note, if the user specifies x: and it fails,
601 * we assume it is a Win32 absolute cd rather than relative and
602 * try a second time with /x: ... Win32 kludge.
604 static int cdcmd(UAContext *ua, TREE_CTX *tree)
612 node = tree_cwd(ua->argk[1], tree->root, tree->node);
614 /* Try once more if Win32 drive -- make absolute */
615 if (ua->argk[1][1] == ':') { /* win32 drive */
616 bstrncpy(cwd, "/", sizeof(cwd));
617 bstrncat(cwd, ua->argk[1], sizeof(cwd));
618 node = tree_cwd(cwd, tree->root, tree->node);
621 bsendmsg(ua, _("Invalid path given.\n"));
628 tree_getpath(tree->node, cwd, sizeof(cwd));
629 bsendmsg(ua, _("cwd is: %s\n"), cwd);
633 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
636 tree_getpath(tree->node, cwd, sizeof(cwd));
637 bsendmsg(ua, _("cwd is: %s\n"), cwd);
642 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
647 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
648 bsendmsg(ua, _("No files unmarked.\n"));
651 for (int i=1; i < ua->argc; i++) {
652 foreach_child(node, tree->node) {
653 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
654 count += set_extract(ua, node, tree, false);
659 bsendmsg(ua, _("No files unmarked.\n"));
661 bsendmsg(ua, _("%d file%s unmarked.\n"), count, count==0?"":"s");
666 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
671 if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
672 bsendmsg(ua, _("No directories unmarked.\n"));
676 for (int i=1; i < ua->argc; i++) {
677 foreach_child(node, tree->node) {
678 if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
679 if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
680 node->extract_dir = false;
688 bsendmsg(ua, _("No directories unmarked.\n"));
690 bsendmsg(ua, _("%d director%s unmarked.\n"), count, count==1?"y":"ies");
696 static int donecmd(UAContext *ua, TREE_CTX *tree)
701 static int quitcmd(UAContext *ua, TREE_CTX *tree)