]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_tree.c
kes More copyright changes.
[bacula/bacula] / bacula / src / dird / ua_tree.c
1 /*
2  *
3  *   Bacula Director -- User Agent Database File tree for Restore
4  *      command. This file interacts with the user implementing the
5  *      UA tree commands.
6  *
7  *     Kern Sibbald, July MMII
8  *
9  *   Version $Id$
10  */
11 /*
12    Bacula® - The Network Backup Solution
13
14    Copyright (C) 2002-2006 Free Software Foundation Europe e.V.
15
16    The main author of Bacula is Kern Sibbald, with contributions from
17    many others, a complete list can be found in the file AUTHORS.
18    This program is Free Software; you can redistribute it and/or
19    modify it under the terms of version two of the GNU General Public
20    License as published by the Free Software Foundation plus additions
21    that are listed in the file LICENSE.
22
23    This program is distributed in the hope that it will be useful, but
24    WITHOUT ANY WARRANTY; without even the implied warranty of
25    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26    General Public License for more details.
27
28    You should have received a copy of the GNU General Public License
29    along with this program; if not, write to the Free Software
30    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
31    02110-1301, USA.
32
33    Bacula® is a registered trademark of John Walker.
34    The licensor of Bacula is the Free Software Foundation Europe
35    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
36    Switzerland, email:ftf@fsfeurope.org.
37 */
38
39 #include "bacula.h"
40 #include "dird.h"
41 #ifdef HAVE_FNMATCH
42 #include <fnmatch.h>
43 #else
44 #include "lib/fnmatch.h"
45 #endif
46 #include "findlib/find.h"
47
48
49 /* Forward referenced commands */
50
51 static int markcmd(UAContext *ua, TREE_CTX *tree);
52 static int markdircmd(UAContext *ua, TREE_CTX *tree);
53 static int countcmd(UAContext *ua, TREE_CTX *tree);
54 static int findcmd(UAContext *ua, TREE_CTX *tree);
55 static int lscmd(UAContext *ua, TREE_CTX *tree);
56 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree);
57 static int dircmd(UAContext *ua, TREE_CTX *tree);
58 static int dot_dircmd(UAContext *ua, TREE_CTX *tree);
59 static int estimatecmd(UAContext *ua, TREE_CTX *tree);
60 static int helpcmd(UAContext *ua, TREE_CTX *tree);
61 static int cdcmd(UAContext *ua, TREE_CTX *tree);
62 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
63 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
64 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree);
65 static int quitcmd(UAContext *ua, TREE_CTX *tree);
66 static int donecmd(UAContext *ua, TREE_CTX *tree);
67
68
69 struct cmdstruct { const char *key; int (*func)(UAContext *ua, TREE_CTX *tree); const char *help; };
70 static struct cmdstruct commands[] = {
71  { NT_("cd"),         cdcmd,        _("change current directory")},
72  { NT_("count"),      countcmd,     _("count marked files in and below the cd")},
73  { NT_("dir"),        dircmd,       _("long list current directory, wildcards allowed")},
74  { NT_(".dir"),       dot_dircmd,   _("long list current directory, wildcards allowed")},
75  { NT_("done"),       donecmd,      _("leave file selection mode")},
76  { NT_("estimate"),   estimatecmd,  _("estimate restore size")},
77  { NT_("exit"),       donecmd,      _("same as done command")},
78  { NT_("find"),       findcmd,      _("find files, wildcards allowed")},
79  { NT_("help"),       helpcmd,      _("print help")},
80  { NT_("ls"),         lscmd,        _("list current directory, wildcards allowed")},
81  { NT_("lsmark"),     lsmarkcmd,    _("list the marked files in and below the cd")},
82  { NT_("mark"),       markcmd,      _("mark dir/file to be restored recursively, wildcards allowed")},
83  { NT_("markdir"),    markdircmd,   _("mark directory name to be restored (no files)")},
84  { NT_("pwd"),        pwdcmd,       _("print current working directory")},
85  { NT_("unmark"),     unmarkcmd,    _("unmark dir/file to be restored recursively in dir")},
86  { NT_("unmarkdir"),  unmarkdircmd, _("unmark directory name only no recursion")},
87  { NT_("quit"),       quitcmd,      _("quit and do not do restore")},
88  { NT_("?"),          helpcmd,      _("print help")},
89              };
90 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
91
92
93 /*
94  * Enter a prompt mode where the user can select/deselect
95  *  files to be restored. This is sort of like a mini-shell
96  *  that allows "cd", "pwd", "add", "rm", ...
97  */
98 bool user_select_files_from_tree(TREE_CTX *tree)
99 {
100    char cwd[2000];
101    bool stat;
102    /* Get a new context so we don't destroy restore command args */
103    UAContext *ua = new_ua_context(tree->ua->jcr);
104    ua->UA_sock = tree->ua->UA_sock;   /* patch in UA socket */
105
106    bsendmsg(tree->ua, _(
107       "\nYou are now entering file selection mode where you add (mark) and\n"
108       "remove (unmark) files to be restored. No files are initially added, unless\n"
109       "you used the \"all\" keyword on the command line.\n"
110       "Enter \"done\" to leave this mode.\n\n"));
111    /*
112     * Enter interactive command handler allowing selection
113     *  of individual files.
114     */
115    tree->node = (TREE_NODE *)tree->root;
116    tree_getpath(tree->node, cwd, sizeof(cwd));
117    bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
118    for ( ;; ) {
119       int found, len, i;
120       if (!get_cmd(ua, "$ ")) {
121          break;
122       }
123       parse_ua_args(ua);
124       if (ua->argc == 0) {
125          bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
126          continue;
127       }
128
129       len = strlen(ua->argk[0]);
130       found = 0;
131       stat = false;
132       for (i=0; i<(int)comsize; i++)       /* search for command */
133          if (strncasecmp(ua->argk[0],  _(commands[i].key), len) == 0) {
134             stat = (*commands[i].func)(ua, tree);   /* go execute command */
135             found = 1;
136             break;
137          }
138       if (!found) {
139          bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
140          continue;
141       }
142       if (!stat) {
143          break;
144       }
145    }
146    ua->UA_sock = NULL;                /* don't release restore socket */
147    stat = !ua->quit;
148    ua->quit = false;
149    free_ua_context(ua);               /* get rid of temp UA context */
150    return stat;
151 }
152
153
154 /*
155  * This callback routine is responsible for inserting the
156  *  items it gets into the directory tree. For each JobId selected
157  *  this routine is called once for each file. We do not allow
158  *  duplicate filenames, but instead keep the info from the most
159  *  recent file entered (i.e. the JobIds are assumed to be sorted)
160  *
161  *   See uar_sel_files in sql_cmds.c for query that calls us.
162  *      row[0]=Path, row[1]=Filename, row[2]=FileIndex
163  *      row[3]=JobId row[4]=LStat
164  */
165 int insert_tree_handler(void *ctx, int num_fields, char **row)
166 {
167    struct stat statp;
168    TREE_CTX *tree = (TREE_CTX *)ctx;
169    TREE_NODE *node;
170    int type;
171    bool hard_link, ok;
172    int FileIndex;
173    JobId_t JobId;
174
175 // Dmsg4(000, "Path=%s%s FI=%s JobId=%s\n", row[0], row[1],
176 //    row[2], row[3]);
177    if (*row[1] == 0) {                 /* no filename => directory */
178       if (!IsPathSeparator(*row[0])) { /* Must be Win32 directory */
179          type = TN_DIR_NLS;
180       } else {
181          type = TN_DIR;
182       }
183    } else {
184       type = TN_FILE;
185    }
186    hard_link = (decode_LinkFI(row[4], &statp) != 0);
187    node = insert_tree_node(row[0], row[1], type, tree->root, NULL);
188    JobId = str_to_int64(row[3]);
189    FileIndex = str_to_int64(row[2]);
190    /*
191     * - The first time we see a file (node->inserted==true), we accept it.
192     * - In the same JobId, we accept only the first copy of a
193     *   hard linked file (the others are simply pointers).
194     * - In the same JobId, we accept the last copy of any other
195     *   file -- in particular directories.
196     *
197     * All the code to set ok could be condensed to a single
198     *  line, but it would be even harder to read.
199     */
200    ok = true;
201    if (!node->inserted && JobId == node->JobId) {
202       if ((hard_link && FileIndex > node->FileIndex) ||
203           (!hard_link && FileIndex < node->FileIndex)) {
204          ok = false;
205       }
206    }
207    if (ok) {
208       node->hard_link = hard_link;
209       node->FileIndex = FileIndex;
210       node->JobId = JobId;
211       node->type = type;
212       node->soft_link = S_ISLNK(statp.st_mode) != 0;
213       if (tree->all) {
214          node->extract = true;          /* extract all by default */
215          if (type == TN_DIR || type == TN_DIR_NLS) {
216             node->extract_dir = true;   /* if dir, extract it */
217          }
218       }
219    }
220    if (node->inserted) {
221       tree->FileCount++;
222       if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) {
223          bsendmsg(tree->ua, "+");
224          tree->LastCount = tree->FileCount;
225       }
226    }
227    tree->cnt++;
228    return 0;
229 }
230
231
232 /*
233  * Set extract to value passed. We recursively walk
234  *  down the tree setting all children if the
235  *  node is a directory.
236  */
237 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
238 {
239    TREE_NODE *n;
240    FILE_DBR fdbr;
241    struct stat statp;
242    int count = 0;
243
244    node->extract = extract;
245    if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
246       node->extract_dir = extract;    /* set/clear dir too */
247    }
248    if (node->type != TN_NEWDIR) {
249       count++;
250    }
251    /* For a non-file (i.e. directory), we see all the children */
252    if (node->type != TN_FILE || (node->soft_link && tree_node_has_child(node))) {
253       /* Recursive set children within directory */
254       foreach_child(n, node) {
255          count += set_extract(ua, n, tree, extract);
256       }
257       /*
258        * Walk up tree marking any unextracted parent to be
259        * extracted.
260        */
261       if (extract) {
262          while (node->parent && !node->parent->extract_dir) {
263             node = node->parent;
264             node->extract_dir = true;
265          }
266       }
267    } else if (extract) {
268       char cwd[2000];
269       /*
270        * Ordinary file, we get the full path, look up the
271        * attributes, decode them, and if we are hard linked to
272        * a file that was saved, we must load that file too.
273        */
274       tree_getpath(node, cwd, sizeof(cwd));
275       fdbr.FileId = 0;
276       fdbr.JobId = node->JobId;
277       if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
278          int32_t LinkFI;
279          decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
280          /*
281           * If we point to a hard linked file, traverse the tree to
282           * find that file, and mark it to be restored as well. It
283           * must have the Link we just obtained and the same JobId.
284           */
285          if (LinkFI) {
286             for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
287                if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
288                   n->extract = true;
289                   if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
290                      n->extract_dir = true;
291                   }
292                   break;
293                }
294             }
295          }
296       }
297    }
298    return count;
299 }
300
301 /*
302  * Recursively mark the current directory to be restored as
303  *  well as all directories and files below it.
304  */
305 static int markcmd(UAContext *ua, TREE_CTX *tree)
306 {
307    TREE_NODE *node;
308    int count = 0;
309    char ec1[50];
310
311    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
312       bsendmsg(ua, _("No files marked.\n"));
313       return 1;
314    }
315    for (int i=1; i < ua->argc; i++) {
316       foreach_child(node, tree->node) {
317          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
318             count += set_extract(ua, node, tree, true);
319          }
320       }
321    }
322    if (count == 0) {
323       bsendmsg(ua, _("No files marked.\n"));
324    } else if (count == 1) {
325       bsendmsg(ua, _("1 file marked.\n"));
326    } else {
327       bsendmsg(ua, _("%s files marked.\n"),
328                edit_uint64_with_commas(count, ec1));
329    }
330    return 1;
331 }
332
333 static int markdircmd(UAContext *ua, TREE_CTX *tree)
334 {
335    TREE_NODE *node;
336    int count = 0;
337    char ec1[50];
338
339    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
340       bsendmsg(ua, _("No files marked.\n"));
341       return 1;
342    }
343    for (int i=1; i < ua->argc; i++) {
344       foreach_child(node, tree->node) {
345          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
346             if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
347                node->extract_dir = true;
348                count++;
349             }
350          }
351       }
352    }
353    if (count == 0) {
354       bsendmsg(ua, _("No directories marked.\n"));
355    } else if (count == 1) {
356       bsendmsg(ua, _("1 directory marked.\n"));
357    } else {
358       bsendmsg(ua, _("%s directories marked.\n"),
359                edit_uint64_with_commas(count, ec1));
360    }
361    return 1;
362 }
363
364
365 static int countcmd(UAContext *ua, TREE_CTX *tree)
366 {
367    int total, num_extract;
368    char ec1[50], ec2[50];
369
370    total = num_extract = 0;
371    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
372       if (node->type != TN_NEWDIR) {
373          total++;
374          if (node->extract || node->extract_dir) {
375             num_extract++;
376          }
377       }
378    }
379    bsendmsg(ua, _("%s total files/dirs. %s marked to be restored.\n"),
380             edit_uint64_with_commas(total, ec1),
381             edit_uint64_with_commas(num_extract, ec2));
382    return 1;
383 }
384
385 static int findcmd(UAContext *ua, TREE_CTX *tree)
386 {
387    char cwd[2000];
388
389    if (ua->argc == 1) {
390       bsendmsg(ua, _("No file specification given.\n"));
391       return 1;      /* make it non-fatal */
392    }
393
394    for (int i=1; i < ua->argc; i++) {
395       for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
396          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
397             const char *tag;
398             tree_getpath(node, cwd, sizeof(cwd));
399             if (node->extract) {
400                tag = "*";
401             } else if (node->extract_dir) {
402                tag = "+";
403             } else {
404                tag = "";
405             }
406             bsendmsg(ua, "%s%s\n", tag, cwd);
407          }
408       }
409    }
410    return 1;
411 }
412
413
414
415 static int lscmd(UAContext *ua, TREE_CTX *tree)
416 {
417    TREE_NODE *node;
418
419    if (!tree_node_has_child(tree->node)) {
420       return 1;
421    }
422    foreach_child(node, tree->node) {
423       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
424          const char *tag;
425          if (node->extract) {
426             tag = "*";
427          } else if (node->extract_dir) {
428             tag = "+";
429          } else {
430             tag = "";
431          }
432          bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
433       }
434    }
435    return 1;
436 }
437
438 /*
439  * Ls command that lists only the marked files
440  */
441 static void rlsmark(UAContext *ua, TREE_NODE *tnode)
442 {
443    TREE_NODE *node;
444    if (!tree_node_has_child(tnode)) {
445       return;
446    }
447    foreach_child(node, tnode) {
448       if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
449           (node->extract || node->extract_dir)) {
450          const char *tag;
451          if (node->extract) {
452             tag = "*";
453          } else if (node->extract_dir) {
454             tag = "+";
455          } else {
456             tag = "";
457          }
458          bsendmsg(ua, "%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
459          if (tree_node_has_child(node)) {
460             rlsmark(ua, node);
461          }
462       }
463    }
464 }
465
466 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
467 {
468    rlsmark(ua, tree->node);
469    return 1;
470 }
471
472
473
474 extern char *getuser(uid_t uid, char *name, int len);
475 extern char *getgroup(gid_t gid, char *name, int len);
476
477 /*
478  * This is actually the long form used for "dir"
479  */
480 static void ls_output(char *buf, const char *fname, const char *tag, 
481                       struct stat *statp, bool dot_cmd) 
482                     
483 {
484    char *p;
485    const char *f;
486    char ec1[30];
487    char en1[30], en2[30];
488    int n;
489    time_t time;
490
491    p = encode_mode(statp->st_mode, buf);
492    if (dot_cmd) {
493       *p++ = ',';
494       n = sprintf(p, "%d,", (uint32_t)statp->st_nlink);
495       p += n;
496       n = sprintf(p, "%s,%s,", getuser(statp->st_uid, en1, sizeof(en1)),
497                   getgroup(statp->st_gid, en2, sizeof(en2)));
498       p += n;
499       n = sprintf(p, "%s,", edit_uint64(statp->st_size, ec1));
500       p += n;
501       p = encode_time(statp->st_mtime, p);
502       *p++ = ',';
503       *p++ = *tag;
504       *p++ = ',';
505    } else {
506       n = sprintf(p, "  %2d ", (uint32_t)statp->st_nlink);
507       p += n;
508       n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid, en1, sizeof(en1)),
509                   getgroup(statp->st_gid, en2, sizeof(en2)));
510       p += n;
511       n = sprintf(p, "%10.10s  ", edit_uint64(statp->st_size, ec1));
512       p += n;
513       if (statp->st_ctime > statp->st_mtime) {
514          time = statp->st_ctime;
515       } else {
516          time = statp->st_mtime;
517       }
518       /* Display most recent time */
519       p = encode_time(time, p);
520       *p++ = ' ';
521       *p++ = *tag;
522    }
523    for (f=fname; *f; ) {
524       *p++ = *f++;
525    }
526    *p = 0;
527 }
528
529 /*
530  * Like ls command, but give more detail on each file
531  */
532 static int do_dircmd(UAContext *ua, TREE_CTX *tree, bool dot_cmd)
533 {
534    TREE_NODE *node;
535    FILE_DBR fdbr;
536    struct stat statp;
537    char buf[1100];
538    char cwd[1100], *pcwd;
539
540    if (!tree_node_has_child(tree->node)) {
541       bsendmsg(ua, _("Node %s has no children.\n"), tree->node->fname);
542       return 1;
543    }
544
545    foreach_child(node, tree->node) {
546       const char *tag;
547       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
548          if (node->extract) {
549             tag = "*";
550          } else if (node->extract_dir) {
551             tag = "+";
552          } else {
553             tag = " ";
554          }
555          tree_getpath(node, cwd, sizeof(cwd));
556          fdbr.FileId = 0;
557          fdbr.JobId = node->JobId;
558          /*
559           * Strip / from soft links to directories.
560           *   This is because soft links to files have a trailing slash
561           *   when returned from tree_getpath, but db_get_file_attr...
562           *   treats soft links as files, so they do not have a trailing
563           *   slash like directory names.
564           */
565          if (node->type == TN_FILE && tree_node_has_child(node)) {
566             bstrncpy(buf, cwd, sizeof(buf));
567             pcwd = buf;
568             int len = strlen(buf);
569             if (len > 1) {
570                buf[len-1] = 0;        /* strip trailing / */
571             }
572          } else {
573             pcwd = cwd;
574          }
575          if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
576             int32_t LinkFI;
577             decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
578          } else {
579             /* Something went wrong getting attributes -- print name */
580             memset(&statp, 0, sizeof(statp));
581          }
582          ls_output(buf, cwd, tag, &statp, dot_cmd);
583          bsendmsg(ua, "%s\n", buf);
584       }
585    }
586    return 1;
587 }
588
589 int dot_dircmd(UAContext *ua, TREE_CTX *tree)
590 {
591    return do_dircmd(ua, tree, true/*dot command*/);
592 }
593
594 static int dircmd(UAContext *ua, TREE_CTX *tree)
595 {
596    return do_dircmd(ua, tree, false/*not dot command*/);
597 }
598
599
600 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
601 {
602    int total, num_extract;
603    uint64_t total_bytes = 0;
604    FILE_DBR fdbr;
605    struct stat statp;
606    char cwd[1100];
607    char ec1[50];
608
609    total = num_extract = 0;
610    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
611       if (node->type != TN_NEWDIR) {
612          total++;
613          /* If regular file, get size */
614          if (node->extract && node->type == TN_FILE) {
615             num_extract++;
616             tree_getpath(node, cwd, sizeof(cwd));
617             fdbr.FileId = 0;
618             fdbr.JobId = node->JobId;
619             if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
620                int32_t LinkFI;
621                decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
622                if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
623                   total_bytes += statp.st_size;
624                }
625             }
626          /* Directory, count only */
627          } else if (node->extract || node->extract_dir) {
628             num_extract++;
629          }
630       }
631    }
632    bsendmsg(ua, _("%d total files; %d marked to be restored; %s bytes.\n"),
633             total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
634    return 1;
635 }
636
637
638
639 static int helpcmd(UAContext *ua, TREE_CTX *tree)
640 {
641    unsigned int i;
642
643    bsendmsg(ua, _("  Command    Description\n  =======    ===========\n"));
644    for (i=0; i<comsize; i++) {
645       /* List only non-dot commands */
646       if (commands[i].key[0] != '.') {
647          bsendmsg(ua, "  %-10s %s\n", _(commands[i].key), _(commands[i].help));
648       }
649    }
650    bsendmsg(ua, "\n");
651    return 1;
652 }
653
654 /*
655  * Change directories.  Note, if the user specifies x: and it fails,
656  *   we assume it is a Win32 absolute cd rather than relative and
657  *   try a second time with /x: ...  Win32 kludge.
658  */
659 static int cdcmd(UAContext *ua, TREE_CTX *tree)
660 {
661    TREE_NODE *node;
662    char cwd[2000];
663
664    if (ua->argc != 2) {
665       return 1;
666    }
667    strip_leading_space(ua->argk[1]);
668    node = tree_cwd(ua->argk[1], tree->root, tree->node);
669    if (!node) {
670       /* Try once more if Win32 drive -- make absolute */
671       if (ua->argk[1][1] == ':') {  /* win32 drive */
672          bstrncpy(cwd, "/", sizeof(cwd));
673          bstrncat(cwd, ua->argk[1], sizeof(cwd));
674          node = tree_cwd(cwd, tree->root, tree->node);
675       }
676       if (!node) {
677          bsendmsg(ua, _("Invalid path given.\n"));
678       } else {
679          tree->node = node;
680       }
681    } else {
682       tree->node = node;
683    }
684    tree_getpath(tree->node, cwd, sizeof(cwd));
685    bsendmsg(ua, _("cwd is: %s\n"), cwd);
686    return 1;
687 }
688
689 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
690 {
691    char cwd[2000];
692    tree_getpath(tree->node, cwd, sizeof(cwd));
693    bsendmsg(ua, _("cwd is: %s\n"), cwd);
694    return 1;
695 }
696
697
698 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
699 {
700    TREE_NODE *node;
701    int count = 0;
702
703    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
704       bsendmsg(ua, _("No files unmarked.\n"));
705       return 1;
706    }
707    for (int i=1; i < ua->argc; i++) {
708       foreach_child(node, tree->node) {
709          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
710             count += set_extract(ua, node, tree, false);
711          }
712       }
713    }
714    if (count == 0) {
715       bsendmsg(ua, _("No files unmarked.\n"));
716    } else if (count == 1) {
717       bsendmsg(ua, _("1 file unmarked.\n"));
718    } else {
719       char ed1[50];
720       bsendmsg(ua, _("%s files unmarked.\n"), edit_uint64_with_commas(count, ed1));
721    }
722    return 1;
723 }
724
725 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
726 {
727    TREE_NODE *node;
728    int count = 0;
729
730    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
731       bsendmsg(ua, _("No directories unmarked.\n"));
732       return 1;
733    }
734
735    for (int i=1; i < ua->argc; i++) {
736       foreach_child(node, tree->node) {
737          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
738             if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
739                node->extract_dir = false;
740                count++;
741             }
742          }
743       }
744    }
745
746    if (count == 0) {
747       bsendmsg(ua, _("No directories unmarked.\n"));
748    } else if (count == 1) {
749       bsendmsg(ua, _("1 directory unmarked.\n"));
750    } else {
751       bsendmsg(ua, _("%d directories unmarked.\n"), count);
752    }
753    return 1;
754 }
755
756
757 static int donecmd(UAContext *ua, TREE_CTX *tree)
758 {
759    return 0;
760 }
761
762 static int quitcmd(UAContext *ua, TREE_CTX *tree)
763 {
764    ua->quit = true;
765    return 0;
766 }