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