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