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