X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=bacula%2Fsrc%2Fdird%2Fua_tree.c;h=389d86861d810c8e9a92fa19a531b5e2a03779ac;hb=fcc920dc99e27d72ec49b2efaf822846f7bc1125;hp=0a87e265ecf157caf80296d624a91477aee1beaa;hpb=18b2b415e3df7fdde73d01651224ac8f3a212d5a;p=bacula%2Fbacula diff --git a/bacula/src/dird/ua_tree.c b/bacula/src/dird/ua_tree.c index 0a87e265ec..389d86861d 100644 --- a/bacula/src/dird/ua_tree.c +++ b/bacula/src/dird/ua_tree.c @@ -1,128 +1,174 @@ /* - * - * Bacula Director -- User Agent Database File tree for Restore - * command. This file interacts with the user implementing the - * UA tree commands. - * - * Kern Sibbald, July MMII - * - * Version $Id$ - */ + Bacula® - The Network Backup Solution -/* - Copyright (C) 2002-2003 Kern Sibbald and John Walker + Copyright (C) 2002-2010 Free Software Foundation Europe e.V. - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version. + The main author of Bacula is Kern Sibbald, with contributions from + many others, a complete list can be found in the file AUTHORS. + This program is Free Software; you can redistribute it and/or + modify it under the terms of version three of the GNU Affero General Public + License as published by the Free Software Foundation and included + in the file LICENSE. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public - License along with this program; if not, write to the Free - Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, - MA 02111-1307, USA. + You should have received a copy of the GNU Affero General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. + Bacula® is a registered trademark of Kern Sibbald. + The licensor of Bacula is the Free Software Foundation Europe + (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich, + Switzerland, email:ftf@fsfeurope.org. +*/ +/* + * + * Bacula Director -- User Agent Database File tree for Restore + * command. This file interacts with the user implementing the + * UA tree commands. + * + * Kern Sibbald, July MMII + * */ #include "bacula.h" #include "dird.h" +#ifdef HAVE_FNMATCH #include +#else +#include "lib/fnmatch.h" +#endif #include "findlib/find.h" /* 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 lsmark(UAContext *ua, TREE_CTX *tree); +static int lsmarkcmd(UAContext *ua, TREE_CTX *tree); static int dircmd(UAContext *ua, TREE_CTX *tree); +static int dot_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 dot_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); +static int dot_lsdircmd(UAContext *ua, TREE_CTX *tree); +static int dot_lscmd(UAContext *ua, TREE_CTX *tree); +static int dot_helpcmd(UAContext *ua, TREE_CTX *tree); +static int dot_lsmarkcmd(UAContext *ua, TREE_CTX *tree); - -struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; }; +struct cmdstruct { const char *key; int (*func)(UAContext *ua, TREE_CTX *tree); const char *help; }; static struct cmdstruct commands[] = { - { N_("cd"), cdcmd, _("change current directory")}, - { N_("count"), countcmd, _("count marked files")}, - { N_("dir"), dircmd, _("list current directory")}, - { N_("done"), quitcmd, _("leave file selection mode")}, - { N_("estimate"), estimatecmd, _("estimate restore size")}, - { N_("exit"), quitcmd, _("exit = done")}, - { N_("find"), findcmd, _("find files")}, - { N_("help"), helpcmd, _("print help")}, - { N_("lsmark"), lsmark, _("list the marked files")}, - { N_("ls"), lscmd, _("list current directory")}, - { N_("mark"), markcmd, _("mark file to be restored")}, - { N_("pwd"), pwdcmd, _("print current working directory")}, - { N_("unmark"), unmarkcmd, _("unmark file to be restored")}, - { N_("?"), helpcmd, _("print help")}, - }; -#define comsize (sizeof(commands)/sizeof(struct cmdstruct)) - + { NT_("add"), markcmd, _("add dir/file to be restored recursively, wildcards allowed")}, + { NT_("cd"), cdcmd, _("change current directory")}, + { NT_("count"), countcmd, _("count marked files in and below the cd")}, + { NT_("delete"), unmarkcmd, _("delete dir/file to be restored recursively in dir")}, + { NT_("dir"), dircmd, _("long list current directory, wildcards allowed")}, + { NT_(".dir"), dot_dircmd, _("long list current directory, wildcards allowed")}, + { NT_("done"), donecmd, _("leave file selection mode")}, + { NT_("estimate"), estimatecmd, _("estimate restore size")}, + { NT_("exit"), donecmd, _("same as done command")}, + { NT_("find"), findcmd, _("find files, wildcards allowed")}, + { NT_("help"), helpcmd, _("print help")}, + { NT_("ls"), lscmd, _("list current directory, wildcards allowed")}, + { NT_(".ls"), dot_lscmd, _("list current directory, wildcards allowed")}, + { NT_(".lsdir"), dot_lsdircmd, _("list subdir in current directory, wildcards allowed")}, + { NT_("lsmark"), lsmarkcmd, _("list the marked files in and below the cd")}, + { NT_(".lsmark"), dot_lsmarkcmd,_("list the marked files in")}, + { NT_("mark"), markcmd, _("mark dir/file to be restored recursively, wildcards allowed")}, + { NT_("markdir"), markdircmd, _("mark directory name to be restored (no files)")}, + { NT_("pwd"), pwdcmd, _("print current working directory")}, + { NT_(".pwd"), dot_pwdcmd, _("print current working directory")}, + { NT_("unmark"), unmarkcmd, _("unmark dir/file to be restored recursively in dir")}, + { NT_("unmarkdir"), unmarkdircmd, _("unmark directory name only no recursion")}, + { NT_("quit"), quitcmd, _("quit and do not do restore")}, + { NT_(".help"), dot_helpcmd, _("print help")}, + { NT_("?"), helpcmd, _("print help")}, + }; +#define comsize ((int)(sizeof(commands)/sizeof(struct cmdstruct))) /* * Enter a prompt mode where the user can select/deselect * 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 */ + ua->api = tree->ua->api; /* keep API flag too */ + BSOCK *user = ua->UA_sock; - bsendmsg(tree->ua, _( - "\nYou are now entering file selection mode where you add and\n" - "remove files to be restored. All files are initially added.\n" + ua->send_msg(_( + "\nYou are now entering file selection mode where you add (mark) and\n" + "remove (unmark) files to be restored. No files are initially added, unless\n" + "you used the \"all\" keyword on the command line.\n" "Enter \"done\" to leave this mode.\n\n")); + if (ua->api) user->signal(BNET_START_RTREE); /* * Enter interactive command handler allowing selection * of individual files. */ tree->node = (TREE_NODE *)tree->root; tree_getpath(tree->node, cwd, sizeof(cwd)); - bsendmsg(tree->ua, _("cwd is: %s\n"), cwd); - for ( ;; ) { - int found, len, stat, i; - if (!get_cmd(ua, "$ ")) { - break; + ua->send_msg(_("cwd is: %s\n"), cwd); + for ( ;; ) { + int found, len, i; + if (!get_cmd(ua, "$ ", true)) { + break; } - parse_ua_args(ua); + if (ua->api) user->signal(BNET_CMD_BEGIN); + parse_args_only(ua->cmd, &ua->args, &ua->argc, ua->argk, ua->argv, MAX_CMD_ARGS); if (ua->argc == 0) { - break; + ua->warning_msg(_("Invalid command \"%s\". Enter \"done\" to exit.\n"), ua->cmd); + if (ua->api) user->signal(BNET_CMD_FAILED); + continue; } len = strlen(ua->argk[0]); found = 0; - stat = 0; - 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 */ - found = 1; - break; - } + stat = false; + for (i=0; iargk[0], commands[i].key, len) == 0) { + stat = (*commands[i].func)(ua, tree); /* go execute command */ + found = 1; + break; + } if (!found) { - bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n")); - continue; + if (*ua->argk[0] == '.') { + /* Some unknow dot command -- probably .messages, ignore it */ + continue; + } + ua->warning_msg(_("Invalid command \"%s\". Enter \"done\" to exit.\n"), ua->cmd); + if (ua->api) user->signal(BNET_CMD_FAILED); + continue; } + if (ua->api) user->signal(BNET_CMD_OK); if (!stat) { - break; + break; } } + if (ua->api) user->signal(BNET_END_RTREE); ua->UA_sock = NULL; /* don't release restore socket */ - free_ua_context(ua); /* get rid of temp UA context */ + stat = !ua->quit; + ua->quit = false; + free_ua_context(ua); /* get rid of temp UA context */ + return stat; } @@ -132,43 +178,74 @@ void user_select_files_from_tree(TREE_CTX *tree) * 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; + TREE_NODE *node; int type; - - strip_trailing_junk(row[1]); - if (*row[1] == 0) { /* no filename => directory */ - if (*row[0] != '/') { /* Must be Win32 directory */ - type = TN_DIR_NLS; + bool hard_link, ok; + int FileIndex; + JobId_t JobId; + + Dmsg4(400, "Path=%s%s FI=%s JobId=%s\n", row[0], row[1], + row[2], row[3]); + if (*row[1] == 0) { /* no filename => directory */ + if (!IsPathSeparator(*row[0])) { /* Must be Win32 directory */ + type = TN_DIR_NLS; } else { - type = TN_DIR; + type = TN_DIR; } } else { type = TN_FILE; } - bsnprintf(fname, sizeof(fname), "%s%s", row[0], row[1]); - if (tree->avail_node) { - node = tree->avail_node; - } else { - node = new_tree_node(tree->root, type); - tree->avail_node = node; - } - 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; - } else { - tree->avail_node = NULL; + hard_link = (decode_LinkFI(row[4], &statp) != 0); + node = insert_tree_node(row[0], row[1], type, tree->root, NULL); + JobId = str_to_int64(row[3]); + FileIndex = str_to_int64(row[2]); + Dmsg2(400, "JobId=%s FileIndex=%s\n", row[3], row[2]); + /* + * - The first time we see a file (node->inserted==true), we accept it. + * - In the same JobId, we accept only the first copy of a + * hard linked file (the others are simply pointers). + * - In the same JobId, we accept the last copy of any other + * file -- in particular directories. + * + * All the code to set ok could be condensed to a single + * line, but it would be even harder to read. + */ + ok = true; + if (!node->inserted && JobId == node->JobId) { + if ((hard_link && FileIndex > node->FileIndex) || + (!hard_link && FileIndex < node->FileIndex)) { + ok = false; + } + } + if (ok) { + node->hard_link = hard_link; + node->FileIndex = FileIndex; + node->JobId = JobId; + node->type = type; + node->soft_link = S_ISLNK(statp.st_mode) != 0; + if (tree->all) { + node->extract = true; /* extract all by default */ + if (type == TN_DIR || type == TN_DIR_NLS) { + node->extract_dir = true; /* if dir, extract it */ + } + } + } + if (node->inserted) { + tree->FileCount++; + if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) { + tree->ua->send_msg("+"); + tree->LastCount = tree->FileCount; + } } - new_node->FileIndex = atoi(row[2]); - new_node->JobId = atoi(row[3]); - new_node->type = type; - new_node->extract = true; /* extract all by default */ tree->cnt++; return 0; } @@ -176,7 +253,7 @@ int insert_tree_handler(void *ctx, int num_fields, char **row) /* * Set extract to value passed. We recursively walk - * down the tree setting all children if the + * down the tree setting all children if the * node is a directory. */ static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract) @@ -187,82 +264,157 @@ static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extr int count = 0; 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) { - for (n=node->child; n; n=n->sibling) { - count += set_extract(ua, n, tree, extract); + if (node->type != TN_FILE || (node->soft_link && tree_node_has_child(node))) { + /* Recursive set children within directory */ + foreach_child(n, node) { + count += set_extract(ua, n, tree, extract); + } + /* + * 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, 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 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 = true; - break; - } - } - } + 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 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 = true; + if (n->type == TN_DIR || n->type == TN_DIR_NLS) { + n->extract_dir = true; + } + break; + } + } + } } } return count; } +static void strip_trailing_slash(char *arg) +{ + int len = strlen(arg); + if (len == 0) { + return; + } + len--; + if (arg[len] == '/') { /* strip any trailing slash */ + arg[len] = 0; + } +} + +/* + * 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; + char ec1[50]; + + if (ua->argc < 2 || !tree_node_has_child(tree->node)) { + ua->send_msg(_("No files marked.\n")); + return 1; + } + for (int i=1; i < ua->argc; i++) { + strip_trailing_slash(ua->argk[i]); + foreach_child(node, tree->node) { + if (fnmatch(ua->argk[i], node->fname, 0) == 0) { + count += set_extract(ua, node, tree, true); + } + } + } + if (count == 0) { + ua->send_msg(_("No files marked.\n")); + } else if (count == 1) { + ua->send_msg(_("1 file marked.\n")); + } else { + ua->send_msg(_("%s files marked.\n"), + edit_uint64_with_commas(count, ec1)); + } + return 1; +} + +static int markdircmd(UAContext *ua, TREE_CTX *tree) +{ + TREE_NODE *node; + int count = 0; + char ec1[50]; - if (ua->argc < 2 || !tree->node->child) { - bsendmsg(ua, _("No files marked.\n")); + if (ua->argc < 2 || !tree_node_has_child(tree->node)) { + ua->send_msg(_("No files marked.\n")); return 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) { - count += set_extract(ua, node, tree, true); - } + strip_trailing_slash(ua->argk[i]); + foreach_child(node, tree->node) { + 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 files marked.\n")); + ua->send_msg(_("No directories marked.\n")); + } else if (count == 1) { + ua->send_msg(_("1 directory marked.\n")); } else { - bsendmsg(ua, _("%d file%s marked.\n"), count, count==0?"":"s"); + ua->send_msg(_("%s directories marked.\n"), + edit_uint64_with_commas(count, ec1)); } return 1; } + static int countcmd(UAContext *ua, TREE_CTX *tree) { int total, num_extract; + char ec1[50], ec2[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 (node->extract) { - num_extract++; - } + total++; + if (node->extract || node->extract_dir) { + num_extract++; + } } } - bsendmsg(ua, "%d total files. %d marked to be restored.\n", total, num_extract); + ua->send_msg(_("%s total files/dirs. %s marked to be restored.\n"), + edit_uint64_with_commas(total, ec1), + edit_uint64_with_commas(num_extract, ec2)); return 1; } @@ -271,34 +423,94 @@ static int findcmd(UAContext *ua, TREE_CTX *tree) char cwd[2000]; if (ua->argc == 1) { - bsendmsg(ua, _("No file specification given.\n")); - return 0; + ua->send_msg(_("No file specification given.\n")); + return 1; /* make it non-fatal */ } - + 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) { - tree_getpath(node, cwd, sizeof(cwd)); - bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd); - } + if (fnmatch(ua->argk[i], node->fname, 0) == 0) { + const char *tag; + tree_getpath(node, cwd, sizeof(cwd)); + if (node->extract) { + tag = "*"; + } else if (node->extract_dir) { + tag = "+"; + } else { + tag = ""; + } + ua->send_msg("%s%s\n", tag, cwd); + } } } return 1; } +static int dot_lsdircmd(UAContext *ua, TREE_CTX *tree) +{ + TREE_NODE *node; + if (!tree_node_has_child(tree->node)) { + return 1; + } + + foreach_child(node, tree->node) { + if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) { + if (tree_node_has_child(node)) { + ua->send_msg("%s/\n", node->fname); + } + } + } + + return 1; +} + +static int dot_helpcmd(UAContext *ua, TREE_CTX *tree) +{ + for (int i=0; isend_msg("%s\n", commands[i].key); + } + } + return 1; +} + +static int dot_lscmd(UAContext *ua, TREE_CTX *tree) +{ + TREE_NODE *node; + + if (!tree_node_has_child(tree->node)) { + return 1; + } + + foreach_child(node, tree->node) { + if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) { + ua->send_msg("%s%s\n", node->fname, tree_node_has_child(node)?"/":""); + } + } + + return 1; +} static int lscmd(UAContext *ua, TREE_CTX *tree) { TREE_NODE *node; - if (!tree->node->child) { + if (!tree_node_has_child(tree->node)) { return 1; } - for (node = tree->node->child; node; node=node->sibling) { + foreach_child(node, tree->node) { 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)?"/":""); + const char *tag; + if (node->extract) { + tag = "*"; + } else if (node->extract_dir) { + tag = "+"; + } else { + tag = ""; + } + ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":""); } } return 1; @@ -307,90 +519,181 @@ static int lscmd(UAContext *ua, TREE_CTX *tree) /* * Ls command that lists only the marked files */ -static int lsmark(UAContext *ua, TREE_CTX *tree) +static int dot_lsmarkcmd(UAContext *ua, TREE_CTX *tree) { TREE_NODE *node; - - if (!tree->node->child) { + if (!tree_node_has_child(tree->node)) { 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) { - bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname, - (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":""); + foreach_child(node, tree->node) { + if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) && + (node->extract || node->extract_dir)) { + ua->send_msg("%s%s\n", node->fname, tree_node_has_child(node)?"/":""); } } return 1; } +/* + * Ls command that lists only the marked files + */ +static void rlsmark(UAContext *ua, TREE_NODE *tnode) +{ + TREE_NODE *node; + if (!tree_node_has_child(tnode)) { + return; + } + foreach_child(node, tnode) { + if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) && + (node->extract || node->extract_dir)) { + const char *tag; + if (node->extract) { + tag = "*"; + } else if (node->extract_dir) { + tag = "+"; + } else { + tag = ""; + } + ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":""); + if (tree_node_has_child(node)) { + rlsmark(ua, node); + } + } + } +} -extern char *getuser(uid_t uid); -extern char *getgroup(gid_t gid); +static int lsmarkcmd(UAContext *ua, TREE_CTX *tree) +{ + rlsmark(ua, tree->node); + return 1; +} /* * This is actually the long form used for "dir" */ -static void ls_output(char *buf, char *fname, bool extract, struct stat *statp) +static void ls_output(guid_list *guid, char *buf, const char *fname, const char *tag, + struct stat *statp, bool dot_cmd) { - char *p, *f; + char *p; + const char *f; char ec1[30]; + char en1[30], en2[30]; int n; + time_t time; p = encode_mode(statp->st_mode, buf); - n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink); - p += n; - n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid)); - p += n; - n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1)); - p += n; - p = encode_time(statp->st_ctime, p); - *p++ = ' '; - if (extract) { - *p++ = '*'; + if (dot_cmd) { + *p++ = ','; + n = sprintf(p, "%d,", (uint32_t)statp->st_nlink); + p += n; + n = sprintf(p, "%s,%s,", + guid->uid_to_name(statp->st_uid, en1, sizeof(en1)), + guid->gid_to_name(statp->st_gid, en2, sizeof(en2))); + p += n; + n = sprintf(p, "%s,", edit_int64(statp->st_size, ec1)); + p += n; + p = encode_time(statp->st_mtime, p); + *p++ = ','; + *p++ = *tag; + *p++ = ','; } else { + n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink); + p += n; + n = sprintf(p, "%-8.8s %-8.8s", + guid->uid_to_name(statp->st_uid, en1, sizeof(en1)), + guid->gid_to_name(statp->st_gid, en2, sizeof(en2))); + p += n; + n = sprintf(p, "%12.12s ", edit_int64(statp->st_size, ec1)); + p += n; + if (statp->st_ctime > statp->st_mtime) { + time = statp->st_ctime; + } else { + time = statp->st_mtime; + } + /* Display most recent time */ + p = encode_time(time, p); *p++ = ' '; + *p++ = *tag; } - for (f=fname; *f; ) + for (f=fname; *f; ) { *p++ = *f++; + } *p = 0; } - /* * Like ls command, but give more detail on each file */ -static int dircmd(UAContext *ua, TREE_CTX *tree) +static int do_dircmd(UAContext *ua, TREE_CTX *tree, bool dot_cmd) { TREE_NODE *node; FILE_DBR fdbr; struct stat statp; - char buf[1000]; - char cwd[1100]; + char buf[1100]; + char cwd[1100], *pcwd; + guid_list *guid; - if (!tree->node->child) { + if (!tree_node_has_child(tree->node)) { + ua->send_msg(_("Node %s has no children.\n"), tree->node->fname); return 1; } - for (node = tree->node->child; node; node=node->sibling) { + + guid = new_guid_list(); + foreach_child(node, tree->node) { + const char *tag; if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) { - 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 */ - 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)?"/":""); - } + 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; + /* + * 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 && tree_node_has_child(node)) { + 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 */ + } else { + /* Something went wrong getting attributes -- print name */ + memset(&statp, 0, sizeof(statp)); + } + ls_output(guid, buf, cwd, tag, &statp, dot_cmd); + ua->send_msg("%s\n", buf); } } + free_guid_list(guid); return 1; } +int dot_dircmd(UAContext *ua, TREE_CTX *tree) +{ + return do_dircmd(ua, tree, true/*dot command*/); +} + +static int dircmd(UAContext *ua, TREE_CTX *tree) +{ + return do_dircmd(ua, tree, false/*not dot command*/); +} + static int estimatecmd(UAContext *ua, TREE_CTX *tree) { @@ -404,57 +707,61 @@ static int estimatecmd(UAContext *ua, TREE_CTX *tree) 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) { - num_extract++; - } + 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)); + ua->send_msg(_("%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) +static int helpcmd(UAContext *ua, TREE_CTX *tree) { unsigned int i; -/* usage(); */ - bsendmsg(ua, _(" Command Description\n ======= ===========\n")); + ua->send_msg(_(" Command Description\n ======= ===========\n")); for (i=0; isend_msg(" %-10s %s\n", _(commands[i].key), _(commands[i].help)); + } } - bsendmsg(ua, "\n"); + ua->send_msg("\n"); return 1; } /* - * Change directories. Note, if the user specifies x: and it fails, + * Change directories. Note, if the user specifies x: and it fails, * we assume it is a Win32 absolute cd rather than relative and * try a second time with /x: ... Win32 kludge. */ -static int cdcmd(UAContext *ua, TREE_CTX *tree) +static int cdcmd(UAContext *ua, TREE_CTX *tree) { TREE_NODE *node; char cwd[2000]; + if (ua->argc != 2) { + ua->error_msg(_("Too few or too many arguments. Try using double quotes.\n")); return 1; } node = tree_cwd(ua->argk[1], tree->root, tree->node); @@ -462,56 +769,108 @@ static int cdcmd(UAContext *ua, TREE_CTX *tree) /* Try once more if Win32 drive -- make absolute */ if (ua->argk[1][1] == ':') { /* win32 drive */ bstrncpy(cwd, "/", sizeof(cwd)); - bstrncat(cwd, ua->argk[1], sizeof(cwd)); - node = tree_cwd(cwd, tree->root, tree->node); + bstrncat(cwd, ua->argk[1], sizeof(cwd)); + node = tree_cwd(cwd, tree->root, tree->node); } if (!node) { - bsendmsg(ua, _("Invalid path given.\n")); + ua->warning_msg(_("Invalid path given.\n")); } else { - tree->node = node; + tree->node = node; } } else { tree->node = node; } + return pwdcmd(ua, tree); +} + +static int pwdcmd(UAContext *ua, TREE_CTX *tree) +{ + char cwd[2000]; tree_getpath(tree->node, cwd, sizeof(cwd)); - bsendmsg(ua, _("cwd is: %s\n"), cwd); + if (ua->api) { + ua->send_msg("%s", cwd); + } else { + ua->send_msg(_("cwd is: %s\n"), cwd); + } return 1; } -static int pwdcmd(UAContext *ua, TREE_CTX *tree) +static int dot_pwdcmd(UAContext *ua, TREE_CTX *tree) { char cwd[2000]; tree_getpath(tree->node, cwd, sizeof(cwd)); - bsendmsg(ua, _("cwd is: %s\n"), cwd); + ua->send_msg("%s", cwd); return 1; } - static int unmarkcmd(UAContext *ua, TREE_CTX *tree) { TREE_NODE *node; int count = 0; - if (ua->argc < 2 || !tree->node->child) { - bsendmsg(ua, _("No files unmarked.\n")); + if (ua->argc < 2 || !tree_node_has_child(tree->node)) { + ua->send_msg(_("No files unmarked.\n")); return 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) { - count += set_extract(ua, node, tree, false); - } + strip_trailing_slash(ua->argk[i]); + foreach_child(node, tree->node) { + 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")); + ua->send_msg(_("No files unmarked.\n")); + } else if (count == 1) { + ua->send_msg(_("1 file unmarked.\n")); } else { - bsendmsg(ua, _("%d file%s unmarked.\n"), count, count==0?"":"s"); + char ed1[50]; + ua->send_msg(_("%s files unmarked.\n"), edit_uint64_with_commas(count, ed1)); } return 1; } -static int quitcmd(UAContext *ua, TREE_CTX *tree) +static int unmarkdircmd(UAContext *ua, TREE_CTX *tree) +{ + TREE_NODE *node; + int count = 0; + + if (ua->argc < 2 || !tree_node_has_child(tree->node)) { + ua->send_msg(_("No directories unmarked.\n")); + return 1; + } + + for (int i=1; i < ua->argc; i++) { + strip_trailing_slash(ua->argk[i]); + foreach_child(node, tree->node) { + 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) { + ua->send_msg(_("No directories unmarked.\n")); + } else if (count == 1) { + ua->send_msg(_("1 directory unmarked.\n")); + } else { + ua->send_msg(_("%d directories unmarked.\n"), count); + } + 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; }