]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_tree.c
da926e1fcda370e8c187723c321cab164a37986d
[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 markdircmd(UAContext *ua, TREE_CTX *tree);
42 static int countcmd(UAContext *ua, TREE_CTX *tree);
43 static int findcmd(UAContext *ua, TREE_CTX *tree);
44 static int lscmd(UAContext *ua, TREE_CTX *tree);
45 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree);
46 static int dircmd(UAContext *ua, TREE_CTX *tree);
47 static int estimatecmd(UAContext *ua, TREE_CTX *tree);
48 static int helpcmd(UAContext *ua, TREE_CTX *tree);
49 static int cdcmd(UAContext *ua, TREE_CTX *tree);
50 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
51 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
52 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree);
53 static int quitcmd(UAContext *ua, TREE_CTX *tree);
54 static int donecmd(UAContext *ua, TREE_CTX *tree);
55
56
57 struct cmdstruct { const char *key; int (*func)(UAContext *ua, TREE_CTX *tree); const char *help; }; 
58 static struct cmdstruct commands[] = {
59  { N_("cd"),         cdcmd,        _("change current directory")},
60  { N_("count"),      countcmd,     _("count marked files in and below the cd")},
61  { N_("dir"),        dircmd,       _("list current directory")},    
62  { N_("done"),       donecmd,      _("leave file selection mode")},
63  { N_("estimate"),   estimatecmd,  _("estimate restore size")},
64  { N_("exit"),       donecmd,      _("exit = done")},
65  { N_("find"),       findcmd,      _("find files -- wildcards allowed")},
66  { N_("help"),       helpcmd,      _("print help")},
67  { N_("ls"),         lscmd,        _("list current directory -- wildcards allowed")},    
68  { N_("lsmark"),     lsmarkcmd,    _("list the marked files in and below the cd")},    
69  { N_("mark"),       markcmd,      _("mark dir/file to be restored -- recursively in dirs")},
70  { N_("markdir"),    markdircmd,   _("mark directory name to be restored (no files)")},
71  { N_("pwd"),        pwdcmd,       _("print current working directory")},
72  { N_("unmark"),     unmarkcmd,    _("unmark dir/file to be restored -- recursively in dir")},
73  { N_("unmarkdir"),  unmarkdircmd, _("unmark directory name only -- no recursion")},
74  { N_("quit"),       quitcmd,      _("quit")},
75  { N_("?"),          helpcmd,      _("print help")},    
76              };
77 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
78
79
80 /*
81  * Enter a prompt mode where the user can select/deselect
82  *  files to be restored. This is sort of like a mini-shell
83  *  that allows "cd", "pwd", "add", "rm", ...
84  */
85 bool user_select_files_from_tree(TREE_CTX *tree)
86 {
87    char cwd[2000];
88    bool stat;
89    /* Get a new context so we don't destroy restore command args */
90    UAContext *ua = new_ua_context(tree->ua->jcr);
91    ua->UA_sock = tree->ua->UA_sock;   /* patch in UA socket */
92
93    bsendmsg(tree->ua, _( 
94       "\nYou are now entering file selection mode where you add (mark) and\n"
95       "remove (unmark) files to be restored. No files are initially added, unless\n"
96       "you used the \"all\" keyword on the command line.\n"
97       "Enter \"done\" to leave this mode.\n\n"));
98    /*
99     * Enter interactive command handler allowing selection
100     *  of individual files.
101     */
102    tree->node = (TREE_NODE *)tree->root;
103    tree_getpath(tree->node, cwd, sizeof(cwd));
104    bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
105    for ( ;; ) {       
106       int found, len, i;
107       if (!get_cmd(ua, "$ ")) {
108          break;
109       }
110       parse_ua_args(ua);
111       if (ua->argc == 0) {
112          break;
113       }
114
115       len = strlen(ua->argk[0]);
116       found = 0;
117       stat = false;
118       for (i=0; i<(int)comsize; i++)       /* search for command */
119          if (strncasecmp(ua->argk[0],  _(commands[i].key), len) == 0) {
120             stat = (*commands[i].func)(ua, tree);   /* go execute command */
121             found = 1;
122             break;
123          }
124       if (!found) {
125          bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
126          continue;
127       }
128       if (!stat) {
129          break;
130       }
131    }
132    ua->UA_sock = NULL;                /* don't release restore socket */
133    stat = !ua->quit;
134    ua->quit = false;
135    free_ua_context(ua);               /* get rid of temp UA context */
136    return stat;
137 }
138
139
140 /*
141  * This callback routine is responsible for inserting the
142  *  items it gets into the directory tree. For each JobId selected
143  *  this routine is called once for each file. We do not allow
144  *  duplicate filenames, but instead keep the info from the most
145  *  recent file entered (i.e. the JobIds are assumed to be sorted)
146  *
147  *   See uar_sel_files in sql_cmds.c for query that calls us.
148  *      row[0]=Path, row[1]=Filename, row[2]=FileIndex
149  *      row[3]=JobId row[4]=LStat
150  */
151 int insert_tree_handler(void *ctx, int num_fields, char **row)
152 {
153    struct stat statp;
154    TREE_CTX *tree = (TREE_CTX *)ctx;
155    TREE_NODE *node;
156    int type;
157    bool hard_link, ok;
158    int FileIndex;
159    JobId_t JobId;
160
161    if (*row[1] == 0) {                /* no filename => directory */
162       if (*row[0] != '/') {           /* Must be Win32 directory */
163          type = TN_DIR_NLS;
164       } else {
165          type = TN_DIR;
166       }
167    } else {
168       type = TN_FILE;
169    }
170    hard_link = (decode_LinkFI(row[4], &statp) != 0);
171    node = insert_tree_node(row[0], row[1], type, tree->root, NULL);
172    JobId = (JobId_t)str_to_int64(row[3]);
173    FileIndex = str_to_int64(row[2]);
174    /*
175     * - The first time we see a file (node->inserted==true), we accept it.
176     * - In the same JobId, we accept only the first copy of a
177     *   hard linked file (the others are simply pointers).
178     * - In the same JobId, we accept the last copy of any other
179     *   file -- in particular directories.
180     *
181     * All the code to set ok could be condensed to a single
182     *  line, but it would be even harder to read.
183     */
184    ok = true;
185    if (!node->inserted && JobId == node->JobId) {
186       if ((hard_link && FileIndex > node->FileIndex) ||
187           (!hard_link && FileIndex < node->FileIndex)) {
188          ok = false;
189       }
190    }
191    if (ok) {
192       node->hard_link = hard_link;
193       node->FileIndex = FileIndex;
194       node->JobId = JobId;
195       node->type = type;
196       node->soft_link = S_ISLNK(statp.st_mode) != 0;
197       if (tree->all) {
198          node->extract = true;          /* extract all by default */
199          if (type == TN_DIR || type == TN_DIR_NLS) {
200             node->extract_dir = true;   /* if dir, extract it */
201          }
202       }
203    }
204    if (node->inserted) {
205       tree->FileCount++;
206       if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) {
207          bsendmsg(tree->ua, "+");
208          tree->LastCount = tree->FileCount;
209       }
210    }
211    tree->cnt++;
212    return 0;
213 }
214
215
216 /*
217  * Set extract to value passed. We recursively walk
218  *  down the tree setting all children if the 
219  *  node is a directory.
220  */
221 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
222 {
223    TREE_NODE *n;
224    FILE_DBR fdbr;
225    struct stat statp;
226    int count = 0;
227
228    node->extract = extract;
229    if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
230       node->extract_dir = extract;    /* set/clear dir too */
231    }
232    if (node->type != TN_NEWDIR) {
233       count++;
234    }
235    /* For a non-file (i.e. directory), we see all the children */
236    if (node->type != TN_FILE || (node->soft_link && tree_node_has_child(node))) {
237       /* Recursive set children within directory */
238       foreach_child(n, node) {
239          count += set_extract(ua, n, tree, extract);
240       }
241       /*
242        * Walk up tree marking any unextracted parent to be
243        * extracted.
244        */
245       if (extract) {
246          while (node->parent && !node->parent->extract_dir) {
247             node = node->parent;
248             node->extract_dir = true;
249          }
250       }
251    } else if (extract) {
252       char cwd[2000];
253       /*
254        * Ordinary file, we get the full path, look up the
255        * attributes, decode them, and if we are hard linked to
256        * a file that was saved, we must load that file too.
257        */
258       tree_getpath(node, cwd, sizeof(cwd));
259       fdbr.FileId = 0;
260       fdbr.JobId = node->JobId;
261       if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
262          int32_t LinkFI;
263          decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
264          /*
265           * If we point to a hard linked file, traverse the tree to
266           * find that file, and mark it to be restored as well. It
267           * must have the Link we just obtained and the same JobId.
268           */
269          if (LinkFI) {
270             for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
271                if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
272                   n->extract = true;
273                   if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
274                      n->extract_dir = true;
275                   }
276                   break;
277                }
278             }
279          }
280       }
281    }
282    return count;
283 }
284
285 /*
286  * Recursively mark the current directory to be restored as 
287  *  well as all directories and files below it.
288  */
289 static int markcmd(UAContext *ua, TREE_CTX *tree)
290 {
291    TREE_NODE *node;
292    int count = 0;
293    char ec1[50];
294
295    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
296       bsendmsg(ua, _("No files marked.\n"));
297       return 1;
298    }
299    for (int i=1; i < ua->argc; i++) {
300       foreach_child(node, tree->node) {
301          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
302             count += set_extract(ua, node, tree, true);
303          }
304       }
305    }
306    if (count == 0) {
307       bsendmsg(ua, _("No files marked.\n"));
308    } else {
309       bsendmsg(ua, _("%s file%s marked.\n"),        
310                edit_uint64_with_commas(count, ec1), count==0?"":"s");
311    }
312    return 1;
313 }
314
315 static int markdircmd(UAContext *ua, TREE_CTX *tree)
316 {
317    TREE_NODE *node;
318    int count = 0;
319    char ec1[50];
320
321    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
322       bsendmsg(ua, _("No files marked.\n"));
323       return 1;
324    }
325    for (int i=1; i < ua->argc; i++) {
326       foreach_child(node, tree->node) {
327          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
328             if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
329                node->extract_dir = true;
330                count++;
331             }
332          }
333       }
334    }
335    if (count == 0) {
336       bsendmsg(ua, _("No directories marked.\n"));
337    } else {
338       bsendmsg(ua, _("%s director%s marked.\n"), 
339                edit_uint64_with_commas(count, ec1), count==1?"y":"ies");
340    }
341    return 1;
342 }
343
344
345 static int countcmd(UAContext *ua, TREE_CTX *tree)
346 {
347    int total, num_extract;
348    char ec1[50];
349
350    total = num_extract = 0;
351    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
352       if (node->type != TN_NEWDIR) {
353          total++;
354          if (node->extract || node->extract_dir) {
355             num_extract++;
356          }
357       }
358    }
359    bsendmsg(ua, "%s total files/dirs. %d marked to be restored.\n", total, 
360             edit_uint64_with_commas(num_extract, ec1));
361    return 1;
362 }
363
364 static int findcmd(UAContext *ua, TREE_CTX *tree)
365 {
366    char cwd[2000];
367
368    if (ua->argc == 1) {
369       bsendmsg(ua, _("No file specification given.\n"));
370       return 0;
371    }
372    
373    for (int i=1; i < ua->argc; i++) {
374       for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
375          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
376             const char *tag;
377             tree_getpath(node, cwd, sizeof(cwd));
378             if (node->extract) {
379                tag = "*";
380             } else if (node->extract_dir) {
381                tag = "+";
382             } else {
383                tag = "";
384             }
385             bsendmsg(ua, "%s%s\n", tag, cwd);
386          }
387       }
388    }
389    return 1;
390 }
391
392
393
394 static int lscmd(UAContext *ua, TREE_CTX *tree)
395 {
396    TREE_NODE *node;
397
398    if (!tree_node_has_child(tree->node)) {     
399       return 1;
400    }
401    foreach_child(node, tree->node) {
402       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
403          const char *tag;
404          if (node->extract) {
405             tag = "*";
406          } else if (node->extract_dir) {
407             tag = "+";
408          } else {
409             tag = "";
410          }
411          bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
412       }
413    }
414    return 1;
415 }
416
417 /*
418  * Ls command that lists only the marked files
419  */
420 static void rlsmark(UAContext *ua, TREE_NODE *node) 
421 {
422    if (!tree_node_has_child(node)) {     
423       return;
424    }
425    foreach_child(node, node) {
426       if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
427           (node->extract || node->extract_dir)) {
428          const char *tag;
429          if (node->extract) {
430             tag = "*";
431          } else if (node->extract_dir) {
432             tag = "+";
433          } else {
434             tag = "";
435          }
436          bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
437          if (tree_node_has_child(node)) {
438             rlsmark(ua, node);
439          }
440       }
441    }
442 }
443
444 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
445 {
446    rlsmark(ua, tree->node);
447    return 1;
448 }
449
450
451
452 extern char *getuser(uid_t uid);
453 extern char *getgroup(gid_t gid);
454
455 /*
456  * This is actually the long form used for "dir"
457  */
458 static void ls_output(char *buf, const char *fname, const char *tag, struct stat *statp)
459 {
460    char *p;
461    const char *f;
462    char ec1[30];
463    int n;
464
465    p = encode_mode(statp->st_mode, buf);
466    n = sprintf(p, "  %2d ", (uint32_t)statp->st_nlink);
467    p += n;
468    n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
469    p += n;
470    n = sprintf(p, "%8.8s  ", edit_uint64(statp->st_size, ec1));
471    p += n;
472    p = encode_time(statp->st_ctime, p);
473    *p++ = ' ';
474    *p++ = *tag;
475    for (f=fname; *f; ) {
476       *p++ = *f++;
477    }
478    *p = 0;
479 }
480
481
482 /*
483  * Like ls command, but give more detail on each file
484  */
485 static int dircmd(UAContext *ua, TREE_CTX *tree)
486 {
487    TREE_NODE *node;
488    FILE_DBR fdbr;
489    struct stat statp;
490    char buf[1100];
491    char cwd[1100], *pcwd;
492
493    if (!tree_node_has_child(tree->node)) {     
494       bsendmsg(ua, "Node %s has no children.\n", tree->node->fname);
495       return 1;
496    }
497
498    foreach_child(node, tree->node) {
499       const char *tag;
500       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
501          if (node->extract) {
502             tag = "*";
503          } else if (node->extract_dir) {
504             tag = "+";
505          } else {
506             tag = " ";
507          }
508          tree_getpath(node, cwd, sizeof(cwd));
509          fdbr.FileId = 0;
510          fdbr.JobId = node->JobId;
511          /*
512           * Strip / from soft links to directories.
513           *   This is because soft links to files have a trailing slash
514           *   when returned from tree_getpath, but db_get_file_attr...
515           *   treats soft links as files, so they do not have a trailing
516           *   slash like directory names.
517           */
518          if (node->type == TN_FILE && tree_node_has_child(node)) {
519             bstrncpy(buf, cwd, sizeof(buf));
520             pcwd = buf;
521             int len = strlen(buf);
522             if (len > 1) {
523                buf[len-1] = 0;        /* strip trailing / */
524             }
525          } else {
526             pcwd = cwd;
527          }
528          if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
529             int32_t LinkFI;
530             decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
531          } else {
532             /* Something went wrong getting attributes -- print name */
533             memset(&statp, 0, sizeof(statp));
534          }
535          ls_output(buf, cwd, tag, &statp);
536          bsendmsg(ua, "%s\n", buf);
537       }
538    }
539    return 1;
540 }
541
542
543 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
544 {
545    int total, num_extract;
546    uint64_t total_bytes = 0;
547    FILE_DBR fdbr;
548    struct stat statp;
549    char cwd[1100];
550    char ec1[50];
551
552    total = num_extract = 0;
553    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
554       if (node->type != TN_NEWDIR) {
555          total++;
556          /* If regular file, get size */
557          if (node->extract && node->type == TN_FILE) {
558             num_extract++;
559             tree_getpath(node, cwd, sizeof(cwd));
560             fdbr.FileId = 0;
561             fdbr.JobId = node->JobId;
562             if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
563                int32_t LinkFI;
564                decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
565                if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
566                   total_bytes += statp.st_size;
567                }
568             }
569          /* Directory, count only */
570          } else if (node->extract || node->extract_dir) {
571             num_extract++;
572          }
573       }
574    }
575    bsendmsg(ua, "%d total files; %d marked to be restored; %s bytes.\n", 
576             total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
577    return 1;
578 }
579
580
581
582 static int helpcmd(UAContext *ua, TREE_CTX *tree) 
583 {
584    unsigned int i;
585
586 /* usage(); */
587    bsendmsg(ua, _("  Command    Description\n  =======    ===========\n"));
588    for (i=0; i<comsize; i++) {
589       bsendmsg(ua, _("  %-10s %s\n"), _(commands[i].key), _(commands[i].help));
590    }
591    bsendmsg(ua, "\n");
592    return 1;
593 }
594
595 /*
596  * Change directories.  Note, if the user specifies x: and it fails,
597  *   we assume it is a Win32 absolute cd rather than relative and
598  *   try a second time with /x: ...  Win32 kludge.
599  */
600 static int cdcmd(UAContext *ua, TREE_CTX *tree) 
601 {
602    TREE_NODE *node;
603    char cwd[2000];
604
605    if (ua->argc != 2) {
606       return 1;
607    }
608    node = tree_cwd(ua->argk[1], tree->root, tree->node);
609    if (!node) {
610       /* Try once more if Win32 drive -- make absolute */
611       if (ua->argk[1][1] == ':') {  /* win32 drive */
612          bstrncpy(cwd, "/", sizeof(cwd));
613          bstrncat(cwd, ua->argk[1], sizeof(cwd));
614          node = tree_cwd(cwd, tree->root, tree->node);
615       }
616       if (!node) {
617          bsendmsg(ua, _("Invalid path given.\n"));
618       } else {
619          tree->node = node;
620       }
621    } else {
622       tree->node = node;
623    }
624    tree_getpath(tree->node, cwd, sizeof(cwd));
625    bsendmsg(ua, _("cwd is: %s\n"), cwd);
626    return 1;
627 }
628
629 static int pwdcmd(UAContext *ua, TREE_CTX *tree) 
630 {
631    char cwd[2000];
632    tree_getpath(tree->node, cwd, sizeof(cwd));
633    bsendmsg(ua, _("cwd is: %s\n"), cwd);
634    return 1;
635 }
636
637
638 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
639 {
640    TREE_NODE *node;
641    int count = 0;
642
643    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {     
644       bsendmsg(ua, _("No files unmarked.\n"));
645       return 1;
646    }
647    for (int i=1; i < ua->argc; i++) {
648       foreach_child(node, tree->node) {
649          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
650             count += set_extract(ua, node, tree, false);
651          }
652       }
653    }
654    if (count == 0) {
655       bsendmsg(ua, _("No files unmarked.\n"));
656    } else {
657       bsendmsg(ua, _("%d file%s unmarked.\n"), count, count==0?"":"s");
658    }
659    return 1;
660 }
661
662 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
663 {
664    TREE_NODE *node;
665    int count = 0;
666
667    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
668       bsendmsg(ua, _("No directories unmarked.\n"));
669       return 1;
670    }
671
672    for (int i=1; i < ua->argc; i++) {
673       foreach_child(node, tree->node) {
674          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
675             if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
676                node->extract_dir = false;
677                count++;
678             }
679          }
680       }
681    }
682
683    if (count == 0) {
684       bsendmsg(ua, _("No directories unmarked.\n"));
685    } else {
686       bsendmsg(ua, _("%d director%s unmarked.\n"), count, count==1?"y":"ies");
687    }
688    return 1;
689 }
690
691
692 static int donecmd(UAContext *ua, TREE_CTX *tree) 
693 {
694    return 0;
695 }
696
697 static int quitcmd(UAContext *ua, TREE_CTX *tree) 
698 {
699    ua->quit = true;
700    return 0;
701 }