/*
* lftp - file transfer program
*
* Copyright (c) 1996-2017 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
#include
#include
#include
#include
#include "CmdExec.h"
#include "xstring.h"
#include "SignalHook.h"
#include "alias.h"
#include "misc.h"
#include "ResMgr.h"
#include "module.h"
#include "url.h"
#include "QueueFeeder.h"
#include "LocalDir.h"
#include "ConnectionSlot.h"
#include "DummyProto.h"
#define RL_PROMPT_START_IGNORE '\001'
#define RL_PROMPT_END_IGNORE '\002'
#define super SessionJob
#define waiting_num waiting.count()
static ResType lftp_cmd_vars[] = {
{"cmd:default-protocol", "ftp", 0,0},
{"cmd:long-running", "30", ResMgr::UNumberValidate,0},
{"cmd:remote-completion", "on", ResMgr::BoolValidate,0},
{"cmd:prompt", "lftp \\S\\? \\u\\@\\h:\\w> ",0,0},
{"cmd:default-title", "lftp \\h:\\w",0,0},
{"cmd:ls-default", "", 0,0},
{"cmd:csh-history", "off", ResMgr::BoolValidate,ResMgr::NoClosure},
{"cmd:verify-path", "yes", ResMgr::BoolValidate,0},
{"cmd:verify-path-cached", "no", ResMgr::BoolValidate,0},
{"cmd:verify-host", "yes", ResMgr::BoolValidate,0},
{"cmd:at-exit", "", 0,0},
{"cmd:at-exit-bg", "", 0,0},
{"cmd:at-exit-fg", "", 0,0},
{"cmd:at-background", "", 0,0},
{"cmd:at-terminate", "", 0,0},
{"cmd:at-finish", "", 0,0},
{"cmd:at-queue-finish", "", 0,0},
{"cmd:fail-exit", "no", ResMgr::BoolValidate,ResMgr::NoClosure},
{"cmd:verbose", "no", ResMgr::BoolValidate,ResMgr::NoClosure},
{"cmd:interactive", "auto", ResMgr::TriBoolValidate,ResMgr::NoClosure},
{"cmd:show-status", "yes", ResMgr::BoolValidate,ResMgr::NoClosure},
{"cmd:move-background", "yes", ResMgr::BoolValidate,ResMgr::NoClosure},
{"cmd:move-background-detach","yes", ResMgr::BoolValidate,ResMgr::NoClosure},
{"cmd:set-term-status", "no", ResMgr::BoolValidate,0},
{"cmd:term-status", "", 0, 0},
{"cmd:trace", "no", ResMgr::BoolValidate,ResMgr::NoClosure},
{"cmd:parallel", "1", ResMgr::UNumberValidate,0},
{"cmd:queue-parallel", "1", ResMgr::UNumberValidate,0},
{"cmd:cls-exact-time", "yes", ResMgr::BoolValidate,ResMgr::NoClosure},
{0}
};
static ResDecls lftp_cmd_vars_register(lftp_cmd_vars);
CmdExec *CmdExec::cwd_owner;
CmdExec *CmdExec::chain;
JobRef CmdExec::top;
void CmdExec::SaveCWD()
{
if(!cwd)
cwd=new LocalDirectory;
cwd->SetFromCWD();
if(cwd_owner==0)
cwd_owner=this;
}
int CmdExec::RestoreCWD()
{
if(cwd_owner==this)
return 0;
if(cwd==0)
return -1;
const char *err=cwd->Chdir();
if(!err)
{
cwd_owner=this;
return 0;
}
const char *name=cwd->GetName();
eprintf(_("Warning: chdir(%s) failed: %s\n"),name?name:"?",err);
return -1;
}
void CmdExec::FeedCmd(const char *c)
{
partial_cmd=false;
start_time=now;
cmd_buf.Put(c);
};
void CmdExec::PrependCmd(const char *c)
{
start_time=now;
int len=strlen(c);
int nl=(len>0 && c[len-1]!='\n');
if(nl)
cmd_buf.Prepend("\n",1);
cmd_buf.Prepend(c,len);
if(alias_field>0)
alias_field+=len+nl;
}
int CmdExec::find_cmd(const char *cmd_name,const struct cmd_rec **ret)
{
int part=0;
const cmd_rec *c=dyn_cmd_table?dyn_cmd_table.get():static_cmd_table;
const int count=dyn_cmd_table?dyn_cmd_table.count():static_cmd_table_length;
for(int i=0; iname,cmd_name))
{
*ret=c;
return 1;
}
if(!strncasecmp(c->name,cmd_name,strlen(cmd_name)))
{
part++;
*ret=c;
}
}
if(part!=1)
*ret=0;
return part;
}
CMD(lcd);
void CmdExec::exec_parsed_command()
{
switch(condition)
{
case(COND_ANY):
if(exit_code!=0 && ResMgr::QueryBool("cmd:fail-exit",0))
{
failed_exit_code=exit_code;
while(feeder)
RemoveFeeder();
cmd_buf.Empty();
return;
}
break;
case(COND_AND):
if(exit_code!=0)
return;
break;
case(COND_OR):
if(exit_code==0)
return;
break;
}
prev_exit_code=exit_code;
exit_code=1;
if(interactive)
{
SignalHook::ResetCount(SIGINT);
SignalHook::ResetCount(SIGHUP);
SignalHook::ResetCount(SIGTSTP);
}
bool did_default=false;
if(ResMgr::QueryBool("cmd:trace",0))
{
xstring_ca c(args->CombineQuoted());
printf("+ %s\n",c.get());
}
restart:
const struct cmd_rec *c;
const char *cmd_name=args->getarg(0);
if(!cmd_name)
return;
int part=find_cmd(cmd_name,&c);
if(part<=0)
eprintf(_("Unknown command `%s'.\n"),cmd_name);
else if(part>1)
eprintf(_("Ambiguous command `%s'.\n"),cmd_name);
else
{
if(RestoreCWD()==-1)
{
if(c->creator!=cmd_lcd)
return;
}
args->setarg(0,c->name); // in case it was abbreviated
args->rewind();
xstring_ca cmdline(args->Combine()); // save the cmdline
Job *new_job=0;
if(c->creator==0)
{
if(did_default)
{
eprintf(_("Module for command `%s' did not register the command.\n"),cmd_name);
exit_code=1;
return;
}
new_job=default_cmd();
did_default=true;
}
else
{
new_job=c->creator(this);
}
if(new_job==this || builtin)
{
if(builtin==BUILTIN_EXEC_RESTART)
{
builtin=BUILTIN_NONE;
goto restart;
}
return;
}
RevertToSavedSession();
if(new_job) {
if(!new_job->cmdline)
new_job->cmdline.move_here(cmdline);
AddNewJob(new_job);
}
}
}
void CmdExec::AddNewJob(Job *new_job)
{
if(new_job->jobno<0)
new_job->AllocJobno();
new_job->SetParentFg(this,!background);
exit_code=0;
AddWaiting(new_job);
if(background) {
Roll(new_job);
if(!new_job->Done())
SuspendJob(new_job);
}
}
void CmdExec::SuspendJob(Job *j)
{
j->Bg();
if(interactive)
j->ListOneJob(0,0,"&");
last_bg=j->jobno;
exit_code=0;
RemoveWaiting(j);
}
void CmdExec::ExecParsed(ArgV *a,FDStream *o,bool b)
{
Enter();
args=a;
output=o;
background=b;
condition=COND_ANY;
exec_parsed_command();
Leave();
}
bool CmdExec::Idle()
{
return(waiting_num==0 && builtin==BUILTIN_NONE && (cmd_buf.Size()==0 || partial_cmd));
}
int CmdExec::Done()
{
Enter();
bool done = (feeder==0 && Idle())
|| (auto_terminate_in_bg && NumberOfChildrenJobs()==0 && !in_foreground_pgrp());
Leave();
return done;
}
void CmdExec::RemoveFeeder()
{
free_used_aliases();
if(!feeder)
return;
// save old cwd if necessary
if(interactive && feeder->prev==0)
cwd_history.Set(session);
cmd_buf.Empty();
cmd_buf.Put(feeder->saved_buf);
partial_cmd=false;
if(feeder==queue_feeder)
queue_feeder=0;
delete replace_value(feeder,feeder->prev);
Reconfig(0);
SetInteractive();
}
void CmdExec::ReuseSavedSession()
{
saved_session=0;
}
void CmdExec::RevertToSavedSession()
{
if(saved_session==0)
return;
ChangeSession(saved_session.borrow());
}
void CmdExec::ChangeSlot(const char *n)
{
if(!n || !*n)
{
slot.set(0);
return;
}
slot.set(n);
const FileAccess *s=ConnectionSlot::FindSession(n);
if(!s)
ConnectionSlot::Set(n,session);
else
ChangeSession(s->Clone());
}
void CmdExec::AtFinish()
{
if(queue_feeder && queue_feeder->JobCount())
return;
if(!fed_at_finish && NumAwaitedJobs()==0 && cmd_buf.Size()==0) {
FeedCmd(ResMgr::Query(queue_feeder?"cmd:at-queue-finish":"cmd:at-finish",0));
FeedCmd("\n");
fed_at_finish=true;
}
}
int CmdExec::Do()
{
int m=STALL;
if(builtin!=BUILTIN_NONE)
{
int res;
switch(builtin)
{
case(BUILTIN_CD):
res=session->Done();
if(res==FA::OK)
{
// done
if(status_line)
status_line->Clear();
if(interactive || verbose)
{
const char *cwd=session->GetCwd();
eprintf(_("cd ok, cwd=%s\n"),cwd?cwd:"~");
cwd_history.Set(session,old_cwd);
}
if(slot)
ConnectionSlot::SetCwd(slot,session->GetCwd());
session->Close();
exit_code=0;
builtin=BUILTIN_NONE;
redirections=0;
beep_if_long();
return MOVED;
}
if(res<0)
{
if(res==FA::FILE_MOVED)
{
// cd to another url.
const char *loc_c=session->GetNewLocation();
int max_redirections=ResMgr::Query("xfer:max-redirections",0);
if(loc_c && max_redirections>0)
{
eprintf(_("%s: received redirection to `%s'\n"),"cd",loc_c);
if(++redirections>max_redirections)
{
eprintf("cd: %s\n",_("Too many redirections"));
goto cd_err_done;
}
char *loc=alloca_strdup(loc_c);
ParsedURL u(loc,true);
if(!u.proto)
{
bool is_file=(last_char(loc)!='/');
FileAccess::Path new_cwd(session->GetNewCwd());
new_cwd.Change(0,is_file,loc);
session->PathVerify(new_cwd);
session->Roll();
return MOVED;
}
session->Close();
exit_code=0;
builtin=BUILTIN_NONE;
PrependCmd(xstring::get_tmp("open ").append_quoted(loc));
return MOVED;
}
}
// error
if(status_line)
status_line->Clear();
eprintf("%s: %s\n",args->getarg(0),session->StrError(res));
cd_err_done:
session->Close();
builtin=BUILTIN_NONE;
redirections=0;
beep_if_long();
exit_code=1;
return MOVED;
}
break;
case(BUILTIN_OPEN):
res=session->Done();
if(res==FA::OK)
{
if(status_line)
status_line->Clear();
session->Close();
ReuseSavedSession();
builtin=BUILTIN_NONE;
redirections=0;
beep_if_long();
exit_code=0;
return MOVED;
}
if(res<0)
{
if(status_line)
status_line->Clear();
eprintf("%s: %s\n",args->getarg(0),session->StrError(res));
session->Close();
RevertToSavedSession();
builtin=BUILTIN_NONE;
redirections=0;
beep_if_long();
exit_code=1;
return MOVED;
}
break;
case(BUILTIN_GLOB):
if(glob->Error())
{
if(status_line)
status_line->Clear();
eprintf("%s: %s\n",args->getarg(0),glob->ErrorText());
}
else if(glob->Done())
{
FileSet &list=*glob->GetResult();
for(int i=0; list[i]; i++)
args_glob->Append(list[i]->name);
}
if(glob->Done() || glob->Error())
{
const char *pat=args->getnext();
if(!pat)
{
glob=0;
// it was last argument
args=args_glob.borrow();
builtin=BUILTIN_NONE;
redirections=0;
if(status_line)
status_line->Clear();
exit_code=prev_exit_code;
RevertToSavedSession();
exec_parsed_command();
return MOVED;
}
glob->NewGlob(pat);
m=MOVED;
}
break;
case(BUILTIN_NONE):
case(BUILTIN_EXEC_RESTART):
abort(); // can't happen
}
if(interactive)
{
if(SignalHook::GetCount(SIGINT))
{
if(status_line)
status_line->WriteLine(_("Interrupt"));
return AcceptSig(SIGINT);
}
if(SignalHook::GetCount(SIGTSTP))
{
if(builtin==BUILTIN_CD || builtin==BUILTIN_OPEN)
{
if(status_line)
status_line->Clear();
if(builtin==BUILTIN_CD)
session->ChdirAccept();
session->Close();
exit_code=0;
builtin=BUILTIN_NONE;
redirections=0;
return MOVED;
}
else
{
SignalHook::ResetCount(SIGTSTP);
}
}
if(SignalHook::GetCount(SIGHUP))
{
SetInteractive(false);
return MOVED;
}
}
if(status_line && show_status && status_line->CanShowNow())
ShowRunStatus(status_line); // this is only for top level CmdExec.
return m;
}
if(waiting_num>0)
{
Job *j;
while((j=FindDoneAwaitedJob())!=0)
{
j->Bg();
if(status_line)
status_line->Clear();
if(interactive || verbose)
j->SayFinal(); // final phrase like 'rm succeed'
exit_code=j->ExitCode();
RemoveWaiting(j);
Delete(j);
beep_if_long();
return MOVED;
}
if(interactive)
{
if(SignalHook::GetCount(SIGINT))
{
for(int i=0; iBg();
SignalHook::ResetCount(SIGINT);
if(status_line)
status_line->WriteLine(_("Interrupt"));
return AcceptSig(SIGINT);
}
if(SignalHook::GetCount(SIGTSTP))
{
while(waiting_num>0)
SuspendJob(waiting[0]);
return MOVED;
}
if(SignalHook::GetCount(SIGHUP))
{
SetInteractive(false);
return MOVED;
}
}
if(status_line && show_status && status_line->CanShowNow())
ShowRunStatus(status_line); // this is only for top level CmdExec.
if(m != STALL || interactive || waiting_num >= max_waiting)
return m;
}
if(!interactive)
{
BuryDoneJobs();
if(FindJob(last_bg)==0)
last_bg=-1;
}
try_get_cmd:
if(cmd_buf.Size()==0 || partial_cmd)
{
if(feeder)
{
if(interactive && !partial_cmd)
{
ListDoneJobs();
BuryDoneJobs();
if(FindJob(last_bg)==0)
last_bg=-1;
}
if(status_line && show_status)
{
const char *def_title = FormatPrompt(ResMgr::Query("cmd:default-title",getenv("TERM")));
status_line->DefaultTitle(def_title);
status_line->Clear();
}
const char *prompt=MakePrompt();
feeder_called=true;
if(fg)
feeder->Fg();
const char *cmd=feeder->NextCmd(this,prompt);
feeder_called=false;
if(!cmd)
{
if(cmd_buf.Size()>0 && partial_cmd)
{
const char *next_cmd=cmd_buf.Get();
if(last_char(next_cmd)!='\n')
{
// missing EOL on last line, add it
FeedCmd("\n");
goto try_get_cmd;
}
fprintf(stderr,_("Warning: discarding incomplete command\n"));
}
if(!feeder->RealEOF() && top_level)
{
cmd_buf.Empty();
FeedCmd("exit;");
return MOVED;
}
if(waiting_num > 0)
return m;
RemoveFeeder();
m=MOVED;
goto try_get_cmd;
}
if(cmd[0])
{
auto_terminate_in_bg=false;
FeedCmd(cmd);
Roll();
if(!Idle())
fed_at_finish=false;
return MOVED;
}
else
{
if(SignalHook::GetCount(SIGINT)>0)
{
SignalHook::ResetCount(SIGINT);
cmd_buf.Empty(); // flush unparsed command
return MOVED;
}
}
}
return m;
}
parse_result res = parse_one_cmd();
if(alias_field<=0)
free_used_aliases();
switch(res)
{
case(PARSE_ERR):
return MOVED;
case(PARSE_AGAIN):
partial_cmd=true;
goto try_get_cmd;
case(PARSE_OK):
if(feeder)
feeder->Bg();
break;
}
if(args==0 || args->count()==0) {
AtFinish();
return MOVED; // empty command
}
if(interactive)
session->DontSleep(); // We don't want to get a delay just after user
// entered a command.
exec_parsed_command();
return MOVED;
}
void CmdExec::ShowRunStatus(const SMTaskRef& s)
{
switch(builtin)
{
case(BUILTIN_CD):
if(session->IsOpen())
s->Show("cd `%s' [%s]",squeeze_file_name(args->getarg(1),s->GetWidthDelayed()-40),session->CurrentStatus());
break;
case(BUILTIN_OPEN):
if(session->IsOpen())
s->Show("open `%s' [%s]",session->GetHostName(),session->CurrentStatus());
break;
case(BUILTIN_GLOB):
s->Show("%s",glob->Status());
break;
case(BUILTIN_EXEC_RESTART):
abort(); // can't happen
case(BUILTIN_NONE):
if(waiting_num>0)
Job::ShowRunStatus(s);
else
s->Clear();
break;
}
}
xstring& CmdExec::FormatStatus(xstring& s,int v,const char *prefix)
{
SessionJob::FormatStatus(s,v,prefix);
if(builtin)
{
xstring_ca ac(args->Combine());
return s.appendf(_("\tExecuting builtin `%s' [%s]\n"),ac.get(),session->CurrentStatus());
}
if(queue_feeder)
{
if(IsSuspended())
s.appendf("%s%s\n",prefix,_("Queue is stopped."));
BuryDoneJobs();
for(int i=0; iFormatOneJob(s,v);
else
waiting[i]->FormatJobTitle(s);
if(i+1FormatStatus(s,v,prefix);
}
if(waiting_num==1)
return s.appendf(_("\tWaiting for job [%d] to terminate\n"),waiting[0]->jobno);
else if(waiting_num>1)
{
s.appendf(_("\tWaiting for termination of jobs: "));
for(int i=0; ijobno);
s.append(i+10)
s.append(_("\tRunning\n"));
else if(feeder)
s.append(_("\tWaiting for command\n"));
return s;
}
void CmdExec::init(LocalDirectory *c)
{
// add this to chain
next=chain;
chain=this;
background=false;
interactive=false;
show_status=true;
top_level=false;
auto_terminate_in_bg=false;
feeder=0;
feeder_called=false;
used_aliases=0;
partial_cmd=false;
alias_field=0;
default_output=0;
condition=COND_ANY;
prev_exit_code=0;
exit_code=0;
failed_exit_code=0;
last_bg=-1;
fed_at_finish=true;
cwd=c;
if(!cwd)
SaveCWD();
remote_completion=false;
long_running=0;
csh_history=false;
verify_host=verify_path=true;
verify_path_cached=false;
start_time=0;
redirections=0;
queue_feeder=0;
max_waiting=1;
saved_session=0;
builtin=BUILTIN_NONE;
Reconfig();
}
CmdExec::CmdExec(FileAccess *s,LocalDirectory *c)
: SessionJob(s?s:new DummyProto), parent_exec(0)
{
init(c);
}
CmdExec::CmdExec(CmdExec *parent)
: SessionJob(parent->session->Clone()), parent_exec(parent)
{
init(parent->cwd->Clone());
}
CmdExec::~CmdExec()
{
// remove this from chain.
for(CmdExec **scan=&chain; *scan; scan=&(*scan)->next)
{
if(this==*scan)
{
*scan=(*scan)->next;
break;
}
}
free_used_aliases();
if(cwd_owner==this)
cwd_owner=0;
}
const char *CmdExec::FormatPrompt(const char *scan)
{
const char *cwd=session->GetCwd();
if(cwd==0 || cwd[0]==0)
cwd="~";
{
const char *home=session->GetHome();
int home_len=xstrlen(home);
if(home_len>1 && !strncmp(cwd,home,home_len)
&& (cwd[home_len]=='/' || cwd[home_len]==0))
{
cwd=xstring::format("~%s",cwd+home_len);
}
}
const char *cwdb=session->GetCwd();
if(cwdb==0 || cwdb[0]==0)
cwdb="~";
const char *p=strrchr(cwdb,'/');
if(p && p>cwdb)
cwdb=p+1;
const char *lcwd=this->cwd->GetName();
{
const char *home=get_home();
int home_len=xstrlen(home);
if(home_len>1 && !strncmp(lcwd,home,home_len)
&& (lcwd[home_len]=='/' || lcwd[home_len]==0))
{
lcwd=xstring::format("~%s",lcwd+home_len);
}
}
const char *lcwdb=this->cwd->GetName();
p=strrchr(lcwdb,'/');
if(p && p>lcwdb)
lcwdb=p+1;
static const char StartIgn[]={RL_PROMPT_START_IGNORE,0};
static const char EndIgn[]={RL_PROMPT_END_IGNORE,0};
subst_t subst[] = {
{ 'a', "\007" },
{ 'e', "\033" },
{ 'n', "\n" },
{ 's', "lftp" },
{ 'v', VERSION },
{ 'h', session->GetHostName() },
{ 'u', session->GetUser() },
// @ if non-default user
{ '@', session->GetUser()?"@":"" },
{ 'U', session->GetConnectURL() },
{ 'S', slot?slot.get():"" },
{ 'w', cwd },
{ 'W', cwdb },
{ 'l', lcwd },
{ 'L', lcwdb },
{ '[', StartIgn },
{ ']', EndIgn },
{ 0, "" }
};
static xstring prompt;
SubstTo(prompt, scan, subst);
return(prompt);
}
const char *CmdExec::MakePrompt()
{
if(partial_cmd)
return "> ";
return FormatPrompt(ResMgr::Query("cmd:prompt",getenv("TERM")));
}
void CmdExec::beep_if_long()
{
if(start_time!=0 && long_running!=0
&& now.UnixTime()>start_time+long_running
&& interactive && Idle() && isatty(1))
write(1,"\007",1);
AtFinish();
}
void CmdExec::Reconfig(const char *name)
{
const char *c=0;
if(session)
c = session->GetConnectURL(FA::NO_PATH);
long_running=ResMgr::Query("cmd:long-running",c);
remote_completion=ResMgr::QueryBool("cmd:remote-completion",c);
csh_history=ResMgr::QueryBool("cmd:csh-history",0);
verify_path=ResMgr::QueryBool("cmd:verify-path",c);
verify_path_cached=ResMgr::QueryBool("cmd:verify-path-cached",c);
verify_host=ResMgr::QueryBool("cmd:verify-host",c);
verbose=ResMgr::QueryBool("cmd:verbose",0);
if(top_level || queue_feeder)
max_waiting=ResMgr::Query(queue_feeder?"cmd:queue-parallel":"cmd:parallel",c);
if(name && !strcmp(name,"cmd:interactive"))
SetInteractive();
show_status=ResMgr::QueryBool("cmd:show-status",0);
}
void CmdExec::pre_stdout()
{
if(status_line)
status_line->Clear(false);
if(feeder_called)
feeder->clear();
current->TimeoutS(1);
}
void CmdExec::top_vfprintf(FILE *file,const char *f,va_list v)
{
pre_stdout();
::vfprintf(file,f,v);
}
void CmdExec::SetCmdFeeder(CmdFeeder *new_feeder)
{
new_feeder->prev=feeder;
new_feeder->saved_buf.set(cmd_buf.Get());
feeder=new_feeder;
cmd_buf.Empty();
SetInteractive();
}
int CmdExec::AcceptSig(int sig)
{
if(sig!=SIGINT && sig!=SIGTERM)
return STALL;
if(builtin)
{
switch(builtin)
{
case(BUILTIN_CD):
session->Close();
break;
case(BUILTIN_OPEN):
session->Close();
RevertToSavedSession();
break;
case(BUILTIN_GLOB):
glob=0;
args_glob=0;
break;
case(BUILTIN_NONE):
case(BUILTIN_EXEC_RESTART):
abort(); // should not happen
}
builtin=BUILTIN_NONE;
redirections=0;
exit_code=1;
return MOVED;
}
if(waiting_num>0)
{
int limit=waiting_num;
for(int i=0; iAcceptSig(sig);
if(res==WANTDIE)
{
exit_code=1;
RemoveWaiting(r);
Delete(r);
i--;
limit--;
}
}
if(waiting_num==0 && parent!=0)
return WANTDIE;
return MOVED;
}
if(parent!=0)
return WANTDIE;
return STALL;
}
void CmdExec::SetInteractive(bool i)
{
if(interactive==i)
return;
if(i)
{
SignalHook::DoCount(SIGINT);
SignalHook::DoCount(SIGTSTP);
}
else
{
SignalHook::Restore(SIGINT);
SignalHook::Restore(SIGTSTP);
}
interactive=i;
}
void CmdExec::SetInteractive()
{
if(!top_level)
return;
bool def=feeder?feeder->IsInteractive():false;
SetInteractive(ResMgr::QueryTriBool("cmd:interactive",0,def));
}
xstring& xstring::append_quoted(const char *str,int len)
{
if(!CmdExec::needs_quotation(str,len))
return append(str);
append('"');
while(len>0)
{
if(*str=='"' || *str=='\\')
append('\\');
append(*str++);
len--;
}
return append('"');
}
bool CmdExec::needs_quotation(const char *buf,int len)
{
while(len>0)
{
if(*buf==' ' || *buf=='\t')
return true;
if(strchr("\"'\\&|>;",*buf))
return true;
buf++;
len--;
}
return false;
}
void CmdExec::FeedQuoted(const char *c)
{
FeedCmd(xstring::get_tmp("").append_quoted(c));
}
// implementation is here because it depends on CmdExec.
xstring& ArgV::CombineQuotedTo(xstring& res,int start) const
{
res.nset("",0);
if(start>=Count())
return res;
for(;;)
{
const char *arg=String(start++);
res.append_quoted(arg);
if(start>=Count())
return(res);
res.append(' ');
}
}
xstring& ArgV::CombineCmdTo(xstring& res,int i) const
{
return i>=count()-1 ? CombineTo(res,i) : CombineQuotedTo(res,i);
}
const char *CmdExec::GetFullCommandName(const char *cmd)
{
const CmdExec::cmd_rec *c;
int part=CmdExec::find_cmd(cmd,&c);
if(part==1)
return c->name;
return cmd;
}
void CmdExec::AtExit()
{
FeedCmd(ResMgr::Query("cmd:at-exit",0));
FeedCmd("\n");
/* Clear the title, and ensure we don't write anything else
* to it in case we're being backgrounded. */
status_line=0;
}
void CmdExec::AtExitBg()
{
FeedCmd(ResMgr::Query("cmd:at-exit-bg",0));
FeedCmd("\n");
}
void CmdExec::AtExitFg()
{
FeedCmd(ResMgr::Query("cmd:at-exit-fg",0));
FeedCmd("\n");
}
void CmdExec::AtBackground()
{
FeedCmd(ResMgr::Query("cmd:at-background",0));
FeedCmd("\n");
}
void CmdExec::AtTerminate()
{
FeedCmd(ResMgr::Query("cmd:at-terminate",0));
FeedCmd("\n");
}
void CmdExec::EmptyCmds()
{
cmd_buf.Empty();
}
bool CmdExec::WriteCmds(int fd) const
{
const char *buf;
int len;
cmd_buf.Get(&buf,&len);
for(;;)
{
if(len==0)
return true;
int res=write(fd,buf,len);
if(res<=0)
return false;
buf+=res;
len-=res;
}
}
bool CmdExec::ReadCmds(int fd)
{
for(;;)
{
int size=0x1000;
size=read(fd,cmd_buf.GetSpace(size),size);
if(size==-1)
return false;
if(size==0)
return true;
cmd_buf.SpaceAdd(size);
}
}
void CmdExec::free_used_aliases()
{
if(used_aliases)
{
TouchedAlias::FreeChain(used_aliases);
used_aliases=0;
}
alias_field=0;
}
void CmdExec::skip_cmd(int len)
{
cmd_buf.Skip(len);
alias_field-=len;
if(alias_field<=0)
free_used_aliases();
}
int CmdExec::cmd_rec::cmp(const CmdExec::cmd_rec *a,const CmdExec::cmd_rec *b)
{
return strcmp(a->name,b->name);
}
xarray CmdExec::dyn_cmd_table;
void CmdExec::RegisterCommand(const char *name,cmd_creator_t creator,const char *short_desc,const char *long_desc)
{
if(dyn_cmd_table==0)
dyn_cmd_table.nset(static_cmd_table,static_cmd_table_length);
cmd_rec new_entry={name,creator,short_desc,long_desc};
int i;
if(dyn_cmd_table.bsearch(new_entry,cmd_rec::cmp,&i))
{
cmd_rec *const c=&dyn_cmd_table[i];
c->creator=creator;
if(short_desc)
c->short_desc=short_desc;
if(long_desc || strlen(c->long_desc)<2)
c->long_desc=long_desc;
return;
}
dyn_cmd_table.insert(new_entry,i);
}
void CmdExec::ChangeSession(FileAccess *new_session)
{
session=new_session;
session->SetPriority(fg?1:0);
Reconfig(0);
if(slot)
ConnectionSlot::Set(slot,session);
}
const char *CmdExec::CmdByIndex(int i)
{
if(dyn_cmd_table)
{
if(ia0();
if(load_cmd_module(op)) {
builtin=BUILTIN_EXEC_RESTART;
return this;
}
return 0;
}
Job *CmdExec::builtin_local()
{
if(args->count()<2) {
eprintf(_("Usage: %s cmd [args...]\n"),args->a0());
return 0;
}
saved_session=session.borrow();
session=FileAccess::New("file");
if(!session) {
eprintf(_("%s: cannot create local session\n"),args->a0());
RevertToSavedSession();
return 0;
}
session->SetCwd(cwd->GetName());
args->delarg(0);
builtin=BUILTIN_EXEC_RESTART;
return this;
}
void CmdExec::FeedArgV(const ArgV *args,int start)
{
xstring cmd;
args->CombineCmdTo(cmd,start);
FeedCmd(cmd);
FeedCmd("\n");
}
bool CmdExec::SameQueueParameters(CmdExec *scan,const char *this_url)
{
return !strcmp(this_url,scan->session->GetConnectURL(FA::NO_PATH))
&& this->slot.eq(scan->slot);
}
/* return the CmdExec containing a queue feeder; create if necessary */
CmdExec *CmdExec::GetQueue(bool create)
{
const char *this_url=session->GetConnectURL(FA::NO_PATH);
// future GetConnectURL overwrite the static buffer, save it.
this_url=alloca_strdup(this_url);
for(CmdExec *scan=chain; scan; scan=scan->next)
{
if(scan->queue_feeder && SameQueueParameters(scan,this_url))
return scan;
}
if(!create)
return NULL;
CmdExec *queue=new CmdExec(session->Clone(),cwd->Clone());
queue->slot.set(slot);
queue->SetParentFg(this,false);
queue->AllocJobno();
const char *url=session->GetConnectURL(FA::NO_PATH);
queue->cmdline.vset("queue (",url,slot?"; ":"",slot?slot.get():"",")",NULL);
queue->queue_feeder=new QueueFeeder(session->GetCwd(), cwd->GetName());
queue->SetCmdFeeder(queue->queue_feeder);
queue->Reconfig(0);
return queue;
}