]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_tree.c
Update technotes and version
[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    ua->api = tree->ua->api;           /* keep API flag too */
107    BSOCK *user = ua->UA_sock;
108
109    bsendmsg(tree->ua, _(
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    bsendmsg(tree->ua, _("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          bsendmsg(tree->ua, _("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          bsendmsg(tree->ua, _("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          bsendmsg(tree->ua, "+");
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
495 extern char *getuser(uid_t uid, char *name, int len);
496 extern char *getgroup(gid_t gid, char *name, int len);
497
498 /*
499  * This is actually the long form used for "dir"
500  */
501 static void ls_output(char *buf, const char *fname, const char *tag, 
502                       struct stat *statp, bool dot_cmd) 
503                     
504 {
505    char *p;
506    const char *f;
507    char ec1[30];
508    char en1[30], en2[30];
509    int n;
510    time_t time;
511
512    p = encode_mode(statp->st_mode, buf);
513    if (dot_cmd) {
514       *p++ = ',';
515       n = sprintf(p, "%d,", (uint32_t)statp->st_nlink);
516       p += n;
517       n = sprintf(p, "%s,%s,", getuser(statp->st_uid, en1, sizeof(en1)),
518                   getgroup(statp->st_gid, en2, sizeof(en2)));
519       p += n;
520       n = sprintf(p, "%s,", edit_uint64(statp->st_size, ec1));
521       p += n;
522       p = encode_time(statp->st_mtime, p);
523       *p++ = ',';
524       *p++ = *tag;
525       *p++ = ',';
526    } else {
527       n = sprintf(p, "  %2d ", (uint32_t)statp->st_nlink);
528       p += n;
529       n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid, en1, sizeof(en1)),
530                   getgroup(statp->st_gid, en2, sizeof(en2)));
531       p += n;
532       n = sprintf(p, "%10.10s  ", edit_uint64(statp->st_size, ec1));
533       p += n;
534       if (statp->st_ctime > statp->st_mtime) {
535          time = statp->st_ctime;
536       } else {
537          time = statp->st_mtime;
538       }
539       /* Display most recent time */
540       p = encode_time(time, p);
541       *p++ = ' ';
542       *p++ = *tag;
543    }
544    for (f=fname; *f; ) {
545       *p++ = *f++;
546    }
547    *p = 0;
548 }
549
550 /*
551  * Like ls command, but give more detail on each file
552  */
553 static int do_dircmd(UAContext *ua, TREE_CTX *tree, bool dot_cmd)
554 {
555    TREE_NODE *node;
556    FILE_DBR fdbr;
557    struct stat statp;
558    char buf[1100];
559    char cwd[1100], *pcwd;
560
561    if (!tree_node_has_child(tree->node)) {
562       ua->send_msg(_("Node %s has no children.\n"), tree->node->fname);
563       return 1;
564    }
565
566    foreach_child(node, tree->node) {
567       const char *tag;
568       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
569          if (node->extract) {
570             tag = "*";
571          } else if (node->extract_dir) {
572             tag = "+";
573          } else {
574             tag = " ";
575          }
576          tree_getpath(node, cwd, sizeof(cwd));
577          fdbr.FileId = 0;
578          fdbr.JobId = node->JobId;
579          /*
580           * Strip / from soft links to directories.
581           *   This is because soft links to files have a trailing slash
582           *   when returned from tree_getpath, but db_get_file_attr...
583           *   treats soft links as files, so they do not have a trailing
584           *   slash like directory names.
585           */
586          if (node->type == TN_FILE && tree_node_has_child(node)) {
587             bstrncpy(buf, cwd, sizeof(buf));
588             pcwd = buf;
589             int len = strlen(buf);
590             if (len > 1) {
591                buf[len-1] = 0;        /* strip trailing / */
592             }
593          } else {
594             pcwd = cwd;
595          }
596          if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
597             int32_t LinkFI;
598             decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
599          } else {
600             /* Something went wrong getting attributes -- print name */
601             memset(&statp, 0, sizeof(statp));
602          }
603          ls_output(buf, cwd, tag, &statp, dot_cmd);
604          ua->send_msg("%s\n", buf);
605       }
606    }
607    return 1;
608 }
609
610 int dot_dircmd(UAContext *ua, TREE_CTX *tree)
611 {
612    return do_dircmd(ua, tree, true/*dot command*/);
613 }
614
615 static int dircmd(UAContext *ua, TREE_CTX *tree)
616 {
617    return do_dircmd(ua, tree, false/*not dot command*/);
618 }
619
620
621 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
622 {
623    int total, num_extract;
624    uint64_t total_bytes = 0;
625    FILE_DBR fdbr;
626    struct stat statp;
627    char cwd[1100];
628    char ec1[50];
629
630    total = num_extract = 0;
631    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
632       if (node->type != TN_NEWDIR) {
633          total++;
634          /* If regular file, get size */
635          if (node->extract && node->type == TN_FILE) {
636             num_extract++;
637             tree_getpath(node, cwd, sizeof(cwd));
638             fdbr.FileId = 0;
639             fdbr.JobId = node->JobId;
640             if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
641                int32_t LinkFI;
642                decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
643                if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
644                   total_bytes += statp.st_size;
645                }
646             }
647          /* Directory, count only */
648          } else if (node->extract || node->extract_dir) {
649             num_extract++;
650          }
651       }
652    }
653    ua->send_msg(_("%d total files; %d marked to be restored; %s bytes.\n"),
654             total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
655    return 1;
656 }
657
658
659
660 static int helpcmd(UAContext *ua, TREE_CTX *tree)
661 {
662    unsigned int i;
663
664    ua->send_msg(_("  Command    Description\n  =======    ===========\n"));
665    for (i=0; i<comsize; i++) {
666       /* List only non-dot commands */
667       if (commands[i].key[0] != '.') {
668          ua->send_msg("  %-10s %s\n", _(commands[i].key), _(commands[i].help));
669       }
670    }
671    ua->send_msg("\n");
672    return 1;
673 }
674
675 /*
676  * Change directories.  Note, if the user specifies x: and it fails,
677  *   we assume it is a Win32 absolute cd rather than relative and
678  *   try a second time with /x: ...  Win32 kludge.
679  */
680 static int cdcmd(UAContext *ua, TREE_CTX *tree)
681 {
682    TREE_NODE *node;
683    char cwd[2000];
684
685
686    if (ua->argc != 2) {
687       ua->error_msg(_("Too few or too many arguments. Try using double quotes.\n"));
688       return 1;
689    }
690    node = tree_cwd(ua->argk[1], tree->root, tree->node);
691    if (!node) {
692       /* Try once more if Win32 drive -- make absolute */
693       if (ua->argk[1][1] == ':') {  /* win32 drive */
694          bstrncpy(cwd, "/", sizeof(cwd));
695          bstrncat(cwd, ua->argk[1], sizeof(cwd));
696          node = tree_cwd(cwd, tree->root, tree->node);
697       }
698       if (!node) {
699          ua->warning_msg(_("Invalid path given.\n"));
700       } else {
701          tree->node = node;
702       }
703    } else {
704       tree->node = node;
705    }
706    return pwdcmd(ua, tree);
707 }
708
709 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
710 {
711    char cwd[2000];
712    tree_getpath(tree->node, cwd, sizeof(cwd));
713    if (ua->api) {
714       ua->send_msg("%s", cwd);
715    } else {
716       ua->send_msg(_("cwd is: %s\n"), cwd);
717    }
718    return 1;
719 }
720
721 static int dot_pwdcmd(UAContext *ua, TREE_CTX *tree)
722 {
723    char cwd[2000];
724    tree_getpath(tree->node, cwd, sizeof(cwd));
725    ua->send_msg("%s", cwd);
726    return 1;
727 }
728
729 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
730 {
731    TREE_NODE *node;
732    int count = 0;
733
734    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
735       ua->send_msg(_("No files unmarked.\n"));
736       return 1;
737    }
738    for (int i=1; i < ua->argc; i++) {
739       strip_trailing_slash(ua->argk[i]);
740       foreach_child(node, tree->node) {
741          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
742             count += set_extract(ua, node, tree, false);
743          }
744       }
745    }
746    if (count == 0) {
747       ua->send_msg(_("No files unmarked.\n"));
748    } else if (count == 1) {
749       ua->send_msg(_("1 file unmarked.\n"));
750    } else {
751       char ed1[50];
752       ua->send_msg(_("%s files unmarked.\n"), edit_uint64_with_commas(count, ed1));
753    }
754    return 1;
755 }
756
757 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
758 {
759    TREE_NODE *node;
760    int count = 0;
761
762    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
763       ua->send_msg(_("No directories unmarked.\n"));
764       return 1;
765    }
766
767    for (int i=1; i < ua->argc; i++) {
768       strip_trailing_slash(ua->argk[i]);
769       foreach_child(node, tree->node) {
770          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
771             if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
772                node->extract_dir = false;
773                count++;
774             }
775          }
776       }
777    }
778
779    if (count == 0) {
780       ua->send_msg(_("No directories unmarked.\n"));
781    } else if (count == 1) {
782       ua->send_msg(_("1 directory unmarked.\n"));
783    } else {
784       ua->send_msg(_("%d directories unmarked.\n"), count);
785    }
786    return 1;
787 }
788
789
790 static int donecmd(UAContext *ua, TREE_CTX *tree)
791 {
792    return 0;
793 }
794
795 static int quitcmd(UAContext *ua, TREE_CTX *tree)
796 {
797    ua->quit = true;
798    return 0;
799 }