/*
- Bacula® - The Network Backup Solution
-
- Copyright (C) 2002-2007 Free Software Foundation Europe e.V.
-
- 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 two of the GNU General Public
- License as published by the Free Software Foundation plus additions
- that are listed 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
- 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., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301, USA.
-
- Bacula® is a registered trademark of John Walker.
- The licensor of Bacula is the Free Software Foundation Europe
- (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
- Switzerland, email:ftf@fsfeurope.org.
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2017 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
*/
/*
- *
* 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$
*/
#include "bacula.h"
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 { const char *key; int (*func)(UAContext *ua, TREE_CTX *tree); const char *help; };
static struct cmdstruct commands[] = {
+ { 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_("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_("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)))
"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.
ua->send_msg(_("cwd is: %s\n"), cwd);
for ( ;; ) {
int found, len, i;
- if (!get_cmd(ua, "$ ")) {
+ if (!get_cmd(ua, "$ ", true)) {
break;
}
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) {
- ua->warning_msg(_("Invalid command. Enter \"done\" to exit.\n"));
+ ua->warning_msg(_("Invalid command \"%s\". Enter \"done\" to exit.\n"), ua->cmd);
if (ua->api) user->signal(BNET_CMD_FAILED);
continue;
}
found = 0;
stat = false;
for (i=0; i<comsize; i++) /* search for command */
- if (strncasecmp(ua->argk[0], _(commands[i].key), len) == 0) {
+ if (strncasecmp(ua->argk[0], commands[i].key, len) == 0) {
stat = (*commands[i].func)(ua, tree); /* go execute command */
found = 1;
break;
}
if (!found) {
- ua->warning_msg(_("Invalid command. Enter \"done\" to exit.\n"));
+ 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;
}
break;
}
}
+ if (ua->api) user->signal(BNET_END_RTREE);
ua->UA_sock = NULL; /* don't release restore socket */
stat = !ua->quit;
ua->quit = false;
return stat;
}
-
/*
* This callback routine is responsible for inserting the
* items it gets into the directory tree. For each JobId selected
*
* 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
+ * row[3]=JobId row[4]=LStat row[5]=DeltaSeq
*/
int insert_tree_handler(void *ctx, int num_fields, char **row)
{
int type;
bool hard_link, ok;
int FileIndex;
+ int32_t delta_seq;
JobId_t JobId;
+ HL_ENTRY *entry = NULL;
+ int32_t LinkFI;
-// Dmsg4(000, "Path=%s%s FI=%s JobId=%s\n", row[0], row[1],
-// row[2], row[3]);
+ Dmsg4(150, "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_FILE;
}
- hard_link = (decode_LinkFI(row[4], &statp) != 0);
+ decode_stat(row[4], &statp, sizeof(statp), &LinkFI);
+
+ hard_link = (LinkFI != 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]);
+ delta_seq = str_to_int64(row[5]);
+ Dmsg6(150, "node=0x%p JobId=%s FileIndex=%s Delta=%s node.delta=%d LinkFI=%d\n",
+ node, row[3], row[2], row[5], node->delta_seq, LinkFI);
+
+ /* TODO: check with hardlinks */
+ if (delta_seq > 0) {
+ if (delta_seq == (node->delta_seq + 1)) {
+ tree_add_delta_part(tree->root, node, node->JobId, node->FileIndex);
+
+ } else {
+ /* File looks to be deleted */
+ if (node->delta_seq == -1) { /* just created */
+ tree_remove_node(tree->root, node);
+
+ } else {
+ tree->ua->warning_msg(_("Something is wrong with the Delta sequence of %s, "
+ "skipping new parts. Current sequence is %d\n"),
+ row[1], node->delta_seq);
+
+ Dmsg3(0, "Something is wrong with Delta, skip it "
+ "fname=%s d1=%d d2=%d\n", row[1], node->delta_seq, delta_seq);
+ }
+ return 0;
+ }
+ }
/*
* - 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
node->JobId = JobId;
node->type = type;
node->soft_link = S_ISLNK(statp.st_mode) != 0;
+ node->delta_seq = delta_seq;
+ node->can_access = true;
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 */
}
}
+ /* insert file having hardlinks into hardlink hashtable */
+ if (statp.st_nlink > 1 && type != TN_DIR && type != TN_DIR_NLS) {
+ if (!LinkFI) {
+ /* first occurrence - file hardlinked to */
+ entry = (HL_ENTRY *)tree->root->hardlinks.hash_malloc(sizeof(HL_ENTRY));
+ entry->key = (((uint64_t) JobId) << 32) + FileIndex;
+ entry->node = node;
+ tree->root->hardlinks.insert(entry->key, entry);
+ } else if (tree->hardlinks_in_mem) {
+ /* hardlink to known file index: lookup original file */
+ uint64_t file_key = (((uint64_t) JobId) << 32) + LinkFI;
+ HL_ENTRY *first_hl = (HL_ENTRY *) tree->root->hardlinks.lookup(file_key);
+ if (first_hl && first_hl->node) {
+ /* then add hardlink entry to linked node*/
+ entry = (HL_ENTRY *)tree->root->hardlinks.hash_malloc(sizeof(HL_ENTRY));
+ entry->key = (((uint64_t) JobId) << 32) + FileIndex;
+ entry->node = first_hl->node;
+ tree->root->hardlinks.insert(entry->key, entry);
+ }
+ }
+ }
}
if (node->inserted) {
tree->FileCount++;
return 0;
}
-
/*
* Set extract to value passed. We recursively walk
* down the tree setting all children if the
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 = extract;
* Walk up tree marking any unextracted parent to be
* extracted.
*/
- if (extract) {
+ if (!tree->no_auto_parent && 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
- * 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 (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 */
+ uint64_t key = 0;
+ if (tree->hardlinks_in_mem) {
+ if (node->hard_link) {
+ key = (((uint64_t) node->JobId) << 32) + node->FileIndex; /* every hardlink is in hashtable, and it points to linked file */
+ }
+ } else {
+ /* Get the hard link if it exists */
+ FILE_DBR fdbr;
+ struct stat statp;
+ char cwd[2000];
/*
- * 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.
+ * 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.
*/
- 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;
- }
- }
+ tree_getpath(node, cwd, sizeof(cwd));
+ fdbr.FileId = 0;
+ fdbr.JobId = node->JobId;
+ if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
+ int32_t LinkFI;
+ decode_stat(fdbr.LStat, &statp, sizeof(statp), &LinkFI); /* decode stat pkt */
+ key = (((uint64_t) node->JobId) << 32) + LinkFI; /* lookup by linked file's fileindex */
+ }
+ }
+ /* If file hard linked and we have a key */
+ if (node->hard_link && key != 0) {
+ /*
+ * If we point to a hard linked file, find that file in
+ * hardlinks hashmap, and mark it to be restored as well.
+ */
+ HL_ENTRY *entry = (HL_ENTRY *)tree->root->hardlinks.lookup(key);
+ if (entry && entry->node) {
+ n = entry->node;
+ n->extract = true;
+ n->extract_dir = (n->type == TN_DIR || n->type == TN_DIR_NLS);
}
}
}
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; i<comsize; i++) {
+ /* List only non-dot commands */
+ if (commands[i].key[0] != '.') {
+ ua->send_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)
{
/*
* Ls command that lists only the marked files
*/
-static void rlsmark(UAContext *ua, TREE_NODE *tnode)
+static int dot_lsmarkcmd(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) &&
+ (node->extract || node->extract_dir)) {
+ ua->send_msg("%s%s\n", node->fname, tree_node_has_child(node)?"/":"");
+ }
+ }
+ return 1;
+}
+
+/*
+ * This recursive ls command that lists only the marked files
+ */
+static void rlsmark(UAContext *ua, TREE_NODE *tnode, int level)
+{
+ TREE_NODE *node;
+ const int max_level = 100;
+ char indent[max_level*2+1];
+ int i, j;
if (!tree_node_has_child(tnode)) {
return;
}
+ level = MIN(level, max_level);
+ j = 0;
+ for (i=0; i<level; i++) {
+ indent[j++] = ' ';
+ indent[j++] = ' ';
+ }
+ indent[j] = 0;
foreach_child(node, tnode) {
if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
(node->extract || node->extract_dir)) {
} else {
tag = "";
}
- ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
+ ua->send_msg("%s%s%s%s\n", indent, tag, node->fname, tree_node_has_child(node)?"/":"");
if (tree_node_has_child(node)) {
- rlsmark(ua, node);
+ rlsmark(ua, node, level+1);
}
}
}
static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
{
- rlsmark(ua, tree->node);
+ rlsmark(ua, tree->node, 0);
return 1;
}
-
-
-extern char *getuser(uid_t uid, char *name, int len);
-extern char *getgroup(gid_t gid, char *name, int len);
-
/*
* This is actually the long form used for "dir"
*/
-static void ls_output(char *buf, const char *fname, const char *tag,
- struct stat *statp, bool dot_cmd)
-
+static void ls_output(guid_list *guid, char *buf, const char *fname, const char *tag,
+ struct stat *statp, bool dot_cmd)
{
char *p;
const char *f;
*p++ = ',';
n = sprintf(p, "%d,", (uint32_t)statp->st_nlink);
p += n;
- n = sprintf(p, "%s,%s,", getuser(statp->st_uid, en1, sizeof(en1)),
- getgroup(statp->st_gid, en2, sizeof(en2)));
+ 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_uint64(statp->st_size, ec1));
+ n = sprintf(p, "%s,", edit_int64(statp->st_size, ec1));
p += n;
p = encode_time(statp->st_mtime, p);
*p++ = ',';
} else {
n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
p += n;
- n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid, en1, sizeof(en1)),
- getgroup(statp->st_gid, en2, sizeof(en2)));
+ 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, "%10.10s ", edit_uint64(statp->st_size, ec1));
+ n = sprintf(p, "%12.12s ", edit_int64(statp->st_size, ec1));
p += n;
if (statp->st_ctime > statp->st_mtime) {
time = statp->st_ctime;
struct stat statp;
char buf[1100];
char cwd[1100], *pcwd;
+ guid_list *guid;
if (!tree_node_has_child(tree->node)) {
ua->send_msg(_("Node %s has no children.\n"), tree->node->fname);
return 1;
}
+ guid = new_guid_list();
foreach_child(node, tree->node) {
const char *tag;
if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
}
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 */
+ decode_stat(fdbr.LStat, &statp, sizeof(statp), &LinkFI); /* decode stat pkt */
} else {
/* Something went wrong getting attributes -- print name */
memset(&statp, 0, sizeof(statp));
}
- ls_output(buf, cwd, tag, &statp, dot_cmd);
+ ls_output(guid, buf, cwd, tag, &statp, dot_cmd);
ua->send_msg("%s\n", buf);
}
}
+ free_guid_list(guid);
return 1;
}
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 */
+ decode_stat(fdbr.LStat, &statp, sizeof(statp), &LinkFI); /* decode stat pkt */
if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
total_bytes += statp.st_size;
}
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);
if (!node) {
/* Try once more if Win32 drive -- make absolute */
}
if (!node) {
ua->warning_msg(_("Invalid path given.\n"));
- } else {
+ }
+ }
+ if (node) {
+ if (node->can_access) {
tree->node = node;
+ } else {
+ ua->warning_msg(_("Invalid path given. Permission denied.\n"));
}
- } else {
- tree->node = node;
}
return pwdcmd(ua, tree);
}