]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_tree.c
c59d1888fae2d69ffcf13c2497e7c2f019a9c2a0
[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 and\n"
95       "remove 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    strip_trailing_junk(row[1]);
162    if (*row[1] == 0) {                /* no filename => directory */
163       if (*row[0] != '/') {           /* Must be Win32 directory */
164          type = TN_DIR_NLS;
165       } else {
166          type = TN_DIR;
167       }
168    } else {
169       type = TN_FILE;
170    }
171    hard_link = (decode_LinkFI(row[4], &statp) != 0);
172    node = insert_tree_node(row[0], row[1], type, tree->root, NULL);
173    JobId = (JobId_t)str_to_int64(row[3]);
174    FileIndex = atoi(row[2]);
175    /*
176     * - The first time we see a file (node->inserted==true), we accept it.
177     * - In the same JobId, we accept only the first copy of a
178     *   hard linked file (the others are simply pointers).
179     * - In the same JobId, we accept the last copy of any other
180     *   file -- in particular directories.
181     *
182     * All the code to set ok could be condensed to a single
183     *  line, but it would be even harder to read.
184     */
185    ok = true;
186    if (!node->inserted && JobId == node->JobId) {
187       if ((hard_link && FileIndex > node->FileIndex) ||
188           (!hard_link && FileIndex < node->FileIndex)) {
189          ok = false;
190       }
191    }
192    if (ok) {
193       node->hard_link = hard_link;
194       node->FileIndex = FileIndex;
195       node->JobId = JobId;
196       node->type = type;
197       node->soft_link = S_ISLNK(statp.st_mode) != 0;
198       if (tree->all) {
199          node->extract = true;          /* extract all by default */
200          if (type == TN_DIR || type == TN_DIR_NLS) {
201             node->extract_dir = true;   /* if dir, extract it */
202          }
203       }
204    }
205    if (node->inserted) {
206       tree->FileCount++;
207       if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) {
208          bsendmsg(tree->ua, "+");
209          tree->LastCount = tree->FileCount;
210       }
211    }
212    tree->cnt++;
213    return 0;
214 }
215
216
217 /*
218  * Set extract to value passed. We recursively walk
219  *  down the tree setting all children if the 
220  *  node is a directory.
221  */
222 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
223 {
224    TREE_NODE *n;
225    FILE_DBR fdbr;
226    struct stat statp;
227    int count = 0;
228
229    node->extract = extract;
230    if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
231       node->extract_dir = extract;    /* set/clear dir too */
232    }
233    if (node->type != TN_NEWDIR) {
234       count++;
235    }
236    /* For a non-file (i.e. directory), we see all the children */
237    if (node->type != TN_FILE || (node->soft_link && tree_node_has_child(node))) {
238       /* Recursive set children within directory */
239       foreach_child(n, node) {
240          count += set_extract(ua, n, tree, extract);
241       }
242       /*
243        * Walk up tree marking any unextracted parent to be
244        * extracted.
245        */
246       if (extract) {
247          while (node->parent && !node->parent->extract_dir) {
248             node = node->parent;
249             node->extract_dir = true;
250          }
251       }
252    } else if (extract) {
253       char cwd[2000];
254       /*
255        * Ordinary file, we get the full path, look up the
256        * attributes, decode them, and if we are hard linked to
257        * a file that was saved, we must load that file too.
258        */
259       tree_getpath(node, cwd, sizeof(cwd));
260       fdbr.FileId = 0;
261       fdbr.JobId = node->JobId;
262       if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
263          int32_t LinkFI;
264          decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
265          /*
266           * If we point to a hard linked file, traverse the tree to
267           * find that file, and mark it to be restored as well. It
268           * must have the Link we just obtained and the same JobId.
269           */
270          if (LinkFI) {
271             for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
272                if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
273                   n->extract = true;
274                   if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
275                      n->extract_dir = true;
276                   }
277                   break;
278                }
279             }
280          }
281       }
282    }
283    return count;
284 }
285
286 /*
287  * Recursively mark the current directory to be restored as 
288  *  well as all directories and files below it.
289  */
290 static int markcmd(UAContext *ua, TREE_CTX *tree)
291 {
292    TREE_NODE *node;
293    int count = 0;
294    char ec1[50];
295
296    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
297       bsendmsg(ua, _("No files marked.\n"));
298       return 1;
299    }
300    for (int i=1; i < ua->argc; i++) {
301       foreach_child(node, tree->node) {
302          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
303             count += set_extract(ua, node, tree, true);
304          }
305       }
306    }
307    if (count == 0) {
308       bsendmsg(ua, _("No files marked.\n"));
309    } else {
310       bsendmsg(ua, _("%s file%s marked.\n"),        
311                edit_uint64_with_commas(count, ec1), count==0?"":"s");
312    }
313    return 1;
314 }
315
316 static int markdircmd(UAContext *ua, TREE_CTX *tree)
317 {
318    TREE_NODE *node;
319    int count = 0;
320    char ec1[50];
321
322    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
323       bsendmsg(ua, _("No files marked.\n"));
324       return 1;
325    }
326    for (int i=1; i < ua->argc; i++) {
327       foreach_child(node, tree->node) {
328          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
329             if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
330                node->extract_dir = true;
331                count++;
332             }
333          }
334       }
335    }
336    if (count == 0) {
337       bsendmsg(ua, _("No directories marked.\n"));
338    } else {
339       bsendmsg(ua, _("%s director%s marked.\n"), 
340                edit_uint64_with_commas(count, ec1), count==1?"y":"ies");
341    }
342    return 1;
343 }
344
345
346 static int countcmd(UAContext *ua, TREE_CTX *tree)
347 {
348    int total, num_extract;
349    char ec1[50];
350
351    total = num_extract = 0;
352    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
353       if (node->type != TN_NEWDIR) {
354          total++;
355          if (node->extract || node->extract_dir) {
356             num_extract++;
357          }
358       }
359    }
360    bsendmsg(ua, "%s total files/dirs. %d marked to be restored.\n", total, 
361             edit_uint64_with_commas(num_extract, ec1));
362    return 1;
363 }
364
365 static int findcmd(UAContext *ua, TREE_CTX *tree)
366 {
367    char cwd[2000];
368
369    if (ua->argc == 1) {
370       bsendmsg(ua, _("No file specification given.\n"));
371       return 0;
372    }
373    
374    for (int i=1; i < ua->argc; i++) {
375       for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
376          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
377             const char *tag;
378             tree_getpath(node, cwd, sizeof(cwd));
379             if (node->extract) {
380                tag = "*";
381             } else if (node->extract_dir) {
382                tag = "+";
383             } else {
384                tag = "";
385             }
386             bsendmsg(ua, "%s%s\n", tag, cwd);
387          }
388       }
389    }
390    return 1;
391 }
392
393
394
395 static int lscmd(UAContext *ua, TREE_CTX *tree)
396 {
397    TREE_NODE *node;
398
399    if (!tree_node_has_child(tree->node)) {     
400       return 1;
401    }
402    foreach_child(node, tree->node) {
403       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
404          const char *tag;
405          if (node->extract) {
406             tag = "*";
407          } else if (node->extract_dir) {
408             tag = "+";
409          } else {
410             tag = "";
411          }
412          bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
413       }
414    }
415    return 1;
416 }
417
418 /*
419  * Ls command that lists only the marked files
420  */
421 static void rlsmark(UAContext *ua, TREE_NODE *node) 
422 {
423    if (!tree_node_has_child(node)) {     
424       return;
425    }
426    foreach_child(node, node) {
427       if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
428           (node->extract || node->extract_dir)) {
429          const char *tag;
430          if (node->extract) {
431             tag = "*";
432          } else if (node->extract_dir) {
433             tag = "+";
434          } else {
435             tag = "";
436          }
437          bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
438          if (tree_node_has_child(node)) {
439             rlsmark(ua, node);
440          }
441       }
442    }
443 }
444
445 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
446 {
447    rlsmark(ua, tree->node);
448    return 1;
449 }
450
451
452
453 extern char *getuser(uid_t uid);
454 extern char *getgroup(gid_t gid);
455
456 /*
457  * This is actually the long form used for "dir"
458  */
459 static void ls_output(char *buf, const char *fname, const char *tag, struct stat *statp)
460 {
461    char *p;
462    const char *f;
463    char ec1[30];
464    int n;
465
466    p = encode_mode(statp->st_mode, buf);
467    n = sprintf(p, "  %2d ", (uint32_t)statp->st_nlink);
468    p += n;
469    n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
470    p += n;
471    n = sprintf(p, "%8.8s  ", edit_uint64(statp->st_size, ec1));
472    p += n;
473    p = encode_time(statp->st_ctime, p);
474    *p++ = ' ';
475    *p++ = *tag;
476    for (f=fname; *f; ) {
477       *p++ = *f++;
478    }
479    *p = 0;
480 }
481
482
483 /*
484  * Like ls command, but give more detail on each file
485  */
486 static int dircmd(UAContext *ua, TREE_CTX *tree)
487 {
488    TREE_NODE *node;
489    FILE_DBR fdbr;
490    struct stat statp;
491    char buf[1100];
492    char cwd[1100], *pcwd;
493
494    if (!tree_node_has_child(tree->node)) {     
495       bsendmsg(ua, "Node %s has no children.\n", tree->node->fname);
496       return 1;
497    }
498
499    foreach_child(node, tree->node) {
500       const char *tag;
501       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
502          if (node->extract) {
503             tag = "*";
504          } else if (node->extract_dir) {
505             tag = "+";
506          } else {
507             tag = " ";
508          }
509          tree_getpath(node, cwd, sizeof(cwd));
510          fdbr.FileId = 0;
511          fdbr.JobId = node->JobId;
512          /*
513           * Strip / from soft links to directories.
514           *   This is because soft links to files have a trailing slash
515           *   when returned from tree_getpath, but db_get_file_attr...
516           *   treats soft links as files, so they do not have a trailing
517           *   slash like directory names.
518           */
519          if (node->type == TN_FILE && tree_node_has_child(node)) {
520             bstrncpy(buf, cwd, sizeof(buf));
521             pcwd = buf;
522             int len = strlen(buf);
523             if (len > 1) {
524                buf[len-1] = 0;        /* strip trailing / */
525             }
526          } else {
527             pcwd = cwd;
528          }
529          if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
530             int32_t LinkFI;
531             decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
532          } else {
533             /* Something went wrong getting attributes -- print name */
534             memset(&statp, 0, sizeof(statp));
535          }
536          ls_output(buf, cwd, tag, &statp);
537          bsendmsg(ua, "%s\n", buf);
538       }
539    }
540    return 1;
541 }
542
543
544 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
545 {
546    int total, num_extract;
547    uint64_t total_bytes = 0;
548    FILE_DBR fdbr;
549    struct stat statp;
550    char cwd[1100];
551    char ec1[50];
552
553    total = num_extract = 0;
554    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
555       if (node->type != TN_NEWDIR) {
556          total++;
557          /* If regular file, get size */
558          if (node->extract && node->type == TN_FILE) {
559             num_extract++;
560             tree_getpath(node, cwd, sizeof(cwd));
561             fdbr.FileId = 0;
562             fdbr.JobId = node->JobId;
563             if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
564                int32_t LinkFI;
565                decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
566                if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
567                   total_bytes += statp.st_size;
568                }
569             }
570          /* Directory, count only */
571          } else if (node->extract || node->extract_dir) {
572             num_extract++;
573          }
574       }
575    }
576    bsendmsg(ua, "%d total files; %d marked to be restored; %s bytes.\n", 
577             total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
578    return 1;
579 }
580
581
582
583 static int helpcmd(UAContext *ua, TREE_CTX *tree) 
584 {
585    unsigned int i;
586
587 /* usage(); */
588    bsendmsg(ua, _("  Command    Description\n  =======    ===========\n"));
589    for (i=0; i<comsize; i++) {
590       bsendmsg(ua, _("  %-10s %s\n"), _(commands[i].key), _(commands[i].help));
591    }
592    bsendmsg(ua, "\n");
593    return 1;
594 }
595
596 /*
597  * Change directories.  Note, if the user specifies x: and it fails,
598  *   we assume it is a Win32 absolute cd rather than relative and
599  *   try a second time with /x: ...  Win32 kludge.
600  */
601 static int cdcmd(UAContext *ua, TREE_CTX *tree) 
602 {
603    TREE_NODE *node;
604    char cwd[2000];
605
606    if (ua->argc != 2) {
607       return 1;
608    }
609    node = tree_cwd(ua->argk[1], tree->root, tree->node);
610    if (!node) {
611       /* Try once more if Win32 drive -- make absolute */
612       if (ua->argk[1][1] == ':') {  /* win32 drive */
613          bstrncpy(cwd, "/", sizeof(cwd));
614          bstrncat(cwd, ua->argk[1], sizeof(cwd));
615          node = tree_cwd(cwd, tree->root, tree->node);
616       }
617       if (!node) {
618          bsendmsg(ua, _("Invalid path given.\n"));
619       } else {
620          tree->node = node;
621       }
622    } else {
623       tree->node = node;
624    }
625    tree_getpath(tree->node, cwd, sizeof(cwd));
626    bsendmsg(ua, _("cwd is: %s\n"), cwd);
627    return 1;
628 }
629
630 static int pwdcmd(UAContext *ua, TREE_CTX *tree) 
631 {
632    char cwd[2000];
633    tree_getpath(tree->node, cwd, sizeof(cwd));
634    bsendmsg(ua, _("cwd is: %s\n"), cwd);
635    return 1;
636 }
637
638
639 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
640 {
641    TREE_NODE *node;
642    int count = 0;
643
644    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {     
645       bsendmsg(ua, _("No files unmarked.\n"));
646       return 1;
647    }
648    for (int i=1; i < ua->argc; i++) {
649       foreach_child(node, tree->node) {
650          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
651             count += set_extract(ua, node, tree, false);
652          }
653       }
654    }
655    if (count == 0) {
656       bsendmsg(ua, _("No files unmarked.\n"));
657    } else {
658       bsendmsg(ua, _("%d file%s unmarked.\n"), count, count==0?"":"s");
659    }
660    return 1;
661 }
662
663 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
664 {
665    TREE_NODE *node;
666    int count = 0;
667
668    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
669       bsendmsg(ua, _("No directories unmarked.\n"));
670       return 1;
671    }
672
673    for (int i=1; i < ua->argc; i++) {
674       foreach_child(node, tree->node) {
675          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
676             if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
677                node->extract_dir = false;
678                count++;
679             }
680          }
681       }
682    }
683
684    if (count == 0) {
685       bsendmsg(ua, _("No directories unmarked.\n"));
686    } else {
687       bsendmsg(ua, _("%d director%s unmarked.\n"), count, count==1?"y":"ies");
688    }
689    return 1;
690 }
691
692
693 static int donecmd(UAContext *ua, TREE_CTX *tree) 
694 {
695    return 0;
696 }
697
698 static int quitcmd(UAContext *ua, TREE_CTX *tree) 
699 {
700    ua->quit = true;
701    return 0;
702 }