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