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