]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_tree.c
kes Remove idcache.c
[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 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    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    ua->api = tree->ua->api;           /* keep API flag too */
107    BSOCK *user = ua->UA_sock;
108
109    ua->send_msg(_(
110       "\nYou are now entering file selection mode where you add (mark) and\n"
111       "remove (unmark) files to be restored. No files are initially added, unless\n"
112       "you used the \"all\" keyword on the command line.\n"
113       "Enter \"done\" to leave this mode.\n\n"));
114    /*
115     * Enter interactive command handler allowing selection
116     *  of individual files.
117     */
118    tree->node = (TREE_NODE *)tree->root;
119    tree_getpath(tree->node, cwd, sizeof(cwd));
120    ua->send_msg(_("cwd is: %s\n"), cwd);
121    for ( ;; ) {
122       int found, len, i;
123       if (!get_cmd(ua, "$ ")) {
124          break;
125       }
126       if (ua->api) user->signal(BNET_CMD_BEGIN);
127       parse_args_only(ua->cmd, &ua->args, &ua->argc, ua->argk, ua->argv, MAX_CMD_ARGS);
128       if (ua->argc == 0) {
129          ua->warning_msg(_("Invalid command. Enter \"done\" to exit.\n"));
130          if (ua->api) user->signal(BNET_CMD_FAILED);
131          continue;
132       }
133
134       len = strlen(ua->argk[0]);
135       found = 0;
136       stat = false;
137       for (i=0; i<comsize; i++)       /* search for command */
138          if (strncasecmp(ua->argk[0],  _(commands[i].key), len) == 0) {
139             stat = (*commands[i].func)(ua, tree);   /* go execute command */
140             found = 1;
141             break;
142          }
143       if (!found) {
144          ua->warning_msg(_("Invalid command. Enter \"done\" to exit.\n"));
145          if (ua->api) user->signal(BNET_CMD_FAILED);
146          continue;
147       }
148       if (ua->api) user->signal(BNET_CMD_OK);
149       if (!stat) {
150          break;
151       }
152    }
153    ua->UA_sock = NULL;                /* don't release restore socket */
154    stat = !ua->quit;
155    ua->quit = false;
156    free_ua_context(ua);               /* get rid of temp UA context */
157    return stat;
158 }
159
160
161 /*
162  * This callback routine is responsible for inserting the
163  *  items it gets into the directory tree. For each JobId selected
164  *  this routine is called once for each file. We do not allow
165  *  duplicate filenames, but instead keep the info from the most
166  *  recent file entered (i.e. the JobIds are assumed to be sorted)
167  *
168  *   See uar_sel_files in sql_cmds.c for query that calls us.
169  *      row[0]=Path, row[1]=Filename, row[2]=FileIndex
170  *      row[3]=JobId row[4]=LStat
171  */
172 int insert_tree_handler(void *ctx, int num_fields, char **row)
173 {
174    struct stat statp;
175    TREE_CTX *tree = (TREE_CTX *)ctx;
176    TREE_NODE *node;
177    int type;
178    bool hard_link, ok;
179    int FileIndex;
180    JobId_t JobId;
181
182 // Dmsg4(000, "Path=%s%s FI=%s JobId=%s\n", row[0], row[1],
183 //    row[2], row[3]);
184    if (*row[1] == 0) {                 /* no filename => directory */
185       if (!IsPathSeparator(*row[0])) { /* Must be Win32 directory */
186          type = TN_DIR_NLS;
187       } else {
188          type = TN_DIR;
189       }
190    } else {
191       type = TN_FILE;
192    }
193    hard_link = (decode_LinkFI(row[4], &statp) != 0);
194    node = insert_tree_node(row[0], row[1], type, tree->root, NULL);
195    JobId = str_to_int64(row[3]);
196    FileIndex = str_to_int64(row[2]);
197    /*
198     * - The first time we see a file (node->inserted==true), we accept it.
199     * - In the same JobId, we accept only the first copy of a
200     *   hard linked file (the others are simply pointers).
201     * - In the same JobId, we accept the last copy of any other
202     *   file -- in particular directories.
203     *
204     * All the code to set ok could be condensed to a single
205     *  line, but it would be even harder to read.
206     */
207    ok = true;
208    if (!node->inserted && JobId == node->JobId) {
209       if ((hard_link && FileIndex > node->FileIndex) ||
210           (!hard_link && FileIndex < node->FileIndex)) {
211          ok = false;
212       }
213    }
214    if (ok) {
215       node->hard_link = hard_link;
216       node->FileIndex = FileIndex;
217       node->JobId = JobId;
218       node->type = type;
219       node->soft_link = S_ISLNK(statp.st_mode) != 0;
220       if (tree->all) {
221          node->extract = true;          /* extract all by default */
222          if (type == TN_DIR || type == TN_DIR_NLS) {
223             node->extract_dir = true;   /* if dir, extract it */
224          }
225       }
226    }
227    if (node->inserted) {
228       tree->FileCount++;
229       if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) {
230          tree->ua->send_msg("+");
231          tree->LastCount = tree->FileCount;
232       }
233    }
234    tree->cnt++;
235    return 0;
236 }
237
238
239 /*
240  * Set extract to value passed. We recursively walk
241  *  down the tree setting all children if the
242  *  node is a directory.
243  */
244 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
245 {
246    TREE_NODE *n;
247    FILE_DBR fdbr;
248    struct stat statp;
249    int count = 0;
250
251    node->extract = extract;
252    if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
253       node->extract_dir = extract;    /* set/clear dir too */
254    }
255    if (node->type != TN_NEWDIR) {
256       count++;
257    }
258    /* For a non-file (i.e. directory), we see all the children */
259    if (node->type != TN_FILE || (node->soft_link && tree_node_has_child(node))) {
260       /* Recursive set children within directory */
261       foreach_child(n, node) {
262          count += set_extract(ua, n, tree, extract);
263       }
264       /*
265        * Walk up tree marking any unextracted parent to be
266        * extracted.
267        */
268       if (extract) {
269          while (node->parent && !node->parent->extract_dir) {
270             node = node->parent;
271             node->extract_dir = true;
272          }
273       }
274    } else if (extract) {
275       char cwd[2000];
276       /*
277        * Ordinary file, we get the full path, look up the
278        * attributes, decode them, and if we are hard linked to
279        * a file that was saved, we must load that file too.
280        */
281       tree_getpath(node, cwd, sizeof(cwd));
282       fdbr.FileId = 0;
283       fdbr.JobId = node->JobId;
284       if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
285          int32_t LinkFI;
286          decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
287          /*
288           * If we point to a hard linked file, traverse the tree to
289           * find that file, and mark it to be restored as well. It
290           * must have the Link we just obtained and the same JobId.
291           */
292          if (LinkFI) {
293             for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
294                if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
295                   n->extract = true;
296                   if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
297                      n->extract_dir = true;
298                   }
299                   break;
300                }
301             }
302          }
303       }
304    }
305    return count;
306 }
307
308 static void strip_trailing_slash(char *arg)
309 {
310    int len = strlen(arg);
311    if (len == 0) {
312       return;
313    }
314    len--;
315    if (arg[len] == '/') {       /* strip any trailing slash */
316       arg[len] = 0;
317    }
318 }
319
320 /*
321  * Recursively mark the current directory to be restored as
322  *  well as all directories and files below it.
323  */
324 static int markcmd(UAContext *ua, TREE_CTX *tree)
325 {
326    TREE_NODE *node;
327    int count = 0;
328    char ec1[50];
329
330    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
331       ua->send_msg(_("No files marked.\n"));
332       return 1;
333    }
334    for (int i=1; i < ua->argc; i++) {
335       strip_trailing_slash(ua->argk[i]);
336       foreach_child(node, tree->node) {
337          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
338             count += set_extract(ua, node, tree, true);
339          }
340       }
341    }
342    if (count == 0) {
343       ua->send_msg(_("No files marked.\n"));
344    } else if (count == 1) {
345       ua->send_msg(_("1 file marked.\n"));
346    } else {
347       ua->send_msg(_("%s files marked.\n"),
348                edit_uint64_with_commas(count, ec1));
349    }
350    return 1;
351 }
352
353 static int markdircmd(UAContext *ua, TREE_CTX *tree)
354 {
355    TREE_NODE *node;
356    int count = 0;
357    char ec1[50];
358
359    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
360       ua->send_msg(_("No files marked.\n"));
361       return 1;
362    }
363    for (int i=1; i < ua->argc; i++) {
364       strip_trailing_slash(ua->argk[i]);
365       foreach_child(node, tree->node) {
366          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
367             if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
368                node->extract_dir = true;
369                count++;
370             }
371          }
372       }
373    }
374    if (count == 0) {
375       ua->send_msg(_("No directories marked.\n"));
376    } else if (count == 1) {
377       ua->send_msg(_("1 directory marked.\n"));
378    } else {
379       ua->send_msg(_("%s directories marked.\n"),
380                edit_uint64_with_commas(count, ec1));
381    }
382    return 1;
383 }
384
385
386 static int countcmd(UAContext *ua, TREE_CTX *tree)
387 {
388    int total, num_extract;
389    char ec1[50], ec2[50];
390
391    total = num_extract = 0;
392    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
393       if (node->type != TN_NEWDIR) {
394          total++;
395          if (node->extract || node->extract_dir) {
396             num_extract++;
397          }
398       }
399    }
400    ua->send_msg(_("%s total files/dirs. %s marked to be restored.\n"),
401             edit_uint64_with_commas(total, ec1),
402             edit_uint64_with_commas(num_extract, ec2));
403    return 1;
404 }
405
406 static int findcmd(UAContext *ua, TREE_CTX *tree)
407 {
408    char cwd[2000];
409
410    if (ua->argc == 1) {
411       ua->send_msg(_("No file specification given.\n"));
412       return 1;      /* make it non-fatal */
413    }
414
415    for (int i=1; i < ua->argc; i++) {
416       for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
417          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
418             const char *tag;
419             tree_getpath(node, cwd, sizeof(cwd));
420             if (node->extract) {
421                tag = "*";
422             } else if (node->extract_dir) {
423                tag = "+";
424             } else {
425                tag = "";
426             }
427             ua->send_msg("%s%s\n", tag, cwd);
428          }
429       }
430    }
431    return 1;
432 }
433
434
435
436 static int lscmd(UAContext *ua, TREE_CTX *tree)
437 {
438    TREE_NODE *node;
439
440    if (!tree_node_has_child(tree->node)) {
441       return 1;
442    }
443    foreach_child(node, tree->node) {
444       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
445          const char *tag;
446          if (node->extract) {
447             tag = "*";
448          } else if (node->extract_dir) {
449             tag = "+";
450          } else {
451             tag = "";
452          }
453          ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
454       }
455    }
456    return 1;
457 }
458
459 /*
460  * Ls command that lists only the marked files
461  */
462 static void rlsmark(UAContext *ua, TREE_NODE *tnode)
463 {
464    TREE_NODE *node;
465    if (!tree_node_has_child(tnode)) {
466       return;
467    }
468    foreach_child(node, tnode) {
469       if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
470           (node->extract || node->extract_dir)) {
471          const char *tag;
472          if (node->extract) {
473             tag = "*";
474          } else if (node->extract_dir) {
475             tag = "+";
476          } else {
477             tag = "";
478          }
479          ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
480          if (tree_node_has_child(node)) {
481             rlsmark(ua, node);
482          }
483       }
484    }
485 }
486
487 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
488 {
489    rlsmark(ua, tree->node);
490    return 1;
491 }
492
493 /*
494  * This is actually the long form used for "dir"
495  */
496 static void ls_output(guid_list *guid, char *buf, const char *fname, const char *tag, 
497                       struct stat *statp, bool dot_cmd) 
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,", 
512                   guid->uid_to_name(statp->st_uid, en1, sizeof(en1)),
513                   guid->gid_to_name(statp->st_gid, en2, sizeof(en2)));
514       p += n;
515       n = sprintf(p, "%s,", edit_uint64(statp->st_size, ec1));
516       p += n;
517       p = encode_time(statp->st_mtime, p);
518       *p++ = ',';
519       *p++ = *tag;
520       *p++ = ',';
521    } else {
522       n = sprintf(p, "  %2d ", (uint32_t)statp->st_nlink);
523       p += n;
524       n = sprintf(p, "%-8.8s %-8.8s", 
525                   guid->uid_to_name(statp->st_uid, en1, sizeof(en1)),
526                   guid->gid_to_name(statp->st_gid, en2, sizeof(en2)));
527       p += n;
528       n = sprintf(p, "%10.10s  ", edit_uint64(statp->st_size, ec1));
529       p += n;
530       if (statp->st_ctime > statp->st_mtime) {
531          time = statp->st_ctime;
532       } else {
533          time = statp->st_mtime;
534       }
535       /* Display most recent time */
536       p = encode_time(time, p);
537       *p++ = ' ';
538       *p++ = *tag;
539    }
540    for (f=fname; *f; ) {
541       *p++ = *f++;
542    }
543    *p = 0;
544 }
545
546 /*
547  * Like ls command, but give more detail on each file
548  */
549 static int do_dircmd(UAContext *ua, TREE_CTX *tree, bool dot_cmd)
550 {
551    TREE_NODE *node;
552    FILE_DBR fdbr;
553    struct stat statp;
554    char buf[1100];
555    char cwd[1100], *pcwd;
556    guid_list *guid;
557
558    if (!tree_node_has_child(tree->node)) {
559       ua->send_msg(_("Node %s has no children.\n"), tree->node->fname);
560       return 1;
561    }
562
563    guid = new_guid_list();
564    foreach_child(node, tree->node) {
565       const char *tag;
566       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
567          if (node->extract) {
568             tag = "*";
569          } else if (node->extract_dir) {
570             tag = "+";
571          } else {
572             tag = " ";
573          }
574          tree_getpath(node, cwd, sizeof(cwd));
575          fdbr.FileId = 0;
576          fdbr.JobId = node->JobId;
577          /*
578           * Strip / from soft links to directories.
579           *   This is because soft links to files have a trailing slash
580           *   when returned from tree_getpath, but db_get_file_attr...
581           *   treats soft links as files, so they do not have a trailing
582           *   slash like directory names.
583           */
584          if (node->type == TN_FILE && tree_node_has_child(node)) {
585             bstrncpy(buf, cwd, sizeof(buf));
586             pcwd = buf;
587             int len = strlen(buf);
588             if (len > 1) {
589                buf[len-1] = 0;        /* strip trailing / */
590             }
591          } else {
592             pcwd = cwd;
593          }
594          if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
595             int32_t LinkFI;
596             decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
597          } else {
598             /* Something went wrong getting attributes -- print name */
599             memset(&statp, 0, sizeof(statp));
600          }
601          ls_output(guid, buf, cwd, tag, &statp, dot_cmd);
602          ua->send_msg("%s\n", buf);
603       }
604    }
605    free_guid_list(guid);
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    ua->send_msg(_("%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    ua->send_msg(_("  Command    Description\n  =======    ===========\n"));
664    for (i=0; i<comsize; i++) {
665       /* List only non-dot commands */
666       if (commands[i].key[0] != '.') {
667          ua->send_msg("  %-10s %s\n", _(commands[i].key), _(commands[i].help));
668       }
669    }
670    ua->send_msg("\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       ua->error_msg(_("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          ua->warning_msg(_("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       ua->send_msg("%s", cwd);
714    } else {
715       ua->send_msg(_("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    ua->send_msg("%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       ua->send_msg(_("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       ua->send_msg(_("No files unmarked.\n"));
747    } else if (count == 1) {
748       ua->send_msg(_("1 file unmarked.\n"));
749    } else {
750       char ed1[50];
751       ua->send_msg(_("%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       ua->send_msg(_("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       ua->send_msg(_("No directories unmarked.\n"));
780    } else if (count == 1) {
781       ua->send_msg(_("1 directory unmarked.\n"));
782    } else {
783       ua->send_msg(_("%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 }