X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=bacula%2Fsrc%2Fdird%2Fua_tree.c;h=7bc68576cf807a66ce5694eec18fe7891c6d1515;hb=fa1cad6e368a3c7d0a1ac4ad25176b7043b1310f;hp=8b855085f8213129b2b94626f9d42d028566cabf;hpb=1408e3066793b95fab2ef13c0d1eb95826801fad;p=bacula%2Fbacula diff --git a/bacula/src/dird/ua_tree.c b/bacula/src/dird/ua_tree.c index 8b855085f8..7bc68576cf 100644 --- a/bacula/src/dird/ua_tree.c +++ b/bacula/src/dird/ua_tree.c @@ -1,7 +1,8 @@ /* * * 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 * @@ -9,7 +10,7 @@ */ /* - 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 @@ -37,30 +38,40 @@ /* 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)) @@ -71,9 +82,13 @@ static struct cmdstruct commands[] = { * 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 */ bsendmsg(tree->ua, _( "\nYou are now entering file selection mode where you add and\n" @@ -87,21 +102,21 @@ void user_select_files_from_tree(TREE_CTX *tree) tree_getpath(tree->node, cwd, sizeof(cwd)); bsendmsg(tree->ua, _("cwd is: %s\n"), cwd); for ( ;; ) { - int found, len, stat, i; - if (!get_cmd(tree->ua, "$ ")) { + int found, len, i; + if (!get_cmd(ua, "$ ")) { break; } - parse_ua_args(tree->ua); - if (tree->ua->argc == 0) { - return; + parse_ua_args(ua); + if (ua->argc == 0) { + break; } - len = strlen(tree->ua->argk[0]); + len = strlen(ua->argk[0]); found = 0; - stat = 0; + stat = false; for (i=0; i<(int)comsize; i++) /* search for command */ - if (strncasecmp(tree->ua->argk[0], _(commands[i].key), len) == 0) { - stat = (*commands[i].func)(tree->ua, tree); /* go execute command */ + if (strncasecmp(ua->argk[0], _(commands[i].key), len) == 0) { + stat = (*commands[i].func)(ua, tree); /* go execute command */ found = 1; break; } @@ -113,6 +128,11 @@ void user_select_files_from_tree(TREE_CTX *tree) break; } } + 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; } @@ -122,16 +142,22 @@ 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; int type; + bool hard_link; strip_trailing_junk(row[1]); - if (*row[1] == 0) { + if (*row[1] == 0) { /* no filename => directory */ if (*row[0] != '/') { /* Must be Win32 directory */ type = TN_DIR_NLS; } else { @@ -140,25 +166,32 @@ int insert_tree_handler(void *ctx, int num_fields, char **row) } else { type = TN_FILE; } - bsnprintf(fname, sizeof(fname), "%s%s", row[0]?row[0]:"", row[1]?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; } @@ -169,78 +202,140 @@ int insert_tree_handler(void *ctx, int num_fields, char **row) * 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); + } + /* + * 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 (value) { + } 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)) { - uint32_t LinkFI; + 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; } @@ -256,8 +351,16 @@ static int findcmd(UAContext *ua, TREE_CTX *tree) 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); } } } @@ -275,20 +378,55 @@ static int lscmd(UAContext *ua, TREE_CTX *tree) } 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]; @@ -303,13 +441,10 @@ static void ls_output(char *buf, char *fname, int extract, struct stat *statp) 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; } @@ -322,33 +457,96 @@ static int dircmd(UAContext *ua, TREE_CTX *tree) 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)) { - uint32_t LinkFI; + /* + * 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; @@ -379,8 +577,8 @@ static int cdcmd(UAContext *ua, TREE_CTX *tree) if (!node) { /* Try once more if Win32 drive -- make absolute */ if (ua->argk[1][1] == ':') { /* win32 drive */ - strcpy(cwd, "/"); - strcat(cwd, ua->argk[1]); + bstrncpy(cwd, "/", sizeof(cwd)); + bstrncat(cwd, ua->argk[1], sizeof(cwd)); node = tree_cwd(cwd, tree->root, tree->node); } if (!node) { @@ -408,21 +606,64 @@ static int pwdcmd(UAContext *ua, TREE_CTX *tree) 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; }