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