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