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