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