]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_tree.c
Massive bat notifier rewrite + fix seg fault + implement text input dialog
[bacula/bacula] / bacula / src / dird / ua_tree.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2002-2009 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version three of the GNU Affero General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU Affero General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of Kern Sibbald.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  *
30  *   Bacula Director -- User Agent Database File tree for Restore
31  *      command. This file interacts with the user implementing the
32  *      UA tree commands.
33  *
34  *     Kern Sibbald, July MMII
35  *
36  *   Version $Id$
37  */
38
39 #include "bacula.h"
40 #include "dird.h"
41 #ifdef HAVE_FNMATCH
42 #include <fnmatch.h>
43 #else
44 #include "lib/fnmatch.h"
45 #endif
46 #include "findlib/find.h"
47
48
49 /* Forward referenced commands */
50
51 static int markcmd(UAContext *ua, TREE_CTX *tree);
52 static int markdircmd(UAContext *ua, TREE_CTX *tree);
53 static int countcmd(UAContext *ua, TREE_CTX *tree);
54 static int findcmd(UAContext *ua, TREE_CTX *tree);
55 static int lscmd(UAContext *ua, TREE_CTX *tree);
56 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree);
57 static int dircmd(UAContext *ua, TREE_CTX *tree);
58 static int dot_dircmd(UAContext *ua, TREE_CTX *tree);
59 static int estimatecmd(UAContext *ua, TREE_CTX *tree);
60 static int helpcmd(UAContext *ua, TREE_CTX *tree);
61 static int cdcmd(UAContext *ua, TREE_CTX *tree);
62 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
63 static int dot_pwdcmd(UAContext *ua, TREE_CTX *tree);
64 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
65 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree);
66 static int quitcmd(UAContext *ua, TREE_CTX *tree);
67 static int donecmd(UAContext *ua, TREE_CTX *tree);
68 static int dot_lsdircmd(UAContext *ua, TREE_CTX *tree);
69 static int dot_lscmd(UAContext *ua, TREE_CTX *tree);
70 static int dot_helpcmd(UAContext *ua, TREE_CTX *tree);
71 static int dot_lsmarkcmd(UAContext *ua, TREE_CTX *tree);
72
73 struct cmdstruct { const char *key; int (*func)(UAContext *ua, TREE_CTX *tree); const char *help; };
74 static struct cmdstruct commands[] = {
75  { NT_("add"),        markcmd,      _("add dir/file to be restored recursively, wildcards allowed")},
76  { NT_("cd"),         cdcmd,        _("change current directory")},
77  { NT_("count"),      countcmd,     _("count marked files in and below the cd")},
78  { NT_("delete"),     unmarkcmd,    _("delete dir/file to be restored recursively in dir")},
79  { NT_("dir"),        dircmd,       _("long list current directory, wildcards allowed")},
80  { NT_(".dir"),       dot_dircmd,   _("long list current directory, wildcards allowed")},
81  { NT_("done"),       donecmd,      _("leave file selection mode")},
82  { NT_("estimate"),   estimatecmd,  _("estimate restore size")},
83  { NT_("exit"),       donecmd,      _("same as done command")},
84  { NT_("find"),       findcmd,      _("find files, wildcards allowed")},
85  { NT_("help"),       helpcmd,      _("print help")},
86  { NT_("ls"),         lscmd,        _("list current directory, wildcards allowed")},
87  { NT_(".ls"),        dot_lscmd,    _("list current directory, wildcards allowed")},
88  { NT_(".lsdir"),     dot_lsdircmd, _("list subdir in current directory, wildcards allowed")},
89  { NT_("lsmark"),     lsmarkcmd,    _("list the marked files in and below the cd")},
90  { NT_(".lsmark"),    dot_lsmarkcmd,_("list the marked files in")},
91  { NT_("mark"),       markcmd,      _("mark dir/file to be restored recursively, wildcards allowed")},
92  { NT_("markdir"),    markdircmd,   _("mark directory name to be restored (no files)")},
93  { NT_("pwd"),        pwdcmd,       _("print current working directory")},
94  { NT_(".pwd"),       dot_pwdcmd,   _("print current working directory")},
95  { NT_("unmark"),     unmarkcmd,    _("unmark dir/file to be restored recursively in dir")},
96  { NT_("unmarkdir"),  unmarkdircmd, _("unmark directory name only no recursion")},
97  { NT_("quit"),       quitcmd,      _("quit and do not do restore")},
98  { NT_(".help"),      dot_helpcmd,  _("print help")},
99  { NT_("?"),          helpcmd,      _("print help")},
100              };
101 #define comsize ((int)(sizeof(commands)/sizeof(struct cmdstruct)))
102
103 /*
104  * Enter a prompt mode where the user can select/deselect
105  *  files to be restored. This is sort of like a mini-shell
106  *  that allows "cd", "pwd", "add", "rm", ...
107  */
108 bool user_select_files_from_tree(TREE_CTX *tree)
109 {
110    char cwd[2000];
111    bool stat;
112    /* Get a new context so we don't destroy restore command args */
113    UAContext *ua = new_ua_context(tree->ua->jcr);
114    ua->UA_sock = tree->ua->UA_sock;   /* patch in UA socket */
115    ua->api = tree->ua->api;           /* keep API flag too */
116    BSOCK *user = ua->UA_sock;
117
118    ua->send_msg(_(
119       "\nYou are now entering file selection mode where you add (mark) and\n"
120       "remove (unmark) files to be restored. No files are initially added, unless\n"
121       "you used the \"all\" keyword on the command line.\n"
122       "Enter \"done\" to leave this mode.\n\n"));
123    if (ua->api) user->signal(BNET_START_RTREE);
124    /*
125     * Enter interactive command handler allowing selection
126     *  of individual files.
127     */
128    tree->node = (TREE_NODE *)tree->root;
129    tree_getpath(tree->node, cwd, sizeof(cwd));
130    ua->send_msg(_("cwd is: %s\n"), cwd);
131    for ( ;; ) {
132       int found, len, i;
133       if (!get_cmd(ua, "$ ", true)) {
134          break;
135       }
136       if (ua->api) user->signal(BNET_CMD_BEGIN);
137       parse_args_only(ua->cmd, &ua->args, &ua->argc, ua->argk, ua->argv, MAX_CMD_ARGS);
138       if (ua->argc == 0) {
139          ua->warning_msg(_("Invalid command \"%s\". Enter \"done\" to exit.\n"), ua->cmd);
140          if (ua->api) user->signal(BNET_CMD_FAILED);
141          continue;
142       }
143
144       len = strlen(ua->argk[0]);
145       found = 0;
146       stat = false;
147       for (i=0; i<comsize; i++)       /* search for command */
148          if (strncasecmp(ua->argk[0],  commands[i].key, len) == 0) {
149             stat = (*commands[i].func)(ua, tree);   /* go execute command */
150             found = 1;
151             break;
152          }
153       if (!found) {
154          ua->warning_msg(_("Invalid command \"%s\". Enter \"done\" to exit.\n"), ua->cmd);
155          if (ua->api) user->signal(BNET_CMD_FAILED);
156          continue;
157       }
158       if (ua->api) user->signal(BNET_CMD_OK);
159       if (!stat) {
160          break;
161       }
162    }
163    if (ua->api) user->signal(BNET_END_RTREE);
164    ua->UA_sock = NULL;                /* don't release restore socket */
165    stat = !ua->quit;
166    ua->quit = false;
167    free_ua_context(ua);               /* get rid of temp UA context */
168    return stat;
169 }
170
171
172 /*
173  * This callback routine is responsible for inserting the
174  *  items it gets into the directory tree. For each JobId selected
175  *  this routine is called once for each file. We do not allow
176  *  duplicate filenames, but instead keep the info from the most
177  *  recent file entered (i.e. the JobIds are assumed to be sorted)
178  *
179  *   See uar_sel_files in sql_cmds.c for query that calls us.
180  *      row[0]=Path, row[1]=Filename, row[2]=FileIndex
181  *      row[3]=JobId row[4]=LStat
182  */
183 int insert_tree_handler(void *ctx, int num_fields, char **row)
184 {
185    struct stat statp;
186    TREE_CTX *tree = (TREE_CTX *)ctx;
187    TREE_NODE *node;
188    int type;
189    bool hard_link, ok;
190    int FileIndex;
191    JobId_t JobId;
192
193    Dmsg4(400, "Path=%s%s FI=%s JobId=%s\n", row[0], row[1],
194       row[2], row[3]);
195    if (*row[1] == 0) {                 /* no filename => directory */
196       if (!IsPathSeparator(*row[0])) { /* Must be Win32 directory */
197          type = TN_DIR_NLS;
198       } else {
199          type = TN_DIR;
200       }
201    } else {
202       type = TN_FILE;
203    }
204    hard_link = (decode_LinkFI(row[4], &statp) != 0);
205    node = insert_tree_node(row[0], row[1], type, tree->root, NULL);
206    JobId = str_to_int64(row[3]);
207    FileIndex = str_to_int64(row[2]);
208    Dmsg2(400, "JobId=%s FileIndex=%s\n", row[3], row[2]);
209    /*
210     * - The first time we see a file (node->inserted==true), we accept it.
211     * - In the same JobId, we accept only the first copy of a
212     *   hard linked file (the others are simply pointers).
213     * - In the same JobId, we accept the last copy of any other
214     *   file -- in particular directories.
215     *
216     * All the code to set ok could be condensed to a single
217     *  line, but it would be even harder to read.
218     */
219    ok = true;
220    if (!node->inserted && JobId == node->JobId) {
221       if ((hard_link && FileIndex > node->FileIndex) ||
222           (!hard_link && FileIndex < node->FileIndex)) {
223          ok = false;
224       }
225    }
226    if (ok) {
227       node->hard_link = hard_link;
228       node->FileIndex = FileIndex;
229       node->JobId = JobId;
230       node->type = type;
231       node->soft_link = S_ISLNK(statp.st_mode) != 0;
232       if (tree->all) {
233          node->extract = true;          /* extract all by default */
234          if (type == TN_DIR || type == TN_DIR_NLS) {
235             node->extract_dir = true;   /* if dir, extract it */
236          }
237       }
238    }
239    if (node->inserted) {
240       tree->FileCount++;
241       if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) {
242          tree->ua->send_msg("+");
243          tree->LastCount = tree->FileCount;
244       }
245    }
246    tree->cnt++;
247    return 0;
248 }
249
250
251 /*
252  * Set extract to value passed. We recursively walk
253  *  down the tree setting all children if the
254  *  node is a directory.
255  */
256 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
257 {
258    TREE_NODE *n;
259    FILE_DBR fdbr;
260    struct stat statp;
261    int count = 0;
262
263    node->extract = extract;
264    if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
265       node->extract_dir = extract;    /* set/clear dir too */
266    }
267    if (node->type != TN_NEWDIR) {
268       count++;
269    }
270    /* For a non-file (i.e. directory), we see all the children */
271    if (node->type != TN_FILE || (node->soft_link && tree_node_has_child(node))) {
272       /* Recursive set children within directory */
273       foreach_child(n, node) {
274          count += set_extract(ua, n, tree, extract);
275       }
276       /*
277        * Walk up tree marking any unextracted parent to be
278        * extracted.
279        */
280       if (extract) {
281          while (node->parent && !node->parent->extract_dir) {
282             node = node->parent;
283             node->extract_dir = true;
284          }
285       }
286    } else if (extract) {
287       char cwd[2000];
288       /*
289        * Ordinary file, we get the full path, look up the
290        * attributes, decode them, and if we are hard linked to
291        * a file that was saved, we must load that file too.
292        */
293       tree_getpath(node, cwd, sizeof(cwd));
294       fdbr.FileId = 0;
295       fdbr.JobId = node->JobId;
296       if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
297          int32_t LinkFI;
298          decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
299          /*
300           * If we point to a hard linked file, traverse the tree to
301           * find that file, and mark it to be restored as well. It
302           * must have the Link we just obtained and the same JobId.
303           */
304          if (LinkFI) {
305             for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
306                if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
307                   n->extract = true;
308                   if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
309                      n->extract_dir = true;
310                   }
311                   break;
312                }
313             }
314          }
315       }
316    }
317    return count;
318 }
319
320 static void strip_trailing_slash(char *arg)
321 {
322    int len = strlen(arg);
323    if (len == 0) {
324       return;
325    }
326    len--;
327    if (arg[len] == '/') {       /* strip any trailing slash */
328       arg[len] = 0;
329    }
330 }
331
332 /*
333  * Recursively mark the current directory to be restored as
334  *  well as all directories and files below it.
335  */
336 static int markcmd(UAContext *ua, TREE_CTX *tree)
337 {
338    TREE_NODE *node;
339    int count = 0;
340    char ec1[50];
341
342    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
343       ua->send_msg(_("No files marked.\n"));
344       return 1;
345    }
346    for (int i=1; i < ua->argc; i++) {
347       strip_trailing_slash(ua->argk[i]);
348       foreach_child(node, tree->node) {
349          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
350             count += set_extract(ua, node, tree, true);
351          }
352       }
353    }
354    if (count == 0) {
355       ua->send_msg(_("No files marked.\n"));
356    } else if (count == 1) {
357       ua->send_msg(_("1 file marked.\n"));
358    } else {
359       ua->send_msg(_("%s files marked.\n"),
360                edit_uint64_with_commas(count, ec1));
361    }
362    return 1;
363 }
364
365 static int markdircmd(UAContext *ua, TREE_CTX *tree)
366 {
367    TREE_NODE *node;
368    int count = 0;
369    char ec1[50];
370
371    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
372       ua->send_msg(_("No files marked.\n"));
373       return 1;
374    }
375    for (int i=1; i < ua->argc; i++) {
376       strip_trailing_slash(ua->argk[i]);
377       foreach_child(node, tree->node) {
378          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
379             if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
380                node->extract_dir = true;
381                count++;
382             }
383          }
384       }
385    }
386    if (count == 0) {
387       ua->send_msg(_("No directories marked.\n"));
388    } else if (count == 1) {
389       ua->send_msg(_("1 directory marked.\n"));
390    } else {
391       ua->send_msg(_("%s directories marked.\n"),
392                edit_uint64_with_commas(count, ec1));
393    }
394    return 1;
395 }
396
397
398 static int countcmd(UAContext *ua, TREE_CTX *tree)
399 {
400    int total, num_extract;
401    char ec1[50], ec2[50];
402
403    total = num_extract = 0;
404    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
405       if (node->type != TN_NEWDIR) {
406          total++;
407          if (node->extract || node->extract_dir) {
408             num_extract++;
409          }
410       }
411    }
412    ua->send_msg(_("%s total files/dirs. %s marked to be restored.\n"),
413             edit_uint64_with_commas(total, ec1),
414             edit_uint64_with_commas(num_extract, ec2));
415    return 1;
416 }
417
418 static int findcmd(UAContext *ua, TREE_CTX *tree)
419 {
420    char cwd[2000];
421
422    if (ua->argc == 1) {
423       ua->send_msg(_("No file specification given.\n"));
424       return 1;      /* make it non-fatal */
425    }
426
427    for (int i=1; i < ua->argc; i++) {
428       for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
429          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
430             const char *tag;
431             tree_getpath(node, cwd, sizeof(cwd));
432             if (node->extract) {
433                tag = "*";
434             } else if (node->extract_dir) {
435                tag = "+";
436             } else {
437                tag = "";
438             }
439             ua->send_msg("%s%s\n", tag, cwd);
440          }
441       }
442    }
443    return 1;
444 }
445
446 static int dot_lsdircmd(UAContext *ua, TREE_CTX *tree)
447 {
448    TREE_NODE *node;
449
450    if (!tree_node_has_child(tree->node)) {
451       return 1;
452    }
453
454    foreach_child(node, tree->node) {
455       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
456          if (tree_node_has_child(node)) {
457             ua->send_msg("%s/\n", node->fname);
458          }
459       }
460    }
461  
462    return 1;
463 }
464
465 static int dot_helpcmd(UAContext *ua, TREE_CTX *tree)
466 {
467    for (int i=0; i<comsize; i++) {
468       /* List only non-dot commands */
469       if (commands[i].key[0] != '.') {
470          ua->send_msg("%s\n", commands[i].key);
471       }
472    }
473    return 1;
474 }
475
476 static int dot_lscmd(UAContext *ua, TREE_CTX *tree)
477 {
478    TREE_NODE *node;
479
480    if (!tree_node_has_child(tree->node)) {
481       return 1;
482    }
483
484    foreach_child(node, tree->node) {
485       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
486          ua->send_msg("%s%s\n", node->fname, tree_node_has_child(node)?"/":"");
487       }
488    }
489  
490    return 1;
491 }
492
493 static int lscmd(UAContext *ua, TREE_CTX *tree)
494 {
495    TREE_NODE *node;
496
497    if (!tree_node_has_child(tree->node)) {
498       return 1;
499    }
500    foreach_child(node, tree->node) {
501       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
502          const char *tag;
503          if (node->extract) {
504             tag = "*";
505          } else if (node->extract_dir) {
506             tag = "+";
507          } else {
508             tag = "";
509          }
510          ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
511       }
512    }
513    return 1;
514 }
515
516 /*
517  * Ls command that lists only the marked files
518  */
519 static int dot_lsmarkcmd(UAContext *ua, TREE_CTX *tree)
520 {
521    TREE_NODE *node;
522    if (!tree_node_has_child(tree->node)) {
523       return 1;
524    }
525    foreach_child(node, tree->node) {
526       if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
527           (node->extract || node->extract_dir)) {
528          ua->send_msg("%s%s\n", node->fname, tree_node_has_child(node)?"/":"");
529       }
530    }
531    return 1;
532 }
533
534 /*
535  * Ls command that lists only the marked files
536  */
537 static void rlsmark(UAContext *ua, TREE_NODE *tnode)
538 {
539    TREE_NODE *node;
540    if (!tree_node_has_child(tnode)) {
541       return;
542    }
543    foreach_child(node, tnode) {
544       if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
545           (node->extract || node->extract_dir)) {
546          const char *tag;
547          if (node->extract) {
548             tag = "*";
549          } else if (node->extract_dir) {
550             tag = "+";
551          } else {
552             tag = "";
553          }
554          ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
555          if (tree_node_has_child(node)) {
556             rlsmark(ua, node);
557          }
558       }
559    }
560 }
561
562 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
563 {
564    rlsmark(ua, tree->node);
565    return 1;
566 }
567
568 /*
569  * This is actually the long form used for "dir"
570  */
571 static void ls_output(guid_list *guid, char *buf, const char *fname, const char *tag, 
572                       struct stat *statp, bool dot_cmd) 
573 {
574    char *p;
575    const char *f;
576    char ec1[30];
577    char en1[30], en2[30];
578    int n;
579    time_t time;
580
581    p = encode_mode(statp->st_mode, buf);
582    if (dot_cmd) {
583       *p++ = ',';
584       n = sprintf(p, "%d,", (uint32_t)statp->st_nlink);
585       p += n;
586       n = sprintf(p, "%s,%s,", 
587                   guid->uid_to_name(statp->st_uid, en1, sizeof(en1)),
588                   guid->gid_to_name(statp->st_gid, en2, sizeof(en2)));
589       p += n;
590       n = sprintf(p, "%s,", edit_int64(statp->st_size, ec1));
591       p += n;
592       p = encode_time(statp->st_mtime, p);
593       *p++ = ',';
594       *p++ = *tag;
595       *p++ = ',';
596    } else {
597       n = sprintf(p, "  %2d ", (uint32_t)statp->st_nlink);
598       p += n;
599       n = sprintf(p, "%-8.8s %-8.8s", 
600                   guid->uid_to_name(statp->st_uid, en1, sizeof(en1)),
601                   guid->gid_to_name(statp->st_gid, en2, sizeof(en2)));
602       p += n;
603       n = sprintf(p, "%12.12s  ", edit_int64(statp->st_size, ec1));
604       p += n;
605       if (statp->st_ctime > statp->st_mtime) {
606          time = statp->st_ctime;
607       } else {
608          time = statp->st_mtime;
609       }
610       /* Display most recent time */
611       p = encode_time(time, p);
612       *p++ = ' ';
613       *p++ = *tag;
614    }
615    for (f=fname; *f; ) {
616       *p++ = *f++;
617    }
618    *p = 0;
619 }
620
621 /*
622  * Like ls command, but give more detail on each file
623  */
624 static int do_dircmd(UAContext *ua, TREE_CTX *tree, bool dot_cmd)
625 {
626    TREE_NODE *node;
627    FILE_DBR fdbr;
628    struct stat statp;
629    char buf[1100];
630    char cwd[1100], *pcwd;
631    guid_list *guid;
632
633    if (!tree_node_has_child(tree->node)) {
634       ua->send_msg(_("Node %s has no children.\n"), tree->node->fname);
635       return 1;
636    }
637
638    guid = new_guid_list();
639    foreach_child(node, tree->node) {
640       const char *tag;
641       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
642          if (node->extract) {
643             tag = "*";
644          } else if (node->extract_dir) {
645             tag = "+";
646          } else {
647             tag = " ";
648          }
649          tree_getpath(node, cwd, sizeof(cwd));
650          fdbr.FileId = 0;
651          fdbr.JobId = node->JobId;
652          /*
653           * Strip / from soft links to directories.
654           *   This is because soft links to files have a trailing slash
655           *   when returned from tree_getpath, but db_get_file_attr...
656           *   treats soft links as files, so they do not have a trailing
657           *   slash like directory names.
658           */
659          if (node->type == TN_FILE && tree_node_has_child(node)) {
660             bstrncpy(buf, cwd, sizeof(buf));
661             pcwd = buf;
662             int len = strlen(buf);
663             if (len > 1) {
664                buf[len-1] = 0;        /* strip trailing / */
665             }
666          } else {
667             pcwd = cwd;
668          }
669          if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
670             int32_t LinkFI;
671             decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
672          } else {
673             /* Something went wrong getting attributes -- print name */
674             memset(&statp, 0, sizeof(statp));
675          }
676          ls_output(guid, buf, cwd, tag, &statp, dot_cmd);
677          ua->send_msg("%s\n", buf);
678       }
679    }
680    free_guid_list(guid);
681    return 1;
682 }
683
684 int dot_dircmd(UAContext *ua, TREE_CTX *tree)
685 {
686    return do_dircmd(ua, tree, true/*dot command*/);
687 }
688
689 static int dircmd(UAContext *ua, TREE_CTX *tree)
690 {
691    return do_dircmd(ua, tree, false/*not dot command*/);
692 }
693
694
695 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
696 {
697    int total, num_extract;
698    uint64_t total_bytes = 0;
699    FILE_DBR fdbr;
700    struct stat statp;
701    char cwd[1100];
702    char ec1[50];
703
704    total = num_extract = 0;
705    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
706       if (node->type != TN_NEWDIR) {
707          total++;
708          /* If regular file, get size */
709          if (node->extract && node->type == TN_FILE) {
710             num_extract++;
711             tree_getpath(node, cwd, sizeof(cwd));
712             fdbr.FileId = 0;
713             fdbr.JobId = node->JobId;
714             if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
715                int32_t LinkFI;
716                decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
717                if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
718                   total_bytes += statp.st_size;
719                }
720             }
721          /* Directory, count only */
722          } else if (node->extract || node->extract_dir) {
723             num_extract++;
724          }
725       }
726    }
727    ua->send_msg(_("%d total files; %d marked to be restored; %s bytes.\n"),
728             total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
729    return 1;
730 }
731
732
733
734 static int helpcmd(UAContext *ua, TREE_CTX *tree)
735 {
736    unsigned int i;
737
738    ua->send_msg(_("  Command    Description\n  =======    ===========\n"));
739    for (i=0; i<comsize; i++) {
740       /* List only non-dot commands */
741       if (commands[i].key[0] != '.') {
742          ua->send_msg("  %-10s %s\n", _(commands[i].key), _(commands[i].help));
743       }
744    }
745    ua->send_msg("\n");
746    return 1;
747 }
748
749 /*
750  * Change directories.  Note, if the user specifies x: and it fails,
751  *   we assume it is a Win32 absolute cd rather than relative and
752  *   try a second time with /x: ...  Win32 kludge.
753  */
754 static int cdcmd(UAContext *ua, TREE_CTX *tree)
755 {
756    TREE_NODE *node;
757    char cwd[2000];
758
759
760    if (ua->argc != 2) {
761       ua->error_msg(_("Too few or too many arguments. Try using double quotes.\n"));
762       return 1;
763    }
764    node = tree_cwd(ua->argk[1], tree->root, tree->node);
765    if (!node) {
766       /* Try once more if Win32 drive -- make absolute */
767       if (ua->argk[1][1] == ':') {  /* win32 drive */
768          bstrncpy(cwd, "/", sizeof(cwd));
769          bstrncat(cwd, ua->argk[1], sizeof(cwd));
770          node = tree_cwd(cwd, tree->root, tree->node);
771       }
772       if (!node) {
773          ua->warning_msg(_("Invalid path given.\n"));
774       } else {
775          tree->node = node;
776       }
777    } else {
778       tree->node = node;
779    }
780    return pwdcmd(ua, tree);
781 }
782
783 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
784 {
785    char cwd[2000];
786    tree_getpath(tree->node, cwd, sizeof(cwd));
787    if (ua->api) {
788       ua->send_msg("%s", cwd);
789    } else {
790       ua->send_msg(_("cwd is: %s\n"), cwd);
791    }
792    return 1;
793 }
794
795 static int dot_pwdcmd(UAContext *ua, TREE_CTX *tree)
796 {
797    char cwd[2000];
798    tree_getpath(tree->node, cwd, sizeof(cwd));
799    ua->send_msg("%s", cwd);
800    return 1;
801 }
802
803 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
804 {
805    TREE_NODE *node;
806    int count = 0;
807
808    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
809       ua->send_msg(_("No files unmarked.\n"));
810       return 1;
811    }
812    for (int i=1; i < ua->argc; i++) {
813       strip_trailing_slash(ua->argk[i]);
814       foreach_child(node, tree->node) {
815          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
816             count += set_extract(ua, node, tree, false);
817          }
818       }
819    }
820    if (count == 0) {
821       ua->send_msg(_("No files unmarked.\n"));
822    } else if (count == 1) {
823       ua->send_msg(_("1 file unmarked.\n"));
824    } else {
825       char ed1[50];
826       ua->send_msg(_("%s files unmarked.\n"), edit_uint64_with_commas(count, ed1));
827    }
828    return 1;
829 }
830
831 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
832 {
833    TREE_NODE *node;
834    int count = 0;
835
836    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
837       ua->send_msg(_("No directories unmarked.\n"));
838       return 1;
839    }
840
841    for (int i=1; i < ua->argc; i++) {
842       strip_trailing_slash(ua->argk[i]);
843       foreach_child(node, tree->node) {
844          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
845             if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
846                node->extract_dir = false;
847                count++;
848             }
849          }
850       }
851    }
852
853    if (count == 0) {
854       ua->send_msg(_("No directories unmarked.\n"));
855    } else if (count == 1) {
856       ua->send_msg(_("1 directory unmarked.\n"));
857    } else {
858       ua->send_msg(_("%d directories unmarked.\n"), count);
859    }
860    return 1;
861 }
862
863
864 static int donecmd(UAContext *ua, TREE_CTX *tree)
865 {
866    return 0;
867 }
868
869 static int quitcmd(UAContext *ua, TREE_CTX *tree)
870 {
871    ua->quit = true;
872    return 0;
873 }