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