]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_tree.c
Merge branch 'master' into basejobv3
[bacula/bacula] / bacula / src / dird / ua_tree.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2002-2009 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 \"%s\". Enter \"done\" to exit.\n"), ua->cmd);
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 \"%s\". Enter \"done\" to exit.\n"), ua->cmd);
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    Dmsg2(400, "JobId=%s FileIndex=%s\n", row[3], row[2]);
202    /*
203     * - The first time we see a file (node->inserted==true), we accept it.
204     * - In the same JobId, we accept only the first copy of a
205     *   hard linked file (the others are simply pointers).
206     * - In the same JobId, we accept the last copy of any other
207     *   file -- in particular directories.
208     *
209     * All the code to set ok could be condensed to a single
210     *  line, but it would be even harder to read.
211     */
212    ok = true;
213    if (!node->inserted && JobId == node->JobId) {
214       if ((hard_link && FileIndex > node->FileIndex) ||
215           (!hard_link && FileIndex < node->FileIndex)) {
216          ok = false;
217       }
218    }
219    if (ok) {
220       node->hard_link = hard_link;
221       node->FileIndex = FileIndex;
222       node->JobId = JobId;
223       node->type = type;
224       node->soft_link = S_ISLNK(statp.st_mode) != 0;
225       if (tree->all) {
226          node->extract = true;          /* extract all by default */
227          if (type == TN_DIR || type == TN_DIR_NLS) {
228             node->extract_dir = true;   /* if dir, extract it */
229          }
230       }
231    }
232    if (node->inserted) {
233       tree->FileCount++;
234       if (tree->DeltaCount > 0 && (tree->FileCount-tree->LastCount) > tree->DeltaCount) {
235          tree->ua->send_msg("+");
236          tree->LastCount = tree->FileCount;
237       }
238    }
239    tree->cnt++;
240    return 0;
241 }
242
243
244 /*
245  * Set extract to value passed. We recursively walk
246  *  down the tree setting all children if the
247  *  node is a directory.
248  */
249 static int set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, bool extract)
250 {
251    TREE_NODE *n;
252    FILE_DBR fdbr;
253    struct stat statp;
254    int count = 0;
255
256    node->extract = extract;
257    if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
258       node->extract_dir = extract;    /* set/clear dir too */
259    }
260    if (node->type != TN_NEWDIR) {
261       count++;
262    }
263    /* For a non-file (i.e. directory), we see all the children */
264    if (node->type != TN_FILE || (node->soft_link && tree_node_has_child(node))) {
265       /* Recursive set children within directory */
266       foreach_child(n, node) {
267          count += set_extract(ua, n, tree, extract);
268       }
269       /*
270        * Walk up tree marking any unextracted parent to be
271        * extracted.
272        */
273       if (extract) {
274          while (node->parent && !node->parent->extract_dir) {
275             node = node->parent;
276             node->extract_dir = true;
277          }
278       }
279    } else if (extract) {
280       char cwd[2000];
281       /*
282        * Ordinary file, we get the full path, look up the
283        * attributes, decode them, and if we are hard linked to
284        * a file that was saved, we must load that file too.
285        */
286       tree_getpath(node, cwd, sizeof(cwd));
287       fdbr.FileId = 0;
288       fdbr.JobId = node->JobId;
289       if (node->hard_link && db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
290          int32_t LinkFI;
291          decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
292          /*
293           * If we point to a hard linked file, traverse the tree to
294           * find that file, and mark it to be restored as well. It
295           * must have the Link we just obtained and the same JobId.
296           */
297          if (LinkFI) {
298             for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
299                if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
300                   n->extract = true;
301                   if (n->type == TN_DIR || n->type == TN_DIR_NLS) {
302                      n->extract_dir = true;
303                   }
304                   break;
305                }
306             }
307          }
308       }
309    }
310    return count;
311 }
312
313 static void strip_trailing_slash(char *arg)
314 {
315    int len = strlen(arg);
316    if (len == 0) {
317       return;
318    }
319    len--;
320    if (arg[len] == '/') {       /* strip any trailing slash */
321       arg[len] = 0;
322    }
323 }
324
325 /*
326  * Recursively mark the current directory to be restored as
327  *  well as all directories and files below it.
328  */
329 static int markcmd(UAContext *ua, TREE_CTX *tree)
330 {
331    TREE_NODE *node;
332    int count = 0;
333    char ec1[50];
334
335    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
336       ua->send_msg(_("No files marked.\n"));
337       return 1;
338    }
339    for (int i=1; i < ua->argc; i++) {
340       strip_trailing_slash(ua->argk[i]);
341       foreach_child(node, tree->node) {
342          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
343             count += set_extract(ua, node, tree, true);
344          }
345       }
346    }
347    if (count == 0) {
348       ua->send_msg(_("No files marked.\n"));
349    } else if (count == 1) {
350       ua->send_msg(_("1 file marked.\n"));
351    } else {
352       ua->send_msg(_("%s files marked.\n"),
353                edit_uint64_with_commas(count, ec1));
354    }
355    return 1;
356 }
357
358 static int markdircmd(UAContext *ua, TREE_CTX *tree)
359 {
360    TREE_NODE *node;
361    int count = 0;
362    char ec1[50];
363
364    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
365       ua->send_msg(_("No files marked.\n"));
366       return 1;
367    }
368    for (int i=1; i < ua->argc; i++) {
369       strip_trailing_slash(ua->argk[i]);
370       foreach_child(node, tree->node) {
371          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
372             if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
373                node->extract_dir = true;
374                count++;
375             }
376          }
377       }
378    }
379    if (count == 0) {
380       ua->send_msg(_("No directories marked.\n"));
381    } else if (count == 1) {
382       ua->send_msg(_("1 directory marked.\n"));
383    } else {
384       ua->send_msg(_("%s directories marked.\n"),
385                edit_uint64_with_commas(count, ec1));
386    }
387    return 1;
388 }
389
390
391 static int countcmd(UAContext *ua, TREE_CTX *tree)
392 {
393    int total, num_extract;
394    char ec1[50], ec2[50];
395
396    total = num_extract = 0;
397    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
398       if (node->type != TN_NEWDIR) {
399          total++;
400          if (node->extract || node->extract_dir) {
401             num_extract++;
402          }
403       }
404    }
405    ua->send_msg(_("%s total files/dirs. %s marked to be restored.\n"),
406             edit_uint64_with_commas(total, ec1),
407             edit_uint64_with_commas(num_extract, ec2));
408    return 1;
409 }
410
411 static int findcmd(UAContext *ua, TREE_CTX *tree)
412 {
413    char cwd[2000];
414
415    if (ua->argc == 1) {
416       ua->send_msg(_("No file specification given.\n"));
417       return 1;      /* make it non-fatal */
418    }
419
420    for (int i=1; i < ua->argc; i++) {
421       for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
422          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
423             const char *tag;
424             tree_getpath(node, cwd, sizeof(cwd));
425             if (node->extract) {
426                tag = "*";
427             } else if (node->extract_dir) {
428                tag = "+";
429             } else {
430                tag = "";
431             }
432             ua->send_msg("%s%s\n", tag, cwd);
433          }
434       }
435    }
436    return 1;
437 }
438
439
440
441 static int lscmd(UAContext *ua, TREE_CTX *tree)
442 {
443    TREE_NODE *node;
444
445    if (!tree_node_has_child(tree->node)) {
446       return 1;
447    }
448    foreach_child(node, tree->node) {
449       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
450          const char *tag;
451          if (node->extract) {
452             tag = "*";
453          } else if (node->extract_dir) {
454             tag = "+";
455          } else {
456             tag = "";
457          }
458          ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
459       }
460    }
461    return 1;
462 }
463
464 /*
465  * Ls command that lists only the marked files
466  */
467 static void rlsmark(UAContext *ua, TREE_NODE *tnode)
468 {
469    TREE_NODE *node;
470    if (!tree_node_has_child(tnode)) {
471       return;
472    }
473    foreach_child(node, tnode) {
474       if ((ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) &&
475           (node->extract || node->extract_dir)) {
476          const char *tag;
477          if (node->extract) {
478             tag = "*";
479          } else if (node->extract_dir) {
480             tag = "+";
481          } else {
482             tag = "";
483          }
484          ua->send_msg("%s%s%s\n", tag, node->fname, tree_node_has_child(node)?"/":"");
485          if (tree_node_has_child(node)) {
486             rlsmark(ua, node);
487          }
488       }
489    }
490 }
491
492 static int lsmarkcmd(UAContext *ua, TREE_CTX *tree)
493 {
494    rlsmark(ua, tree->node);
495    return 1;
496 }
497
498 /*
499  * This is actually the long form used for "dir"
500  */
501 static void ls_output(guid_list *guid, char *buf, const char *fname, const char *tag, 
502                       struct stat *statp, bool dot_cmd) 
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,", 
517                   guid->uid_to_name(statp->st_uid, en1, sizeof(en1)),
518                   guid->gid_to_name(statp->st_gid, en2, sizeof(en2)));
519       p += n;
520       n = sprintf(p, "%s,", edit_int64(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", 
530                   guid->uid_to_name(statp->st_uid, en1, sizeof(en1)),
531                   guid->gid_to_name(statp->st_gid, en2, sizeof(en2)));
532       p += n;
533       n = sprintf(p, "%10.10s  ", edit_int64(statp->st_size, ec1));
534       p += n;
535       if (statp->st_ctime > statp->st_mtime) {
536          time = statp->st_ctime;
537       } else {
538          time = statp->st_mtime;
539       }
540       /* Display most recent time */
541       p = encode_time(time, p);
542       *p++ = ' ';
543       *p++ = *tag;
544    }
545    for (f=fname; *f; ) {
546       *p++ = *f++;
547    }
548    *p = 0;
549 }
550
551 /*
552  * Like ls command, but give more detail on each file
553  */
554 static int do_dircmd(UAContext *ua, TREE_CTX *tree, bool dot_cmd)
555 {
556    TREE_NODE *node;
557    FILE_DBR fdbr;
558    struct stat statp;
559    char buf[1100];
560    char cwd[1100], *pcwd;
561    guid_list *guid;
562
563    if (!tree_node_has_child(tree->node)) {
564       ua->send_msg(_("Node %s has no children.\n"), tree->node->fname);
565       return 1;
566    }
567
568    guid = new_guid_list();
569    foreach_child(node, tree->node) {
570       const char *tag;
571       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
572          if (node->extract) {
573             tag = "*";
574          } else if (node->extract_dir) {
575             tag = "+";
576          } else {
577             tag = " ";
578          }
579          tree_getpath(node, cwd, sizeof(cwd));
580          fdbr.FileId = 0;
581          fdbr.JobId = node->JobId;
582          /*
583           * Strip / from soft links to directories.
584           *   This is because soft links to files have a trailing slash
585           *   when returned from tree_getpath, but db_get_file_attr...
586           *   treats soft links as files, so they do not have a trailing
587           *   slash like directory names.
588           */
589          if (node->type == TN_FILE && tree_node_has_child(node)) {
590             bstrncpy(buf, cwd, sizeof(buf));
591             pcwd = buf;
592             int len = strlen(buf);
593             if (len > 1) {
594                buf[len-1] = 0;        /* strip trailing / */
595             }
596          } else {
597             pcwd = cwd;
598          }
599          if (db_get_file_attributes_record(ua->jcr, ua->db, pcwd, NULL, &fdbr)) {
600             int32_t LinkFI;
601             decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
602          } else {
603             /* Something went wrong getting attributes -- print name */
604             memset(&statp, 0, sizeof(statp));
605          }
606          ls_output(guid, buf, cwd, tag, &statp, dot_cmd);
607          ua->send_msg("%s\n", buf);
608       }
609    }
610    free_guid_list(guid);
611    return 1;
612 }
613
614 int dot_dircmd(UAContext *ua, TREE_CTX *tree)
615 {
616    return do_dircmd(ua, tree, true/*dot command*/);
617 }
618
619 static int dircmd(UAContext *ua, TREE_CTX *tree)
620 {
621    return do_dircmd(ua, tree, false/*not dot command*/);
622 }
623
624
625 static int estimatecmd(UAContext *ua, TREE_CTX *tree)
626 {
627    int total, num_extract;
628    uint64_t total_bytes = 0;
629    FILE_DBR fdbr;
630    struct stat statp;
631    char cwd[1100];
632    char ec1[50];
633
634    total = num_extract = 0;
635    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
636       if (node->type != TN_NEWDIR) {
637          total++;
638          /* If regular file, get size */
639          if (node->extract && node->type == TN_FILE) {
640             num_extract++;
641             tree_getpath(node, cwd, sizeof(cwd));
642             fdbr.FileId = 0;
643             fdbr.JobId = node->JobId;
644             if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, NULL, &fdbr)) {
645                int32_t LinkFI;
646                decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
647                if (S_ISREG(statp.st_mode) && statp.st_size > 0) {
648                   total_bytes += statp.st_size;
649                }
650             }
651          /* Directory, count only */
652          } else if (node->extract || node->extract_dir) {
653             num_extract++;
654          }
655       }
656    }
657    ua->send_msg(_("%d total files; %d marked to be restored; %s bytes.\n"),
658             total, num_extract, edit_uint64_with_commas(total_bytes, ec1));
659    return 1;
660 }
661
662
663
664 static int helpcmd(UAContext *ua, TREE_CTX *tree)
665 {
666    unsigned int i;
667
668    ua->send_msg(_("  Command    Description\n  =======    ===========\n"));
669    for (i=0; i<comsize; i++) {
670       /* List only non-dot commands */
671       if (commands[i].key[0] != '.') {
672          ua->send_msg("  %-10s %s\n", _(commands[i].key), _(commands[i].help));
673       }
674    }
675    ua->send_msg("\n");
676    return 1;
677 }
678
679 /*
680  * Change directories.  Note, if the user specifies x: and it fails,
681  *   we assume it is a Win32 absolute cd rather than relative and
682  *   try a second time with /x: ...  Win32 kludge.
683  */
684 static int cdcmd(UAContext *ua, TREE_CTX *tree)
685 {
686    TREE_NODE *node;
687    char cwd[2000];
688
689
690    if (ua->argc != 2) {
691       ua->error_msg(_("Too few or too many arguments. Try using double quotes.\n"));
692       return 1;
693    }
694    node = tree_cwd(ua->argk[1], tree->root, tree->node);
695    if (!node) {
696       /* Try once more if Win32 drive -- make absolute */
697       if (ua->argk[1][1] == ':') {  /* win32 drive */
698          bstrncpy(cwd, "/", sizeof(cwd));
699          bstrncat(cwd, ua->argk[1], sizeof(cwd));
700          node = tree_cwd(cwd, tree->root, tree->node);
701       }
702       if (!node) {
703          ua->warning_msg(_("Invalid path given.\n"));
704       } else {
705          tree->node = node;
706       }
707    } else {
708       tree->node = node;
709    }
710    return pwdcmd(ua, tree);
711 }
712
713 static int pwdcmd(UAContext *ua, TREE_CTX *tree)
714 {
715    char cwd[2000];
716    tree_getpath(tree->node, cwd, sizeof(cwd));
717    if (ua->api) {
718       ua->send_msg("%s", cwd);
719    } else {
720       ua->send_msg(_("cwd is: %s\n"), cwd);
721    }
722    return 1;
723 }
724
725 static int dot_pwdcmd(UAContext *ua, TREE_CTX *tree)
726 {
727    char cwd[2000];
728    tree_getpath(tree->node, cwd, sizeof(cwd));
729    ua->send_msg("%s", cwd);
730    return 1;
731 }
732
733 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
734 {
735    TREE_NODE *node;
736    int count = 0;
737
738    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
739       ua->send_msg(_("No files unmarked.\n"));
740       return 1;
741    }
742    for (int i=1; i < ua->argc; i++) {
743       strip_trailing_slash(ua->argk[i]);
744       foreach_child(node, tree->node) {
745          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
746             count += set_extract(ua, node, tree, false);
747          }
748       }
749    }
750    if (count == 0) {
751       ua->send_msg(_("No files unmarked.\n"));
752    } else if (count == 1) {
753       ua->send_msg(_("1 file unmarked.\n"));
754    } else {
755       char ed1[50];
756       ua->send_msg(_("%s files unmarked.\n"), edit_uint64_with_commas(count, ed1));
757    }
758    return 1;
759 }
760
761 static int unmarkdircmd(UAContext *ua, TREE_CTX *tree)
762 {
763    TREE_NODE *node;
764    int count = 0;
765
766    if (ua->argc < 2 || !tree_node_has_child(tree->node)) {
767       ua->send_msg(_("No directories unmarked.\n"));
768       return 1;
769    }
770
771    for (int i=1; i < ua->argc; i++) {
772       strip_trailing_slash(ua->argk[i]);
773       foreach_child(node, tree->node) {
774          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
775             if (node->type == TN_DIR || node->type == TN_DIR_NLS) {
776                node->extract_dir = false;
777                count++;
778             }
779          }
780       }
781    }
782
783    if (count == 0) {
784       ua->send_msg(_("No directories unmarked.\n"));
785    } else if (count == 1) {
786       ua->send_msg(_("1 directory unmarked.\n"));
787    } else {
788       ua->send_msg(_("%d directories unmarked.\n"), count);
789    }
790    return 1;
791 }
792
793
794 static int donecmd(UAContext *ua, TREE_CTX *tree)
795 {
796    return 0;
797 }
798
799 static int quitcmd(UAContext *ua, TREE_CTX *tree)
800 {
801    ua->quit = true;
802    return 0;
803 }