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