1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2002-2011 Free Software Foundation Europe e.V.
5    Copyright (C) 2011-2016 Planets Communications B.V.
6    Copyright (C) 2013-2016 Bareos GmbH & Co. KG
7 
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    Affero 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 /*
24  * Kern Sibbald, July MMII
25  */
26 /**
27  * @file
28  * User Agent Database File tree for Restore
29  *                    command. This file interacts with the user implementing the
30  *                    UA tree commands.
31  *
32  */
33 
34 #include "include/bareos.h"
35 #include "dird.h"
36 #include "dird/dird_globals.h"
37 #ifdef BAREOS_LIB_LIB_H_
38 #include <fnmatch.h>
39 #else
40 #include "lib/fnmatch.h"
41 #endif
42 #include "findlib/find.h"
43 #include "dird/ua_input.h"
44 #include "dird/ua_server.h"
45 #include "lib/edit.h"
46 
47 namespace directordaemon {
48 
49 /* Forward referenced commands */
50 static int markcmd(UaContext *ua, TreeContext *tree);
51 static int Markdircmd(UaContext *ua, TreeContext *tree);
52 static int countcmd(UaContext *ua, TreeContext *tree);
53 static int findcmd(UaContext *ua, TreeContext *tree);
54 static int lscmd(UaContext *ua, TreeContext *tree);
55 static int Lsmarkcmd(UaContext *ua, TreeContext *tree);
56 static int dircmd(UaContext *ua, TreeContext *tree);
57 static int DotDircmd(UaContext *ua, TreeContext *tree);
58 static int Estimatecmd(UaContext *ua, TreeContext *tree);
59 static int HelpCmd(UaContext *ua, TreeContext *tree);
60 static int cdcmd(UaContext *ua, TreeContext *tree);
61 static int pwdcmd(UaContext *ua, TreeContext *tree);
62 static int DotPwdcmd(UaContext *ua, TreeContext *tree);
63 static int Unmarkcmd(UaContext *ua, TreeContext *tree);
64 static int UnMarkdircmd(UaContext *ua, TreeContext *tree);
65 static int QuitCmd(UaContext *ua, TreeContext *tree);
66 static int donecmd(UaContext *ua, TreeContext *tree);
67 static int DotLsdircmd(UaContext *ua, TreeContext *tree);
68 static int DotLscmd(UaContext *ua, TreeContext *tree);
69 static int DotHelpcmd(UaContext *ua, TreeContext *tree);
70 static int DotLsmarkcmd(UaContext *ua, TreeContext *tree);
71 
72 struct cmdstruct {
73    const char *key;
74    int (*func)(UaContext *ua, TreeContext *tree);
75    const char *help;
76 };
77 static struct cmdstruct commands[] = {
78    { NT_("abort"), QuitCmd, _("abort and do not do restore") },
79    { NT_("add"), markcmd, _("add dir/file to be restored recursively, wildcards allowed") },
80    { NT_("cd"), cdcmd, _("change current directory") },
81    { NT_("count"), countcmd, _("count marked files in and below the cd") },
82    { NT_("delete"), Unmarkcmd, _("delete dir/file to be restored recursively in dir") },
83    { NT_("dir"), dircmd, _("long list current directory, wildcards allowed") },
84    { NT_(".dir"), DotDircmd, _("long list current directory, wildcards allowed") },
85    { NT_("done"), donecmd, _("leave file selection mode") },
86    { NT_("estimate"), Estimatecmd, _("estimate restore size") },
87    { NT_("exit"), donecmd, _("same as done command") },
88    { NT_("find"), findcmd, _("find files, wildcards allowed") },
89    { NT_("help"), HelpCmd, _("print help") },
90    { NT_("ls"), lscmd, _("list current directory, wildcards allowed") },
91    { NT_(".ls"), DotLscmd, _("list current directory, wildcards allowed") },
92    { NT_(".lsdir"), DotLsdircmd, _("list subdir in current directory, wildcards allowed") },
93    { NT_("lsmark"), Lsmarkcmd, _("list the marked files in and below the cd") },
94    { NT_(".lsmark"), DotLsmarkcmd,_("list the marked files in") },
95    { NT_("mark"), markcmd, _("mark dir/file to be restored recursively, wildcards allowed") },
96    { NT_("markdir"), Markdircmd, _("mark directory name to be restored (no files)") },
97    { NT_("pwd"), pwdcmd, _("print current working directory") },
98    { NT_(".pwd"), DotPwdcmd, _("print current working directory") },
99    { NT_("unmark"), Unmarkcmd, _("unmark dir/file to be restored recursively in dir") },
100    { NT_("unmarkdir"), UnMarkdircmd, _("unmark directory name only no recursion") },
101    { NT_("quit"), QuitCmd, _("quit and do not do restore") },
102    { NT_(".help"), DotHelpcmd, _("print help") },
103    { NT_("?"), HelpCmd, _("print help") },
104 };
105 #define comsize ((int)(sizeof(commands)/sizeof(struct cmdstruct)))
106 
107 /**
108  * Enter a prompt mode where the user can select/deselect
109  * files to be restored. This is sort of like a mini-shell
110  * that allows "cd", "pwd", "add", "rm", ...
111  */
UserSelectFilesFromTree(TreeContext * tree)112 bool UserSelectFilesFromTree(TreeContext *tree)
113 {
114    POOLMEM *cwd;
115    UaContext *ua;
116    BareosSocket *user;
117    bool status;
118 
119    /*
120     * Get a new context so we don't destroy restore command args
121     */
122    ua = new_ua_context(tree->ua->jcr);
123    ua->UA_sock = tree->ua->UA_sock;   /* patch in UA socket */
124    ua->api = tree->ua->api;           /* keep API flag too */
125    user = ua->UA_sock;
126 
127    ua->SendMsg(_("\nYou are now entering file selection mode where you add (mark) and\n"
128                   "remove (unmark) files to be restored. No files are initially added, unless\n"
129                   "you used the \"all\" keyword on the command line.\n"
130                   "Enter \"done\" to leave this mode.\n\n"));
131    user->signal(BNET_START_RTREE);
132 
133    /*
134     * Enter interactive command handler allowing selection of individual files.
135     */
136    tree->node = (TREE_NODE *)tree->root;
137    cwd = tree_getpath(tree->node);
138    if (cwd) {
139       ua->SendMsg(_("cwd is: %s\n"), cwd);
140       FreePoolMemory(cwd);
141    }
142 
143    while (1) {
144       int found, len, i;
145       if (!GetCmd(ua, "$ ", true)) {
146          break;
147       }
148 
149       if (ua->api) {
150          user->signal(BNET_CMD_BEGIN);
151       }
152 
153       ParseArgsOnly(ua->cmd, ua->args, &ua->argc, ua->argk, ua->argv, MAX_CMD_ARGS);
154       if (ua->argc == 0) {
155          ua->WarningMsg(_("Invalid command \"%s\". Enter \"done\" to exit.\n"), ua->cmd);
156          if (ua->api) {
157             user->signal(BNET_CMD_FAILED);
158          }
159          continue;
160       }
161 
162       found = 0;
163       status = false;
164       len = strlen(ua->argk[0]);
165       for (i = 0; i < comsize; i++) {     /* search for command */
166          if (bstrncasecmp(ua->argk[0],  commands[i].key, len)) {
167             status = (*commands[i].func)(ua, tree);   /* go execute command */
168             found = 1;
169             break;
170          }
171       }
172 
173       if (!found) {
174          if (*ua->argk[0] == '.') {
175             /* Some unknow dot command -- probably .messages, ignore it */
176             continue;
177          }
178          ua->WarningMsg(_("Invalid command \"%s\". Enter \"done\" to exit.\n"), ua->cmd);
179          if (ua->api) {
180             user->signal(BNET_CMD_FAILED);
181          }
182          continue;
183       }
184 
185       if (ua->api) {
186          user->signal(BNET_CMD_OK);
187       }
188 
189       if (!status) {
190          break;
191       }
192    }
193 
194    user->signal(BNET_END_RTREE);
195 
196    ua->UA_sock = NULL;                /* don't release restore socket */
197    status = !ua->quit;
198    ua->quit = false;
199    FreeUaContext(ua);               /* get rid of temp UA context */
200 
201    return status;
202 }
203 
204 /**
205  * This callback routine is responsible for inserting the
206  * items it gets into the directory tree. For each JobId selected
207  * this routine is called once for each file. We do not allow
208  * duplicate filenames, but instead keep the info from the most
209  * recent file entered (i.e. the JobIds are assumed to be sorted)
210  *
211  * See uar_sel_files in sql_cmds.c for query that calls us.
212  * row[0]=Path, row[1]=Filename, row[2]=FileIndex
213  * row[3]=JobId row[4]=LStat row[5]=DeltaSeq row[6]=Fhinfo row[7]=Fhnode
214  */
InsertTreeHandler(void * ctx,int num_fields,char ** row)215 int InsertTreeHandler(void *ctx, int num_fields, char **row)
216 {
217    struct stat statp;
218    TreeContext *tree = (TreeContext *)ctx;
219    TREE_NODE *node;
220    int type;
221    bool hard_link, ok;
222    int FileIndex;
223    int32_t delta_seq;
224    JobId_t JobId;
225    HL_ENTRY *entry = NULL;
226    int32_t LinkFI;
227 
228    Dmsg4(150, "Path=%s%s FI=%s JobId=%s\n", row[0], row[1],
229          row[2], row[3]);
230    if (*row[1] == 0) {                 /* no filename => directory */
231       if (!IsPathSeparator(*row[0])) { /* Must be Win32 directory */
232          type = TN_DIR_NLS;
233       } else {
234          type = TN_DIR;
235       }
236    } else {
237       type = TN_FILE;
238    }
239    DecodeStat(row[4], &statp, sizeof(statp), &LinkFI);
240    hard_link = (LinkFI != 0);
241    node = insert_tree_node(row[0], row[1], type, tree->root, NULL);
242    JobId = str_to_int64(row[3]);
243    FileIndex = str_to_int64(row[2]);
244    delta_seq = str_to_int64(row[5]);
245    node->fhinfo = str_to_int64(row[6]);
246    node->fhnode = str_to_int64(row[7]);
247    Dmsg8(150, "node=0x%p JobId=%s FileIndex=%s Delta=%s node.delta=%d LinkFI=%d, fhinfo=%d, fhnode=%d\n",
248          node, row[3], row[2], row[5], node->delta_seq, LinkFI, node->fhinfo, node->fhnode);
249 
250    /*
251     * TODO: check with hardlinks
252     */
253    if (delta_seq > 0) {
254       if (delta_seq == (node->delta_seq + 1)) {
255          TreeAddDeltaPart(tree->root, node, node->JobId, node->FileIndex);
256 
257       } else {
258          /* File looks to be deleted */
259          if (node->delta_seq == -1) { /* just created */
260             TreeRemoveNode(tree->root, node);
261 
262          } else {
263             tree->ua->WarningMsg(_("Something is wrong with the Delta sequence of %s, "
264                                     "skipping new parts. Current sequence is %d\n"),
265                                   row[1], node->delta_seq);
266 
267             Dmsg3(0, "Something is wrong with Delta, skip it "
268                   "fname=%s d1=%d d2=%d\n", row[1], node->delta_seq, delta_seq);
269          }
270          return 0;
271       }
272    }
273 
274    /*
275     * - The first time we see a file (node->inserted==true), we accept it.
276     * - In the same JobId, we accept only the first copy of a
277     *   hard linked file (the others are simply pointers).
278     * - In the same JobId, we accept the last copy of any other
279     *   file -- in particular directories.
280     *
281     * All the code to set ok could be condensed to a single
282     * line, but it would be even harder to read.
283     */
284    ok = true;
285    if (!node->inserted && JobId == node->JobId) {
286       if ((hard_link && FileIndex > node->FileIndex) ||
287           (!hard_link && FileIndex < node->FileIndex)) {
288          ok = false;
289       }
290    }
291    if (ok) {
292       node->hard_link = hard_link;
293       node->FileIndex = FileIndex;
294       node->JobId = JobId;
295       node->type = type;
296       node->soft_link = S_ISLNK(statp.st_mode) != 0;
297       node->delta_seq = delta_seq;
298 
299       if (tree->all) {
300          node->extract = true;          /* extract all by default */
301          if (type == TN_DIR || type == TN_DIR_NLS) {
302             node->extract_dir = true;   /* if dir, extract it */
303          }
304       }
305 
306       /*
307        * Insert file having hardlinks into hardlink hashtable.
308        */
309       if (statp.st_nlink > 1 && type != TN_DIR && type != TN_DIR_NLS) {
310          if (!LinkFI) {
311             /*
312              * First occurence - file hardlinked to
313              */
314             entry = (HL_ENTRY *)tree->root->hardlinks.hash_malloc(sizeof(HL_ENTRY));
315             entry->key = (((uint64_t) JobId) << 32) + FileIndex;
316             entry->node = node;
317             tree->root->hardlinks.insert(entry->key, entry);
318          } else {
319             /*
320              * See if we are optimizing for speed or size.
321              */
322             if (!me->optimize_for_size && me->optimize_for_speed) {
323                /*
324                 * Hardlink to known file index: lookup original file
325                 */
326                uint64_t file_key = (((uint64_t) JobId) << 32) + LinkFI;
327                HL_ENTRY *first_hl = (HL_ENTRY *) tree->root->hardlinks.lookup(file_key);
328 
329                if (first_hl && first_hl->node) {
330                   /*
331                    * Then add hardlink entry to linked node.
332                    */
333                   entry = (HL_ENTRY *)tree->root->hardlinks.hash_malloc(sizeof(HL_ENTRY));
334                   entry->key = (((uint64_t) JobId) << 32) + FileIndex;
335                   entry->node = first_hl->node;
336                   tree->root->hardlinks.insert(entry->key, entry);
337                }
338             }
339          }
340       }
341    }
342 
343    if (node->inserted) {
344       tree->FileCount++;
345       if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) {
346          tree->ua->SendMsg("+");
347          tree->LastCount = tree->FileCount;
348       }
349    }
350 
351    tree->cnt++;
352    return 0;
353 }
354 
355 /**
356  * Set extract to value passed. We recursively walk down the tree setting all children
357  * if the node is a directory.
358  */
SetExtract(UaContext * ua,TREE_NODE * node,TreeContext * tree,bool extract)359 static int SetExtract(UaContext *ua, TREE_NODE *node, TreeContext *tree, bool extract)
360 {
361    TREE_NODE *n;
362    int count = 0;
363 
364    node->extract = extract;
365    if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
366       node->extract_dir = extract;    /* set/clear dir too */
367    }
368 
369    if (node->type != TN_NEWDIR) {
370       count++;
371    }
372 
373    /*
374     * For a non-file (i.e. directory), we see all the children
375     */
376    if (node->type != TN_FILE || (node->soft_link && TreeNodeHasChild(node))) {
377       /*
378        * Recursive set children within directory
379        */
380       foreach_child(n, node) {
381          count += SetExtract(ua, n, tree, extract);
382       }
383 
384       /*
385        * Walk up tree marking any unextracted parent to be extracted.
386        */
387       if (extract) {
388          while (node->parent && !node->parent->extract_dir) {
389             node = node->parent;
390             node->extract_dir = true;
391          }
392       }
393    } else {
394       if (extract) {
395          uint64_t key = 0;
396          bool is_hardlinked = false;
397 
398          /*
399           * See if we are optimizing for speed or size.
400           */
401          if (!me->optimize_for_size && me->optimize_for_speed) {
402             if (node->hard_link) {
403                /*
404                 * Every hardlink is in hashtable, and it points to linked file.
405                 */
406                key = (((uint64_t) node->JobId) << 32) + node->FileIndex;
407                is_hardlinked = true;
408             }
409          } else {
410             FileDbRecord fdbr;
411             POOLMEM *cwd;
412 
413             /*
414              * Ordinary file, we get the full path, look up the attributes, decode them,
415              * and if we are hard linked to a file that was saved, we must load that file too.
416              */
417             cwd = tree_getpath(node);
418             if (cwd) {
419                fdbr.FileId = 0;
420                fdbr.JobId = node->JobId;
421 
422                if (node->hard_link && ua->db->GetFileAttributesRecord(ua->jcr, cwd, NULL, &fdbr)) {
423                   int32_t LinkFI;
424                   struct stat statp;
425 
426                   DecodeStat(fdbr.LStat, &statp, sizeof(statp), &LinkFI); /* decode stat pkt */
427                   key = (((uint64_t) node->JobId) << 32) + LinkFI;  /* lookup by linked file's fileindex */
428                   is_hardlinked = true;
429                }
430                FreePoolMemory(cwd);
431             }
432          }
433 
434          if (is_hardlinked) {
435             /*
436              * If we point to a hard linked file, find that file in hardlinks hashmap,
437              * and mark it to be restored as well.
438              */
439             HL_ENTRY *entry = (HL_ENTRY *) tree->root->hardlinks.lookup(key);
440             if (entry && entry->node) {
441                n = entry->node;
442                n->extract = true;
443                n->extract_dir = (n->type == TN_DIR || n->type == TN_DIR_NLS);
444             }
445          }
446       }
447    }
448 
449    return count;
450 }
451 
StripTrailingSlash(char * arg)452 static void StripTrailingSlash(char *arg)
453 {
454    int len = strlen(arg);
455    if (len == 0) {
456       return;
457    }
458    len--;
459    if (arg[len] == '/') {       /* strip any trailing slash */
460       arg[len] = 0;
461    }
462 }
463 
464 /**
465  * Recursively mark the current directory to be restored as
466  *  well as all directories and files below it.
467  */
markcmd(UaContext * ua,TreeContext * tree)468 static int markcmd(UaContext *ua, TreeContext *tree)
469 {
470    TREE_NODE *node;
471    int count = 0;
472    char ec1[50];
473    POOLMEM *cwd;
474    bool restore_cwd = false;
475 
476    if (ua->argc < 2 || !TreeNodeHasChild(tree->node)) {
477       ua->SendMsg(_("No files marked.\n"));
478       return 1;
479    }
480 
481    /*
482     * Save the current CWD.
483     */
484    cwd = tree_getpath(tree->node);
485 
486    for (int i = 1; i < ua->argc; i++) {
487       StripTrailingSlash(ua->argk[i]);
488 
489       /*
490        * See if this is a full path.
491        */
492       if (strchr(ua->argk[i], '/')) {
493          int pnl, fnl;
494          POOLMEM *file = GetPoolMemory(PM_FNAME);
495          POOLMEM *path = GetPoolMemory(PM_FNAME);
496 
497          /*
498           * Split the argument into a path and file part.
499           */
500          SplitPathAndFilename(ua->argk[i], path, &pnl, file, &fnl);
501 
502          /*
503           * First change the CWD to the correct PATH.
504           */
505          node = tree_cwd(path, tree->root, tree->node);
506          if (!node) {
507             ua->WarningMsg(_("Invalid path %s given.\n"), path);
508             FreePoolMemory(file);
509             FreePoolMemory(path);
510             continue;
511          }
512          tree->node = node;
513          restore_cwd = true;
514 
515          foreach_child(node, tree->node) {
516             if (fnmatch(file, node->fname, 0) == 0) {
517                count += SetExtract(ua, node, tree, true);
518             }
519          }
520 
521          FreePoolMemory(file);
522          FreePoolMemory(path);
523       } else {
524          /*
525           * Only a pattern without a / so do things relative to CWD.
526           */
527          foreach_child(node, tree->node) {
528             if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
529                count += SetExtract(ua, node, tree, true);
530             }
531          }
532       }
533    }
534 
535    if (count == 0) {
536       ua->SendMsg(_("No files marked.\n"));
537    } else if (count == 1) {
538       ua->SendMsg(_("1 file marked.\n"));
539    } else {
540       ua->SendMsg(_("%s files marked.\n"),
541                    edit_uint64_with_commas(count, ec1));
542    }
543 
544    /*
545     * Restore the CWD when we changed it.
546     */
547    if (restore_cwd && cwd) {
548       node = tree_cwd(cwd, tree->root, tree->node);
549       if (!node) {
550          ua->WarningMsg(_("Invalid path %s given.\n"), cwd);
551       } else {
552          tree->node = node;
553       }
554    }
555 
556    if (cwd) {
557       FreePoolMemory(cwd);
558    }
559 
560    return 1;
561 }
562 
Markdircmd(UaContext * ua,TreeContext * tree)563 static int Markdircmd(UaContext *ua, TreeContext *tree)
564 {
565    TREE_NODE *node;
566    int count = 0;
567    char ec1[50];
568 
569    if (ua->argc < 2 || !TreeNodeHasChild(tree->node)) {
570       ua->SendMsg(_("No files marked.\n"));
571       return 1;
572    }
573    for (int i = 1; i < ua->argc; i++) {
574       StripTrailingSlash(ua->argk[i]);
575       foreach_child(node, tree->node) {
576          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
577             if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
578                node->extract_dir = true;
579                count++;
580             }
581          }
582       }
583    }
584    if (count == 0) {
585       ua->SendMsg(_("No directories marked.\n"));
586    } else if (count == 1) {
587       ua->SendMsg(_("1 directory marked.\n"));
588    } else {
589       ua->SendMsg(_("%s directories marked.\n"),
590                edit_uint64_with_commas(count, ec1));
591    }
592    return 1;
593 }
594 
countcmd(UaContext * ua,TreeContext * tree)595 static int countcmd(UaContext *ua, TreeContext *tree)
596 {
597    TREE_NODE *node;
598    int total, num_extract;
599    char ec1[50], ec2[50];
600 
601    total = num_extract = 0;
602    for (node = FirstTreeNode(tree->root);
603         node;
604         node = NextTreeNode(node)) {
605       if (node->type != TN_NEWDIR) {
606          total++;
607          if (node->extract || node->extract_dir) {
608             num_extract++;
609          }
610       }
611    }
612    ua->SendMsg(_("%s total files/dirs. %s marked to be restored.\n"),
613             edit_uint64_with_commas(total, ec1),
614             edit_uint64_with_commas(num_extract, ec2));
615    return 1;
616 }
617 
findcmd(UaContext * ua,TreeContext * tree)618 static int findcmd(UaContext *ua, TreeContext *tree)
619 {
620    TREE_NODE *node;
621    POOLMEM *cwd;
622 
623    if (ua->argc == 1) {
624       ua->SendMsg(_("No file specification given.\n"));
625       return 1;      /* make it non-fatal */
626    }
627 
628    for (int i = 1; i < ua->argc; i++) {
629       for (node = FirstTreeNode(tree->root);
630            node;
631            node = NextTreeNode(node)) {
632          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
633             const char *tag;
634 
635             cwd = tree_getpath(node);
636             if (node->extract) {
637                tag = "*";
638             } else if (node->extract_dir) {
639                tag = "+";
640             } else {
641                tag = "";
642             }
643             ua->SendMsg("%s%s\n", tag, cwd);
644             FreePoolMemory(cwd);
645          }
646       }
647    }
648    return 1;
649 }
650 
DotLsdircmd(UaContext * ua,TreeContext * tree)651 static int DotLsdircmd(UaContext *ua, TreeContext *tree)
652 {
653    TREE_NODE *node;
654 
655    if (!TreeNodeHasChild(tree->node)) {
656       return 1;
657    }
658 
659    foreach_child(node, tree->node) {
660       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
661          if (TreeNodeHasChild(node)) {
662             ua->SendMsg("%s/\n", node->fname);
663          }
664       }
665    }
666 
667    return 1;
668 }
669 
DotHelpcmd(UaContext * ua,TreeContext * tree)670 static int DotHelpcmd(UaContext *ua, TreeContext *tree)
671 {
672    for (int i = 0; i < comsize; i++) {
673       /* List only non-dot commands */
674       if (commands[i].key[0] != '.') {
675          ua->SendMsg("%s\n", commands[i].key);
676       }
677    }
678    return 1;
679 }
680 
DotLscmd(UaContext * ua,TreeContext * tree)681 static int DotLscmd(UaContext *ua, TreeContext *tree)
682 {
683    TREE_NODE *node;
684 
685    if (!TreeNodeHasChild(tree->node)) {
686       return 1;
687    }
688 
689    foreach_child(node, tree->node) {
690       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
691          ua->SendMsg("%s%s\n", node->fname, TreeNodeHasChild(node)?"/":"");
692       }
693    }
694 
695    return 1;
696 }
697 
lscmd(UaContext * ua,TreeContext * tree)698 static int lscmd(UaContext *ua, TreeContext *tree)
699 {
700    TREE_NODE *node;
701 
702    if (!TreeNodeHasChild(tree->node)) {
703       return 1;
704    }
705    foreach_child(node, tree->node) {
706       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
707          const char *tag;
708          if (node->extract) {
709             tag = "*";
710          } else if (node->extract_dir) {
711             tag = "+";
712          } else {
713             tag = "";
714          }
715          ua->SendMsg("%s%s%s\n", tag, node->fname, TreeNodeHasChild(node)?"/":"");
716       }
717    }
718    return 1;
719 }
720 
721 /**
722  * Ls command that lists only the marked files
723  */
DotLsmarkcmd(UaContext * ua,TreeContext * tree)724 static int DotLsmarkcmd(UaContext *ua, TreeContext *tree)
725 {
726    TREE_NODE *node;
727    if (!TreeNodeHasChild(tree->node)) {
728       return 1;
729    }
730    foreach_child(node, tree->node) {
731       if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
732           (node->extract || node->extract_dir)) {
733          ua->SendMsg("%s%s\n", node->fname, TreeNodeHasChild(node)?"/":"");
734       }
735    }
736    return 1;
737 }
738 
739 /**
740  * This recursive ls command that lists only the marked files
741  */
rlsmark(UaContext * ua,TREE_NODE * tnode,int level)742 static void rlsmark(UaContext *ua, TREE_NODE *tnode, int level)
743 {
744    TREE_NODE *node;
745    const int max_level = 100;
746    char indent[max_level*2+1];
747    int i, j;
748 
749    if (!TreeNodeHasChild(tnode)) {
750       return;
751    }
752 
753    level = MIN(level, max_level);
754    j = 0;
755    for (i = 0; i < level; i++) {
756       indent[j++] = ' ';
757       indent[j++] = ' ';
758    }
759    indent[j] = 0;
760 
761    foreach_child(node, tnode) {
762       if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
763           (node->extract || node->extract_dir)) {
764          const char *tag;
765          if (node->extract) {
766             tag = "*";
767          } else if (node->extract_dir) {
768             tag = "+";
769          } else {
770             tag = "";
771          }
772          ua->SendMsg("%s%s%s%s\n", indent, tag, node->fname, TreeNodeHasChild(node)?"/":"");
773          if (TreeNodeHasChild(node)) {
774             rlsmark(ua, node, level+1);
775          }
776       }
777    }
778 }
779 
Lsmarkcmd(UaContext * ua,TreeContext * tree)780 static int Lsmarkcmd(UaContext *ua, TreeContext *tree)
781 {
782    rlsmark(ua, tree->node, 0);
783    return 1;
784 }
785 
786 /**
787  * This is actually the long form used for "dir"
788  */
ls_output(guid_list * guid,POOLMEM * & buf,const char * fname,const char * tag,struct stat * statp,bool dot_cmd)789 static inline void ls_output(guid_list *guid, POOLMEM *&buf,
790                              const char *fname, const char *tag,
791                              struct stat *statp, bool dot_cmd)
792 {
793    char mode_str[11];
794    char time_str[22];
795    char ec1[30];
796    char en1[30], en2[30];
797 
798    /*
799     * Insert mode e.g. -r-xr-xr-x
800     */
801    encode_mode(statp->st_mode, mode_str);
802 
803    if (dot_cmd) {
804       encode_time(statp->st_mtime, time_str);
805       Mmsg(buf, "%s,%d,%s,%s,%s,%s,%c,%s",
806            mode_str,
807            (uint32_t)statp->st_nlink,
808            guid->uid_to_name(statp->st_uid, en1, sizeof(en1)),
809            guid->gid_to_name(statp->st_gid, en2, sizeof(en2)),
810            edit_int64(statp->st_size, ec1),
811            time_str,
812            *tag,
813            fname);
814    } else {
815       time_t time;
816 
817       if (statp->st_ctime > statp->st_mtime) {
818          time = statp->st_ctime;
819       } else {
820          time = statp->st_mtime;
821       }
822 
823       /*
824        * Display most recent time
825        */
826       encode_time(time, time_str);
827 
828       Mmsg(buf, "%s  %2d %-8.8s %-8.8s  %12.12s  %s %c%s",
829            mode_str,
830            (uint32_t)statp->st_nlink,
831            guid->uid_to_name(statp->st_uid, en1, sizeof(en1)),
832            guid->gid_to_name(statp->st_gid, en2, sizeof(en2)),
833            edit_int64(statp->st_size, ec1),
834            time_str,
835            *tag,
836            fname);
837    }
838 }
839 
840 /**
841  * Like ls command, but give more detail on each file
842  */
DoDircmd(UaContext * ua,TreeContext * tree,bool dot_cmd)843 static int DoDircmd(UaContext *ua, TreeContext *tree, bool dot_cmd)
844 {
845    TREE_NODE *node;
846    POOLMEM *cwd, *buf;
847    FileDbRecord fdbr;
848    struct stat statp;
849    char *pcwd;
850 
851    if (!TreeNodeHasChild(tree->node)) {
852       ua->SendMsg(_("Node %s has no children.\n"), tree->node->fname);
853       return 1;
854    }
855 
856    ua->guid = new_guid_list();
857    buf = GetPoolMemory(PM_FNAME);
858 
859    foreach_child(node, tree->node) {
860       const char *tag;
861       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
862          if (node->extract) {
863             tag = "*";
864          } else if (node->extract_dir) {
865             tag = "+";
866          } else {
867             tag = " ";
868          }
869 
870          cwd = tree_getpath(node);
871 
872          fdbr.FileId = 0;
873          fdbr.JobId = node->JobId;
874 
875          /*
876           * Strip / from soft links to directories.
877           *
878           * This is because soft links to files have a trailing slash
879           * when returned from tree_getpath, but get_file_attr...
880           * treats soft links as files, so they do not have a trailing
881           * slash like directory names.
882           */
883          if (node->type == TN_FILE && TreeNodeHasChild(node)) {
884             PmStrcpy(buf, cwd);
885             pcwd = buf;
886             int len = strlen(buf);
887             if (len > 1) {
888                buf[len - 1] = '\0'; /* strip trailing / */
889             }
890          } else {
891             pcwd = cwd;
892          }
893 
894          if (ua->db->GetFileAttributesRecord(ua->jcr, pcwd, NULL, &fdbr)) {
895             int32_t LinkFI;
896             DecodeStat(fdbr.LStat, &statp, sizeof(statp), &LinkFI); /* decode stat pkt */
897          } else {
898             /* Something went wrong getting attributes -- print name */
899             memset(&statp, 0, sizeof(statp));
900          }
901 
902          ls_output(ua->guid, buf, cwd, tag, &statp, dot_cmd);
903          ua->SendMsg("%s\n", buf);
904 
905          FreePoolMemory(cwd);
906       }
907    }
908 
909    FreePoolMemory(buf);
910 
911    return 1;
912 }
913 
DotDircmd(UaContext * ua,TreeContext * tree)914 int DotDircmd(UaContext *ua, TreeContext *tree)
915 {
916    return DoDircmd(ua, tree, true/*dot command*/);
917 }
918 
dircmd(UaContext * ua,TreeContext * tree)919 static int dircmd(UaContext *ua, TreeContext *tree)
920 {
921    return DoDircmd(ua, tree, false/*not dot command*/);
922 }
923 
Estimatecmd(UaContext * ua,TreeContext * tree)924 static int Estimatecmd(UaContext *ua, TreeContext *tree)
925 {
926    TREE_NODE *node;
927    POOLMEM *cwd;
928    int total, num_extract;
929    uint64_t total_bytes = 0;
930    FileDbRecord fdbr;
931    struct stat statp;
932    char ec1[50];
933 
934    total = num_extract = 0;
935    for (node = FirstTreeNode(tree->root);
936         node;
937         node = NextTreeNode(node)) {
938       if (node->type != TN_NEWDIR) {
939          total++;
940          if (node->extract && node->type == TN_FILE) {
941             /*
942              * If regular file, get size
943              */
944             num_extract++;
945             cwd = tree_getpath(node);
946 
947             fdbr.FileId = 0;
948             fdbr.JobId = node->JobId;
949 
950             if (ua->db->GetFileAttributesRecord(ua->jcr, cwd, NULL, &fdbr)) {
951                int32_t LinkFI;
952                DecodeStat(fdbr.LStat, &statp, sizeof(statp), &LinkFI); /* decode stat pkt */
953                if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
954                   total_bytes += statp.st_size;
955                }
956             }
957 
958             FreePoolMemory(cwd);
959          } else if (node->extract || node->extract_dir) {
960             /*
961              * Directory, count only
962              */
963             num_extract++;
964          }
965       }
966    }
967    ua->SendMsg(_("%d total files; %d marked to be restored; %s bytes.\n"),
968             total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
969    return 1;
970 }
971 
HelpCmd(UaContext * ua,TreeContext * tree)972 static int HelpCmd(UaContext *ua, TreeContext *tree)
973 {
974    unsigned int i;
975 
976    ua->SendMsg(_("  Command    Description\n  =======    ===========\n"));
977    for (i = 0; i < comsize; i++) {
978       /*
979        * List only non-dot commands
980        */
981       if (commands[i].key[0] != '.') {
982          ua->SendMsg("  %-10s %s\n", _(commands[i].key), _(commands[i].help));
983       }
984    }
985    ua->SendMsg("\n");
986    return 1;
987 }
988 
989 /**
990  * Change directories.  Note, if the user specifies x: and it fails,
991  * we assume it is a Win32 absolute cd rather than relative and
992  * try a second time with /x: ...  Win32 kludge.
993  */
cdcmd(UaContext * ua,TreeContext * tree)994 static int cdcmd(UaContext *ua, TreeContext *tree)
995 {
996    TREE_NODE *node;
997    POOLMEM *cwd;
998 
999    if (ua->argc != 2) {
1000       ua->ErrorMsg(_("Too few or too many arguments. Try using double quotes.\n"));
1001       return 1;
1002    }
1003 
1004    node = tree_cwd(ua->argk[1], tree->root, tree->node);
1005    if (!node) {
1006       /*
1007        * Try once more if Win32 drive -- make absolute
1008        */
1009       if (ua->argk[1][1] == ':') {  /* win32 drive */
1010          cwd = GetPoolMemory(PM_FNAME);
1011          PmStrcpy(cwd, "/");
1012          PmStrcat(cwd, ua->argk[1]);
1013          node = tree_cwd(cwd, tree->root, tree->node);
1014          FreePoolMemory(cwd);
1015       }
1016 
1017       if (!node) {
1018          ua->WarningMsg(_("Invalid path given.\n"));
1019       } else {
1020          tree->node = node;
1021       }
1022    } else {
1023       tree->node = node;
1024    }
1025 
1026    return pwdcmd(ua, tree);
1027 }
1028 
pwdcmd(UaContext * ua,TreeContext * tree)1029 static int pwdcmd(UaContext *ua, TreeContext *tree)
1030 {
1031    POOLMEM *cwd;
1032 
1033    cwd = tree_getpath(tree->node);
1034    if (cwd) {
1035       if (ua->api) {
1036          ua->SendMsg("%s", cwd);
1037       } else {
1038          ua->SendMsg(_("cwd is: %s\n"), cwd);
1039       }
1040       FreePoolMemory(cwd);
1041    }
1042 
1043    return 1;
1044 }
1045 
DotPwdcmd(UaContext * ua,TreeContext * tree)1046 static int DotPwdcmd(UaContext *ua, TreeContext *tree)
1047 {
1048    POOLMEM *cwd;
1049 
1050    cwd = tree_getpath(tree->node);
1051    if (cwd) {
1052       ua->SendMsg("%s", cwd);
1053       FreePoolMemory(cwd);
1054    }
1055 
1056    return 1;
1057 }
1058 
Unmarkcmd(UaContext * ua,TreeContext * tree)1059 static int Unmarkcmd(UaContext *ua, TreeContext *tree)
1060 {
1061    POOLMEM *cwd;
1062    TREE_NODE *node;
1063    int count = 0;
1064    bool restore_cwd = false;
1065 
1066    if (ua->argc < 2 || !TreeNodeHasChild(tree->node)) {
1067       ua->SendMsg(_("No files unmarked.\n"));
1068       return 1;
1069    }
1070 
1071    /*
1072     * Save the current CWD.
1073     */
1074    cwd = tree_getpath(tree->node);
1075 
1076    for (int i = 1; i < ua->argc; i++) {
1077       StripTrailingSlash(ua->argk[i]);
1078 
1079       /*
1080        * See if this is a full path.
1081        */
1082       if (strchr(ua->argk[i], '/')) {
1083          int pnl, fnl;
1084          POOLMEM *file = GetPoolMemory(PM_FNAME);
1085          POOLMEM *path = GetPoolMemory(PM_FNAME);
1086 
1087          /*
1088           * Split the argument into a path and file part.
1089           */
1090          SplitPathAndFilename(ua->argk[i], path, &pnl, file, &fnl);
1091 
1092          /*
1093           * First change the CWD to the correct PATH.
1094           */
1095          node = tree_cwd(path, tree->root, tree->node);
1096          if (!node) {
1097             ua->WarningMsg(_("Invalid path %s given.\n"), path);
1098             FreePoolMemory(file);
1099             FreePoolMemory(path);
1100             continue;
1101          }
1102          tree->node = node;
1103          restore_cwd = true;
1104 
1105          foreach_child(node, tree->node) {
1106             if (fnmatch(file, node->fname, 0) == 0) {
1107                count += SetExtract(ua, node, tree, false);
1108             }
1109          }
1110 
1111          FreePoolMemory(file);
1112          FreePoolMemory(path);
1113       } else {
1114          /*
1115           * Only a pattern without a / so do things relative to CWD.
1116           */
1117          foreach_child(node, tree->node) {
1118             if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
1119                count += SetExtract(ua, node, tree, false);
1120             }
1121          }
1122       }
1123    }
1124 
1125    if (count == 0) {
1126       ua->SendMsg(_("No files unmarked.\n"));
1127    } else if (count == 1) {
1128       ua->SendMsg(_("1 file unmarked.\n"));
1129    } else {
1130       char ed1[50];
1131       ua->SendMsg(_("%s files unmarked.\n"), edit_uint64_with_commas(count, ed1));
1132    }
1133 
1134    /*
1135     * Restore the CWD when we changed it.
1136     */
1137    if (restore_cwd && cwd) {
1138       node = tree_cwd(cwd, tree->root, tree->node);
1139       if (!node) {
1140          ua->WarningMsg(_("Invalid path %s given.\n"), cwd);
1141       } else {
1142          tree->node = node;
1143       }
1144    }
1145 
1146    if (cwd) {
1147       FreePoolMemory(cwd);
1148    }
1149 
1150    return 1;
1151 }
1152 
UnMarkdircmd(UaContext * ua,TreeContext * tree)1153 static int UnMarkdircmd(UaContext *ua, TreeContext *tree)
1154 {
1155    TREE_NODE *node;
1156    int count = 0;
1157 
1158    if (ua->argc < 2 || !TreeNodeHasChild(tree->node)) {
1159       ua->SendMsg(_("No directories unmarked.\n"));
1160       return 1;
1161    }
1162 
1163    for (int i = 1; i < ua->argc; i++) {
1164       StripTrailingSlash(ua->argk[i]);
1165       foreach_child(node, tree->node) {
1166          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
1167             if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
1168                node->extract_dir = false;
1169                count++;
1170             }
1171          }
1172       }
1173    }
1174 
1175    if (count == 0) {
1176       ua->SendMsg(_("No directories unmarked.\n"));
1177    } else if (count == 1) {
1178       ua->SendMsg(_("1 directory unmarked.\n"));
1179    } else {
1180       ua->SendMsg(_("%d directories unmarked.\n"), count);
1181    }
1182    return 1;
1183 }
1184 
donecmd(UaContext * ua,TreeContext * tree)1185 static int donecmd(UaContext *ua, TreeContext *tree)
1186 {
1187    return 0;
1188 }
1189 
QuitCmd(UaContext * ua,TreeContext * tree)1190 static int QuitCmd(UaContext *ua, TreeContext *tree)
1191 {
1192    ua->quit = true;
1193    return 0;
1194 }
1195 } /* namespace directordaemon */
1196