/*
*
* Bacula Director -- User Agent Database File tree for Restore
- * command.
+ * command. This file interacts with the user implementing the
+ * UA tree commands.
*
* Kern Sibbald, July MMII
*
*/
/*
- Copyright (C) 2002-2003 Kern Sibbald and John Walker
+ Copyright (C) 2002-2004 Kern Sibbald and John Walker
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
/* Forward referenced commands */
static int markcmd(UAContext *ua, TREE_CTX *tree);
+static int markdircmd(UAContext *ua, TREE_CTX *tree);
static int countcmd(UAContext *ua, TREE_CTX *tree);
static int findcmd(UAContext *ua, TREE_CTX *tree);
static int lscmd(UAContext *ua, TREE_CTX *tree);
+static int lsmarkcmd(UAContext *ua, TREE_CTX *tree);
static int dircmd(UAContext *ua, TREE_CTX *tree);
+static int estimatecmd(UAContext *ua, TREE_CTX *tree);
static int helpcmd(UAContext *ua, TREE_CTX *tree);
static int cdcmd(UAContext *ua, TREE_CTX *tree);
static int pwdcmd(UAContext *ua, TREE_CTX *tree);
static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
+static int unmarkdircmd(UAContext *ua, TREE_CTX *tree);
static int quitcmd(UAContext *ua, TREE_CTX *tree);
+static int donecmd(UAContext *ua, TREE_CTX *tree);
struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; };
static struct cmdstruct commands[] = {
- { N_("mark"), markcmd, _("mark file for restoration")},
- { N_("unmark"), unmarkcmd, _("unmark file for restoration")},
{ N_("cd"), cdcmd, _("change current directory")},
- { N_("pwd"), pwdcmd, _("print current working directory")},
- { N_("ls"), lscmd, _("list current directory")},
+ { N_("count"), countcmd, _("count marked files in and below the cd")},
{ N_("dir"), dircmd, _("list current directory")},
- { N_("count"), countcmd, _("count marked files")},
- { N_("find"), findcmd, _("find files")},
- { N_("done"), quitcmd, _("leave file selection mode")},
- { N_("exit"), quitcmd, _("exit = done")},
+ { N_("done"), donecmd, _("leave file selection mode")},
+ { N_("estimate"), estimatecmd, _("estimate restore size")},
+ { N_("exit"), donecmd, _("exit = done")},
+ { N_("find"), findcmd, _("find files -- wildcards allowed")},
{ N_("help"), helpcmd, _("print help")},
+ { N_("ls"), lscmd, _("list current directory -- wildcards allowed")},
+ { N_("lsmark"), lsmarkcmd, _("list the marked files in and below the cd")},
+ { N_("mark"), markcmd, _("mark file to be restored")},
+ { N_("markdir"), markdircmd, _("mark directory entry to be restored -- nonrecursive")},
+ { N_("pwd"), pwdcmd, _("print current working directory")},
+ { N_("unmark"), unmarkcmd, _("unmark file to be restored")},
+ { N_("unmarkdir"), unmarkdircmd, _("unmark directory -- no recursion")},
+ { N_("quit"), quitcmd, _("quit")},
{ N_("?"), helpcmd, _("print help")},
};
#define comsize (sizeof(commands)/sizeof(struct cmdstruct))
* files to be restored. This is sort of like a mini-shell
* that allows "cd", "pwd", "add", "rm", ...
*/
-void user_select_files_from_tree(TREE_CTX *tree)
+bool user_select_files_from_tree(TREE_CTX *tree)
{
char cwd[2000];
+ bool stat;
/* Get a new context so we don't destroy restore command args */
UAContext *ua = new_ua_context(tree->ua->jcr);
ua->UA_sock = tree->ua->UA_sock; /* patch in UA socket */
tree_getpath(tree->node, cwd, sizeof(cwd));
bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
for ( ;; ) {
- int found, len, stat, i;
+ int found, len, i;
if (!get_cmd(ua, "$ ")) {
break;
}
len = strlen(ua->argk[0]);
found = 0;
- stat = 0;
+ stat = false;
for (i=0; i<(int)comsize; i++) /* search for command */
if (strncasecmp(ua->argk[0], _(commands[i].key), len) == 0) {
stat = (*commands[i].func)(ua, tree); /* go execute command */
}
}
ua->UA_sock = NULL; /* don't release restore socket */
+ stat = !ua->quit;
+ ua->quit = false;
free_ua_context(ua); /* get rid of temp UA context */
+ return stat;
}
* this routine is called once for each file. We do not allow
* duplicate filenames, but instead keep the info from the most
* recent file entered (i.e. the JobIds are assumed to be sorted)
+ *
+ * See uar_sel_files in sql_cmds.c for query that calls us.
+ * row[0]=Path, row[1]=Filename, row[2]=FileIndex
+ * row[3]=JobId row[4]=LStat
*/
int insert_tree_handler(void *ctx, int num_fields, char **row)
{
+ struct stat statp;
TREE_CTX *tree = (TREE_CTX *)ctx;
char fname[5000];
TREE_NODE *node, *new_node;
int type;
+ bool hard_link;
strip_trailing_junk(row[1]);
if (*row[1] == 0) { /* no filename => directory */
} else {
type = TN_FILE;
}
- bsnprintf(fname, sizeof(fname), "%s%s", row[0], row[1]);
if (tree->avail_node) {
- node = tree->avail_node;
+ node = tree->avail_node; /* if prev node avail use it */
} else {
- node = new_tree_node(tree->root, type);
+ node = new_tree_node(tree->root, type); /* get new node */
tree->avail_node = node;
}
+ hard_link = (decode_LinkFI(row[4], &statp) != 0);
+ bsnprintf(fname, sizeof(fname), "%s%s%s", row[0], row[1], "");
+// S_ISLNK(statp.st_mode)?" ->":"");
Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
new_node = insert_tree_node(fname, node, tree->root, NULL);
/* Note, if node already exists, save new one for next time */
if (new_node != node) {
- tree->avail_node = node;
+ tree->avail_node = node; /* node already exists */
} else {
- tree->avail_node = NULL;
+ tree->avail_node = NULL; /* added node to tree */
}
new_node->FileIndex = atoi(row[2]);
- new_node->JobId = atoi(row[3]);
+ new_node->JobId = (JobId_t)str_to_int64(row[3]);
new_node->type = type;
- new_node->extract = 1; /* extract all by default */
+ new_node->extract = true; /* extract all by default */
+ new_node->hard_link = hard_link;
+ new_node->soft_link = S_ISLNK(statp.st_mode) != 0;
+ if (type == TN_DIR || type == TN_DIR_NLS) {
+ new_node->extract_dir = true; /* if dir, extract it */
+ }
tree->cnt++;
return 0;
}
* down the tree setting all children if the
* node is a directory.
*/
-static void set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, int value)
+static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
{
TREE_NODE *n;
FILE_DBR fdbr;
struct stat statp;
+ int count = 0;
- node->extract = value;
+ node->extract = extract;
+ if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
+ node->extract_dir = extract; /* set/clear dir too */
+ }
+ if (node->type != TN_NEWDIR) {
+ count++;
+ }
/* For a non-file (i.e. directory), we see all the children */
- if (node->type != TN_FILE) {
+ if (node->type != TN_FILE || (node->soft_link && node->child)) {
+ /* Recursive set children within directory */
for (n=node->child; n; n=n->sibling) {
- set_extract(ua, n, tree, value);
+ count += set_extract(ua, n, tree, extract);
}
- } else if (value) {
+ /*
+ * Walk up tree marking any unextracted parent to be
+ * extracted.
+ */
+ if (extract) {
+ while (node->parent && !node->parent->extract_dir) {
+ node = node->parent;
+ node->extract_dir = true;
+ }
+ }
+ } else if (extract) {
char cwd[2000];
- /* Ordinary file, we get the full path, look up the
+ /*
+ * Ordinary file, we get the full path, look up the
* attributes, decode them, and if we are hard linked to
* a file that was saved, we must load that file too.
*/
tree_getpath(node, cwd, sizeof(cwd));
fdbr.FileId = 0;
fdbr.JobId = node->JobId;
- if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
+ if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
int32_t LinkFI;
decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
/*
* If we point to a hard linked file, traverse the tree to
- * find that file, and mark it for restoration as well. It
+ * find that file, and mark it to be restored as well. It
* must have the Link we just obtained and the same JobId.
*/
if (LinkFI) {
for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
- n->extract = 1;
+ n->extract = true;
+ if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
+ n->extract_dir = true;
+ }
break;
}
}
}
}
}
+ return count;
}
+/*
+ * Recursively mark the current directory to be restored as
+ * well as all directories and files below it.
+ */
static int markcmd(UAContext *ua, TREE_CTX *tree)
{
TREE_NODE *node;
+ int count = 0;
- if (ua->argc < 2)
+ if (ua->argc < 2 || !tree->node->child) {
+ bsendmsg(ua, _("No files marked.\n"));
return 1;
- if (!tree->node->child) {
+ }
+ for (int i=1; i < ua->argc; i++) {
+ for (node = tree->node->child; node; node=node->sibling) {
+ if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
+ count += set_extract(ua, node, tree, true);
+ }
+ }
+ }
+ if (count == 0) {
+ bsendmsg(ua, _("No files marked.\n"));
+ } else {
+ bsendmsg(ua, _("%d file%s marked.\n"), count, count==0?"":"s");
+ }
+ return 1;
+}
+
+static int markdircmd(UAContext *ua, TREE_CTX *tree)
+{
+ TREE_NODE *node;
+ int count = 0;
+
+ if (ua->argc < 2 || !tree->node->child) {
+ bsendmsg(ua, _("No files marked.\n"));
return 1;
}
- for (node = tree->node->child; node; node=node->sibling) {
- if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
- set_extract(ua, node, tree, 1);
+ for (int i=1; i < ua->argc; i++) {
+ for (node = tree->node->child; node; node=node->sibling) {
+ if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
+ if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
+ node->extract_dir = true;
+ count++;
+ }
+ }
}
}
+ if (count == 0) {
+ bsendmsg(ua, _("No directories marked.\n"));
+ } else {
+ bsendmsg(ua, _("%d director%s marked.\n"), count, count==1?"y":"ies");
+ }
return 1;
}
+
static int countcmd(UAContext *ua, TREE_CTX *tree)
{
- int total, extract;
+ int total, num_extract;
- total = extract = 0;
+ total = num_extract = 0;
for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
if (node->type != TN_NEWDIR) {
total++;
- if (node->extract) {
- extract++;
+ if (node->extract || node->extract_dir) {
+ num_extract++;
}
}
}
- bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
+ bsendmsg(ua, "%d total files/dirs. %d marked to be restored.\n", total, num_extract);
return 1;
}
for (int i=1; i < ua->argc; i++) {
for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
+ char *tag;
tree_getpath(node, cwd, sizeof(cwd));
- bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
+ if (node->extract) {
+ tag = "*";
+ } else if (node->extract_dir) {
+ tag = "+";
+ } else {
+ tag = "";
+ }
+ bsendmsg(ua, "%s%s\n", tag, cwd);
}
}
}
}
for (node = tree->node->child; node; node=node->sibling) {
if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
- bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
- (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
+ char *tag;
+ if (node->extract) {
+ tag = "*";
+ } else if (node->extract_dir) {
+ tag = "+";
+ } else {
+ tag = "";
+ }
+ bsendmsg(ua, "%s%s%s\n", tag, node->fname, node->child?"/":"");
+ }
+ }
+ return 1;
+}
+
+/*
+ * Ls command that lists only the marked files
+ */
+static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
+{
+ TREE_NODE *node;
+
+ if (!tree->node->child) {
+ return 1;
+ }
+ for (node = tree->node->child; node; node=node->sibling) {
+ if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
+ (node->extract || node->extract_dir)) {
+ char *tag;
+ if (node->extract) {
+ tag = "*";
+ } else if (node->extract_dir) {
+ tag = "+";
+ } else {
+ tag = "";
+ }
+ bsendmsg(ua, "%s%s%s\n", tag, node->fname, node->child?"/":"");
}
}
return 1;
}
+
extern char *getuser(uid_t uid);
extern char *getgroup(gid_t gid);
/*
* This is actually the long form used for "dir"
*/
-static void ls_output(char *buf, char *fname, int extract, struct stat *statp)
+static void ls_output(char *buf, char *fname, char *tag, struct stat *statp)
{
char *p, *f;
char ec1[30];
p += n;
p = encode_time(statp->st_ctime, p);
*p++ = ' ';
- if (extract) {
- *p++ = '*';
- } else {
- *p++ = ' ';
- }
- for (f=fname; *f; )
+ *p++ = *tag;
+ for (f=fname; *f; ) {
*p++ = *f++;
+ }
*p = 0;
}
TREE_NODE *node;
FILE_DBR fdbr;
struct stat statp;
- char buf[1000];
- char cwd[1100];
+ char buf[1100];
+ char cwd[1100], *pcwd;
if (!tree->node->child) {
return 1;
}
for (node = tree->node->child; node; node=node->sibling) {
+ char *tag;
if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
+ if (node->extract) {
+ tag = "*";
+ } else if (node->extract_dir) {
+ tag = "+";
+ } else {
+ tag = " ";
+ }
tree_getpath(node, cwd, sizeof(cwd));
fdbr.FileId = 0;
fdbr.JobId = node->JobId;
- if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
+ /*
+ * Strip / from soft links to directories.
+ * This is because soft links to files have a trailing slash
+ * when returned from tree_getpath, but db_get_file_attr...
+ * treats soft links as files, so they do not have a trailing
+ * slash like directory names.
+ */
+ if (node->type == TN_FILE && node->child) {
+ bstrncpy(buf, cwd, sizeof(buf));
+ pcwd = buf;
+ int len = strlen(buf);
+ if (len > 1) {
+ buf[len-1] = 0; /* strip trailing / */
+ }
+ } else {
+ pcwd = cwd;
+ }
+ if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
int32_t LinkFI;
decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
- ls_output(buf, cwd, node->extract, &statp);
- bsendmsg(ua, "%s\n", buf);
} else {
/* Something went wrong getting attributes -- print name */
- bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
- (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
+ memset(&statp, 0, sizeof(statp));
+ }
+ ls_output(buf, cwd, tag, &statp);
+ bsendmsg(ua, "%s\n", buf);
+ }
+ }
+ return 1;
+}
+
+
+static int estimatecmd(UAContext *ua, TREE_CTX *tree)
+{
+ int total, num_extract;
+ uint64_t total_bytes = 0;
+ FILE_DBR fdbr;
+ struct stat statp;
+ char cwd[1100];
+ char ec1[50];
+
+ total = num_extract = 0;
+ for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
+ if (node->type != TN_NEWDIR) {
+ total++;
+ /* If regular file, get size */
+ if (node->extract && node->type == TN_FILE) {
+ num_extract++;
+ tree_getpath(node, cwd, sizeof(cwd));
+ fdbr.FileId = 0;
+ fdbr.JobId = node->JobId;
+ if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
+ int32_t LinkFI;
+ decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
+ if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
+ total_bytes += statp.st_size;
+ }
+ }
+ /* Directory, count only */
+ } else if (node->extract || node->extract_dir) {
+ num_extract++;
}
}
}
+ bsendmsg(ua, "%d total files; %d marked to be restored; %s bytes.\n",
+ total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
return 1;
}
+
static int helpcmd(UAContext *ua, TREE_CTX *tree)
{
unsigned int i;
static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
{
TREE_NODE *node;
+ int count = 0;
- if (ua->argc < 2)
+ if (ua->argc < 2 || !tree->node->child) {
+ bsendmsg(ua, _("No files unmarked.\n"));
return 1;
- if (!tree->node->child) {
+ }
+ for (int i=1; i < ua->argc; i++) {
+ for (node = tree->node->child; node; node=node->sibling) {
+ if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
+ count += set_extract(ua, node, tree, false);
+ }
+ }
+ }
+ if (count == 0) {
+ bsendmsg(ua, _("No files unmarked.\n"));
+ } else {
+ bsendmsg(ua, _("%d file%s unmarked.\n"), count, count==0?"":"s");
+ }
+ return 1;
+}
+
+static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
+{
+ TREE_NODE *node;
+ int count = 0;
+
+ if (ua->argc < 2 || !tree->node->child) {
+ bsendmsg(ua, _("No directories unmarked.\n"));
return 1;
}
- for (node = tree->node->child; node; node=node->sibling) {
- if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
- set_extract(ua, node, tree, 0);
+
+ for (int i=1; i < ua->argc; i++) {
+ for (node = tree->node->child; node; node=node->sibling) {
+ if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
+ if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
+ node->extract_dir = false;
+ count++;
+ }
+ }
}
}
+
+ if (count == 0) {
+ bsendmsg(ua, _("No directories unmarked.\n"));
+ } else {
+ bsendmsg(ua, _("%d director%s unmarked.\n"), count, count==1?"y":"ies");
+ }
return 1;
}
+
+static int donecmd(UAContext *ua, TREE_CTX *tree)
+{
+ return 0;
+}
+
static int quitcmd(UAContext *ua, TREE_CTX *tree)
{
+ ua->quit = true;
return 0;
}