]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_tree.c
Add heap stats to Dir and SD -- eliminate #ifdefs
[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. All files are initially added.\n"
96       "Enter \"done\" to leave this mode.\n\n"));
97    /*
98     * Enter interactive command handler allowing selection
99     *  of individual files.
100     */
101    tree->node = (TREE_NODE *)tree->root;
102    tree_getpath(tree->node, cwd, sizeof(cwd));
103    bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
104    for ( ;; ) {       
105       int found, len, i;
106       if (!get_cmd(ua, "$ ")) {
107          break;
108       }
109       parse_ua_args(ua);
110       if (ua->argc == 0) {
111          break;
112       }
113
114       len = strlen(ua->argk[0]);
115       found = 0;
116       stat = false;
117       for (i=0; i<(int)comsize; i++)       /* search for command */
118          if (strncasecmp(ua->argk[0],  _(commands[i].key), len) == 0) {
119             stat = (*commands[i].func)(ua, tree);   /* go execute command */
120             found = 1;
121             break;
122          }
123       if (!found) {
124          bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
125          continue;
126       }
127       if (!stat) {
128          break;
129       }
130    }
131    ua->UA_sock = NULL;                /* don't release restore socket */
132    stat = !ua->quit;
133    ua->quit = false;
134    free_ua_context(ua);               /* get rid of temp UA context */
135    return stat;
136 }
137
138
139 /*
140  * This callback routine is responsible for inserting the
141  *  items it gets into the directory tree. For each JobId selected
142  *  this routine is called once for each file. We do not allow
143  *  duplicate filenames, but instead keep the info from the most
144  *  recent file entered (i.e. the JobIds are assumed to be sorted)
145  *
146  *   See uar_sel_files in sql_cmds.c for query that calls us.
147  *      row[0]=Path, row[1]=Filename, row[2]=FileIndex
148  *      row[3]=JobId row[4]=LStat
149  */
150 int insert_tree_handler(void *ctx, int num_fields, char **row)
151 {
152    struct stat statp;
153    TREE_CTX *tree = (TREE_CTX *)ctx;
154    TREE_NODE *node;
155    int type;
156    bool hard_link, ok;
157    int FileIndex;
158    JobId_t JobId;
159
160    strip_trailing_junk(row[1]);
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 = atoi(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 }