]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_tree.c
Make mark run *MUCH* faster in restore tree + update copyright on changed files
[bacula/bacula] / bacula / src / dird / ua_tree.c
1 /*
2  *
3  *   Bacula Director -- User Agent Database File tree for Restore
4  *      command. This file interacts with the user implementing the
5  *      UA tree commands.
6  *
7  *     Kern Sibbald, July MMII
8  *
9  *   Version $Id$
10  */
11
12 /*
13    Copyright (C) 2002-2004 Kern Sibbald and John Walker
14
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.
19
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.
24
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,
28    MA 02111-1307, USA.
29
30  */
31
32 #include "bacula.h"
33 #include "dird.h"
34 #include <fnmatch.h>
35 #include "findlib/find.h"
36
37
38 /* Forward referenced commands */
39
40 static int markcmd(UAContext *ua, TREE_CTX *tree);
41 static int countcmd(UAContext *ua, TREE_CTX *tree);
42 static int findcmd(UAContext *ua, TREE_CTX *tree);
43 static int lscmd(UAContext *ua, TREE_CTX *tree);
44 static int lsmark(UAContext *ua, TREE_CTX *tree);
45 static int dircmd(UAContext *ua, TREE_CTX *tree);
46 static int estimatecmd(UAContext *ua, TREE_CTX *tree);
47 static int helpcmd(UAContext *ua, TREE_CTX *tree);
48 static int cdcmd(UAContext *ua, TREE_CTX *tree);
49 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
50 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
51 static int quitcmd(UAContext *ua, TREE_CTX *tree);
52
53
54 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; }; 
55 static struct cmdstruct commands[] = {
56  { N_("cd"),         cdcmd,        _("change current directory")},
57  { N_("count"),      countcmd,     _("count marked files")},
58  { N_("dir"),        dircmd,       _("list current directory")},    
59  { N_("done"),       quitcmd,      _("leave file selection mode")},
60  { N_("estimate"),   estimatecmd,  _("estimate restore size")},
61  { N_("exit"),       quitcmd,      _("exit = done")},
62  { N_("find"),       findcmd,      _("find files")},
63  { N_("help"),       helpcmd,      _("print help")},
64  { N_("lsmark"),     lsmark,       _("list the marked files")},    
65  { N_("ls"),         lscmd,        _("list current directory")},    
66  { N_("mark"),       markcmd,      _("mark file to be restored")},
67  { N_("pwd"),        pwdcmd,       _("print current working directory")},
68  { N_("unmark"),     unmarkcmd,    _("unmark file to be restored")},
69  { N_("?"),          helpcmd,      _("print help")},    
70              };
71 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
72
73
74 /*
75  * Enter a prompt mode where the user can select/deselect
76  *  files to be restored. This is sort of like a mini-shell
77  *  that allows "cd", "pwd", "add", "rm", ...
78  */
79 void user_select_files_from_tree(TREE_CTX *tree)
80 {
81    char cwd[2000];
82    /* Get a new context so we don't destroy restore command args */
83    UAContext *ua = new_ua_context(tree->ua->jcr);
84    ua->UA_sock = tree->ua->UA_sock;   /* patch in UA socket */
85
86    bsendmsg(tree->ua, _( 
87       "\nYou are now entering file selection mode where you add and\n"
88       "remove files to be restored. All files are initially added.\n"
89       "Enter \"done\" to leave this mode.\n\n"));
90    /*
91     * Enter interactive command handler allowing selection
92     *  of individual files.
93     */
94    tree->node = (TREE_NODE *)tree->root;
95    tree_getpath(tree->node, cwd, sizeof(cwd));
96    bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
97    for ( ;; ) {       
98       int found, len, stat, i;
99       if (!get_cmd(ua, "$ ")) {
100          break;
101       }
102       parse_ua_args(ua);
103       if (ua->argc == 0) {
104          break;
105       }
106
107       len = strlen(ua->argk[0]);
108       found = 0;
109       stat = 0;
110       for (i=0; i<(int)comsize; i++)       /* search for command */
111          if (strncasecmp(ua->argk[0],  _(commands[i].key), len) == 0) {
112             stat = (*commands[i].func)(ua, tree);   /* go execute command */
113             found = 1;
114             break;
115          }
116       if (!found) {
117          bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
118          continue;
119       }
120       if (!stat) {
121          break;
122       }
123    }
124    ua->UA_sock = NULL;                /* don't release restore socket */
125    free_ua_context(ua);               /* get rid of temp UA context */
126 }
127
128
129 /*
130  * This callback routine is responsible for inserting the
131  *  items it gets into the directory tree. For each JobId selected
132  *  this routine is called once for each file. We do not allow
133  *  duplicate filenames, but instead keep the info from the most
134  *  recent file entered (i.e. the JobIds are assumed to be sorted)
135  *
136  *   See uar_sel_files in sql_cmds.c for query that calls us.
137  *      row[0]=Path, row[1]=Filename, row[2]=FileIndex
138  *      row[3]=JobId row[4]=LStat
139  */
140 int insert_tree_handler(void *ctx, int num_fields, char **row)
141 {
142    TREE_CTX *tree = (TREE_CTX *)ctx;
143    char fname[5000];
144    TREE_NODE *node, *new_node;
145    int type;
146
147    strip_trailing_junk(row[1]);
148    if (*row[1] == 0) {                /* no filename => directory */
149       if (*row[0] != '/') {           /* Must be Win32 directory */
150          type = TN_DIR_NLS;
151       } else {
152          type = TN_DIR;
153       }
154    } else {
155       type = TN_FILE;
156    }
157    bsnprintf(fname, sizeof(fname), "%s%s", row[0], row[1]);
158    if (tree->avail_node) {
159       node = tree->avail_node;
160    } else {
161       node = new_tree_node(tree->root, type);
162       tree->avail_node = node;
163    }
164    Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
165    new_node = insert_tree_node(fname, node, tree->root, NULL);
166    /* Note, if node already exists, save new one for next time */
167    if (new_node != node) {
168       tree->avail_node = node;
169    } else {
170       tree->avail_node = NULL;
171    }
172    new_node->FileIndex = atoi(row[2]);
173    new_node->JobId = (JobId_t)str_to_int64(row[3]);
174    new_node->type = type;
175    new_node->extract = true;          /* extract all by default */
176    new_node->extract_dir = true;      /* if dir, extract it */
177    new_node->have_link = (decode_LinkFI(row[4]) != 0);
178    tree->cnt++;
179    return 0;
180 }
181
182
183 /*
184  * Set extract to value passed. We recursively walk
185  *  down the tree setting all children if the 
186  *  node is a directory.
187  */
188 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
189 {
190    TREE_NODE *n;
191    FILE_DBR fdbr;
192    struct stat statp;
193    int count = 0;
194
195    node->extract = extract;
196    if (node->type != TN_NEWDIR) {
197       count++;
198    }
199    /* For a non-file (i.e. directory), we see all the children */
200    if (node->type != TN_FILE) {
201       for (n=node->child; n; n=n->sibling) {
202          count += set_extract(ua, n, tree, extract);
203       }
204    } else if (extract) {
205       char cwd[2000];
206       /*
207        * Ordinary file, we get the full path, look up the
208        * attributes, decode them, and if we are hard linked to
209        * a file that was saved, we must load that file too.
210        */
211       tree_getpath(node, cwd, sizeof(cwd));
212       fdbr.FileId = 0;
213       fdbr.JobId = node->JobId;
214       if (node->have_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
215          int32_t LinkFI;
216          decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
217          /*
218           * If we point to a hard linked file, traverse the tree to
219           * find that file, and mark it to be restored as well. It
220           * must have the Link we just obtained and the same JobId.
221           */
222          if (LinkFI) {
223             for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
224                if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
225                   n->extract = true;
226                   break;
227                }
228             }
229          }
230       }
231    }
232    return count;
233 }
234
235 static int markcmd(UAContext *ua, TREE_CTX *tree)
236 {
237    TREE_NODE *node;
238    int count = 0;
239
240    if (ua->argc < 2 || !tree->node->child) {
241       bsendmsg(ua, _("No files marked.\n"));
242       return 1;
243    }
244    for (int i=1; i < ua->argc; i++) {
245       for (node = tree->node->child; node; node=node->sibling) {
246          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
247             count += set_extract(ua, node, tree, true);
248          }
249       }
250    }
251    if (count == 0) {
252       bsendmsg(ua, _("No files marked.\n"));
253    } else {
254       bsendmsg(ua, _("%d file%s marked.\n"), count, count==0?"":"s");
255    }
256    return 1;
257 }
258
259 static int countcmd(UAContext *ua, TREE_CTX *tree)
260 {
261    int total, num_extract;
262
263    total = num_extract = 0;
264    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
265       if (node->type != TN_NEWDIR) {
266          total++;
267          if (node->extract) {
268             num_extract++;
269          }
270       }
271    }
272    bsendmsg(ua, "%d total files. %d marked to be restored.\n", total, num_extract);
273    return 1;
274 }
275
276 static int findcmd(UAContext *ua, TREE_CTX *tree)
277 {
278    char cwd[2000];
279
280    if (ua->argc == 1) {
281       bsendmsg(ua, _("No file specification given.\n"));
282       return 0;
283    }
284    
285    for (int i=1; i < ua->argc; i++) {
286       for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
287          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
288             tree_getpath(node, cwd, sizeof(cwd));
289             bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
290          }
291       }
292    }
293    return 1;
294 }
295
296
297
298 static int lscmd(UAContext *ua, TREE_CTX *tree)
299 {
300    TREE_NODE *node;
301
302    if (!tree->node->child) {     
303       return 1;
304    }
305    for (node = tree->node->child; node; node=node->sibling) {
306       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
307          bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
308             (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
309       }
310    }
311    return 1;
312 }
313
314 /*
315  * Ls command that lists only the marked files
316  */
317 static int lsmark(UAContext *ua, TREE_CTX *tree)
318 {
319    TREE_NODE *node;
320
321    if (!tree->node->child) {     
322       return 1;
323    }
324    for (node = tree->node->child; node; node=node->sibling) {
325       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0 &&
326           node->extract) {
327          bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
328             (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
329       }
330    }
331    return 1;
332 }
333
334
335 extern char *getuser(uid_t uid);
336 extern char *getgroup(gid_t gid);
337
338 /*
339  * This is actually the long form used for "dir"
340  */
341 static void ls_output(char *buf, char *fname, bool extract, struct stat *statp)
342 {
343    char *p, *f;
344    char ec1[30];
345    int n;
346
347    p = encode_mode(statp->st_mode, buf);
348    n = sprintf(p, "  %2d ", (uint32_t)statp->st_nlink);
349    p += n;
350    n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
351    p += n;
352    n = sprintf(p, "%8.8s  ", edit_uint64(statp->st_size, ec1));
353    p += n;
354    p = encode_time(statp->st_ctime, p);
355    *p++ = ' ';
356    if (extract) {
357       *p++ = '*';
358    } else {
359       *p++ = ' ';
360    }
361    for (f=fname; *f; )
362       *p++ = *f++;
363    *p = 0;
364 }
365
366
367 /*
368  * Like ls command, but give more detail on each file
369  */
370 static int dircmd(UAContext *ua, TREE_CTX *tree)
371 {
372    TREE_NODE *node;
373    FILE_DBR fdbr;
374    struct stat statp;
375    char buf[1000];
376    char cwd[1100];
377
378    if (!tree->node->child) {     
379       return 1;
380    }
381    for (node = tree->node->child; node; node=node->sibling) {
382       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
383          tree_getpath(node, cwd, sizeof(cwd));
384          fdbr.FileId = 0;
385          fdbr.JobId = node->JobId;
386          if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
387             int32_t LinkFI;
388             decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
389             ls_output(buf, cwd, node->extract, &statp);
390             bsendmsg(ua, "%s\n", buf);
391          } else {
392             /* Something went wrong getting attributes -- print name */
393             bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
394                (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
395          }
396       }
397    }
398    return 1;
399 }
400
401
402 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
403 {
404    int total, num_extract;
405    uint64_t total_bytes = 0;
406    FILE_DBR fdbr;
407    struct stat statp;
408    char cwd[1100];
409    char ec1[50];
410
411    total = num_extract = 0;
412    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
413       if (node->type != TN_NEWDIR) {
414          total++;
415          /* If regular file, get size */
416          if (node->extract && node->type == TN_FILE) {
417             num_extract++;
418             tree_getpath(node, cwd, sizeof(cwd));
419             fdbr.FileId = 0;
420             fdbr.JobId = node->JobId;
421             if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
422                int32_t LinkFI;
423                decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
424                if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
425                   total_bytes += statp.st_size;
426                }
427             }
428          /* Directory, count only */
429          } else if (node->extract) {
430             num_extract++;
431          }
432       }
433    }
434    bsendmsg(ua, "%d total files; %d marked to be restored; %s bytes.\n", 
435             total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
436    return 1;
437 }
438
439
440
441 static int helpcmd(UAContext *ua, TREE_CTX *tree) 
442 {
443    unsigned int i;
444
445 /* usage(); */
446    bsendmsg(ua, _("  Command    Description\n  =======    ===========\n"));
447    for (i=0; i<comsize; i++) {
448       bsendmsg(ua, _("  %-10s %s\n"), _(commands[i].key), _(commands[i].help));
449    }
450    bsendmsg(ua, "\n");
451    return 1;
452 }
453
454 /*
455  * Change directories.  Note, if the user specifies x: and it fails,
456  *   we assume it is a Win32 absolute cd rather than relative and
457  *   try a second time with /x: ...  Win32 kludge.
458  */
459 static int cdcmd(UAContext *ua, TREE_CTX *tree) 
460 {
461    TREE_NODE *node;
462    char cwd[2000];
463
464    if (ua->argc != 2) {
465       return 1;
466    }
467    node = tree_cwd(ua->argk[1], tree->root, tree->node);
468    if (!node) {
469       /* Try once more if Win32 drive -- make absolute */
470       if (ua->argk[1][1] == ':') {  /* win32 drive */
471          bstrncpy(cwd, "/", sizeof(cwd));
472          bstrncat(cwd, ua->argk[1], sizeof(cwd));
473          node = tree_cwd(cwd, tree->root, tree->node);
474       }
475       if (!node) {
476          bsendmsg(ua, _("Invalid path given.\n"));
477       } else {
478          tree->node = node;
479       }
480    } else {
481       tree->node = node;
482    }
483    tree_getpath(tree->node, cwd, sizeof(cwd));
484    bsendmsg(ua, _("cwd is: %s\n"), cwd);
485    return 1;
486 }
487
488 static int pwdcmd(UAContext *ua, TREE_CTX *tree) 
489 {
490    char cwd[2000];
491    tree_getpath(tree->node, cwd, sizeof(cwd));
492    bsendmsg(ua, _("cwd is: %s\n"), cwd);
493    return 1;
494 }
495
496
497 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
498 {
499    TREE_NODE *node;
500    int count = 0;
501
502    if (ua->argc < 2 || !tree->node->child) {     
503       bsendmsg(ua, _("No files unmarked.\n"));
504       return 1;
505    }
506    for (int i=1; i < ua->argc; i++) {
507       for (node = tree->node->child; node; node=node->sibling) {
508          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
509             count += set_extract(ua, node, tree, false);
510          }
511       }
512    }
513    if (count == 0) {
514       bsendmsg(ua, _("No files unmarked.\n"));
515    } else {
516       bsendmsg(ua, _("%d file%s unmarked.\n"), count, count==0?"":"s");
517    }
518    return 1;
519 }
520
521 static int quitcmd(UAContext *ua, TREE_CTX *tree) 
522 {
523    return 0;
524 }