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