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 <sys/types.h>
26 #include <sys/stat.h> // for mkdir()
27 #include <stdlib.h>
28 #include <errno.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <locale.h>
32 #include <ctype.h>
33
34 #include "xstring.h"
35 #include "xmalloc.h"
36 #include "alias.h"
37 #include "CmdExec.h"
38 #include "SignalHook.h"
39 #include "GetPass.h"
40 #include "History.h"
41 #include "log.h"
42 #include "DummyProto.h"
43 #include "ResMgr.h"
44 #include "LsCache.h"
45 #include "IdNameCache.h"
46 #include "LocalDir.h"
47 #include "ConnectionSlot.h"
48 #include "misc.h"
49 #include "ArgV.h"
50 #include "attach.h"
51
52 #include "configmake.h"
53
54 #include "lftp_rl.h"
55 #include "complete.h"
56
57 CDECL_BEGIN
58 #include <glob.h>
59 CDECL_END
60
61 #define top_exec CmdExec::top
62
hook_signals()63 void hook_signals()
64 {
65 SignalHook::DoCount(SIGHUP);
66 SignalHook::Ignore(SIGTTOU);
67 ProcWait::Signal(true);
68 }
69
70 ResDecl res_save_cwd_history
71 ("cmd:save-cwd-history","yes",ResMgr::BoolValidate,ResMgr::NoClosure);
72 ResDecl res_save_rl_history
73 ("cmd:save-rl-history","yes",ResMgr::BoolValidate,ResMgr::NoClosure);
74 ResDecl res_stifle_rl_history
75 ("cmd:stifle-rl-history","500",ResMgr::UNumberValidate,ResMgr::NoClosure);
76
77 class ReadlineFeeder : public CmdFeeder, private ResClient
78 {
79 bool tty:1;
80 bool ctty:1;
81 bool add_newline:1;
82 int eof_count;
83 xstring_c cmd_buf;
84 xstring_c for_history;
85
86 static bool readline_inited;
readline_init()87 void readline_init()
88 {
89 if(readline_inited)
90 return;
91 readline_inited=true;
92 lftp_readline_init();
93 lftp_rl_read_history();
94 if(for_history)
95 {
96 lftp_add_history_nodups(for_history);
97 for_history.set(0);
98 }
99 Reconfig(0);
100 }
101
102 public:
ReadlineFeeder(const ArgV * args)103 ReadlineFeeder(const ArgV *args)
104 {
105 tty=isatty(0);
106 ctty=(tcgetpgrp(0)!=(pid_t)-1);
107 add_newline=false;
108 eof_count=0;
109 if(args && args->count()>1)
110 args->CombineQuotedTo(for_history);
111 }
~ReadlineFeeder()112 virtual ~ReadlineFeeder()
113 {
114 if(readline_inited)
115 {
116 if(res_save_cwd_history.QueryBool(0))
117 cwd_history.Save();
118 if(res_save_rl_history.QueryBool(0))
119 lftp_rl_write_history();
120 }
121 }
IsInteractive() const122 bool IsInteractive() const
123 {
124 return tty;
125 }
RealEOF()126 bool RealEOF()
127 {
128 return !tty || eof_count>3;
129 }
130
NextCmd(class CmdExec * exec,const char * prompt)131 const char *NextCmd(class CmdExec *exec,const char *prompt)
132 {
133 if(add_newline)
134 {
135 add_newline=false;
136 return "\n";
137 }
138
139 ::completion_shell=exec;
140 ::remote_completion=exec->remote_completion;
141
142 if(tty)
143 {
144 readline_init();
145
146 if(ctty) // controlling terminal
147 {
148 if(!in_foreground_pgrp())
149 {
150 // looks like we are in background. Can't read from tty
151 exec->Timeout(500);
152 return "";
153 }
154 }
155
156 SignalHook::ResetCount(SIGINT);
157 cmd_buf.set_allocated(lftp_readline(prompt));
158 xmalloc_register_block(cmd_buf.get_non_const());
159 if(cmd_buf && *cmd_buf)
160 {
161 if(exec->csh_history)
162 {
163 char *history_value0=0;
164 int expanded = lftp_history_expand (cmd_buf, &history_value0);
165 if (expanded)
166 {
167 if(history_value0)
168 xmalloc_register_block(history_value0);
169 xstring_ca history_value(history_value0);
170
171 if (expanded < 0)
172 fprintf (stderr, "%s\n", history_value.get());
173
174 /* If there was an error, return nothing. */
175 if (expanded < 0 || expanded == 2) /* 2 == print only */
176 {
177 exec->Timeout(0); // and retry immediately
178 return "";
179 }
180
181 cmd_buf.move_here(history_value);
182 }
183 }
184 lftp_add_history_nodups(cmd_buf);
185 }
186 else if(cmd_buf==0 && exec->interactive)
187 puts("exit");
188
189 if(cmd_buf==0)
190 eof_count++;
191 else
192 eof_count=0;
193 }
194 else // not a tty
195 {
196 if(exec->interactive)
197 {
198 while(*prompt)
199 {
200 char ch=*prompt++;
201 if(ch!=1 && ch!=2)
202 putchar(ch);
203 }
204 fflush(stdout);
205 }
206 cmd_buf.set_allocated(readline_from_file(0));
207 }
208
209 ::completion_shell=0;
210
211 if(cmd_buf && last_char(cmd_buf)!='\n')
212 {
213 exec->Timeout(0);
214 add_newline=true;
215 }
216 return cmd_buf;
217 }
clear()218 void clear()
219 {
220 if(!tty)
221 return;
222 lftp_rl_clear();
223 }
Reconfig(const char *)224 void Reconfig(const char *) {
225 lftp_rl_history_stifle(res_stifle_rl_history.Query(0));
226 }
227 };
228 bool ReadlineFeeder::readline_inited;
229
230 #define args (parent->args)
231 #define exit_code (parent->exit_code)
232 #define output (parent->output)
233 #define session (parent->session)
234 #define eprintf parent->eprintf
CMD(history)235 CMD(history)
236 {
237 enum { READ, WRITE, CLEAR, LIST } mode = LIST;
238 const char *fn = NULL;
239 static struct option history_options[]=
240 {
241 {"read",required_argument,0,'r'},
242 {"write",required_argument,0,'w'},
243 {"clear",no_argument,0,'c'},
244 {"list",required_argument,0,'l'},
245 {0,0,0,0}
246 };
247
248 exit_code=0;
249 int opt;
250 while((opt=args->getopt_long("+r:w:cl",history_options,0))!=EOF) {
251 switch(opt) {
252 case 'r':
253 mode = READ;
254 fn = optarg;
255 break;
256 case 'w':
257 mode = WRITE;
258 fn = optarg;
259 break;
260 case 'c':
261 mode = CLEAR;
262 break;
263 case 'l':
264 mode = LIST;
265 break;
266 case '?':
267 eprintf(_("Try `help %s' for more information.\n"),args->a0());
268 return 0;
269 }
270 }
271
272 int cnt = 16;
273 if(const char *arg = args->getcurr()) {
274 if(!strcasecmp(arg, "all"))
275 cnt = -1;
276 else if(isdigit((unsigned char)arg[0]))
277 cnt = atoi(arg);
278 else {
279 eprintf(_("%s: %s - not a number\n"), args->a0(), args->getcurr());
280 exit_code=1;
281 return 0;
282 }
283 }
284
285 switch(mode) {
286 case READ:
287 if(int err = lftp_history_read(fn)) {
288 eprintf("%s: %s: %s\n", args->a0(), fn, strerror(err));
289 exit_code=1;
290 }
291 break;
292
293 case WRITE:
294 if(int err = lftp_history_write(fn)) {
295 eprintf("%s: %s: %s\n", args->a0(), fn, strerror(err));
296 exit_code=1;
297 }
298 break;
299
300 case LIST:
301 lftp_history_list(cnt);
302 break;
303 case CLEAR:
304 lftp_history_clear();
305 break;
306 }
307
308 return 0;
309 }
CMD(attach)310 CMD(attach)
311 {
312 const char *pid_s=args->getarg(1);
313 if(!pid_s) {
314 xstring& path=AcceptTermFD::get_sock_path(1);
315 path.rtrim('1');
316 path.append('*');
317 glob_t g;
318 glob(path, 0, NULL, &g);
319 for(size_t i=0; i<g.gl_pathc; i++) {
320 const char *sock_path=g.gl_pathv[i];
321 pid_s=strrchr(sock_path,'-');
322 if(!pid_s)
323 continue;
324 pid_s++;
325 int p=atoi(pid_s);
326 if(p<=1) {
327 pid_s=0;
328 continue;
329 }
330 if(kill(p,0)==-1) {
331 if(errno==ESRCH) {
332 eprintf("%s: removing stale socket `%s'.\n",args->a0(),sock_path);
333 if(unlink(sock_path)==-1)
334 eprintf("%s: unlink(%s): %s\n",args->a0(),sock_path,strerror(errno));
335 }
336 pid_s=0;
337 continue;
338 }
339 pid_s=alloca_strdup(pid_s);
340 break;
341 }
342 globfree(&g);
343 if(!pid_s) {
344 eprintf("%s: no backgrounded lftp processes found.\n",args->a0());
345 return 0;
346 }
347 }
348 int pid=atoi(pid_s);
349 SMTaskRef<SendTermFD> term_sender(new SendTermFD(pid));
350 while(!term_sender->Done()) {
351 SMTask::Schedule();
352 SMTask::Block();
353 }
354 exit_code=0;
355 if(term_sender->Failed()) {
356 eprintf("%s\n",term_sender->ErrorText());
357 exit_code=1;
358 }
359 return 0;
360 }
361 #undef args
362 #undef exit_code
363 #undef output
364 #undef session
365 #undef eprintf
366
367
sig_term(int sig)368 static void sig_term(int sig)
369 {
370 printf(_("[%u] Terminated by signal %d. %s\n"),(unsigned)getpid(),sig,SMTask::now.IsoDateTime());
371 if(top_exec) {
372 top_exec->KillAll();
373 alarm(30);
374 while(Job::NumberOfJobs()>0) {
375 SMTask::Schedule();
376 SMTask::Block();
377 }
378 }
379 exit(1);
380 }
381
detach()382 static void detach()
383 {
384 SignalHook::Ignore(SIGINT);
385 SignalHook::Ignore(SIGHUP);
386 SignalHook::Ignore(SIGTSTP);
387
388 const char *home=get_lftp_data_dir();
389 if(home)
390 {
391 xstring& log=xstring::get_tmp(home);
392 if(access(log,F_OK)==-1)
393 log.append("_log");
394 else
395 log.append("/log");
396
397 int fd=open(log,O_WRONLY|O_APPEND|O_CREAT,0600);
398 if(fd>=0)
399 {
400 dup2(fd,1); // stdout
401 dup2(fd,2); // stderr
402 if(fd!=1 && fd!=2)
403 close(fd);
404 }
405 const char *c="debug";
406 ResMgr::Set("log:show-pid",c,"yes");
407 ResMgr::Set("log:show-time",c,"yes");
408 ResMgr::Set("log:show-ctx",c,"yes");
409 }
410 close(0); // close stdin.
411 open("/dev/null",O_RDONLY); // reopen it, just in case.
412
413 #ifdef HAVE_SETSID
414 setsid(); // start a new session.
415 #endif
416
417 SignalHook::Handle(SIGTERM,sig_term);
418 }
419
move_to_background()420 static void move_to_background()
421 {
422 // notify jobs
423 Job::lftpMovesToBackground_ToAll();
424 // wait they do something, but no more than 1 sec.
425 SMTask::RollAll(TimeInterval(1,0));
426 // if all jobs terminated, don't really move to bg.
427 if(Job::NumberOfJobs()==0)
428 return;
429
430 top_exec->AtBackground();
431 top_exec->WaitDone();
432 if(Job::NumberOfJobs()==0)
433 return;
434
435 fflush(stdout);
436 fflush(stderr);
437
438 pid_t pid=fork();
439 switch(pid)
440 {
441 case(0): // child
442 {
443 pid=getpid();
444 detach();
445 printf(_("[%u] Started. %s\n"),(unsigned)pid,SMTask::now.IsoDateTime());
446 SMTaskRef<AcceptTermFD> term_acceptor(new AcceptTermFD());
447 for(;;)
448 {
449 SMTask::Schedule();
450 if(Job::NumberOfJobs()==0)
451 break;
452 SMTask::Block();
453 if(term_acceptor->Accepted()) {
454 hook_signals();
455 top_exec->SetInteractive();
456 top_exec->SetStatusLine(new StatusLine(1));
457 top_exec->SetCmdFeeder(new ReadlineFeeder(0));
458 for(;;)
459 {
460 SMTask::Schedule();
461 if(top_exec->Done() || term_acceptor->Detached()) {
462 if(Job::NumberOfJobs()>0) {
463 printf(_("[%u] Detaching from the terminal to complete transfers...\n"),(unsigned)pid);
464 } else if(top_exec->Done()) {
465 printf(_("[%u] Exiting and detaching from the terminal.\n"),(unsigned)pid);
466 }
467 fflush(stdout);
468 term_acceptor->Detach();
469 detach();
470 printf(_("[%u] Detached from terminal. %s\n"),(unsigned)pid,SMTask::now.IsoDateTime());
471 break;
472 }
473 SMTask::Block();
474 }
475 }
476 }
477 top_exec->AtExitBg();
478 top_exec->AtTerminate();
479 top_exec->WaitDone();
480 printf(_("[%u] Finished. %s\n"),(unsigned)pid,SMTask::now.IsoDateTime());
481 break;
482 }
483 default: // parent
484 printf(_("[%u] Moving to background to complete transfers...\n"),
485 (unsigned)pid);
486 fflush(stdout);
487 _exit(0);
488 case(-1):
489 perror("fork()");
490 }
491 }
492
lftp_slot(int count,int key)493 int lftp_slot(int count,int key)
494 {
495 if(!top_exec)
496 return 0;
497 char slot[2];
498 slot[0]=key;
499 slot[1]=0;
500 top_exec->ChangeSlot(slot);
501 lftp_rl_set_prompt(top_exec->MakePrompt());
502 return 0;
503 }
504
source_if_exist(const char * rc)505 void source_if_exist(const char *rc)
506 {
507 if(access(rc,R_OK)!=-1)
508 {
509 top_exec->FeedCmd("source ");
510 top_exec->FeedCmd(rc);
511 top_exec->FeedCmd("\n");
512 }
513 }
514
tty_clear()515 static void tty_clear()
516 {
517 if(top_exec)
518 top_exec->pre_stdout();
519 }
520
521 // look for the option, remove it and return true if found
pick_option(int & argc,char ** argv,const char * option)522 static bool pick_option(int& argc,char **argv,const char *option)
523 {
524 for(int i=1; i<argc; i++) {
525 if(!strcmp(argv[i],option)) {
526 // remove the option, move trailing NULL too.
527 memmove(argv+i,argv+i+1,(argc-i)*sizeof(*argv));
528 argc--;
529 return true;
530 }
531 if(!strcmp(argv[i],"--")) // end of options
532 break;
533 }
534 return false;
535 }
536
537 char *program_name;
538
main(int argc,char ** argv)539 int main(int argc,char **argv)
540 {
541 program_name=argv[0];
542
543 #ifdef SOCKS4
544 SOCKSinit(program_name);
545 #endif
546
547 setlocale (LC_ALL, "");
548 setlocale (LC_NUMERIC, "C");
549 bindtextdomain (PACKAGE, LOCALEDIR);
550 textdomain (PACKAGE);
551
552 CmdExec::RegisterCommand("history",cmd_history,
553 N_("history -w file|-r file|-c|-l [cnt]"),
554 N_(" -w <file> Write history to file.\n"
555 " -r <file> Read history from file; appends to current history.\n"
556 " -c Clear the history.\n"
557 " -l List the history (default).\n"
558 "Optional argument cnt specifies the number of history lines to list,\n"
559 "or \"all\" to list all entries.\n"));
560 CmdExec::RegisterCommand("attach",cmd_attach,"attach [PID]",
561 N_("Attach the terminal to specified backgrounded lftp process.\n"));
562
563 top_exec=new CmdExec(0,0);
564 hook_signals();
565 top_exec->SetStatusLine(new StatusLine(1));
566 Log::global=new Log("debug");
567 Log::global->SetCB(tty_clear);
568
569 source_if_exist(SYSCONFDIR"/lftp.conf");
570
571 if(!pick_option(argc,argv,"--norc")) {
572 const char *home=getenv("HOME");
573 if(home)
574 source_if_exist(dir_file(home,".lftprc"));
575 home=get_lftp_config_dir();
576 if(home)
577 source_if_exist(dir_file(home,"rc"));
578 }
579
580 top_exec->WaitDone();
581 top_exec->SetTopLevel();
582 top_exec->Fg();
583
584 Ref<ArgV> args(new ArgV(argc,argv));
585 args->setarg(0,"lftp");
586
587 lftp_feeder=new ReadlineFeeder(args);
588
589 top_exec->ExecParsed(args.borrow());
590 top_exec->WaitDone();
591 int exit_code=top_exec->ExitCode();
592
593 top_exec->AtExit();
594 top_exec->WaitDone();
595
596 if(Job::NumberOfJobs()>0)
597 {
598 top_exec->SetInteractive(false);
599 move_to_background();
600 }
601 else
602 {
603 top_exec->AtExitFg();
604 top_exec->AtTerminate();
605 top_exec->WaitDone();
606 }
607 top_exec->KillAll();
608 top_exec=0;
609
610 Job::Cleanup();
611 ConnectionSlot::Cleanup();
612 SessionPool::ClearAll();
613 FileAccess::ClassCleanup();
614 ProcWait::DeleteAll();
615 IdNameCacheCleanup();
616 SignalHook::Cleanup();
617 Log::Cleanup();
618 SMTask::Cleanup();
619 return exit_code;
620 }
621