]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_tree.c
Ignore unknown dot commands in restore tree code
[bacula/bacula] / bacula / src / dird / ua_tree.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2002-2010 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
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    JobId_t JobId;
195
196    Dmsg4(400, "Path=%s%s FI=%s JobId=%s\n", row[0], row[1],
197       row[2], row[3]);
198    if (*row[1] == 0) {                 /* no filename => directory */
199       if (!IsPathSeparator(*row[0])) { /* Must be Win32 directory */
200          type = TN_DIR_NLS;
201       } else {
202          type = TN_DIR;
203       }
204    } else {
205       type = TN_FILE;
206    }
207    hard_link = (decode_LinkFI(row[4], &statp) != 0);
208    node = insert_tree_node(row[0], row[1], type, tree->root, NULL);
209    JobId = str_to_int64(row[3]);
210    FileIndex = str_to_int64(row[2]);
211    Dmsg2(400, "JobId=%s FileIndex=%s\n", row[3], row[2]);
212    /*
213     * - The first time we see a file (node->inserted==true), we accept it.
214     * - In the same JobId, we accept only the first copy of a
215     *   hard linked file (the others are simply pointers).
216     * - In the same JobId, we accept the last copy of any other
217     *   file -- in particular directories.
218     *
219     * All the code to set ok could be condensed to a single
220     *  line, but it would be even harder to read.
221     */
222    ok = true;
223    if (!node->inserted && JobId == node->JobId) {
224       if ((hard_link && FileIndex > node->FileIndex) ||
225           (!hard_link && FileIndex < node->FileIndex)) {
226          ok = false;
227       }
228    }
229    if (ok) {
230       node->hard_link = hard_link;
231       node->FileIndex = FileIndex;
232       node->JobId = JobId;
233       node->type = type;
234       node->soft_link = S_ISLNK(statp.st_mode) != 0;
235       if (tree->all) {
236          node->extract = true;          /* extract all by default */
237          if (type == TN_DIR || type == TN_DIR_NLS) {
238             node->extract_dir = true;   /* if dir, extract it */
239          }
240       }
241    }
242    if (node->inserted) {
243       tree->FileCount++;
244       if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) {
245          tree->ua->send_msg("+");
246          tree->LastCount = tree->FileCount;
247       }
248    }
249    tree->cnt++;
250    return 0;
251 }
252
253
254 /*
255  * Set extract to value passed. We recursively walk
256  *  down the tree setting all children if the
257  *  node is a directory.
258  */
259 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
260 {
261    TREE_NODE *n;
262    FILE_DBR fdbr;
263    struct stat statp;
264    int count = 0;
265
266    node->extract = extract;
267    if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
268       node->extract_dir = extract;    /* set/clear dir too */
269    }
270    if (node->type != TN_NEWDIR) {
271       count++;
272    }
273    /* For a non-file (i.e. directory), we see all the children */
274    if (node->type != TN_FILE || (node->soft_link && tree_node_has_child(node))) {
275       /* Recursive set children within directory */
276       foreach_child(n, node) {
277          count += set_extract(ua, n, tree, extract);
278       }
279       /*
280        * Walk up tree marking any unextracted parent to be
281        * extracted.
282        */
283       if (extract) {
284          while (node->parent && !node->parent->extract_dir) {
285             node = node->parent;
286             node->extract_dir = true;
287          }
288       }
289    } else if (extract) {
290       char cwd[2000];
291       /*
292        * Ordinary file, we get the full path, look up the
293        * attributes, decode them, and if we are hard linked to
294        * a file that was saved, we must load that file too.
295        */
296       tree_getpath(node, cwd, sizeof(cwd));
297       fdbr.FileId = 0;
298       fdbr.JobId = node->JobId;
299       if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
300          int32_t LinkFI;
301          decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
302          /*
303           * If we point to a hard linked file, traverse the tree to
304           * find that file, and mark it to be restored as well. It
305           * must have the Link we just obtained and the same JobId.
306           */
307          if (LinkFI) {
308             for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
309                if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
310                   n->extract = true;
311                   if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
312                      n->extract_dir = true;
313                   }
314                   break;
315                }
316             }
317          }
318       }
319    }
320    return count;
321 }
322
323 static void strip_trailing_slash(char *arg)
324 {
325    int len = strlen(arg);
326    if (len == 0) {
327       return;
328    }
329    len--;
330    if (arg[len] == '/') {       /* strip any trailing slash */
331       arg[len] = 0;
332    }
333 }
334
335 /*
336  * Recursively mark the current directory to be restored as
337  *  well as all directories and files below it.
338  */
339 static int markcmd(UAContext *ua, TREE_CTX *tree)
340 {
341    TREE_NODE *node;
342    int count = 0;
343    char ec1[50];
344
345    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
346       ua->send_msg(_("No files marked.\n"));
347       return 1;
348    }
349    for (int i=1; i < ua->argc; i++) {
350       strip_trailing_slash(ua->argk[i]);
351       foreach_child(node, tree->node) {
352          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
353             count += set_extract(ua, node, tree, true);
354          }
355       }
356    }
357    if (count == 0) {
358       ua->send_msg(_("No files marked.\n"));
359    } else if (count == 1) {
360       ua->send_msg(_("1 file marked.\n"));
361    } else {
362       ua->send_msg(_("%s files marked.\n"),
363                edit_uint64_with_commas(count, ec1));
364    }
365    return 1;
366 }
367
368 static int markdircmd(UAContext *ua, TREE_CTX *tree)
369 {
370    TREE_NODE *node;
371    int count = 0;
372    char ec1[50];
373
374    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
375       ua->send_msg(_("No files marked.\n"));
376       return 1;
377    }
378    for (int i=1; i < ua->argc; i++) {
379       strip_trailing_slash(ua->argk[i]);
380       foreach_child(node, tree->node) {
381          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
382             if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
383                node->extract_dir = true;
384                count++;
385             }
386          }
387       }
388    }
389    if (count == 0) {
390       ua->send_msg(_("No directories marked.\n"));
391    } else if (count == 1) {
392       ua->send_msg(_("1 directory marked.\n"));
393    } else {
394       ua->send_msg(_("%s directories marked.\n"),
395                edit_uint64_with_commas(count, ec1));
396    }
397    return 1;
398 }
399
400
401 static int countcmd(UAContext *ua, TREE_CTX *tree)
402 {
403    int total, num_extract;
404    char ec1[50], ec2[50];
405
406    total = num_extract = 0;
407    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
408       if (node->type != TN_NEWDIR) {
409          total++;
410          if (node->extract || node->extract_dir) {
411             num_extract++;
412          }
413       }
414    }
415    ua->send_msg(_("%s total files/dirs. %s marked to be restored.\n"),
416             edit_uint64_with_commas(total, ec1),
417             edit_uint64_with_commas(num_extract, ec2));
418    return 1;
419 }
420
421 static int findcmd(UAContext *ua, TREE_CTX *tree)
422 {
423    char cwd[2000];
424
425    if (ua->argc == 1) {
426       ua->send_msg(_("No file specification given.\n"));
427       return 1;      /* make it non-fatal */
428    }
429
430    for (int i=1; i < ua->argc; i++) {
431       for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
432          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
433             const char *tag;
434             tree_getpath(node, cwd, sizeof(cwd));
435             if (node->extract) {
436                tag = "*";
437             } else if (node->extract_dir) {
438                tag = "+";
439             } else {
440                tag = "";
441             }
442             ua->send_msg("%s%s\n", tag, cwd);
443          }
444       }
445    }
446    return 1;
447 }
448
449 static int dot_lsdircmd(UAContext *ua, TREE_CTX *tree)
450 {
451    TREE_NODE *node;
452
453    if (!tree_node_has_child(tree->node)) {
454       return 1;
455    }
456
457    foreach_child(node, tree->node) {
458       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
459          if (tree_node_has_child(node)) {
460             ua->send_msg("%s/\n", node->fname);
461          }
462       }
463    }
464  
465    return 1;
466 }
467
468 static int dot_helpcmd(UAContext *ua, TREE_CTX *tree)
469 {
470    for (int i=0; i<comsize; i++) {
471       /* List only non-dot commands */
472       if (commands[i].key[0] != '.') {
473          ua->send_msg("%s\n", commands[i].key);
474       }
475    }
476    return 1;
477 }
478
479 static int dot_lscmd(UAContext *ua, TREE_CTX *tree)
480 {
481    TREE_NODE *node;
482
483    if (!tree_node_has_child(tree->node)) {
484       return 1;
485    }
486
487    foreach_child(node, tree->node) {
488       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
489          ua->send_msg("%s%s\n", node->fname, tree_node_has_child(node)?"/":"");
490       }
491    }
492  
493    return 1;
494 }
495
496 static int lscmd(UAContext *ua, TREE_CTX *tree)
497 {
498    TREE_NODE *node;
499
500    if (!tree_node_has_child(tree->node)) {
501       return 1;
502    }
503    foreach_child(node, tree->node) {
504       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
505          const char *tag;
506          if (node->extract) {
507             tag = "*";
508          } else if (node->extract_dir) {
509             tag = "+";
510          } else {
511             tag = "";
512          }
513          ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
514       }
515    }
516    return 1;
517 }
518
519 /*
520  * Ls command that lists only the marked files
521  */
522 static int dot_lsmarkcmd(UAContext *ua, TREE_CTX *tree)
523 {
524    TREE_NODE *node;
525    if (!tree_node_has_child(tree->node)) {
526       return 1;
527    }
528    foreach_child(node, tree->node) {
529       if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
530           (node->extract || node->extract_dir)) {
531          ua->send_msg("%s%s\n", node->fname, tree_node_has_child(node)?"/":"");
532       }
533    }
534    return 1;
535 }
536
537 /*
538  * Ls command that lists only the marked files
539  */
540 static void rlsmark(UAContext *ua, TREE_NODE *tnode)
541 {
542    TREE_NODE *node;
543    if (!tree_node_has_child(tnode)) {
544       return;
545    }
546    foreach_child(node, tnode) {
547       if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
548           (node->extract || node->extract_dir)) {
549          const char *tag;
550          if (node->extract) {
551             tag = "*";
552          } else if (node->extract_dir) {
553             tag = "+";
554          } else {
555             tag = "";
556          }
557          ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
558          if (tree_node_has_child(node)) {
559             rlsmark(ua, node);
560          }
561       }
562    }
563 }
564
565 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
566 {
567    rlsmark(ua, tree->node);
568    return 1;
569 }
570
571 /*
572  * This is actually the long form used for "dir"
573  */
574 static void ls_output(guid_list *guid, char *buf, const char *fname, const char *tag, 
575                       struct stat *statp, bool dot_cmd) 
576 {
577    char *p;
578    const char *f;
579    char ec1[30];
580    char en1[30], en2[30];
581    int n;
582    time_t time;
583
584    p = encode_mode(statp->st_mode, buf);
585    if (dot_cmd) {
586       *p++ = ',';
587       n = sprintf(p, "%d,", (uint32_t)statp->st_nlink);
588       p += n;
589       n = sprintf(p, "%s,%s,", 
590                   guid->uid_to_name(statp->st_uid, en1, sizeof(en1)),
591                   guid->gid_to_name(statp->st_gid, en2, sizeof(en2)));
592       p += n;
593       n = sprintf(p, "%s,", edit_int64(statp->st_size, ec1));
594       p += n;
595       p = encode_time(statp->st_mtime, p);
596       *p++ = ',';
597       *p++ = *tag;
598       *p++ = ',';
599    } else {
600       n = sprintf(p, "  %2d ", (uint32_t)statp->st_nlink);
601       p += n;
602       n = sprintf(p, "%-8.8s %-8.8s", 
603                   guid->uid_to_name(statp->st_uid, en1, sizeof(en1)),
604                   guid->gid_to_name(statp->st_gid, en2, sizeof(en2)));
605       p += n;
606       n = sprintf(p, "%12.12s  ", edit_int64(statp->st_size, ec1));
607       p += n;
608       if (statp->st_ctime > statp->st_mtime) {
609          time = statp->st_ctime;
610       } else {
611          time = statp->st_mtime;
612       }
613       /* Display most recent time */
614       p = encode_time(time, p);
615       *p++ = ' ';
616       *p++ = *tag;
617    }
618    for (f=fname; *f; ) {
619       *p++ = *f++;
620    }
621    *p = 0;
622 }
623
624 /*
625  * Like ls command, but give more detail on each file
626  */
627 static int do_dircmd(UAContext *ua, TREE_CTX *tree, bool dot_cmd)
628 {
629    TREE_NODE *node;
630    FILE_DBR fdbr;
631    struct stat statp;
632    char buf[1100];
633    char cwd[1100], *pcwd;
634    guid_list *guid;
635
636    if (!tree_node_has_child(tree->node)) {
637       ua->send_msg(_("Node %s has no children.\n"), tree->node->fname);
638       return 1;
639    }
640
641    guid = new_guid_list();
642    foreach_child(node, tree->node) {
643       const char *tag;
644       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
645          if (node->extract) {
646             tag = "*";
647          } else if (node->extract_dir) {
648             tag = "+";
649          } else {
650             tag = " ";
651          }
652          tree_getpath(node, cwd, sizeof(cwd));
653          fdbr.FileId = 0;
654          fdbr.JobId = node->JobId;
655          /*
656           * Strip / from soft links to directories.
657           *   This is because soft links to files have a trailing slash
658           *   when returned from tree_getpath, but db_get_file_attr...
659           *   treats soft links as files, so they do not have a trailing
660           *   slash like directory names.
661           */
662          if (node->type == TN_FILE && tree_node_has_child(node)) {
663             bstrncpy(buf, cwd, sizeof(buf));
664             pcwd = buf;
665             int len = strlen(buf);
666             if (len > 1) {
667                buf[len-1] = 0;        /* strip trailing / */
668             }
669          } else {
670             pcwd = cwd;
671          }
672          if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
673             int32_t LinkFI;
674             decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
675          } else {
676             /* Something went wrong getting attributes -- print name */
677             memset(&statp, 0, sizeof(statp));
678          }
679          ls_output(guid, buf, cwd, tag, &statp, dot_cmd);
680          ua->send_msg("%s\n", buf);
681       }
682    }
683    free_guid_list(guid);
684    return 1;
685 }
686
687 int dot_dircmd(UAContext *ua, TREE_CTX *tree)
688 {
689    return do_dircmd(ua, tree, true/*dot command*/);
690 }
691
692 static int dircmd(UAContext *ua, TREE_CTX *tree)
693 {
694    return do_dircmd(ua, tree, false/*not dot command*/);
695 }
696
697
698 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
699 {
700    int total, num_extract;
701    uint64_t total_bytes = 0;
702    FILE_DBR fdbr;
703    struct stat statp;
704    char cwd[1100];
705    char ec1[50];
706
707    total = num_extract = 0;
708    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
709       if (node->type != TN_NEWDIR) {
710          total++;
711          /* If regular file, get size */
712          if (node->extract && node->type == TN_FILE) {
713             num_extract++;
714             tree_getpath(node, cwd, sizeof(cwd));
715             fdbr.FileId = 0;
716             fdbr.JobId = node->JobId;
717             if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
718                int32_t LinkFI;
719                decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
720                if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
721                   total_bytes += statp.st_size;
722                }
723             }
724          /* Directory, count only */
725          } else if (node->extract || node->extract_dir) {
726             num_extract++;
727          }
728       }
729    }
730    ua->send_msg(_("%d total files; %d marked to be restored; %s bytes.\n"),
731             total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
732    return 1;
733 }
734
735
736
737 static int helpcmd(UAContext *ua, TREE_CTX *tree)
738 {
739    unsigned int i;
740
741    ua->send_msg(_("  Command    Description\n  =======    ===========\n"));
742    for (i=0; i<comsize; i++) {
743       /* List only non-dot commands */
744       if (commands[i].key[0] != '.') {
745          ua->send_msg("  %-10s %s\n", _(commands[i].key), _(commands[i].help));
746       }
747    }
748    ua->send_msg("\n");
749    return 1;
750 }
751
752 /*
753  * Change directories.  Note, if the user specifies x: and it fails,
754  *   we assume it is a Win32 absolute cd rather than relative and
755  *   try a second time with /x: ...  Win32 kludge.
756  */
757 static int cdcmd(UAContext *ua, TREE_CTX *tree)
758 {
759    TREE_NODE *node;
760    char cwd[2000];
761
762
763    if (ua->argc != 2) {
764       ua->error_msg(_("Too few or too many arguments. Try using double quotes.\n"));
765       return 1;
766    }
767    node = tree_cwd(ua->argk[1], tree->root, tree->node);
768    if (!node) {
769       /* Try once more if Win32 drive -- make absolute */
770       if (ua->argk[1][1] == ':') {  /* win32 drive */
771          bstrncpy(cwd, "/", sizeof(cwd));
772          bstrncat(cwd, ua->argk[1], sizeof(cwd));
773          node = tree_cwd(cwd, tree->root, tree->node);
774       }
775       if (!node) {
776          ua->warning_msg(_("Invalid path given.\n"));
777       } else {
778          tree->node = node;
779       }
780    } else {
781       tree->node = node;
782    }
783    return pwdcmd(ua, tree);
784 }
785
786 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
787 {
788    char cwd[2000];
789    tree_getpath(tree->node, cwd, sizeof(cwd));
790    if (ua->api) {
791       ua->send_msg("%s", cwd);
792    } else {
793       ua->send_msg(_("cwd is: %s\n"), cwd);
794    }
795    return 1;
796 }
797
798 static int dot_pwdcmd(UAContext *ua, TREE_CTX *tree)
799 {
800    char cwd[2000];
801    tree_getpath(tree->node, cwd, sizeof(cwd));
802    ua->send_msg("%s", cwd);
803    return 1;
804 }
805
806 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
807 {
808    TREE_NODE *node;
809    int count = 0;
810
811    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
812       ua->send_msg(_("No files unmarked.\n"));
813       return 1;
814    }
815    for (int i=1; i < ua->argc; i++) {
816       strip_trailing_slash(ua->argk[i]);
817       foreach_child(node, tree->node) {
818          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
819             count += set_extract(ua, node, tree, false);
820          }
821       }
822    }
823    if (count == 0) {
824       ua->send_msg(_("No files unmarked.\n"));
825    } else if (count == 1) {
826       ua->send_msg(_("1 file unmarked.\n"));
827    } else {
828       char ed1[50];
829       ua->send_msg(_("%s files unmarked.\n"), edit_uint64_with_commas(count, ed1));
830    }
831    return 1;
832 }
833
834 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
835 {
836    TREE_NODE *node;
837    int count = 0;
838
839    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
840       ua->send_msg(_("No directories unmarked.\n"));
841       return 1;
842    }
843
844    for (int i=1; i < ua->argc; i++) {
845       strip_trailing_slash(ua->argk[i]);
846       foreach_child(node, tree->node) {
847          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
848             if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
849                node->extract_dir = false;
850                count++;
851             }
852          }
853       }
854    }
855
856    if (count == 0) {
857       ua->send_msg(_("No directories unmarked.\n"));
858    } else if (count == 1) {
859       ua->send_msg(_("1 directory unmarked.\n"));
860    } else {
861       ua->send_msg(_("%d directories unmarked.\n"), count);
862    }
863    return 1;
864 }
865
866
867 static int donecmd(UAContext *ua, TREE_CTX *tree)
868 {
869    return 0;
870 }
871
872 static int quitcmd(UAContext *ua, TREE_CTX *tree)
873 {
874    ua->quit = true;
875    return 0;
876 }