/* * lftp - file transfer program * * Copyright (c) 1996-2013 by Alexander V. Lukyanov (lav@yars.free.net) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include "modconfig.h" #include "trio.h" #include #include // for mkdir() #include #include #include #include #include #include #include "xstring.h" #include "xmalloc.h" #include "alias.h" #include "CmdExec.h" #include "SignalHook.h" #include "GetPass.h" #include "History.h" #include "log.h" #include "DummyProto.h" #include "ResMgr.h" #include "LsCache.h" #include "IdNameCache.h" #include "LocalDir.h" #include "ConnectionSlot.h" #include "misc.h" #include "ArgV.h" #include "attach.h" #include "configmake.h" #include "lftp_rl.h" #include "complete.h" CDECL_BEGIN #include CDECL_END #define top_exec CmdExec::top void hook_signals() { SignalHook::DoCount(SIGHUP); SignalHook::Ignore(SIGTTOU); ProcWait::Signal(true); } ResDecl res_save_cwd_history ("cmd:save-cwd-history","yes",ResMgr::BoolValidate,ResMgr::NoClosure); ResDecl res_save_rl_history ("cmd:save-rl-history","yes",ResMgr::BoolValidate,ResMgr::NoClosure); ResDecl res_stifle_rl_history ("cmd:stifle-rl-history","500",ResMgr::UNumberValidate,ResMgr::NoClosure); class ReadlineFeeder : public CmdFeeder, private ResClient { bool tty:1; bool ctty:1; bool add_newline:1; int eof_count; xstring_c cmd_buf; xstring_c for_history; static bool readline_inited; void readline_init() { if(readline_inited) return; readline_inited=true; lftp_readline_init(); lftp_rl_read_history(); if(for_history) { lftp_add_history_nodups(for_history); for_history.set(0); } Reconfig(0); } public: ReadlineFeeder(const ArgV *args) { tty=isatty(0); ctty=(tcgetpgrp(0)!=(pid_t)-1); add_newline=false; eof_count=0; if(args && args->count()>1) args->CombineQuotedTo(for_history); } virtual ~ReadlineFeeder() { if(readline_inited) { if(res_save_cwd_history.QueryBool(0)) cwd_history.Save(); if(res_save_rl_history.QueryBool(0)) lftp_rl_write_history(); } } bool IsInteractive() const { return tty; } bool RealEOF() { return !tty || eof_count>3; } const char *NextCmd(class CmdExec *exec,const char *prompt) { if(add_newline) { add_newline=false; return "\n"; } ::completion_shell=exec; ::remote_completion=exec->remote_completion; if(tty) { readline_init(); if(ctty) // controlling terminal { if(!in_foreground_pgrp()) { // looks like we are in background. Can't read from tty exec->Timeout(500); return ""; } } SignalHook::ResetCount(SIGINT); cmd_buf.set_allocated(lftp_readline(prompt)); xmalloc_register_block(cmd_buf.get_non_const()); if(cmd_buf && *cmd_buf) { if(exec->csh_history) { char *history_value0=0; int expanded = lftp_history_expand (cmd_buf, &history_value0); if (expanded) { if(history_value0) xmalloc_register_block(history_value0); xstring_ca history_value(history_value0); if (expanded < 0) fprintf (stderr, "%s\n", history_value.get()); /* If there was an error, return nothing. */ if (expanded < 0 || expanded == 2) /* 2 == print only */ { exec->Timeout(0); // and retry immediately return ""; } cmd_buf.move_here(history_value); } } lftp_add_history_nodups(cmd_buf); } else if(cmd_buf==0 && exec->interactive) puts("exit"); if(cmd_buf==0) eof_count++; else eof_count=0; } else // not a tty { if(exec->interactive) { while(*prompt) { char ch=*prompt++; if(ch!=1 && ch!=2) putchar(ch); } fflush(stdout); } cmd_buf.set_allocated(readline_from_file(0)); } ::completion_shell=0; if(cmd_buf && last_char(cmd_buf)!='\n') { exec->Timeout(0); add_newline=true; } return cmd_buf; } void clear() { if(!tty) return; lftp_rl_clear(); } void Reconfig(const char *) { lftp_rl_history_stifle(res_stifle_rl_history.Query(0)); } }; bool ReadlineFeeder::readline_inited; #define args (parent->args) #define exit_code (parent->exit_code) #define output (parent->output) #define session (parent->session) #define eprintf parent->eprintf CMD(history) { enum { READ, WRITE, CLEAR, LIST } mode = LIST; const char *fn = NULL; static struct option history_options[]= { {"read",required_argument,0,'r'}, {"write",required_argument,0,'w'}, {"clear",no_argument,0,'c'}, {"list",required_argument,0,'l'}, {0,0,0,0} }; exit_code=0; int opt; while((opt=args->getopt_long("+r:w:cl",history_options,0))!=EOF) { switch(opt) { case 'r': mode = READ; fn = optarg; break; case 'w': mode = WRITE; fn = optarg; break; case 'c': mode = CLEAR; break; case 'l': mode = LIST; break; case '?': eprintf(_("Try `help %s' for more information.\n"),args->a0()); return 0; } } int cnt = 16; if(const char *arg = args->getcurr()) { if(!strcasecmp(arg, "all")) cnt = -1; else if(isdigit((unsigned char)arg[0])) cnt = atoi(arg); else { eprintf(_("%s: %s - not a number\n"), args->a0(), args->getcurr()); exit_code=1; return 0; } } switch(mode) { case READ: if(int err = lftp_history_read(fn)) { eprintf("%s: %s: %s\n", args->a0(), fn, strerror(err)); exit_code=1; } break; case WRITE: if(int err = lftp_history_write(fn)) { eprintf("%s: %s: %s\n", args->a0(), fn, strerror(err)); exit_code=1; } break; case LIST: lftp_history_list(cnt); break; case CLEAR: lftp_history_clear(); break; } return 0; } CMD(attach) { const char *pid_s=args->getarg(1); if(!pid_s) { xstring& path=AcceptTermFD::get_sock_path(1); path.rtrim('1'); path.append('*'); glob_t g; glob(path, 0, NULL, &g); for(size_t i=0; ia0(),sock_path); if(unlink(sock_path)==-1) eprintf("%s: unlink(%s): %s\n",args->a0(),sock_path,strerror(errno)); } pid_s=0; continue; } pid_s=alloca_strdup(pid_s); break; } globfree(&g); if(!pid_s) { eprintf("%s: no backgrounded lftp processes found.\n",args->a0()); return 0; } } int pid=atoi(pid_s); SMTaskRef term_sender(new SendTermFD(pid)); while(!term_sender->Done()) { SMTask::Schedule(); SMTask::Block(); } exit_code=0; if(term_sender->Failed()) { eprintf("%s\n",term_sender->ErrorText()); exit_code=1; } return 0; } #undef args #undef exit_code #undef output #undef session #undef eprintf static void sig_term(int sig) { printf(_("[%u] Terminated by signal %d. %s\n"),(unsigned)getpid(),sig,SMTask::now.IsoDateTime()); if(top_exec) { top_exec->KillAll(); alarm(30); while(Job::NumberOfJobs()>0) { SMTask::Schedule(); SMTask::Block(); } } exit(1); } static void detach() { SignalHook::Ignore(SIGINT); SignalHook::Ignore(SIGHUP); SignalHook::Ignore(SIGTSTP); const char *home=get_lftp_data_dir(); if(home) { xstring& log=xstring::get_tmp(home); if(access(log,F_OK)==-1) log.append("_log"); else log.append("/log"); int fd=open(log,O_WRONLY|O_APPEND|O_CREAT,0600); if(fd>=0) { dup2(fd,1); // stdout dup2(fd,2); // stderr if(fd!=1 && fd!=2) close(fd); } const char *c="debug"; ResMgr::Set("log:show-pid",c,"yes"); ResMgr::Set("log:show-time",c,"yes"); ResMgr::Set("log:show-ctx",c,"yes"); } close(0); // close stdin. open("/dev/null",O_RDONLY); // reopen it, just in case. #ifdef HAVE_SETSID setsid(); // start a new session. #endif SignalHook::Handle(SIGTERM,sig_term); } static void move_to_background() { // notify jobs Job::lftpMovesToBackground_ToAll(); // wait they do something, but no more than 1 sec. SMTask::RollAll(TimeInterval(1,0)); // if all jobs terminated, don't really move to bg. if(Job::NumberOfJobs()==0) return; top_exec->AtBackground(); top_exec->WaitDone(); if(Job::NumberOfJobs()==0) return; fflush(stdout); fflush(stderr); pid_t pid=fork(); switch(pid) { case(0): // child { pid=getpid(); detach(); printf(_("[%u] Started. %s\n"),(unsigned)pid,SMTask::now.IsoDateTime()); SMTaskRef term_acceptor(new AcceptTermFD()); for(;;) { SMTask::Schedule(); if(Job::NumberOfJobs()==0) break; SMTask::Block(); if(term_acceptor->Accepted()) { hook_signals(); top_exec->SetInteractive(); top_exec->SetStatusLine(new StatusLine(1)); top_exec->SetCmdFeeder(new ReadlineFeeder(0)); for(;;) { SMTask::Schedule(); if(top_exec->Done() || term_acceptor->Detached()) { if(Job::NumberOfJobs()>0) { printf(_("[%u] Detaching from the terminal to complete transfers...\n"),(unsigned)pid); } else if(top_exec->Done()) { printf(_("[%u] Exiting and detaching from the terminal.\n"),(unsigned)pid); } fflush(stdout); term_acceptor->Detach(); detach(); printf(_("[%u] Detached from terminal. %s\n"),(unsigned)pid,SMTask::now.IsoDateTime()); break; } SMTask::Block(); } } } top_exec->AtExitBg(); top_exec->AtTerminate(); top_exec->WaitDone(); printf(_("[%u] Finished. %s\n"),(unsigned)pid,SMTask::now.IsoDateTime()); break; } default: // parent printf(_("[%u] Moving to background to complete transfers...\n"), (unsigned)pid); fflush(stdout); _exit(0); case(-1): perror("fork()"); } } int lftp_slot(int count,int key) { if(!top_exec) return 0; char slot[2]; slot[0]=key; slot[1]=0; top_exec->ChangeSlot(slot); lftp_rl_set_prompt(top_exec->MakePrompt()); return 0; } void source_if_exist(const char *rc) { if(access(rc,R_OK)!=-1) { top_exec->FeedCmd("source "); top_exec->FeedCmd(rc); top_exec->FeedCmd("\n"); } } static void tty_clear() { if(top_exec) top_exec->pre_stdout(); } // look for the option, remove it and return true if found static bool pick_option(int& argc,char **argv,const char *option) { for(int i=1; i Write history to file.\n" " -r Read history from file; appends to current history.\n" " -c Clear the history.\n" " -l List the history (default).\n" "Optional argument cnt specifies the number of history lines to list,\n" "or \"all\" to list all entries.\n")); CmdExec::RegisterCommand("attach",cmd_attach,"attach [PID]", N_("Attach the terminal to specified backgrounded lftp process.\n")); top_exec=new CmdExec(0,0); hook_signals(); top_exec->SetStatusLine(new StatusLine(1)); Log::global=new Log("debug"); Log::global->SetCB(tty_clear); source_if_exist(SYSCONFDIR"/lftp.conf"); if(!pick_option(argc,argv,"--norc")) { const char *home=getenv("HOME"); if(home) source_if_exist(dir_file(home,".lftprc")); home=get_lftp_config_dir(); if(home) source_if_exist(dir_file(home,"rc")); } top_exec->WaitDone(); top_exec->SetTopLevel(); top_exec->Fg(); Ref args(new ArgV(argc,argv)); args->setarg(0,"lftp"); lftp_feeder=new ReadlineFeeder(args); top_exec->ExecParsed(args.borrow()); top_exec->WaitDone(); int exit_code=top_exec->ExitCode(); top_exec->AtExit(); top_exec->WaitDone(); if(Job::NumberOfJobs()>0) { top_exec->SetInteractive(false); move_to_background(); } else { top_exec->AtExitFg(); top_exec->AtTerminate(); top_exec->WaitDone(); } top_exec->KillAll(); top_exec=0; Job::Cleanup(); ConnectionSlot::Cleanup(); SessionPool::ClearAll(); FileAccess::ClassCleanup(); ProcWait::DeleteAll(); IdNameCacheCleanup(); SignalHook::Cleanup(); Log::Cleanup(); SMTask::Cleanup(); return exit_code; }