lftp  4.4.6
About: lftp is a command line ftp client (FTP, HTTP, ssl support, background transfer, reget, reput, ...)
  Fossies Dox: lftp-4.4.6.tar.gz  ("inofficial" and yet experimental doxygen-generated source code documentation)  

 All Classes Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups
commands.cc
Go to the documentation of this file.
1 /*
2  * lftp - file transfer program
3  *
4  * Copyright (c) 1996-2013 by Alexander V. Lukyanov (lav@yars.free.net)
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <config.h>
21 
22 #include "modconfig.h"
23 
24 #include "trio.h"
25 #include <unistd.h>
26 #include <errno.h>
27 #include <ctype.h>
28 #include <pwd.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <sys/wait.h>
32 #include <fcntl.h>
33 #include <assert.h>
34 #ifdef HAVE_DLFCN_H
35 # include <dlfcn.h>
36 #endif
37 #include <mbswidth.h>
38 #include <human.h>
39 
40 #include "CmdExec.h"
41 #include "GetJob.h"
42 #include "CatJob.h"
43 #include "LsCache.h"
44 #include "mgetJob.h"
45 #include "mkdirJob.h"
46 #include "rmJob.h"
47 #include "SysCmdJob.h"
48 #include "mvJob.h"
49 #include "pgetJob.h"
50 #include "SleepJob.h"
51 #include "FindJob.h"
52 #include "FindJobDu.h"
53 #include "ChmodJob.h"
54 #include "CopyJob.h"
55 #include "OutputJob.h"
56 #include "echoJob.h"
57 
58 #include "misc.h"
59 #include "alias.h"
60 #include "netrc.h"
61 #include "url.h"
62 #include "GetPass.h"
63 #include "SignalHook.h"
64 #include "FileFeeder.h"
65 #include "bookmark.h"
66 #include "log.h"
67 #include "module.h"
68 #include "FileCopy.h"
69 #include "DummyProto.h"
70 #include "QueueFeeder.h"
71 #include "lftp_rl.h"
72 #include "FileSetOutput.h"
73 #include "PatternSet.h"
74 #include "LocalDir.h"
75 #include "ConnectionSlot.h"
76 
77 #include "configmake.h"
78 
80 
81 CMD(alias); CMD(anon); CMD(cd); CMD(debug); CMD(du);
82 CMD(exit); CMD(get); CMD(help); CMD(jobs);
83 CMD(kill); CMD(lcd); CMD(ls); CMD(cls);
84 CMD(open); CMD(pwd); CMD(set); CMD(eval);
85 CMD(shell); CMD(source); CMD(user); CMD(rm);
86 CMD(wait); CMD(subsh); CMD(mirror); CMD(local);
87 CMD(mv); CMD(cat); CMD(cache); CMD(ln);
88 CMD(mkdir); CMD(scache); CMD(mrm);
89 CMD(ver); CMD(close); CMD(bookmark);CMD(lftp);
90 CMD(echo); CMD(suspend);CMD(sleep); CMD(slot);
91 CMD(at); CMD(find); CMD(command); CMD(module);
92 CMD(lpwd); CMD(glob); CMD(chmod); CMD(queue);
93 CMD(repeat);CMD(get1); CMD(tasks); CMD(torrent);
94 
95 #ifdef MODULE_CMD_MIRROR
96 # define cmd_mirror 0
97 #endif
98 #ifdef MODULE_CMD_SLEEP
99 # define cmd_sleep 0
100 # define cmd_at 0
101 # define cmd_repeat 0
102 #endif
103 #ifdef MODULE_CMD_TORRENT
104 # define cmd_torrent 0
105 #endif
106 
107 #define S "\001"
108 
109 const struct CmdExec::cmd_rec CmdExec::static_cmd_table[]=
110 {
111  {"!", cmd_shell, N_("!<shell-command>"),
112  N_("Launch shell or shell command\n")},
113  {"(", cmd_subsh, N_("(commands)"),
114  N_("Group commands together to be executed as one command\n"
115  "You can launch such a group in background\n")},
116  {"?", cmd_help, 0,"help"},
117  {"alias", cmd_alias, N_("alias [<name> [<value>]]"),
118  N_("Define or undefine alias <name>. If <value> omitted,\n"
119  "the alias is undefined, else is takes the value <value>.\n"
120  "If no argument is given the current aliases are listed.\n")},
121  {"anon", cmd_anon, 0,
122  N_("anon - login anonymously (by default)\n")},
123  {"at", cmd_at},
124  {"bookmark",cmd_bookmark,N_("bookmark [SUBCMD]"),
125  N_("bookmark command controls bookmarks\n\n"
126  "The following subcommands are recognized:\n"
127  " add <name> [<loc>] - add current place or given location to bookmarks\n"
128  " and bind to given name\n"
129  " del <name> - remove bookmark with the name\n"
130  " edit - start editor on bookmarks file\n"
131  " import <type> - import foreign bookmarks\n"
132  " list - list bookmarks (default)\n")},
133  {"bye", cmd_exit, 0,"exit"},
134  {"cache", cmd_cache, N_("cache [SUBCMD]"),
135  N_("cache command controls local memory cache\n\n"
136  "The following subcommands are recognized:\n"
137  " stat - print cache status (default)\n"
138  " on|off - turn on/off caching\n"
139  " flush - flush cache\n"
140  " size <lim> - set memory limit\n"
141  " expire <Nx> - set cache expiration time to N seconds (x=s)\n"
142  " minutes (x=m) hours (x=h) or days (x=d)\n")},
143  {"cat", cmd_cat, N_("cat [-b] <files>"),
144  N_("cat - output remote files to stdout (can be redirected)\n"
145  " -b use binary mode (ascii is the default)\n")},
146  {"cd", cmd_cd, N_("cd <rdir>"),
147  N_("Change current remote directory to <rdir>. The previous remote directory\n"
148  "is stored as `-'. You can do `cd -' to change the directory back.\n"
149  "The previous directory for each site is also stored on disk, so you can\n"
150  "do `open site; cd -' even after lftp restart.\n")},
151  {"chmod", cmd_chmod, N_("chmod [OPTS] mode file..."),
152  N_("Change the mode of each FILE to MODE.\n"
153  "\n"
154  " -c, --changes - like verbose but report only when a change is made\n"
155  " -f, --quiet - suppress most error messages\n"
156  " -v, --verbose - output a diagnostic for every file processed\n"
157  " -R, --recursive - change files and directories recursively\n"
158  "\n"
159  "MODE can be an octal number or symbolic mode (see chmod(1))\n")},
160  {"close", cmd_close, "close [-a]",
161  N_("Close idle connections. By default only with current server.\n"
162  " -a close idle connections with all servers\n")},
163  {"cls", cmd_cls, N_("[re]cls [opts] [path/][pattern]"),
164  N_("List remote files. You can redirect output of this command to file\n"
165  "or via pipe to external command.\n"
166  "\n"
167  /* note: I've tried to keep options which are likely to be always
168  * turned on (via cmd:cls-default, etc) capital, to leave lowercase
169  * available for options more commonly used manually. -s/-S is an
170  * exception; they both seem to be options used manually, so I made
171  * them align with GNU ls options. */
172  " -1 - single-column output\n"
173  " -a, --all - show dot files\n"
174  " -B, --basename - show basename of files only\n"
175  " --block-size=SIZ - use SIZ-byte blocks\n"
176  " -d, --directory - list directory entries instead of contents\n"
177  " -F, --classify - append indicator (one of /@) to entries\n"
178  " -h, --human-readable - print sizes in human readable format (e.g., 1K)\n"
179  " --si - likewise, but use powers of 1000 not 1024\n"
180  " -k, --kilobytes - like --block-size=1024\n"
181  " -l, --long - use a long listing format\n"
182  " -q, --quiet - don't show status\n"
183  " -s, --size - print size of each file\n"
184  " --filesize - if printing size, only print size for files\n"
185  " -i, --nocase - case-insensitive pattern matching\n"
186  " -I, --sortnocase - sort names case-insensitively\n"
187  " -D, --dirsfirst - list directories first\n"
188  " --sort=OPT - \"name\", \"size\", \"date\"\n"
189  " -S - sort by file size\n"
190  " --user, --group, --perms, --date, --linkcount, --links\n"
191  " - show individual fields\n"
192  " --time-style=STYLE - use specified time format\n"
193  "\n"
194  "By default, cls output is cached, to see new listing use `recls' or\n"
195  "`cache flush'.\n"
196  "\n"
197  "The variables cls-default and cls-completion-default can be used to\n"
198  "specify defaults for cls listings and completion listings, respectively.\n"
199  "For example, to make completion listings show file sizes, set\n"
200  "cls-completion-default to \"-s\".\n"
201  "\n"
202  /* FIXME: poorly worded. another explanation of --filesize: if a person
203  * wants to only see file sizes for files (not dirs) when he uses -s,
204  * add --filesize; it won't have any effect unless -s is also used, so
205  * it can be enabled all the time. (that's also poorly worded, and too
206  * long.) */
207  "Tips: Use --filesize with -D to pack the listing better. If you don't\n"
208  "always want to see file sizes, --filesize in cls-default will affect the\n"
209  "-s flag on the commandline as well. Add `-i' to cls-completion-default\n"
210  "to make filename completion case-insensitive.\n"
211  )},
212  {"connect", cmd_open, 0,"open"},
213  {"command", cmd_command},
214  {"debug", cmd_debug, N_("debug [<level>|off] [-o <file>]"),
215  N_("Set debug level to given value or turn debug off completely.\n"
216  " -o <file> redirect debug output to the file.\n")},
217  {"du", cmd_du, N_("du [options] <dirs>"),
218  N_("Summarize disk usage.\n"
219  " -a, --all write counts for all files, not just directories\n"
220  " --block-size=SIZ use SIZ-byte blocks\n"
221  " -b, --bytes print size in bytes\n"
222  " -c, --total produce a grand total\n"
223  " -d, --max-depth=N print the total for a directory (or file, with --all)\n"
224  " only if it is N or fewer levels below the command\n"
225  " line argument; --max-depth=0 is the same as\n"
226  " --summarize\n"
227  " -F, --files print number of files instead of sizes\n"
228  " -h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G)\n"
229  " -H, --si likewise, but use powers of 1000 not 1024\n"
230  " -k, --kilobytes like --block-size=1024\n"
231  " -m, --megabytes like --block-size=1048576\n"
232  " -S, --separate-dirs do not include size of subdirectories\n"
233  " -s, --summarize display only a total for each argument\n"
234  " --exclude=PAT exclude files that match PAT\n")},
235  {"echo", cmd_echo, 0},
236  {"eval", cmd_eval, 0},
237  {"exit", cmd_exit, N_("exit [<code>|bg]"),
238  N_("exit - exit from lftp or move to background if jobs are active\n\n"
239  "If no jobs active, the code is passed to operating system as lftp\n"
240  "termination status. If omitted, exit code of last command is used.\n"
241  "`bg' forces moving to background if cmd:move-background is false.\n")},
242  {"fg", cmd_wait, 0,"wait"},
243  {"find", cmd_find,0,
244  N_("Usage: find [OPTS] [directory]\n"
245  "Print contents of specified directory or current directory recursively.\n"
246  "Directories in the list are marked with trailing slash.\n"
247  "You can redirect output of this command.\n"
248  " -d, --maxdepth=LEVELS Descend at most LEVELS of directories.\n")},
249  {"get", cmd_get, N_("get [OPTS] <rfile> [-o <lfile>]"),
250  N_("Retrieve remote file <rfile> and store it to local file <lfile>.\n"
251  " -o <lfile> specifies local file name (default - basename of rfile)\n"
252  " -c continue, resume transfer\n"
253  " -E delete remote files after successful transfer\n"
254  " -a use ascii mode (binary is the default)\n"
255  " -O <base> specifies base directory or URL where files should be placed\n")},
256  {"get1", cmd_get1, 0,0},
257  {"glob", cmd_glob, N_("glob [OPTS] <cmd> <args>"),
258  N_(
259  "Expand wildcards and run specified command.\n"
260  "Options can be used to expand wildcards to list of files, directories,\n"
261  "or both types. Type selection is not very reliable and depends on server.\n"
262  "If entry type cannot be determined, it will be included in the list.\n"
263  " -f plain files (default)\n"
264  " -d directories\n"
265  " -a all types\n")},
266  {"help", cmd_help, N_("help [<cmd>]"),
267  N_("Print help for command <cmd>, or list of available commands\n")},
268  {"jobs", cmd_jobs, "jobs [-v] [<job_no...>]",
269  N_("List running jobs. -v means verbose, several -v can be specified.\n"
270  "If <job_no> is specified, only list a job with that number.\n")},
271  {"kill", cmd_kill, N_("kill all|<job_no>"),
272  N_("Delete specified job with <job_no> or all jobs\n")},
273  {"lcd", cmd_lcd, N_("lcd <ldir>"),
274  N_("Change current local directory to <ldir>. The previous local directory\n"
275  "is stored as `-'. You can do `lcd -' to change the directory back.\n")},
276  {"lftp", cmd_lftp, N_("lftp [OPTS] <site>"),
277  N_("`lftp' is the first command executed by lftp after rc files\n"
278  " -f <file> execute commands from the file and exit\n"
279  " -c <cmd> execute the commands and exit\n"
280  " --help print this help and exit\n"
281  " --version print lftp version and exit\n"
282  "Other options are the same as in `open' command\n"
283  " -e <cmd> execute the command just after selecting\n"
284  " -u <user>[,<pass>] use the user/password for authentication\n"
285  " -p <port> use the port for connection\n"
286  " <site> host name, URL or bookmark name\n")},
287  {"ln", cmd_ln, N_("ln [-s] <file1> <file2>"),
288  N_("Link <file1> to <file2>\n")},
289  {"lpwd", cmd_lpwd},
290  {"local", cmd_local},
291  {"login", cmd_user, 0,"user"},
292  {"ls", cmd_ls, N_("ls [<args>]"),
293  N_("List remote files. You can redirect output of this command to file\n"
294  "or via pipe to external command.\n"
295  "By default, ls output is cached, to see new listing use `rels' or\n"
296  "`cache flush'.\n"
297  "See also `help cls'.\n")},
298  {"mget", cmd_get, N_("mget [OPTS] <files>"),
299  N_("Gets selected files with expanded wildcards\n"
300  " -c continue, resume transfer\n"
301  " -d create directories the same as in file names and get the\n"
302  " files into them instead of current directory\n"
303  " -E delete remote files after successful transfer\n"
304  " -a use ascii mode (binary is the default)\n"
305  " -O <base> specifies base directory or URL where files should be placed\n")},
306  {"mirror", cmd_mirror, N_("mirror [OPTS] [remote [local]]"),
307  N_("\nMirror specified remote directory to local directory\n\n"
308  " -c, --continue continue a mirror job if possible\n"
309  " -e, --delete delete files not present at remote site\n"
310  " --delete-first delete old files before transferring new ones\n"
311  " -s, --allow-suid set suid/sgid bits according to remote site\n"
312  " --allow-chown try to set owner and group on files\n"
313  " --ignore-time ignore time when deciding whether to download\n"
314  " -n, --only-newer download only newer files (-c won't work)\n"
315  " -r, --no-recursion don't go to subdirectories\n"
316  " -p, --no-perms don't set file permissions\n"
317  " --no-umask don't apply umask to file modes\n"
318  " -R, --reverse reverse mirror (put files)\n"
319  " -L, --dereference download symbolic links as files\n"
320  " -N, --newer-than=SPEC download only files newer than specified time\n"
321  " -P, --parallel[=N] download N files in parallel\n"
322  " -i RX, --include RX include matching files\n"
323  " -x RX, --exclude RX exclude matching files\n"
324  " RX is extended regular expression\n"
325  " -v, --verbose[=N] verbose operation\n"
326  " --log=FILE write lftp commands being executed to FILE\n"
327  " --script=FILE write lftp commands to FILE, but don't execute them\n"
328  " --just-print, --dry-run same as --script=-\n"
329  "\n"
330  "When using -R, the first directory is local and the second is remote.\n"
331  "If the second directory is omitted, basename of first directory is used.\n"
332  "If both directories are omitted, current local and remote directories are used.\n"
333  )},
334  {"mkdir", cmd_mkdir, N_("mkdir [-p] <dirs>"),
335  N_("Make remote directories\n"
336  " -p make all levels of path\n")},
337  {"module", cmd_module, N_("module name [args]"),
338  N_("Load module (shared object). The module should contain function\n"
339  " void module_init(int argc,const char *const *argv)\n"
340  "If name contains a slash, then the module is searched in current\n"
341  "directory, otherwise in directories specified by setting module:path.\n")},
342  {"more", cmd_cat, N_("more <files>"),
343  N_("Same as `cat <files> | more'. if PAGER is set, it is used as filter\n")},
344  {"mput", cmd_get, N_("mput [OPTS] <files>"),
345  N_("Upload files with wildcard expansion\n"
346  " -c continue, reput\n"
347  " -d create directories the same as in file names and put the\n"
348  " files into them instead of current directory\n"
349  " -E delete local files after successful transfer (dangerous)\n"
350  " -a use ascii mode (binary is the default)\n"
351  " -O <base> specifies base directory or URL where files should be placed\n")},
352  {"mrm", cmd_mrm, N_("mrm <files>"),
353  N_("Removes specified files with wildcard expansion\n")},
354  {"mv", cmd_mv, N_("mv <file1> <file2>"),
355  N_("Rename <file1> to <file2>\n")},
356  {"nlist", cmd_ls, N_("[re]nlist [<args>]"),
357  N_("List remote file names.\n"
358  "By default, nlist output is cached, to see new listing use `renlist' or\n"
359  "`cache flush'.\n")},
360  {"open", cmd_open, N_("open [OPTS] <site>"),
361  N_("Select a server, URL or bookmark\n"
362  " -e <cmd> execute the command just after selecting\n"
363  " -u <user>[,<pass>] use the user/password for authentication\n"
364  " -p <port> use the port for connection\n"
365  " -s <slot> assign the connection to this slot\n"
366  " <site> host name, URL or bookmark name\n")},
367  {"pget", cmd_get, N_("pget [OPTS] <rfile> [-o <lfile>]"),
368  N_("Gets the specified file using several connections. This can speed up transfer,\n"
369  "but loads the net heavily impacting other users. Use only if you really\n"
370  "have to transfer the file ASAP.\n"
371  "\nOptions:\n"
372  " -c continue transfer. Requires <lfile>.lftp-pget-status file.\n"
373  " -n <maxconn> set maximum number of connections (default is is taken from\n"
374  " pget:default-n setting)\n"
375  " -O <base> specifies base directory where files should be placed\n")},
376  {"put", cmd_get, N_("put [OPTS] <lfile> [-o <rfile>]"),
377  N_("Upload <lfile> with remote name <rfile>.\n"
378  " -o <rfile> specifies remote file name (default - basename of lfile)\n"
379  " -c continue, reput\n"
380  " it requires permission to overwrite remote files\n"
381  " -E delete local files after successful transfer (dangerous)\n"
382  " -a use ascii mode (binary is the default)\n"
383  " -O <base> specifies base directory or URL where files should be placed\n")},
384  {"pwd", cmd_pwd, "pwd [-p]",
385  N_("Print current remote URL.\n"
386  " -p show password\n")},
387  {"queue", cmd_queue, N_("queue [OPTS] [<cmd>]"),
388  N_("\n"
389  " queue [-n num] <command>\n\n"
390  "Add the command to queue for current site. Each site has its own command\n"
391  "queue. `-n' adds the command before the given item in the queue. It is\n"
392  "possible to queue up a running job by using command `queue wait <jobno>'.\n"
393  "\n"
394  " queue --delete|-d [index or wildcard expression]\n\n"
395  "Delete one or more items from the queue. If no argument is given, the last\n"
396  "entry in the queue is deleted.\n"
397  "\n"
398  " queue --move|-m <index or wildcard expression> [index]\n\n"
399  "Move the given items before the given queue index, or to the end if no\n"
400  "destination is given.\n"
401  "\n"
402  "Options:\n"
403  " -q Be quiet.\n"
404  " -v Be verbose.\n"
405  " -Q Output in a format that can be used to re-queue.\n"
406  " Useful with --delete.\n"
407  )},
408  {"quit", cmd_exit, 0,"exit"},
409  {"quote", cmd_ls, N_("quote <cmd>"),
410  N_("Send the command uninterpreted. Use with caution - it can lead to\n"
411  "unknown remote state and thus will cause reconnect. You cannot\n"
412  "be sure that any change of remote state because of quoted command\n"
413  "is solid - it can be reset by reconnect at any time.\n")},
414  {"recls", cmd_cls, 0,
415  N_("recls [<args>]\n"
416  "Same as `cls', but don't look in cache\n")},
417  {"reget", cmd_get, 0,
418  N_("Usage: reget [OPTS] <rfile> [-o <lfile>]\n"
419  "Same as `get -c'\n")},
420  {"rels", cmd_ls, 0,
421  N_("Usage: rels [<args>]\n"
422  "Same as `ls', but don't look in cache\n")},
423  {"renlist", cmd_ls, 0,
424  N_("Usage: renlist [<args>]\n"
425  "Same as `nlist', but don't look in cache\n")},
426  {"repeat", cmd_repeat, N_("repeat [OPTS] [delay] [command]"),
427  N_("Repeat specified command with a delay between iterations.\n"
428  "Default delay is one second, default command is empty.\n"
429  " -c <count> maximum number of iterations\n"
430  " -d <delay> delay between iterations\n"
431  " --while-ok stop when command exits with non-zero code\n"
432  " --until-ok stop when command exits with zero code\n"
433  " --weak stop when lftp moves to background.\n")},
434  {"reput", cmd_get, 0,
435  N_("Usage: reput <lfile> [-o <rfile>]\n"
436  "Same as `put -c'\n")},
437  {"rm", cmd_rm, N_("rm [-r] [-f] <files>"),
438  N_("Remove remote files\n"
439  " -r recursive directory removal, be careful\n"
440  " -f work quietly\n")},
441  {"rmdir", cmd_rm, N_("rmdir [-f] <dirs>"),
442  N_("Remove remote directories\n")},
443  {"scache", cmd_scache, N_("scache [<session_no>]"),
444  N_("List cached sessions or switch to specified session number\n")},
445  {"set", cmd_set, N_("set [OPT] [<var> [<val>]]"),
446  N_("Set variable to given value. If the value is omitted, unset the variable.\n"
447  "Variable name has format ``name/closure'', where closure can specify\n"
448  "exact application of the setting. See lftp(1) for details.\n"
449  "If set is called with no variable then only altered settings are listed.\n"
450  "It can be changed by options:\n"
451  " -a list all settings, including default values\n"
452  " -d list only default values, not necessary current ones\n")},
453  {"shell", cmd_shell, 0,"!"},
454  {"site", cmd_ls, N_("site <site-cmd>"),
455  N_("Execute site command <site_cmd> and output the result\n"
456  "You can redirect its output\n")},
457  {"sleep", cmd_sleep, 0,
458  N_("Usage: sleep <time>[unit]\n"
459  "Sleep for given amount of time. The time argument can be optionally\n"
460  "followed by unit specifier: d - days, h - hours, m - minutes, s - seconds.\n"
461  "By default time is assumed to be seconds.\n")},
462  {"slot", cmd_slot, 0,
463  N_("Usage: slot [<label>]\n"
464  "List assigned slots.\n"
465  "If <label> is specified, switch to the slot named <label>.\n")},
466  {"source", cmd_source, N_("source <file>"),
467  N_("Execute commands recorded in file <file>\n")},
468  {"suspend", cmd_suspend},
469  {"torrent", cmd_torrent, N_("torrent [-O <dir>] <file|URL>...")},
470  {"user", cmd_user, N_("user <user|URL> [<pass>]"),
471  N_("Use specified info for remote login. If you specify URL, the password\n"
472  "will be cached for future usage.\n")},
473  {"version", cmd_ver, 0,
474  N_("Shows lftp version\n")},
475  {"wait", cmd_wait, N_("wait [<jobno>]"),
476  N_("Wait for specified job to terminate. If jobno is omitted, wait\n"
477  "for last backgrounded job.\n")},
478  {"zcat", cmd_cat, N_("zcat <files>"),
479  N_("Same as cat, but filter each file through zcat\n")},
480  {"zmore", cmd_cat, N_("zmore <files>"),
481  N_("Same as more, but filter each file through zcat\n")},
482  {"bzcat", cmd_cat, 0,
483  N_("Same as cat, but filter each file through bzcat\n")},
484  {"bzmore", cmd_cat, 0,
485  N_("Same as more, but filter each file through bzcat\n")},
486 
487  {".tasks", cmd_tasks, 0,0},
488 };
489 const int CmdExec::static_cmd_table_length=sizeof(static_cmd_table)/sizeof(static_cmd_table[0]);
490 
491 #define charcasecmp(a,b) (tolower((unsigned char)(a))-tolower((unsigned char)(b)))
492 // returns:
493 // 0 - no match
494 // 1 - found, if *res==0 then ambiguous
495 static
496 int find_command(const char *unprec_name,const char * const *names,
497  const char **res)
498 {
499  const char *match=0;
500  for( ; *names; names++)
501  {
502  const char *s,*u;
503  for(s=*names,u=unprec_name; *s && !charcasecmp(*u,*s); s++,u++)
504  ;
505  if(*s && !*u)
506  {
507  if(match)
508  {
509  *res=0;
510  return 1;
511  }
512  match=*names;
513  }
514  else if(!*s && !*u)
515  {
516  *res=*names;
517  return 1;
518  }
519  }
520  if(match)
521  {
522  *res=match;
523  return 1;
524  }
525  *res=0;
526  return 0;
527 }
528 
530 {
531  if(args->count()==1)
532  args->Append("~");
533 
534  if(args->count()!=2)
535  {
536  eprintf(_("Usage: %s local-dir\n"),args->getarg(0));
537  return 0;
538  }
539  const char *cd_to=args->getarg(1);
540 
541  if(!strcmp(cd_to,"-"))
542  {
543  if(old_lcwd)
544  cd_to=old_lcwd;
545  }
546 
547  cd_to=expand_home_relative(cd_to);
548 
549  if(RestoreCWD()==-1)
550  {
551  if(cd_to[0]!='/')
552  {
553  eprintf("No current local directory, use absolute path.\n");
554  return 0;
555  }
556  }
557 
558  int res=chdir(cd_to);
559  if(res==-1)
560  {
561  perror(cd_to);
562  exit_code=1;
563  return 0;
564  }
565 
566  old_lcwd.set(cwd->GetName());
567 
568  SaveCWD();
569 
570  const char *name=cwd->GetName();
571  if(interactive)
572  eprintf(_("lcd ok, local cwd=%s\n"),name?name:"?");
573 
574  exit_code=0;
575  return 0;
576 }
577 
579 {
580  if(args->count()==1)
581  args->Append("~");
582 
583  bool is_file=false;
584 
585  if(args->count()!=2)
586  {
587  // xgettext:c-format
588  eprintf(_("Usage: cd remote-dir\n"));
589  return 0;
590  }
591 
592  const char *dir=args->getarg(1);
593  const char *url=0;
594 
595  if(!strcmp(dir,"-"))
596  {
597  dir=cwd_history.Lookup(session);
598  if(!dir)
599  {
600  eprintf(_("%s: no old directory for this site\n"),args->a0());
601  return 0;
602  }
603  args->setarg(1,dir); // for status line
604  dir=args->getarg(1);
605  }
606 
607  if(url::is_url(dir))
608  {
609  ParsedURL u(dir,true);
610  FileAccess *new_session=FileAccess::New(&u);
611  bool same_site=session->SameSiteAs(new_session);
612  Delete(new_session);
613  if(!same_site)
614  return builtin_open();
615  url=dir;
616  dir=alloca_strdup(u.path);
618  is_file=(last_char(dir)!='/');
619  }
620  else
621  {
622  if(url::dir_needs_trailing_slash(session->GetProto()))
623  is_file=(last_char(dir)!='/');
624  }
625 
626  int cache_is_dir=FileAccess::cache->IsDirectory(session,dir);
627  if(cache_is_dir==1)
628  is_file=false;
629  else if(cache_is_dir==0)
630  is_file=true;
631 
632  old_cwd=session->GetCwd();
633  FileAccess::Path new_cwd(old_cwd);
634  new_cwd.Change(dir,is_file);
635  if(url)
636  new_cwd.SetURL(url);
637  if(!verify_path || background
638  || (!verify_path_cached && cache_is_dir==1))
639  {
640  cwd_history.Set(session,old_cwd);
641  session->SetCwd(new_cwd);
642  if(slot)
643  ConnectionSlot::SetCwd(slot,new_cwd);
644  exit_code=0;
645  return 0;
646  }
647  session->PathVerify(new_cwd);
648  session->Roll();
649  builtin=BUILTIN_CD;
650  return this;
651 }
652 
654 {
655  bool detach=ResMgr::QueryBool("cmd:move-background-detach",0);
656  bool bg=false;
657  bool kill=false;
658  int code=prev_exit_code;
659  CmdExec *exec=this;
660  const char *a;
661  args->rewind();
662  while((a=args->getnext())!=0)
663  {
664  if(!strcmp(a,"bg"))
665  bg=true;
666  if(!strcmp(a,"top") || !strcmp(a,"bg"))
667  {
668  if(top)
669  exec=top.get_non_const();
670  }
671  else if(!strcmp(a,"kill"))
672  {
673  kill=true;
674  bg=false;
675  }
676  else if(sscanf(a,"%i",&code)!=1)
677  {
678  eprintf(_("Usage: %s [<exit_code>]\n"),args->a0());
679  return 0;
680  }
681  }
682  // Note: one job is this CmdExec.
683  if(!bg && exec->top_level
684  && !ResMgr::QueryBool("cmd:move-background",0) && NumberOfJobs()>0)
685  {
686  eprintf(_(
687  "There are running jobs and `cmd:move-background' is not set.\n"
688  "Use `exit bg' to force moving to background or `kill all' to terminate jobs.\n"
689  ));
690  return 0;
691  }
692  if(!detach && Job::NumberOfJobs()==0)
693  detach=true;
694  if(kill)
695  Job::KillAll();
696  if(detach)
697  exec->Exit(code);
698  else {
699  int loc=0;
700  exec->SetAutoTerminateInBackground(true);
701  eprintf(_(
702  "\n"
703  "lftp now tricks the shell to move it to background process group.\n"
704  "lftp continues to run in the background despite the `Stopped' message.\n"
705  "lftp will automatically terminate when all jobs are finished.\n"
706  "Use `fg' shell command to return to lftp if it is still running.\n"
707  ));
708  // trick the shell
709  switch(pid_t pid=fork()) {
710  case 0: // child
711  sleep(1); // wait for the parent to stop (is there a safer way?)
712  ::kill(getppid(),SIGCONT);
713  _exit(0);
714  default: // parent
715  raise(SIGSTOP);
716  waitpid(pid,&loc,0); // clean-up
717  break;
718  case -1:
719  exec->Exit(code);
720  break;
721  }
722  }
723  exit_code=code;
724  return 0;
725 }
726 void CmdExec::Exit(int code)
727 {
728  while(feeder)
729  RemoveFeeder();
730  if(interactive)
731  {
732  ListDoneJobs();
733  BuryDoneJobs();
734  if(FindJob(last_bg)==0)
735  last_bg=-1;
736  }
738  return;
739 }
740 
743 {
744  int c;
745  xstring cmd;
746  bool debug=false;
747  static struct option lftp_options[]=
748  {
749  {"help",no_argument,0,'h'},
750  {"version",no_argument,0,'v'},
751  {0,0,0,0}
752  };
753 
754  opterr=false;
755  while((c=args->getopt_long("+f:c:vhd",lftp_options))!=EOF)
756  {
757  switch(c)
758  {
759  case('h'):
760  cmd.set("help lftp;");
761  break;
762  case('v'):
763  cmd.set("version;");
764  break;
765  case('f'):
766  cmd.set("source ");
767  cmd.append_quoted(optarg);
768  cmd.append(';');
769  break;
770  case('c'):
772  cmd.append("\n\n");
773  break;
774  case('d'):
775  debug=true;
776  break;
777  }
778  }
779  opterr=true;
780 
781  if(cmd)
782  {
783  PrependCmd(cmd);
784  if(debug)
785  PrependCmd("debug;");
786  }
787 
788  if(Done() && lftp_feeder) // no feeder and no commands
789  {
790  SetCmdFeeder(lftp_feeder);
791  lftp_feeder=0;
792  FeedCmd("||command exit\n"); // if the command fails, quit
793  }
794 
795  if(!cmd)
796  {
797  /* if no lftp-specific options were found, call open */
798  args->rewind();
799  return builtin_open();
800  }
801  exit_code=0;
802  return 0;
803 }
804 
806 {
807  ReuseSavedSession();
808 
809  bool debug=false;
810  const char *port=NULL;
811  const char *host=NULL;
812  const char *path=NULL;
813  const char *user=NULL;
814  const char *pass=NULL;
815  int c;
816  NetRC::Entry *nrc=0;
817  char *cmd_to_exec=0;
818  const char *op=args->a0();
819  bool insecure=false;
820  bool no_bm=false;
821 
822  enum {
823  OPT_USER,
824  OPT_PASSWORD
825  };
826  static struct option open_options[]=
827  {
828  {"port",required_argument,0,'p'},
829  {"user",required_argument,0,OPT_USER},
830  {"password",required_argument,0,OPT_PASSWORD},
831  {"execute",required_argument,0,'e'},
832  {"debug",optional_argument,0,'d'},
833  {"no-bookmark",no_argument,0,'B'},
834  {"slot",required_argument,0,'s'},
835  {"help",no_argument,0,'h'},
836  {0,0,0,0}
837  };
838 
839  while((c=args->getopt_long("u:p:e:s:dBh",open_options))!=EOF)
840  {
841  switch(c)
842  {
843  case('s'):
844  if (*optarg) ChangeSlot(optarg);
845  break;
846  case('p'):
847  port=optarg;
848  break;
849  case('u'):
850  {
851  user=optarg;
852  char *sep=strchr(optarg,',');
853  if(sep==NULL)
854  sep=strchr(optarg,' ');
855  if(sep==NULL)
856  sep=strchr(optarg,':');
857  if(sep==NULL)
858  break;
859  *sep=0;
860  pass=sep+1;
861  insecure=true;
862  break;
863  }
864  case(OPT_USER):
865  user=optarg;
866  break;
867  case(OPT_PASSWORD):
868  pass=optarg;
869  break;
870  case('d'):
871  debug=true;
872  break;
873  case('e'):
874  cmd_to_exec=optarg;
875  break;
876  case('B'):
877  no_bm=true;
878  break;
879  case('h'):
880  if(!strcmp(op,"lftp"))
881  {
882  PrependCmd("help lftp");
883  return 0;
884  }
885  case('?'):
886  if(!strcmp(op,"lftp"))
887  eprintf(_("Try `%s --help' for more information\n"),op);
888  else
889  eprintf(_("Usage: %s [-e cmd] [-p port] [-u user[,pass]] <host|url>\n"),
890  op);
891  return 0;
892  }
893  }
894 
895  if(optind<args->count())
896  host=args->getarg(optind++);
897 
899 
900  const char *bm=0;
901 
902  if(cmd_to_exec)
903  PrependCmd(cmd_to_exec);
904 
905  if(!no_bm && host && (bm=lftp_bookmarks.Lookup(host))!=0)
906  {
907  xstring& cmd=xstring::get_tmp("open -B ");
908  if(user)
909  {
910  cmd.append("-u ");
911  cmd.append_quoted(user);
912  if(pass)
913  {
914  cmd.append(",");
915  cmd.append_quoted(pass);
916  }
917  cmd.append(' ');
918  }
919  if(port)
920  {
921  cmd.append("-p ");
922  cmd.append_quoted(port);
923  cmd.append(' ');
924  }
925 
926  cmd.append(bm);
927 
928  if(background)
929  cmd.append(" &\n");
930  else
931  cmd.append(";\n");
932 
933  PrependCmd(cmd);
934  }
935  else
936  {
937  if(host && host[0])
938  {
939  url=new ParsedURL(host);
940  bool no_proto=(!url->proto);
941 
942  if(no_proto && url->host)
943  {
944  const char *p=ResMgr::Query("cmd:default-protocol",url->host);
945  if(!p)
946  p="ftp";
947  url=new ParsedURL(xstring::format("%s://%s",p,host));
948  }
949  if(user || port)
950  {
951  if(user)
952  {
953  url->user.set(user);
954  url->pass.set(pass);
955  }
956  if(port)
957  url->port.set(port);
958  xstring_ca host1(url->Combine());
959  url=new ParsedURL(host1);
960  }
961 
962  const ParsedURL &uc=*url;
963  if(uc.host && uc.host[0] && uc.proto)
964  {
965  cwd_history.Set(session,session->GetCwd());
966 
967  if(uc.user && !user)
968  user=uc.user;
969  if(uc.pass && !pass)
970  {
971  pass=uc.pass;
972  insecure=true;
973  }
974  host=uc.host;
975  if(uc.port && !port)
976  port=uc.port;
977  if(uc.path && !path)
978  path=uc.path;
979 
980  FileAccess *new_session=FileAccess::New(uc.proto,host,port);
981  if(!new_session)
982  {
983  eprintf("%s: %s%s\n",args->a0(),uc.proto.get(),
984  _(" - not supported protocol"));
985  return 0;
986  }
987 
988  saved_session=session.borrow();
989  ChangeSession(new_session);
990  }
991 
992  // user gets substituted only if no proto is specified.
993  if(!pass && (user || no_proto))
994  {
995  nrc=NetRC::LookupHost(host,user);
996  if(nrc)
997  {
998  if(!user)
999  ProtoLog::LogNote(3,"using user `%s' and password from ~/.netrc",nrc->user.get());
1000  else
1001  ProtoLog::LogNote(3,"using password from ~/.netrc");
1002  user=nrc->user;
1003  pass=nrc->pass;
1004  }
1005  }
1006  }
1007  else if(host && !host[0])
1008  {
1010  }
1011  if(host && host[0] && session->GetHostName()==0)
1012  session->Connect(host,port);
1013  if(user)
1014  {
1015  if(!pass)
1016  pass=GetPass(_("Password: "));
1017  if(!pass)
1018  eprintf(_("%s: GetPass() failed -- assume anonymous login\n"),
1019  args->getarg(0));
1020  else
1021  {
1022  session->Login(user,pass);
1023  // assume the new password is the correct one.
1024  session->SetPasswordGlobal(pass);
1025  session->InsecurePassword(insecure && !no_bm);
1026  }
1027  }
1028  if(host && host[0])
1029  {
1030  if(verify_host && !background)
1031  {
1032  session->ConnectVerify();
1033  builtin=BUILTIN_OPEN;
1034  }
1035  }
1036  if(nrc)
1037  delete nrc;
1038  } // !bookmark
1039 
1040  if(path)
1041  {
1042  const char *old=cwd_history.Lookup(session);
1043  if(old)
1044  {
1045  bool is_file=false;
1046  const char *old_url=0;
1047  if(url::is_url(old))
1048  {
1049  ParsedURL u(old,true);
1050  old_url=old;
1051  old=alloca_strdup(u.path);
1053  is_file=(last_char(old)!='/');
1054  }
1055  else
1056  {
1057  if(url::dir_needs_trailing_slash(session->GetProto()))
1058  is_file=(last_char(old)!='/');
1059  }
1060  session->SetCwd(FileAccess::Path(old,is_file,old_url));
1061  }
1062 
1063  const char *cd_arg=(url && url->orig_url)?url->orig_url.get():path;
1064  xstring& s=xstring::get_tmp("&& cd ");
1065  s.append_quoted(cd_arg);
1066  if(background)
1067  s.append('&');
1068  s.append('\n');
1069  PrependCmd(s);
1070  }
1071 
1072  if(debug)
1073  PrependCmd("debug\n");
1074 
1075  if(slot)
1077 
1078  Reconfig(0);
1079 
1080  if(builtin==BUILTIN_OPEN)
1081  return this;
1082 
1083  ReuseSavedSession();
1084 
1085  exit_code=0;
1086  return 0;
1087 }
1088 
1090 {
1091  builtin=BUILTIN_EXEC_RESTART;
1092  return this;
1093 }
1094 
1096 {
1097  const char *op=args->a0();
1098  int opt;
1100 
1101  while((opt=args->getopt("+adf"))!=EOF)
1102  {
1103  switch(opt)
1104  {
1105  case('a'):
1106  glob_type=GlobURL::ALL;
1107  break;
1108  case('d'):
1109  glob_type=GlobURL::DIRS_ONLY;
1110  break;
1111  case('f'):
1112  glob_type=GlobURL::FILES_ONLY;
1113  break;
1114  case('?'):
1115  eprintf(_("Try `help %s' for more information.\n"),op);
1116  return 0;
1117  }
1118  }
1119  while(args->getindex()>1)
1120  args->delarg(1); // remove options.
1121  if(args->count()<2)
1122  {
1123  eprintf(_("Usage: %s [OPTS] command args...\n"),op);
1124  return 0;
1125  }
1126  assert(args_glob==0 && glob==0);
1127  args_glob=new ArgV();
1128  args->rewind();
1129  args_glob->Append(args->getnext());
1130  const char *pat=args->getnext();
1131  if(!pat)
1132  {
1133  args_glob=0;
1134  args->rewind();
1135  return cmd_command(this);
1136  }
1137  glob=new GlobURL(session,pat,glob_type);
1138  RevertToSavedSession();
1139  builtin=BUILTIN_GLOB;
1140  return this;
1141 }
1142 
1144 {
1145  static struct option queue_options[]=
1146  {
1147  {"move",required_argument,0,'m'},
1148  {"delete",no_argument,0,'d'},
1149  {"quiet",no_argument,0,'q'},
1150  {"verbose",no_argument,0,'v'},
1151  {"queue",required_argument,0,'Q'},
1152  {0,0,0,0}
1153  };
1154  enum { ins, del, move } mode = ins;
1155 
1156  const char *arg = NULL;
1157  /* position to insert at (ins only) */
1158  int pos = -1; /* default to the end */
1159  int verbose = -1; /* default */
1160 
1161  int opt;
1162  while((opt=args->getopt_long("+dm:n:qvQw",queue_options))!=EOF)
1163  {
1164  switch(opt)
1165  {
1166  case 'n':
1167  /* Actually, sending pos == -1 will work, but it'll put the
1168  * job at the end; it's confusing for "-n 0" to mean "put
1169  * it at the end", and that's the default anyway, so disallow
1170  * it. */
1171  if(!isdigit((unsigned char)optarg[0]) || atoi(optarg) == 0)
1172  {
1173  eprintf(_("%s: -n: positive number expected. "), args->a0());
1174  goto err;
1175  }
1176  /* make offsets match the jobs output (starting at 1) */
1177  pos = atoi(optarg) - 1;
1178  break;
1179 
1180  case 'm':
1181  mode = move;
1182  arg = optarg;
1183  break;
1184 
1185  case 'd':
1186  mode = del;
1187  break;
1188 
1189  case 'q':
1190  verbose = 0;
1191  break;
1192 
1193  case 'v':
1194  verbose = 2;
1195  break;
1196 
1197  case 'Q':
1198  verbose = QueueFeeder::PrintRequeue;
1199  break;
1200 
1201  case '?':
1202  err:
1203  eprintf(_("Try `help %s' for more information.\n"),args->a0());
1204  return 0;
1205  }
1206  }
1207 
1208  if(verbose == -1)
1209  {
1210  if(mode == ins || mode == move)
1211  verbose = 0;
1212  else
1213  verbose = 1;
1214  }
1215 
1216  const int args_remaining = args->count() - args->getindex();
1217  switch(mode) {
1218  case ins: {
1219  CmdExec *queue=GetQueue(false);
1220  if(args_remaining==0)
1221  {
1222  if(!queue)
1223  {
1224  if(verbose)
1225  printf(_("Created a stopped queue.\n"));
1226  queue=GetQueue(true);
1227  queue->Suspend();
1228  }
1229  else
1230  {
1231  xstring& buf=xstring::get_tmp("");
1232  queue->FormatStatus(buf,2,"");
1233  printf("%s",buf.get());
1234  }
1235  exit_code=0;
1236  break;
1237  }
1238  if(!queue)
1239  queue=GetQueue(true);
1240 
1242 
1243  if(!strcasecmp(cmd,"stop"))
1244  queue->Suspend();
1245  else if(!strcasecmp(cmd,"start"))
1246  queue->Resume();
1247  else
1248  queue->queue_feeder->QueueCmd(cmd, session->GetCwd(),
1249  cwd?cwd->GetName():0, pos, verbose);
1250 
1251  last_bg=queue->jobno;
1252  exit_code=0;
1253  }
1254  break;
1255 
1256  case del: {
1257  /* Accept:
1258  * queue -d (delete the last job)
1259  * queue -d 1 (delete entry 1)
1260  * queue -d "get" (delete all *get*)
1261  *
1262  * We want an optional argument, but don't use getopt ::, since
1263  * that'll disallow the space between arguments, which we want. */
1264  arg = args->getarg(args->getindex());
1265 
1266  CmdExec *queue=GetQueue(false);
1267  if(!queue) {
1268  eprintf(_("%s: No queue is active.\n"), args->a0());
1269  break;
1270  }
1271 
1272  if(!arg)
1273  exit_code=!queue->queue_feeder->DelJob(-1, verbose); /* delete the last job */
1274  else if(atoi(arg) != 0)
1275  exit_code=!queue->queue_feeder->DelJob(atoi(arg)-1, verbose);
1276  else
1277  exit_code=!queue->queue_feeder->DelJob(arg, verbose);
1278  }
1279  break;
1280 
1281  case move: {
1282  /* Accept:
1283  * queue -m 1 2 (move entry 1 to position 2)
1284  * queue -m "*get*" 1
1285  * queue -m 3 (move entry 3 to the end) */
1286  const char *a1 = args->getarg(args->getindex());
1287  if(a1 && !isdigit((unsigned char)a1[0])) {
1288  eprintf(_("%s: -m: Number expected as second argument. "), args->a0());
1289  goto err;
1290  }
1291  /* default to moving to the end */
1292  int to = a1? atoi(a1)-1:-1;
1293 
1294  CmdExec *queue=GetQueue(false);
1295  if(!queue) {
1296  eprintf(_("%s: No queue is active.\n"), args->a0());
1297  break;
1298  }
1299 
1300  if(atoi(arg) != 0) {
1301  exit_code=!queue->queue_feeder->MoveJob(atoi(arg)-1, to, verbose);
1302  break;
1303  }
1304 
1305  exit_code=!queue->queue_feeder->MoveJob(arg, to, verbose);
1306  }
1307  break;
1308  }
1309 
1310  return 0;
1311 }
1312 
1313 // below are only non-builtin commands
1314 #define args (parent->args)
1315 #define exit_code (parent->exit_code)
1316 #define output (parent->output)
1317 #define session (parent->session)
1318 #define eprintf parent->eprintf
1319 
1320 CMD(lcd)
1321 {
1322  return parent->builtin_lcd();
1323 }
1324 
1325 CMD(ls)
1326 {
1327  bool nlist=false;
1328  bool re=false;
1329  int mode=FA::LIST;
1330  const char *op=args->a0();
1331  bool ascii=true;
1332  if(strstr(op,"nlist"))
1333  nlist=true;
1334  if(!strncmp(op,"re",2))
1335  re=true;
1336  if(!strcmp(op,"quote") || !strcmp(op,"site"))
1337  {
1338  if(args->count()<=1)
1339  {
1340  eprintf(_("Usage: %s <cmd>\n"),op);
1341  return 0;
1342  }
1343  nlist=true;
1344  ascii=false;
1345  mode=FA::QUOTE_CMD;
1346  if(!strcmp(op,"site"))
1347  args->insarg(1,"SITE");
1348  }
1349 
1350  xstring_ca a(args->Combine(nlist?1:0));
1351 
1352  const char *var_ls=ResMgr::Query("cmd:ls-default",session->GetConnectURL(FA::NO_PATH));
1353  if(!nlist && args->count()==1 && var_ls[0])
1354  args->Append(var_ls);
1355 
1356  bool no_status=(!output || output->usesfd(1));
1357 
1358  FileCopyPeer *src_peer=0;
1359  if(!nlist)
1360  {
1361  FileCopyPeerDirList *dir_list=new FileCopyPeerDirList(session->Clone(),args.borrow());
1362  dir_list->UseColor(ResMgr::QueryTriBool("color:use-color",0,(!output && isatty(1))));
1363  src_peer=dir_list;
1364  }
1365  else
1366  src_peer=new FileCopyPeerFA(session->Clone(),a,mode);
1367 
1368  if(re)
1369  src_peer->NoCache();
1370  src_peer->SetDate(NO_DATE);
1371  src_peer->SetSize(NO_SIZE);
1373 
1374  FileCopy *c=FileCopy::New(src_peer,dst_peer,false);
1375  c->DontCopyDate();
1376  c->LineBuffered();
1377  if(ascii)
1378  c->Ascii();
1379 
1380  CopyJob *j=new CopyJob(c,a,op);
1381  if(no_status)
1382  j->NoStatusOnWrite();
1383 
1384  return j;
1385 }
1386 
1387 /* this seems to belong here more than in FileSetOutput.cc ... */
1389 {
1390  enum {
1391  OPT_BLOCK_SIZE,
1392  OPT_DATE,
1393  OPT_FILESIZE,
1394  OPT_GROUP,
1395  OPT_LINKCOUNT,
1396  OPT_LINKS,
1397  OPT_PERMS,
1398  OPT_SI,
1399  OPT_SORT,
1400  OPT_TIME_STYLE,
1401  OPT_USER
1402  };
1403  static struct option cls_options[] = {
1404  {"all",no_argument,0,'a'},
1405  {"basename",no_argument,0,'B'},
1406  {"directory",no_argument,0,'d'},
1407  {"human-readable",no_argument,0,'h'},
1408  {"block-size",required_argument,0,OPT_BLOCK_SIZE},
1409  {"si",no_argument,0,OPT_SI},
1410  {"classify",no_argument,0,'F'},
1411  {"long",no_argument,0,'l'},
1412  {"quiet",no_argument,0,'q'},
1413  {"size",no_argument,0,'s'}, /* show size */
1414  {"filesize",no_argument,0,OPT_FILESIZE}, /* for files only */
1415  {"nocase",no_argument,0,'i'},
1416  {"sortnocase",no_argument,0,'I'},
1417  {"dirsfirst",no_argument,0,'D'},
1418  {"time-style",required_argument,0,OPT_TIME_STYLE},
1419 
1420  {"sort",required_argument,0,OPT_SORT},
1421  {"reverse",no_argument,0,'r'},
1422  {"user",no_argument,0,OPT_USER},
1423  {"group",no_argument,0,OPT_GROUP},
1424  {"perms",no_argument,0,OPT_PERMS},
1425  {"date",no_argument,0,OPT_DATE},
1426  {"linkcount",no_argument,0,OPT_LINKCOUNT},
1427  {"links",no_argument,0,OPT_LINKS},
1428  {0,0,0,0}
1429  };
1430 
1431  const char *time_style=ResMgr::Query("cmd:time-style",0);
1432 
1433  int opt;
1434  while((opt=a->getopt_long(":a1BdFhiklqsDISrt", cls_options))!=EOF)
1435  {
1436  switch(opt) {
1437  case OPT_SORT:
1438  if(!strcasecmp(optarg, "name")) sort = FileSet::BYNAME;
1439  else if(!strcasecmp(optarg, "size")) sort = FileSet::BYSIZE;
1440  else if(!strcasecmp(optarg, "date")) sort = FileSet::BYDATE;
1441  else if(!strcasecmp(optarg, "time")) sort = FileSet::BYDATE;
1442  else return _("invalid argument for `--sort'");
1443  break;
1444  case OPT_FILESIZE:
1445  size_filesonly = true;
1446  break;
1447  case OPT_USER:
1448  mode |= USER;
1449  break;
1450  case OPT_GROUP:
1451  mode |= GROUP;
1452  break;
1453  case OPT_PERMS:
1454  mode |= PERMS;
1455  break;
1456  case OPT_DATE:
1457  mode |= DATE;
1458  break;
1459  case OPT_LINKCOUNT:
1460  mode |= NLINKS;
1461  break;
1462  case OPT_LINKS:
1463  mode |= LINKS;
1464  break;
1465  case OPT_SI:
1466  output_block_size = 1;
1468  break;
1469  case OPT_BLOCK_SIZE:
1470  output_block_size = atoi(optarg);
1471  if(output_block_size == 0)
1472  return _("invalid block size");
1473  break;
1474  case('a'):
1475  showdots = true;
1476  break;
1477  case('1'):
1478  single_column = true;
1479  break;
1480  case('B'):
1481  basenames = true;
1482  break;
1483  case('d'):
1484  list_directories = true;
1485  break;
1486  case('h'):
1487  output_block_size = 1;
1489  break;
1490  case('l'):
1491  long_list();
1492  break;
1493  case('i'):
1494  patterns_casefold = true;
1495  break;
1496  case('k'):
1497  output_block_size = 1024;
1498  break;
1499  case('F'):
1500  classify=true;
1501  break;
1502  case('q'):
1503  quiet = true;
1504  break;
1505  case('s'):
1507  break;
1508  case('D'):
1509  sort_dirs_first = true;
1510  break;
1511  case('I'):
1512  sort_casefold = true;
1513  break;
1514  case('S'):
1516  break;
1517  case('t'):
1519  break;
1520  case('r'):
1521  sort_reverse = true;
1522  break;
1523  case OPT_TIME_STYLE:
1524  time_style=optarg;
1525  break;
1526 
1527  default:
1528  return a->getopt_error_message(opt);
1529  }
1530  }
1531 
1532  time_fmt.set(0);
1533  if(time_style && time_style[0]) {
1534  if (mode & DATE)
1535  need_exact_time=ResMgr::QueryBool("cmd:cls-exact-time",0);
1536  if(time_style[0]=='+')
1537  time_fmt.set(time_style+1);
1538  else if(!strcmp(time_style,"full-iso"))
1539 // time_fmt.set("%Y-%m-%d %H:%M:%S.%N %z"); // %N and %z are GNU extensions
1540  time_fmt.set("%Y-%m-%d %H:%M:%S");
1541  else if(!strcmp(time_style,"long-iso"))
1542  time_fmt.set("%Y-%m-%d %H:%M");
1543  else if(!strcmp(time_style,"iso"))
1544  time_fmt.set("%Y-%m-%d \n%m-%d %H:%M");
1545  else
1546  time_fmt.set(time_style);
1547  }
1548 
1549  // remove parsed options.
1550  while(a->getindex()>1)
1551  a->delarg(1);
1552  a->rewind();
1553 
1554  return NULL;
1555 }
1556 
1557 CMD(cls)
1558 {
1559  exit_code=0;
1560 
1561  const char *op=args->a0();
1562  bool re=false;
1563 
1564  JobRef<OutputJob> out(new OutputJob(output.borrow(), args->a0()));
1566  fso->config(out);
1567 
1568  if(!strncmp(op,"re",2))
1569  re=true;
1570 
1571  fso->parse_res(ResMgr::Query("cmd:cls-default", 0));
1572 
1573  if(const char *err = fso->parse_argv(args)) {
1574  eprintf("%s: %s\n", op, err);
1575  eprintf(_("Try `help %s' for more information.\n"),op);
1576  return 0;
1577  }
1578 
1579  clsJob *j = new clsJob(session->Clone(), args.borrow(), fso.borrow(), out.borrow());
1580  if(re)
1581  j->UseCache(false);
1582 
1583  return j;
1584 }
1585 
1586 CMD(cat)
1587 {
1588  const char *op=args->a0();
1589  int opt;
1590  bool ascii=false;
1591  bool auto_ascii=true;
1592 
1593  while((opt=args->getopt("+bau"))!=EOF)
1594  {
1595  switch(opt)
1596  {
1597  case('a'):
1598  ascii=true;
1599  auto_ascii=false;
1600  break;
1601  case('b'):
1602  ascii=false;
1603  auto_ascii=false;
1604  break;
1605  case('?'):
1606  eprintf(_("Try `help %s' for more information.\n"),op);
1607  return 0;
1608  }
1609  }
1610  while(args->getindex()>1)
1611  args->delarg(1);
1612  args->rewind();
1613  if(args->count()<=1)
1614  {
1615  eprintf(_("Usage: %s [OPTS] files...\n"),op);
1616  return 0;
1617  }
1618  OutputJob *out=new OutputJob(output.borrow(), args->a0());
1619  CatJob *j=new CatJob(session->Clone(),out,args.borrow());
1620  if(!auto_ascii)
1621  {
1622  if(ascii)
1623  j->Ascii();
1624  else
1625  j->Binary();
1626  }
1627  return j;
1628 }
1629 
1630 CMD(get)
1631 {
1632  int opt;
1633  bool cont=false;
1634  const char *opts="+cEeuaO:";
1635  const char *op=args->a0();
1636  ArgV *get_args=new ArgV(op);
1637  int n_conn=1;
1638  bool del=false;
1639  bool del_target=false;
1640  bool ascii=false;
1641  bool glob=false;
1642  bool make_dirs=false;
1643  bool reverse=false;
1644  xstring_c output_dir;
1645 
1646  if(!strncmp(op,"re",2))
1647  {
1648  cont=true;
1649  opts="+EuaO:";
1650  }
1651  if(!strcmp(op,"pget"))
1652  {
1653  opts="+n:ceuO:";
1654  n_conn=0; // default, which means to take pget:default-n
1655  }
1656  else if(!strcmp(op,"put") || !strcmp(op,"reput"))
1657  {
1658  reverse=true;
1659  }
1660  else if(!strcmp(op,"mget"))
1661  {
1662  glob=true;
1663  opts="cEeadO:";
1664  }
1665  else if(!strcmp(op,"mput"))
1666  {
1667  glob=true;
1668  opts="cEeadO:";
1669  reverse=true;
1670  }
1671  if(!reverse)
1672  {
1673  const char *od=ResMgr::Query("xfer:destination-directory",session->GetHostName());
1674  if(od && *od)
1675  output_dir.set(od);
1676  }
1677  while((opt=args->getopt(opts))!=EOF)
1678  {
1679  switch(opt)
1680  {
1681  case('c'):
1682  cont=true;
1683  break;
1684  case('n'):
1685  if(!isdigit((unsigned char)optarg[0]))
1686  {
1687  eprintf(_("%s: -n: Number expected. "),op);
1688  goto err;
1689  }
1690  n_conn=atoi(optarg);
1691  break;
1692  case('E'):
1693  del=true;
1694  break;
1695  case('e'):
1696  del_target=true;
1697  break;
1698  case('a'):
1699  ascii=true;
1700  break;
1701  case('d'):
1702  make_dirs=true;
1703  break;
1704  case('O'):
1705  output_dir.set(optarg);
1706  break;
1707  case('?'):
1708  err:
1709  eprintf(_("Try `help %s' for more information.\n"),op);
1710  delete get_args;
1711  return 0;
1712  }
1713  }
1714  if(glob)
1715  {
1716  if(args->getcurr()==0)
1717  {
1718  file_name_missed:
1719  // xgettext:c-format
1720  eprintf(_("File name missed. "));
1721  goto err;
1722  }
1723  delete get_args;
1724  // remove options
1725  while(args->getindex()>1)
1726  args->delarg(1);
1727  mgetJob *j=new mgetJob(session->Clone(),args.borrow(),cont,make_dirs);
1728  if(reverse)
1729  j->Reverse();
1730  if(del)
1731  j->DeleteFiles();
1732  if(ascii)
1733  j->Ascii();
1734  if(output_dir)
1735  j->OutputDir(output_dir.borrow());
1736  return j;
1737  }
1738  args->back();
1739  const char *a=args->getnext();
1740  if(a==0)
1741  goto file_name_missed;
1742  while(a)
1743  {
1744  const char *src=a;
1745  const char *dst=0;
1746  a=args->getnext();
1747  if(a && !strcmp(a,"-o"))
1748  {
1749  dst=args->getnext();
1750  a=args->getnext();
1751  }
1752  if(reverse)
1753  src=expand_home_relative(src);
1754  dst=output_file_name(src,dst,!reverse,output_dir,false);
1755  get_args->Append(src);
1756  get_args->Append(dst);
1757  }
1758 
1759  GetJob *j=new GetJob(session->Clone(),get_args,cont);
1760  if(del)
1761  j->DeleteFiles();
1762  if(del_target)
1763  j->RemoveTargetFirst();
1764  if(ascii)
1765  j->Ascii();
1766  if(reverse)
1767  j->Reverse();
1768  if(n_conn!=1)
1769  j->SetCopyJobCreator(new pCopyJobCreator(n_conn));
1770  return j;
1771 }
1772 
1773 CMD(shell)
1774 {
1775  Job *j;
1776  if(args->count()<=1)
1777  j=new SysCmdJob(0);
1778  else
1779  {
1780  xstring_ca a(args->Combine(1));
1781  j=new SysCmdJob(a);
1782  }
1783  return j;
1784 }
1785 
1786 CMD(mrm)
1787 {
1788  args->setarg(0,"glob");
1789  args->insarg(1,"rm");
1790  return parent->builtin_restart();
1791 }
1792 CMD(rm)
1793 {
1794  int opt;
1795  bool recursive=false;
1796  bool silent=false;
1797  const char *opts="+rf";
1798 
1799  bool rmdir = false;
1800  if(!strcmp(args->a0(),"rmdir"))
1801  {
1802  rmdir = true;
1803  opts="+f";
1804  }
1805 
1806  while((opt=args->getopt(opts))!=EOF)
1807  {
1808  switch(opt)
1809  {
1810  case('r'):
1811  recursive=true;
1812  break;
1813  case('f'):
1814  silent=true;
1815  break;
1816  case('?'):
1817  print_usage:
1818  eprintf(_("Usage: %s %s[-f] files...\n"),args->a0(), rmdir? "":"[-r] ");
1819  return 0;
1820  }
1821  }
1822 
1823  if(args->getcurr()==0)
1824  goto print_usage;
1825 
1826  rmJob *j=new rmJob(session->Clone(),args.borrow());
1827 
1828  if(recursive)
1829  j->Recurse();
1830  if(rmdir)
1831  j->Rmdir();
1832 
1833  if(silent)
1834  j->BeQuiet();
1835 
1836  return j;
1837 }
1838 CMD(mkdir)
1839 {
1840  return new mkdirJob(session->Clone(),args.borrow());
1841 }
1842 
1843 #ifndef O_ASCII
1844 # define O_ASCII 0
1845 #endif
1846 
1847 CMD(source)
1848 {
1849  int opt;
1850  bool e=false;
1851  while((opt=args->getopt("+e"))!=EOF)
1852  {
1853  switch(opt)
1854  {
1855  case('e'):
1856  e=true;
1857  break;
1858  case('?'):
1859  usage:
1860  // xgettext:c-format
1861  eprintf(_("Usage: %s [-e] <file|command>\n"),args->a0());
1862  return 0;
1863  }
1864  }
1865  if(args->getindex()>=args->count())
1866  goto usage;
1867  FDStream *f=0;
1868  if(e)
1869  {
1870  xstring_ca cmd(args->Combine(args->getindex()));
1871  f=new InputFilter(cmd);
1872  }
1873  else
1874  f=new FileStream(args->getarg(1),O_RDONLY|O_ASCII);
1875  // try to open the file to return error code if failed, as FileFeeder
1876  // cannot feed error codes.
1877  if(f->getfd()==-1)
1878  {
1879  if(f->error())
1880  {
1881  fprintf(stderr,"%s: %s\n",args->a0(),f->error_text.get());
1882  delete f;
1883  return 0;
1884  }
1885  }
1886  parent->SetCmdFeeder(new FileFeeder(f));
1887  exit_code=0;
1888  return 0;
1889 }
1890 
1891 CMD(jobs)
1892 {
1893  int opt;
1894  int v=1;
1895  while((opt=args->getopt("+v"))!=EOF)
1896  {
1897  switch(opt)
1898  {
1899  case('v'):
1900  v++;
1901  break;
1902  case('?'):
1903  // xgettext:c-format
1904  eprintf(_("Usage: %s [-v] [-v] ...\n"),args->a0());
1905  return 0;
1906  }
1907  }
1908  exit_code=0;
1909  args->back();
1910  const char *op=args->a0();
1911  const char *arg=args->getnext();
1912  xstring s("");
1913  if(!arg) {
1914  parent->top->FormatJobs(s,v);
1915  } else {
1916  for(; arg; arg=args->getnext()) {
1917  if(!isdigit((unsigned char)*arg)) {
1918  eprintf(_("%s: %s - not a number\n"),op,arg);
1919  exit_code=1;
1920  continue;
1921  }
1922  int n=atoi(arg);
1923  Job *j=parent->FindJob(n);
1924  if(!j) {
1925  eprintf(_("%s: %d - no such job\n"),op,n);
1926  exit_code=1;
1927  continue;
1928  }
1929  j->FormatOneJob(s,v);
1930  }
1931  }
1932  if(exit_code)
1933  return 0;
1934  OutputJob *out=new OutputJob(output.borrow(), args->a0());
1935  Job *j=new echoJob(s,s.length(),out);
1936  return j;
1937 }
1938 
1939 CMD(cd)
1940 {
1941  return parent->builtin_cd();
1942 }
1943 
1944 CMD(pwd)
1945 {
1946  int opt;
1947  int flags=0;
1948  while((opt=args->getopt("p"))!=EOF)
1949  {
1950  switch(opt)
1951  {
1952  case('p'):
1953  flags|=FA::WITH_PASSWORD;
1954  break;
1955  case('?'):
1956  // xgettext:c-format
1957  eprintf(_("Usage: %s [-p]\n"),args->a0());
1958  return 0;
1959  }
1960  }
1961  const char *url_c=session->GetConnectURL(flags);
1962  char *url=alloca_strdup(url_c);
1963  int len=strlen(url_c);
1964  url[len++]='\n'; // replaces \0
1965 
1966  OutputJob *out=new OutputJob(output.borrow(), args->a0());
1967  Job *j=new echoJob(url,len,out);
1968 
1969  return j;
1970 }
1971 
1972 CMD(exit)
1973 {
1974  return parent->builtin_exit();
1975 }
1976 
1978 {
1979  const char *op=args->a0();
1980  int new_dlevel=9;
1981  char *debug_file_name=0;
1982  int fd=-1;
1983  bool enabled=true;
1984  bool show_pid=false;
1985  bool show_time=false;
1986  bool show_context=false;
1987 
1988  int opt;
1989  while((opt=args->getopt("o:ptc"))!=EOF)
1990  {
1991  switch(opt)
1992  {
1993  case('o'):
1994  debug_file_name=optarg;
1995  if(fd!=-1)
1996  close(fd);
1997  fd=open(debug_file_name,O_WRONLY|O_CREAT|O_APPEND,0600);
1998  if(fd==-1)
1999  {
2000  perror(debug_file_name);
2001  return 0;
2002  }
2003  fcntl(fd,F_SETFL,O_NONBLOCK);
2004  fcntl(fd,F_SETFD,FD_CLOEXEC);
2005  break;
2006  case 'p':
2007  show_pid=true;
2008  break;
2009  case 't':
2010  show_time=true;
2011  break;
2012  case 'c':
2013  show_context=true;
2014  break;
2015  case('?'):
2016  eprintf(_("Try `help %s' for more information.\n"),op);
2017  return 0;
2018  }
2019  }
2020 
2021  if(fd==-1)
2022  Log::global->SetOutput(2,false);
2023  else
2024  Log::global->SetOutput(fd,true);
2025 
2026  const char *a=args->getcurr();
2027  if(a)
2028  {
2029  if(!strcasecmp(a,"off"))
2030  {
2031  enabled=false;
2032  }
2033  else
2034  {
2035  new_dlevel=atoi(a);
2036  if(new_dlevel<0)
2037  new_dlevel=0;
2038  enabled=true;
2039  }
2040  }
2041 
2042  if(enabled)
2043  {
2044  Log::global->Enable();
2045  Log::global->SetLevel(new_dlevel);
2046  }
2047  else
2048  Log::global->Disable();
2049 
2050  Log::global->ShowPID(show_pid);
2051  Log::global->ShowTime(show_time);
2052  Log::global->ShowContext(show_context);
2053 
2054 #if 0
2055  if(interactive)
2056  {
2057  if(enabled)
2058  printf(_("debug level is %d, output goes to %s\n"),new_dlevel,
2059  debug_file_name?debug_file_name:"<stderr>");
2060  else
2061  printf(_("debug is off\n"));
2062  }
2063 #endif
2064  exit_code=0;
2065  return 0;
2066 }
2067 
2068 CMD(user)
2069 {
2070  if(args->count()<2 || args->count()>3)
2071  {
2072  eprintf(_("Usage: %s <user|URL> [<pass>]\n"),args->getarg(0));
2073  return 0;
2074  }
2075  const char *user=args->getarg(1);
2076  const char *pass=args->getarg(2);
2077  bool insecure=(pass!=0);
2078 
2079  ParsedURL u(user,true);
2080  if(u.proto && !u.user)
2081  {
2082  exit_code=0;
2083  return 0;
2084  }
2085  if(u.proto && u.user && u.pass)
2086  {
2087  pass=u.pass;
2088  insecure=true;
2089  }
2090  if(!pass)
2091  pass=GetPass(_("Password: "));
2092  if(!pass)
2093  return 0;
2094 
2095  if(u.proto && u.user)
2096  {
2097  FA *s=FA::New(&u,false);
2098  if(s)
2099  {
2100  s->SetPasswordGlobal(pass);
2101  s->InsecurePassword(insecure);
2102  SessionPool::Reuse(s);
2103  }
2104  else
2105  {
2106  eprintf("%s: %s%s\n",args->a0(),u.proto.get(),
2107  _(" - not supported protocol"));
2108  return 0;
2109  }
2110  }
2111  else
2112  {
2113  session->Login(args->getarg(1),0);
2114  session->SetPasswordGlobal(pass);
2115  session->InsecurePassword(insecure);
2116  }
2117  exit_code=0;
2118  return 0;
2119 }
2120 CMD(anon)
2121 {
2122  session->AnonymousLogin();
2123  exit_code=0;
2124  return 0;
2125 }
2126 
2127 CMD(lftp)
2128 {
2129  return parent->builtin_lftp();
2130 }
2131 
2133 {
2134  return parent->builtin_open();
2135 }
2136 
2137 CMD(kill)
2138 {
2139  int n;
2140  const char *op=args->a0();
2141  if(args->count()<2)
2142  {
2143 #if 0 // too dangerous to kill last job. Better require explicit number.
2144  n=parent->last_bg;
2145  if(n==-1)
2146  {
2147  eprintf(_("%s: no current job\n"),op);
2148  return 0;
2149  }
2150  printf("%s %d\n",op,n);
2151  if(Job::Running(n))
2152  {
2153  parent->Kill(n);
2154  exit_code=0;
2155  }
2156  else
2157  eprintf(_("%s: %d - no such job\n"),op,n);
2158 #else
2159  eprintf(_("Usage: %s <jobno> ... | all\n"),args->getarg(0));
2160 #endif
2161  return 0;
2162  }
2163  if(!strcasecmp(args->getarg(1),"all"))
2164  {
2165  parent->KillAll();
2166  exit_code=0;
2167  return 0;
2168  }
2169  args->rewind();
2170  exit_code=0;
2171  for(;;)
2172  {
2173  const char *arg=args->getnext();
2174  if(arg==0)
2175  break;
2176  if(!isdigit((unsigned char)arg[0]))
2177  {
2178  eprintf(_("%s: %s - not a number\n"),op,arg);
2179  exit_code=1;
2180  continue;
2181  }
2182  n=atoi(arg);
2183  if(Job::Running(n))
2184  parent->Kill(n);
2185  else
2186  {
2187  eprintf(_("%s: %d - no such job\n"),op,n);
2188  exit_code=1;
2189  }
2190  }
2191  return 0;
2192 }
2193 
2194 CMD(set)
2195 {
2196  const char *op=args->a0();
2197  bool with_defaults=false;
2198  bool only_defaults=false;
2199  int c;
2200 
2201  while((c=args->getopt("+ad"))!=EOF)
2202  {
2203  switch(c)
2204  {
2205  case 'a':
2206  with_defaults=true;
2207  break;
2208  case 'd':
2209  only_defaults=true;
2210  break;
2211  default:
2212  eprintf(_("Try `help %s' for more information.\n"),op);
2213  return 0;
2214  }
2215  }
2216  args->back();
2217  const char *ac=args->getnext();
2218  char *a=alloca_strdup(ac);
2219 
2220  if(a==0)
2221  {
2222  xstring_ca s(ResMgr::Format(with_defaults,only_defaults));
2223  OutputJob *out=new OutputJob(output.borrow(), args->a0());
2224  Job *j=new echoJob(s,out);
2225  return j;
2226  }
2227 
2228  char *sl=strchr(a,'/');
2229  char *closure=0;
2230  if(sl)
2231  {
2232  *sl=0;
2233  closure=sl+1;
2234  }
2235 
2236  const ResType *type;
2237  // find type of given variable
2238  const char *msg=ResMgr::FindVar(a,&type);
2239  if(msg)
2240  {
2241  eprintf(_("%s: %s. Use `set -a' to look at all variables.\n"),a,msg);
2242  return 0;
2243  }
2244 
2245  args->getnext();
2246  xstring_ca val(args->getcurr()==0?0:args->Combine(args->getindex()));
2247  msg=ResMgr::Set(a,closure,val);
2248 
2249  if(msg)
2250  {
2251  eprintf("%s: %s.\n",val.get(),msg);
2252  return 0;
2253  }
2254  exit_code=0;
2255  return 0;
2256 }
2257 
2258 CMD(alias)
2259 {
2260  if(args->count()<2)
2261  {
2262  xstring_ca list(Alias::Format());
2263  OutputJob *out=new OutputJob(output.borrow(), args->a0());
2264  Job *j=new echoJob(list,out);
2265  return j;
2266  }
2267  else if(args->count()==2)
2268  {
2269  Alias::Del(args->getarg(1));
2270  }
2271  else
2272  {
2273  xstring_ca val(args->Combine(2));
2274  Alias::Add(args->getarg(1),val);
2275  }
2276  exit_code=0;
2277  return 0;
2278 }
2279 
2280 CMD(wait)
2281 {
2282  const char *op=args->a0();
2283  if(args->count()>2)
2284  {
2285  eprintf(_("Usage: %s [<jobno>]\n"),op);
2286  return 0;
2287  }
2288  int n=-1;
2289  const char *jn=args->getnext();
2290  if(jn)
2291  {
2292  if(!strcasecmp(jn,"all"))
2293  {
2294  parent->WaitForAllChildren();
2295  parent->AllWaitingFg();
2296  exit_code=0;
2297  return 0;
2298  }
2299  if(!isdigit((unsigned char)jn[0]))
2300  {
2301  eprintf(_("%s: %s - not a number\n"),op,jn);
2302  return 0;
2303  }
2304  n=atoi(jn);
2305  }
2306  if(n==-1)
2307  {
2308  n=parent->last_bg;
2309  if(n==-1)
2310  {
2311  eprintf(_("%s: no current job\n"),op);
2312  return 0;
2313  }
2314  printf("%s %d\n",op,n);
2315  }
2316  Job *j=parent->FindJob(n);
2317  if(j==0)
2318  {
2319  eprintf(_("%s: %d - no such job\n"),op,n);
2320  return 0;
2321  }
2322  if(Job::FindWhoWaitsFor(j)!=0)
2323  {
2324  eprintf(_("%s: some other job waits for job %d\n"),op,n);
2325  return 0;
2326  }
2327  if(j->Job::CheckForWaitLoop(parent))
2328  {
2329  eprintf(_("%s: wait loop detected\n"),op);
2330  return 0;
2331  }
2332  j->parent=0;
2333  j->Bg();
2334  return j;
2335 }
2336 
2337 CMD(subsh)
2338 {
2339  CmdExec *e=new CmdExec(parent);
2340 
2341  const char *c=args->getarg(1);
2342  e->FeedCmd(c);
2343  e->FeedCmd("\n");
2344  e->cmdline.vset("(",c,")",NULL);
2345  return e;
2346 }
2347 
2348 CMD(mv)
2349 {
2350  if(args->count()!=3)
2351  {
2352  // xgettext:c-format
2353  eprintf(_("Usage: mv <file1> <file2>\n"));
2354  return 0;
2355  }
2356  Job *j=new mvJob(session->Clone(),args->getarg(1),args->getarg(2));
2357  return j;
2358 }
2359 
2360 CMD(ln)
2361 {
2362  FA::open_mode m=FA::LINK;
2363  const char *op=args->a0();
2364  int c;
2365  while((c=args->getopt("+s"))!=EOF)
2366  {
2367  switch(c)
2368  {
2369  case 's':
2370  m=FA::SYMLINK;
2371  break;
2372  default:
2373  error:
2374  eprintf(_("Try `help %s' for more information.\n"),op);
2375  return 0;
2376  }
2377  }
2378  args->back();
2379  const char *file1=args->getnext();
2380  const char *file2=args->getnext();
2381  if(!file1 || !file2)
2382  goto error;
2383 
2384  return new mvJob(session->Clone(),file1,file2,m);
2385 }
2386 
2387 const char *const cache_subcmd[]={
2388  "status","flush","on","off","size","expire",
2389  NULL
2390 };
2391 
2392 CMD(cache) // cache control
2393 {
2394  const char *op=args->getnext();
2395 
2396  if(!op)
2397  op="status";
2398  else if(!find_command(op,cache_subcmd,&op))
2399  {
2400  // xgettext:c-format
2401  eprintf(_("Invalid command. "));
2402  eprintf(_("Try `help %s' for more information.\n"),args->a0());
2403  return 0;
2404  }
2405  if(!op)
2406  {
2407  // xgettext:c-format
2408  eprintf(_("Ambiguous command. "));
2409  eprintf(_("Try `help %s' for more information.\n"),args->a0());
2410  return 0;
2411  }
2412 
2413  exit_code=0;
2414  if(!op || !strcasecmp(op,"status"))
2415  FileAccess::cache->List();
2416  else if(!strcasecmp(op,"flush"))
2417  FileAccess::cache->Flush();
2418  else if(!strcasecmp(op,"on"))
2419  ResMgr::Set("cache:enable",0,"yes");
2420  else if(!strcasecmp(op,"off"))
2421  ResMgr::Set("cache:enable",0,"no");
2422  else if(!strcasecmp(op,"size"))
2423  {
2424  op=args->getnext();
2425  if(!op)
2426  {
2427  eprintf(_("%s: Operand missed for size\n"),args->a0());
2428  exit_code=1;
2429  return 0;
2430  }
2431  const char *err=ResMgr::Set("cache:size",0,op);
2432  if(err)
2433  {
2434  eprintf("%s: %s: %s\n",args->a0(),op,err);
2435  exit_code=1;
2436  return 0;
2437  }
2438  }
2439  else if(!strcasecmp(op,"expire"))
2440  {
2441  op=args->getnext();
2442  if(!op)
2443  {
2444  eprintf(_("%s: Operand missed for `expire'\n"),args->a0());
2445  exit_code=1;
2446  return 0;
2447  }
2448  const char *err=ResMgr::Set("cache:expire",0,op);
2449  if(err)
2450  {
2451  eprintf("%s: %s: %s\n",args->a0(),op,err);
2452  exit_code=1;
2453  return 0;
2454  }
2455  }
2456  return 0;
2457 }
2458 
2459 CMD(scache)
2460 {
2461  if(args->count()==1)
2462  {
2463  SessionPool::Print(stdout);
2464  exit_code=0;
2465  }
2466  else
2467  {
2468  const char *a=args->getarg(1);
2469  if(!isdigit((unsigned char)a[0]))
2470  {
2471  eprintf(_("%s: %s - not a number\n"),args->a0(),a);
2472  return 0;
2473  }
2474  FileAccess *new_session=SessionPool::GetSession(atoi(a));
2475  if(new_session==0)
2476  {
2477  eprintf(_("%s: %s - no such cached session. Use `scache' to look at session list.\n"),args->a0(),a);
2478  return 0;
2479  }
2480  parent->ChangeSession(new_session);
2481  }
2482  return 0;
2483 }
2484 
2485 void CmdExec::print_cmd_help(const char *cmd)
2486 {
2487  const cmd_rec *c;
2488  int part=find_cmd(cmd,&c);
2489 
2490  if(part==1)
2491  {
2492  if(c->long_desc==0 && c->short_desc==0)
2493  {
2494  printf(_("Sorry, no help for %s\n"),cmd);
2495  return;
2496  }
2497  if(c->short_desc==0 && !strchr(c->long_desc,' '))
2498  {
2499  printf(_("%s is a built-in alias for %s\n"),cmd,c->long_desc);
2500  print_cmd_help(c->long_desc);
2501  return;
2502  }
2503  if(c->short_desc)
2504  printf(_("Usage: %s\n"),_(c->short_desc));
2505  if(c->long_desc)
2506  printf("%s",_(c->long_desc));
2507  return;
2508  }
2509  const char *a=Alias::Find(cmd);
2510  if(a)
2511  {
2512  printf(_("%s is an alias to `%s'\n"),cmd,a);
2513  return;
2514  }
2515  if(part==0)
2516  printf(_("No such command `%s'. Use `help' to see available commands.\n"),cmd);
2517  else
2518  printf(_("Ambiguous command `%s'. Use `help' to see available commands.\n"),cmd);
2519 }
2520 
2522 {
2523  int i=0;
2524  const cmd_rec *cmd_table=dyn_cmd_table?dyn_cmd_table.get():static_cmd_table;
2525  const int count=dyn_cmd_table?dyn_cmd_table.count():static_cmd_table_length;
2526  int width=fd_width(1);
2527  int pos=0;
2528  const int align=37;
2529  const int first_align=4;
2530  while(i<count)
2531  {
2532  while(i<count && !cmd_table[i].short_desc)
2533  i++;
2534  if(i>=count)
2535  break;
2536  const char *c1=gettext(cmd_table[i].short_desc);
2537  i++;
2538  int w1=mbswidth(c1,0);
2539 
2540  int pad=0;
2541  if(pos<first_align)
2542  pad=first_align-pos;
2543  else if(pos>first_align)
2544  pad=align-(pos-first_align)%align;
2545  if(pos>first_align && pos+pad+w1>=width) {
2546  printf("\n");
2547  pos=0;
2548  pad=first_align;
2549  }
2550 
2551  printf("%*s%s",pad,"",c1);
2552  pos+=pad+w1;
2553  }
2554  if(pos>0)
2555  printf("\n");
2556 }
2557 
2558 CMD(help)
2559 {
2560  if(args->count()>1)
2561  {
2562  for(;;)
2563  {
2564  const char *cmd=args->getnext();
2565  if(cmd==0)
2566  break;
2567  parent->print_cmd_help(cmd);
2568  }
2569  return 0;
2570  }
2571 
2572  parent->print_cmd_index();
2573 
2574  exit_code=0;
2575  return 0;
2576 }
2577 
2578 CMD(ver)
2579 {
2580  printf(
2581  _("LFTP | Version %s | Copyright (c) 1996-%d Alexander V. Lukyanov\n"),VERSION,2013);
2582  printf("\n");
2583  printf(
2584  _("LFTP is free software: you can redistribute it and/or modify\n"
2585  "it under the terms of the GNU General Public License as published by\n"
2586  "the Free Software Foundation, either version 3 of the License, or\n"
2587  "(at your option) any later version.\n"
2588  "\n"
2589  "This program is distributed in the hope that it will be useful,\n"
2590  "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
2591  "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
2592  "GNU General Public License for more details.\n"
2593  "\n"
2594  "You should have received a copy of the GNU General Public License\n"
2595  "along with LFTP. If not, see <http://www.gnu.org/licenses/>.\n"));
2596  printf("\n");
2597  printf(
2598  _("Send bug reports and questions to the mailing list <%s>.\n"),"lftp@uniyar.ac.ru");
2599 
2600 #if defined(HAVE_DLOPEN) && defined(RTLD_DEFAULT)
2601  /* Show some of loaded libraries. Modules can load those libraries on
2602  demand so use dlsym to avoid linking with them just for showing version. */
2603  printf("\n");
2604  const char *msg=_("Libraries used: ");
2605  int mbflags=0;
2606  int col=mbswidth(msg,mbflags);
2607  int width=parent->status_line?parent->status_line->GetWidth():80;
2608  printf("%s",msg);
2609 
2610  struct VersionInfo
2611  {
2612  const char *lib_name;
2613  const char *symbol;
2614  enum type_t { STRING_PTR, FUNC0, INT8_8 } type;
2615  const char *skip_prefix;
2616  typedef const char *(*func0)(int);
2617  const char *query() const
2618  {
2619  int v;
2620  void *sym_ptr=dlsym(RTLD_DEFAULT,symbol);
2621  if(!sym_ptr)
2622  return 0;
2623  const char *str=0;
2624  switch(type)
2625  {
2626  case STRING_PTR:
2627  str=*(const char**)sym_ptr;
2628  break;
2629  case FUNC0:
2630  str=((func0)sym_ptr)(0);
2631  break;
2632  case INT8_8:
2633  v=*(int*)sym_ptr;
2634  str=xstring::format("%d.%d",(v>>8)&255,v&255);
2635  }
2636  if(!str)
2637  return 0;
2638  if(skip_prefix && !strncmp(str,skip_prefix,strlen(skip_prefix)))
2639  str+=strlen(skip_prefix);
2640  return str;
2641  }
2642  }
2643  static const libs[]=
2644  {
2645  {"Readline", "rl_library_version", VersionInfo::STRING_PTR,0},
2646  {"Expat", "XML_ExpatVersion", VersionInfo::FUNC0, "expat_"},
2647  {"OpenSSL", "SSL_version_str", VersionInfo::STRING_PTR,"OpenSSL "},
2648  {"GnuTLS", "gnutls_check_version", VersionInfo::FUNC0, 0},
2649  {"libiconv", "_libiconv_version", VersionInfo::INT8_8, 0},
2650  {"zlib", "zlibVersion", VersionInfo::FUNC0, 0},
2651  {0}
2652  };
2653 
2654  bool need_comma=false;
2655  for(const VersionInfo *scan=libs; scan->lib_name; scan++)
2656  {
2657  const char *v=scan->query();
2658  if(!v)
2659  continue;
2660  char buf[256];
2661  snprintf(buf,sizeof(buf),", %s %s",scan->lib_name,v);
2662  int skip=need_comma?0:2;
2663  int w=mbswidth(buf+skip,mbflags);
2664  if(col+w>=width)
2665  {
2666  buf[1]='\n';
2667  col=w-2+skip;
2668  skip/=2;
2669  }
2670  else
2671  col+=w;
2672  printf("%s",buf+skip);
2673  need_comma=true;
2674  }
2675  printf("\n");
2676 #endif // HAVE_DLOPEN
2677 
2678  exit_code=0;
2679  return 0;
2680 }
2681 
2682 CMD(close)
2683 {
2684  const char *op=args->a0();
2685  bool all=false;
2686  int opt;
2687  while((opt=args->getopt("a"))!=EOF)
2688  {
2689  switch(opt)
2690  {
2691  case('a'):
2692  all=true;
2693  break;
2694  case('?'):
2695  eprintf(_("Try `help %s' for more information.\n"),op);
2696  return 0;
2697  }
2698  }
2699  if(all)
2700  session->CleanupAll();
2701  else
2702  session->Cleanup();
2703  exit_code=0;
2704  return 0;
2705 }
2706 
2707 const char * const bookmark_subcmd[]=
2708  {"add","delete","list","list-p","edit","import",0};
2709 static ResDecl res_save_passwords
2710  ("bmk:save-passwords","no",ResMgr::BoolValidate,0);
2711 
2712 CMD(bookmark)
2713 {
2714  const char *op=args->getnext();
2715 
2716  if(!op)
2717  op="list";
2718  else if(!find_command(op,bookmark_subcmd,&op))
2719  {
2720  // xgettext:c-format
2721  eprintf(_("Invalid command. "));
2722  eprintf(_("Try `help %s' for more information.\n"),args->a0());
2723  return 0;
2724  }
2725  if(!op)
2726  {
2727  // xgettext:c-format
2728  eprintf(_("Ambiguous command. "));
2729  eprintf(_("Try `help %s' for more information.\n"),args->a0());
2730  return 0;
2731  }
2732 
2733  if(!strcasecmp(op,"list") || !strcasecmp(op,"list-p"))
2734  {
2736  OutputJob *out=new OutputJob(output.borrow(), args->a0());
2737  Job *j=new echoJob(list,out);
2738  return j;
2739  }
2740  else if(!strcasecmp(op,"add"))
2741  {
2742  const char *key=args->getnext();
2743  if(key==0 || key[0]==0)
2744  eprintf(_("%s: bookmark name required\n"),args->a0());
2745  else
2746  {
2747  const char *value=args->getnext();
2748  int flags=0;
2749  if(res_save_passwords.QueryBool(session->GetHostName()))
2750  flags|=session->WITH_PASSWORD;
2751  if(value==0)
2752  {
2753  value=session->GetConnectURL(flags);
2754  // encode some more characters, special to CmdExec parser.
2755  xstring& a=url::encode(value,"&;|\"'\\");
2756  if(value[0] && last_char(value)!='/')
2757  a.append('/');
2758  value=a;
2759  }
2760  if(value==0 || value[0]==0)
2761  value="\"\"";
2762  if(strchr(key,' ') || strchr(key,'\t'))
2763  {
2764  eprintf(_("%s: spaces in bookmark name are not allowed\n"),args->a0());
2765  return 0;
2766  }
2767  lftp_bookmarks.Add(key,value);
2768  exit_code=0;
2769  }
2770  }
2771  else if(!strcasecmp(op,"delete"))
2772  {
2773  const char *key=args->getnext();
2774  if(key==0 || key[0]==0)
2775  eprintf(_("%s: bookmark name required\n"),args->a0());
2776  else if(lftp_bookmarks.Lookup(key)==0)
2777  eprintf(_("%s: no such bookmark `%s'\n"),args->a0(),key);
2778  else
2779  {
2780  lftp_bookmarks.Remove(key);
2781  exit_code=0;
2782  }
2783  }
2784  else if(!strcasecmp(op,"edit"))
2785  {
2786  lftp_bookmarks.Remove(""); // force bookmark file creation
2787  parent->PrependCmd("shell \"/bin/sh -c 'exec ${EDITOR:-vi} ${LFTP_HOME:-$HOME/.lftp}/bookmarks'\"\n");
2788  }
2789  else if(!strcasecmp(op,"import"))
2790  {
2791  op=args->getnext();
2792  if(!op)
2793  eprintf(_("%s: import type required (netscape,ncftp)\n"),args->a0());
2794  else
2795  {
2796  parent->PrependCmd(xstring::cat("shell " PKGDATADIR "/import-",op,"\n",NULL));
2797  exit_code=0;
2798  }
2799  }
2800  else if(!strcasecmp(op,"load"))
2801  {
2803  exit_code=0;
2804  }
2805  else if(!strcasecmp(op,"save"))
2806  {
2808  exit_code=0;
2809  }
2810  return 0;
2811 }
2812 
2813 CMD(echo)
2814 {
2815  xstring s;
2816  s.set_allocated(args->Combine(1));
2817  if(args->count()>1 && !strcmp(args->getarg(1),"-n"))
2818  {
2819  if(s.length()<=3)
2820  {
2821  exit_code=0;
2822  return 0;
2823  }
2824  s.set_substr(0,3,"");
2825  }
2826  else
2827  {
2828  s.append('\n');
2829  }
2830 
2831  OutputJob *out=new OutputJob(output.borrow(), args->a0());
2832  Job *j=new echoJob(s,s.length(),out);
2833  return j;
2834 }
2835 
2836 CMD(suspend)
2837 {
2838  kill(getpid(),SIGSTOP);
2839  return 0;
2840 }
2841 
2842 CMD(find)
2843 {
2844  static struct option find_options[]=
2845  {
2846  {"maxdepth",required_argument,0,'d'},
2847  {0,0,0,0}
2848  };
2849  int opt;
2850  int maxdepth = -1;
2851  const char *op=args->a0();
2852 
2853  while((opt=args->getopt_long("+d:",find_options))!=EOF)
2854  {
2855  switch(opt)
2856  {
2857  case 'd':
2858  if(!isdigit((unsigned char)*optarg))
2859  {
2860  eprintf(_("%s: %s - not a number\n"),op,optarg);
2861  return 0;
2862  }
2863  maxdepth = atoi(optarg);
2864  break;
2865  case '?':
2866  eprintf(_("Usage: %s [-d #] dir\n"),op);
2867  return 0;
2868  }
2869  }
2870 
2871  if(!args->getcurr())
2872  args->Append(".");
2873  FinderJob_List *j=new class FinderJob_List(session->Clone(),args.borrow(),output.borrow());
2874  j->set_maxdepth(maxdepth);
2875  return j;
2876 }
2877 
2878 CMD(du)
2879 {
2880  enum {
2881  OPT_BLOCK_SIZE,
2882  OPT_EXCLUDE
2883  };
2884  static struct option du_options[]=
2885  {
2886  {"all",no_argument,0,'a'},
2887  /* alias: both GNU-like max-depth and lftp-like maxdepth;
2888  * only document one of them. */
2889  {"bytes",no_argument,0,'b'},
2890  {"block-size",required_argument,0,OPT_BLOCK_SIZE},
2891  {"maxdepth",required_argument,0,'d'},
2892  {"total",no_argument,0,'c'},
2893  {"max-depth",required_argument,0,'d'},
2894  {"files",no_argument,0,'F'},
2895  {"human-readable",no_argument,0,'h'},
2896  {"si",no_argument,0,'H'},
2897  {"kilobytes",required_argument,0,'k'},
2898  {"megabytes",required_argument,0,'m'},
2899  {"separate-dirs",no_argument,0,'S'},
2900  {"summarize",no_argument,0,'s'},
2901  {"exclude",required_argument,0,OPT_EXCLUDE},
2902  {0,0,0,0}
2903  };
2904  int maxdepth = -1;
2905  bool max_depth_specified = false;
2906  int blocksize = 1024;
2907  bool separate_dirs = false;
2908  bool summarize_only = false;
2909  bool print_totals=false;
2910  bool all_files=false;
2911  bool file_count=false;
2912  const char *exclude=0;
2913  int human_opts=0;
2914 
2915  exit_code=1;
2916 
2917  const char *op=args->a0();
2918 
2919  int opt;
2920  while((opt=args->getopt_long("+abcd:FhHkmsS",du_options))!=EOF)
2921  {
2922  switch(opt)
2923  {
2924  case 'a':
2925  all_files=true;
2926  break;
2927  case 'b':
2928  blocksize = 1;
2929  break;
2930  case 'c':
2931  print_totals=true;
2932  break;
2933  case 'd':
2934  if(!isdigit((unsigned char)*optarg))
2935  {
2936  eprintf(_("%s: %s - not a number\n"),op,optarg);
2937  return 0;
2938  }
2939  maxdepth = atoi(optarg);
2940  max_depth_specified = true;
2941  break;
2942  case 'F':
2943  file_count=true;
2944  break;
2945  case 'h':
2946  blocksize=1;
2948  break;
2949  case 'H':
2950  blocksize=1;
2951  human_opts |= human_autoscale|human_SI;
2952  break;
2953  case 'k': /* the default; here for completeness */
2954  blocksize = 1024;
2955  break;
2956  case 'm':
2957  blocksize = 1024*1024;
2958  break;
2959  case 's':
2960  summarize_only = true;
2961  break;
2962  case 'S':
2963  separate_dirs = true;
2964  break;
2965  case OPT_BLOCK_SIZE:
2966  blocksize = atoi(optarg);
2967  if(blocksize == 0)
2968  {
2969  eprintf(_("%s: invalid block size `%s'\n"),op,optarg);
2970  return 0;
2971  }
2972  break;
2973  case OPT_EXCLUDE:
2974  exclude=optarg;
2975  break;
2976  case '?':
2977  default:
2978  eprintf(_("Usage: %s [options] <dirs>\n"),op);
2979  return 0;
2980  }
2981  }
2982 
2983  if (summarize_only && max_depth_specified && maxdepth == 0)
2984  eprintf(_("%s: warning: summarizing is the same as using --max-depth=0\n"), op);
2985 
2986  if (summarize_only && max_depth_specified && maxdepth != 0)
2987  {
2988  eprintf(_("%s: summarizing conflicts with --max-depth=%i\n"), op, maxdepth);
2989  return 0;
2990  }
2991 
2992  /* It doesn't really make sense to show all files when doing a file count.
2993  * We might have -a in an alias as defaults, so let's just silently turn
2994  * it off. (I'm not sure if we should do this for summarize_only and
2995  * max_depth_specified, too.) */
2996  if (file_count && all_files)
2997  all_files=false;
2998  if (file_count)
2999  blocksize=1;
3000 
3001  exit_code=0;
3002 
3003  if (summarize_only)
3004  maxdepth = 0;
3005 
3006  if(!args->getcurr())
3007  args->Append(".");
3008  FinderJob_Du *j=new class FinderJob_Du(session->Clone(),args.borrow(),output.borrow());
3009  j->PrintDepth(maxdepth);
3010  j->SetBlockSize(blocksize,human_opts);
3011  if(print_totals)
3012  j->PrintTotals();
3013  if(all_files)
3014  j->AllFiles();
3015  if(separate_dirs)
3016  j->SeparateDirs();
3017  if(file_count)
3018  j->FileCount();
3019  /* if separate_dirs is on, then there's no point in traversing past
3020  * max_print_depth at all */
3021  if(separate_dirs && maxdepth != -1)
3022  j->set_maxdepth(maxdepth);
3023  if(exclude)
3024  {
3025  PatternSet *p=new PatternSet();
3026  p->Add(p->EXCLUDE,new PatternSet::Glob(exclude));
3027  j->SetExclude(p);
3028  }
3029  return j;
3030 }
3031 
3032 CMD(command)
3033 {
3034  if(args->count()<2)
3035  {
3036  eprintf(_("Usage: %s command args...\n"),args->a0());
3037  return 0;
3038  }
3039  args->delarg(0);
3040  return parent->builtin_restart();
3041 }
3042 
3043 CMD(module)
3044 {
3045  const char *op=args->a0();
3046  if(args->count()<2)
3047  {
3048  eprintf(_("Usage: %s module [args...]\n"),args->a0());
3049  eprintf(_("Try `help %s' for more information.\n"),op);
3050  return 0;
3051  }
3052  void *map=module_load(args->getarg(1),args->count()-1,args->GetV()+1);
3053  if(map==0)
3054  {
3055  eprintf("%s\n",module_error_message());
3056  return 0;
3057  }
3058  exit_code=0;
3059  return 0;
3060 }
3061 
3062 CMD(lpwd)
3063 {
3064  if(!parent->cwd)
3065  {
3066  eprintf("%s: %s\n",args->a0(),_("cannot get current directory"));
3067  return 0;
3068  }
3069  const char *name=parent->cwd->GetName();
3070  const char *buf=xstring::cat(name?name:"?","\n",NULL);
3071  Job *j=new echoJob(buf,new OutputJob(output.borrow(), args->a0()));
3072  return j;
3073 }
3074 
3075 CMD(local)
3076 {
3077  return parent->builtin_local();
3078 }
3079 
3081 {
3082  return parent->builtin_glob();
3083 }
3084 
3085 CMD(chmod)
3086 {
3088  bool recurse = false, quiet = false;
3089 
3090  static struct option chmod_options[]=
3091  {
3092  {"verbose",no_argument,0,'v'},
3093  {"changes",no_argument,0,'c'},
3094  {"recursive",no_argument,0,'R'},
3095  {"silent",no_argument,0,'f'},
3096  {"quiet",no_argument,0,'f'},
3097  {0,0,0,0}
3098  };
3099  int opt;
3100  int modeind = 0;
3101 
3102  while((opt=args->getopt_long("vcRfrwxXstugoa,+-=",chmod_options))!=EOF)
3103  {
3104  switch(opt)
3105  {
3106  case 'r': case 'w': case 'x':
3107  case 'X': case 's': case 't':
3108  case 'u': case 'g': case 'o':
3109  case 'a':
3110  case ',':
3111  case '+': case '=':
3112  modeind = optind?optind-1:1;
3113  break; /* mode string that begins with - */
3114 
3115  case 'v':
3116  verbose=ChmodJob::V_ALL;
3117  break;
3118  case 'c':
3119  verbose=ChmodJob::V_CHANGES;
3120  break;
3121  case 'R':
3122  recurse = true;
3123  break;
3124  case 'f':
3125  quiet = true;
3126  break;
3127 
3128  case '?':
3129  usage:
3130  eprintf(_("Usage: %s [OPTS] mode file...\n"),args->a0());
3131  return 0;
3132  }
3133  }
3134 
3135  if(modeind == 0)
3136  modeind = args->getindex();
3137 
3138  const char *arg = args->getarg(modeind);
3139  if(!arg)
3140  goto usage;
3141  arg = alloca_strdup(arg);
3142  args->delarg(modeind);
3143 
3144  if(!args->getcurr())
3145  goto usage;
3146 
3147  mode_change *m = mode_compile(arg);
3148  if(!m)
3149  {
3150  eprintf(_("invalid mode string: %s\n"), arg);
3151  return 0;
3152  }
3153 
3154  ChmodJob *j=new ChmodJob(session->Clone(),args.borrow());
3155  j->SetVerbosity(verbose);
3156  j->SetMode(m);
3157  if(quiet)
3158  j->BeQuiet(); /* does not affect messages from Verbosity */
3159  if(recurse)
3160  j->Recurse();
3161  return j;
3162 }
3163 
3164 CMD(queue)
3165 {
3166  return parent->builtin_queue();
3167 }
3168 
3169 CMD(get1)
3170 {
3171  static struct option get1_options[]=
3172  {
3173  {"ascii",no_argument,0,'a'},
3174  {"source-region",required_argument,0,256+'r'},
3175  {"target-position",required_argument,0,256+'R'},
3176  {"continue",no_argument,0,'c'},
3177  {"output",required_argument,0,'o'},
3178  {"remove-source-later",no_argument,0,'E'},
3179  {"remove-target-first",no_argument,0,'e'},
3180  {0,0,0,0}
3181  };
3182  int opt;
3183  const char *src=0;
3184  const char *dst=0;
3185  bool cont=false;
3186  bool ascii=false;
3187  long long source_region_begin=0,source_region_end=FILE_END;
3188  long long target_region_begin=0,target_region_end=FILE_END;
3189  int n,p;
3190 
3191  while((opt=args->getopt_long("arco:",get1_options))!=EOF)
3192  {
3193  switch(opt)
3194  {
3195  case 'c':
3196  cont=true;
3197  break;
3198  case 'a':
3199  ascii=true;
3200  break;
3201  case 'o':
3202  dst=optarg;
3203  break;
3204  case 256+'r':
3205  source_region_end=FILE_END;
3206  n=sscanf(optarg,"%lld%n-%lld",&source_region_begin,&p,&source_region_end);
3207  if(n<1 || (n==1 && (optarg[p] && (optarg[p]!='-' || optarg[p+1]))))
3208  {
3209  eprintf("%s\n",_("Invalid range format. Format is min-max, e.g. 10-20."));
3210  goto usage;
3211  }
3212  break;
3213  case 256+'R':
3214  target_region_end=FILE_END;
3215  n=sscanf(optarg,"%lld",&target_region_begin);
3216  if(n<1)
3217  {
3218  eprintf("%s\n",_("Invalid range format. Format is min-max, e.g. 10-20."));
3219  goto usage;
3220  }
3221  break;
3222  case '?':
3223  usage:
3224  eprintf(_("Usage: %s [OPTS] file\n"),args->a0());
3225  return 0;
3226  }
3227  }
3228  src=args->getcurr();
3229  if(src==0)
3230  goto usage;
3231  if(args->getnext()!=0)
3232  goto usage;
3233 
3234  bool auto_rename=false;
3235  if(dst==0 || dst[0]==0)
3236  {
3237  dst=basename_ptr(src);
3238  auto_rename=true;
3239  }
3240  else
3241  {
3242  if(last_char(dst)=='/' && basename_ptr(dst)[0]!='/')
3243  {
3244  const char *bn=basename_ptr(src);
3245  if(bn[0]!='/')
3246  {
3247  dst=xstring::get_tmp(dst).append(bn);
3248  auto_rename=true;
3249  }
3250  }
3251  }
3252 
3253  ParsedURL dst_url(dst,true);
3254 
3255  if(dst_url.proto==0)
3256  {
3257  dst=expand_home_relative(dst);
3258  // check if dst is a directory.
3259  struct stat st;
3260  if(stat(dst,&st)!=-1)
3261  {
3262  if(S_ISDIR(st.st_mode))
3263  {
3264  const char *slash=strrchr(src,'/');
3265  if(slash)
3266  slash++;
3267  else
3268  slash=src;
3269  dst=xstring::cat(dst,"/",slash,NULL);
3270  }
3271  }
3272  }
3273 
3274  FileCopyPeer *src_peer=0;
3275  FileCopyPeer *dst_peer=0;
3276 
3277  src_peer=FileCopyPeerFA::New(session->Clone(),src,FA::RETRIEVE);
3278  if(!cont && (source_region_begin>0 || source_region_end!=FILE_END))
3279  src_peer->SetRange(source_region_begin,source_region_end);
3280 
3281  if(dst_url.proto==0)
3282  dst_peer=FileCopyPeerFDStream::NewPut(dst,cont||target_region_begin>0);
3283  else
3284  dst_peer=new FileCopyPeerFA(&dst_url,FA::STORE);
3285  dst_peer->AutoRename(auto_rename && ResMgr::QueryBool("xfer:auto-rename",0));
3286  if(!cont && (target_region_begin>0 || target_region_end!=FILE_END))
3287  dst_peer->SetRange(target_region_begin,target_region_end);
3288 
3289  FileCopy *c=FileCopy::New(src_peer,dst_peer,cont);
3290 
3291  if(ascii)
3292  c->Ascii();
3293 
3294  return new CopyJob(c,src,args->a0());
3295 }
3296 
3297 CMD(slot)
3298 {
3299  const char *n=args->getarg(1);
3300  if(n)
3301  {
3302  parent->ChangeSlot(n);
3303  exit_code=0;
3304  return 0;
3305  }
3306  else
3307  {
3309  Job *j=new echoJob(slots,new OutputJob(output.borrow(),args->a0()));
3310  return j;
3311  }
3312 }
3313 
3314 CMD(tasks)
3315 {
3316  printf("task_count=%d\n",SMTask::TaskCount());
3318  exit_code=0;
3319  return 0;
3320 }
3321 
3322 CMD(eval)
3323 {
3324  int opt;
3325  const char *fmt=0;
3326  const char *op=args->getarg(0);
3327  while((opt=args->getopt("+f:"))!=EOF)
3328  {
3329  switch(opt)
3330  {
3331  case 'f':
3332  fmt=optarg;
3333  break;
3334  default:
3335  eprintf(_("Try `%s --help' for more information\n"),op);
3336  return 0;
3337  }
3338  }
3339  int base=optind;
3340  xstring cmd;
3341  if(!fmt)
3342  cmd.set_allocated(args->Combine(optind));
3343  else
3344  {
3345  while(*fmt)
3346  {
3347  if(*fmt=='\\' && (fmt[1]=='$' || fmt[1]=='\\'))
3348  {
3349  cmd.append(fmt[1]);
3350  fmt+=2;
3351  continue;
3352  }
3353  if(*fmt=='$' && fmt[1]>='0' && fmt[1]<='9')
3354  {
3355  int n=fmt[1]-'0';
3356  if(n+base<args->count())
3357  cmd.append(args->getarg(n+base));
3358  fmt+=2;
3359  continue;
3360  }
3361  if(*fmt=='$' && fmt[1]=='@')
3362  {
3363  xstring_ca c(args->CombineQuoted(base));
3364  cmd.append(c);
3365  fmt+=2;
3366  continue;
3367  }
3368  if(*fmt=='$' && fmt[1]=='$')
3369  {
3370  cmd.appendf("%d",(int)getpid());
3371  fmt+=2;
3372  continue;
3373  }
3374  cmd.append(*fmt++);
3375  }
3376  }
3377  cmd.append(";\n\n");
3378  parent->PrependCmd(cmd);
3379  exit_code=parent->prev_exit_code;
3380  return 0;
3381 }