X-Git-Url: https://git.cweiske.de/enigma2.git/blobdiff_plain/d63d2c3c6cbbd574dda4f8b00ebe6c661735edd5..7e9d85cd54eec034bf4e98afef42f827084f54c3:/lib/base/console.cpp diff --git a/lib/base/console.cpp b/lib/base/console.cpp index 2609582f..6dc21d2e 100644 --- a/lib/base/console.cpp +++ b/lib/base/console.cpp @@ -1,34 +1,15 @@ -/* - * console.cpp - * - * Copyright (C) 2002 Felix Domke - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * $Id: console.cpp,v 1.1 2003-10-17 15:35:47 tmbinc Exp $ - */ - #include - -#include +#include #include // for statfs #include #include #include +#include +#include +#include +#include -int bidirpipe(int pfd[], char *cmd , char *argv[]) +int bidirpipe(int pfd[], const char *cmd , const char * const argv[], const char *cwd ) { int pfdin[2]; /* from child to parent */ int pfdout[2]; /* from parent to child */ @@ -38,10 +19,11 @@ int bidirpipe(int pfd[], char *cmd , char *argv[]) if ( pipe(pfdin) == -1 || pipe(pfdout) == -1 || pipe(pfderr) == -1) return(-1); - if ( ( pid = fork() ) == -1 ) + if ( ( pid = vfork() ) == -1 ) return(-1); else if (pid == 0) /* child process */ { + setsid(); if ( close(0) == -1 || close(1) == -1 || close(2) == -1 ) _exit(0); @@ -53,7 +35,14 @@ int bidirpipe(int pfd[], char *cmd , char *argv[]) close(pfderr[0]) == -1 || close(pfderr[1]) == -1 ) _exit(0); - execv(cmd,argv); + for (unsigned int i=3; i < 90; ++i ) + close(i); + + if (cwd) + chdir(cwd); + + execvp(cmd, (char * const *)argv); + /* the vfork will actually suspend the parent thread until execvp is called. thus it's ok to use the shared arg/cmdline pointers here. */ _exit(0); } if (close(pfdout[0]) == -1 || close(pfdin[1]) == -1 || close(pfderr[1]) == -1) @@ -66,183 +55,386 @@ int bidirpipe(int pfd[], char *cmd , char *argv[]) return(pid); } -eConsoleAppContainer::eConsoleAppContainer( const eString &cmd ) -:pid(-1), killstate(0), outbuf(0) +eConsoleAppContainer::eConsoleAppContainer() +:pid(-1), killstate(0), in(0), out(0), err(0) { -// eDebug("cmd = %s", cmd.c_str() ); - memset(fd, 0, sizeof(fd) ); - int cnt=2; // path to app + terminated 0 - eString str(cmd?cmd:""); + for (int i=0; i < 3; ++i) + { + fd[i]=-1; + filefd[i]=-1; + } +} - while( str.length() && str[0] == ' ' ) // kill spaces at beginning - str = str.mid(1); +static char brakets[][2] = { + { '\'','\'' }, + {'"','"'}, + {'`','`'}, + {'(',')'}, + {'{','}'}, + {'[',']'}, + {'<','>'} +}; + +static char *find_bracket(char ch) +{ + size_t idx=0; + while (idx < sizeof(brakets)/2) { + if (brakets[idx][0] == ch) + return &brakets[idx][0]; + ++idx; + } + return NULL; +} - while( str.length() && str[str.length()-1] == ' ' ) // kill spaces at the end - str = str.left( str.length() - 1 ); +int eConsoleAppContainer::setCWD( const char *path ) +{ + struct stat dir_stat; - if (!str.length()) - return; + if (stat(path, &dir_stat) == -1) + return -1; - unsigned int idx=0; - eString path = str.left( (idx = str.find(' ')) != eString::npos ? idx : str.length() ); -// eDebug("path = %s", path.c_str() ); + if (!S_ISDIR(dir_stat.st_mode)) + return -2; - eString cmds = str.mid( path.length()+1 ); -// eDebug("cmds = %s", cmds.c_str() ); + m_cwd = path; + return 0; +} - idx = 0; - while ( (idx = cmds.find(' ',idx) ) != eString::npos ) // count args - { - cnt++; - idx++; +int eConsoleAppContainer::execute( const char *cmd ) +{ + int cnt=0, slen=strlen(cmd); + char buf[slen+1]; + char *tmp=0, *argv[64], *path=buf, *cmds = buf; + memcpy(buf, cmd, slen+1); + +// printf("cmd = %s, len %d\n", cmd, slen); + + // kill spaces at beginning + while(path[0] == ' ') { + ++path; + ++cmds; + --slen; } -// eDebug("idx = %d, %d counted spaces", idx, cnt-2); - - if ( cmds.length() ) - { - cnt++; -// eDebug("increase cnt"); + // kill spaces at the end + while(slen && path[slen-1] == ' ') { + path[slen-1] = 0; + --slen; } -// eDebug("%d args", cnt-2); - char **argv = new char*[cnt]; // min two args... path and terminating 0 - argv[0] = new char[ path.length() ]; - strcpy( argv[0], path.c_str() ); - argv[cnt-1] = 0; // set terminating null - - if ( cnt > 2 ) // more then default args? - { - cnt=1; // do not overwrite path in argv[0] + if (!slen) + return -2; - while ( (idx = cmds.find(' ')) != eString::npos ) // parse all args.. - { - argv[cnt] = new char[ idx ]; -// eDebug("idx=%d, arg = %s", idx, cmds.left(idx).c_str() ); - strcpy( argv[cnt++], cmds.left( idx ).c_str() ); - cmds = cmds.mid(idx+1); -// eDebug("str = %s", cmds.c_str() ); + tmp = strchr(path, ' '); + if (tmp) { + *tmp = 0; + cmds = tmp+1; + while(*cmds && *cmds == ' ') + ++cmds; + } + else + cmds = path+slen; + + memset(argv, 0, sizeof(argv)); + argv[cnt++] = path; + + if (*cmds) { + char *argb=NULL, *it=NULL; + while ( (tmp = strchr(cmds, ' ')) ) { + if (!it && *cmds && (it = find_bracket(*cmds)) ) + *cmds = 'X'; // replace open braket... + if (!argb) // not arg begin + argb = cmds; + if (it && *(tmp-1) == it[1]) { + *argb = it[0]; // set old char for open braket + it = 0; + } + if (!it) { // end of arg + *tmp = 0; + argv[cnt++] = argb; + argb=0; // reset arg begin + } + cmds = tmp+1; + while (*cmds && *cmds == ' ') + ++cmds; } - // store the last arg - argv[cnt] = new char[ cmds.length() ]; - strcpy( argv[cnt], cmds.c_str() ); + argv[cnt++] = argb ? argb : cmds; + if (it) + *argv[cnt-1] = it[0]; // set old char for open braket } - // get one read ,one write and the err pipe to the prog.. - - if ( (pid = bidirpipe(fd, argv[0], argv)) == -1 ) - { - while ( cnt-- > 0 ) - delete [] argv[cnt]; - delete [] argv; - return; - } +// int tmp=0; +// while(argv[tmp]) +// eDebug("%d is %s", tmp, argv[tmp++]); + return execute(argv[0], argv); +} + +int eConsoleAppContainer::execute(const char *cmdline, const char * const argv[]) +{ + if (running()) + return -1; + + pid=-1; + killstate=0; + + // get one read ,one write and the err pipe to the prog.. + pid = bidirpipe(fd, cmdline, argv, m_cwd.length() ? m_cwd.c_str() : 0); - while ( cnt-- > 0 ) // release heap memory - delete [] argv[cnt]; - delete [] argv; + if ( pid == -1 ) + return -3; - eDebug("pipe in = %d, out = %d, err = %d", fd[0], fd[1], fd[2]); +// eDebug("pipe in = %d, out = %d, err = %d", fd[0], fd[1], fd[2]); - in = new eSocketNotifier(eApp, fd[0], 19 ); // 19 = POLLIN, POLLPRI, POLLHUP - out = new eSocketNotifier(eApp, fd[1], eSocketNotifier::Write); // POLLOUT - err = new eSocketNotifier(eApp, fd[2], 19 ); // 19 = POLLIN, POLLPRI, POLLHUP + ::fcntl(fd[1], F_SETFL, O_NONBLOCK); + ::fcntl(fd[2], F_SETFL, O_NONBLOCK); + in = new eSocketNotifier(eApp, fd[0], eSocketNotifier::Read|eSocketNotifier::Priority|eSocketNotifier::Hungup ); + out = new eSocketNotifier(eApp, fd[1], eSocketNotifier::Write, false); + err = new eSocketNotifier(eApp, fd[2], eSocketNotifier::Read|eSocketNotifier::Priority ); CONNECT(in->activated, eConsoleAppContainer::readyRead); CONNECT(out->activated, eConsoleAppContainer::readyWrite); CONNECT(err->activated, eConsoleAppContainer::readyErrRead); - signal(SIGCHLD, SIG_IGN); // no zombie when child killed + + return 0; +} + +int eConsoleAppContainer::execute( PyObject *cmdline, PyObject *args ) +{ + if (!PyString_Check(cmdline)) + return -1; + if (!PyList_Check(args)) + return -1; + const char *argv[PyList_Size(args) + 1]; + int i; + for (i = 0; i < PyList_Size(args); ++i) + { + PyObject *arg = PyList_GetItem(args, i); /* borrowed ref */ + if (!arg) + return -1; + if (!PyString_Check(arg)) + return -1; + argv[i] = PyString_AsString(arg); /* borrowed pointer */ + } + argv[i] = 0; + + return execute(PyString_AsString(cmdline), argv); /* borrowed pointer */ } eConsoleAppContainer::~eConsoleAppContainer() { - if ( running() ) + kill(); +} + +void eConsoleAppContainer::kill() +{ + if ( killstate != -1 && pid != -1 ) { + eDebug("user kill(SIGKILL) console App"); killstate=-1; - kill(); + /* + * Use a negative pid value, to signal the whole process group + * ('pid' might not even be running anymore at this point) + */ + ::kill(-pid, SIGKILL); + closePipes(); + } + while( outbuf.size() ) // cleanup out buffer + { + queue_data d = outbuf.front(); + outbuf.pop(); + delete [] d.data; + } + delete in; + delete out; + delete err; + in=out=err=0; + + for (int i=0; i < 3; ++i) + { + if ( filefd[i] > 0 ) + close(filefd[i]); } - if ( outbuf ) - delete [] outbuf; } -void eConsoleAppContainer::kill() +void eConsoleAppContainer::sendCtrlC() { - killstate=-1; - system( eString().sprintf("kill %d", pid).c_str() ); - eDebug("user kill console App"); + if ( killstate != -1 && pid != -1 ) + { + eDebug("user send SIGINT(Ctrl-C) to console App"); + /* + * Use a negative pid value, to signal the whole process group + * ('pid' might not even be running anymore at this point) + */ + ::kill(-pid, SIGINT); + } } void eConsoleAppContainer::closePipes() { - in->stop(); - out->stop(); - err->stop(); - ::close(fd[0]); - fd[0]=0; - ::close(fd[1]); - fd[1]=0; - ::close(fd[2]); - fd[2]=0; + if (in) + in->stop(); + if (out) + out->stop(); + if (err) + err->stop(); + if (fd[0] != -1) + { + ::close(fd[0]); + fd[0]=-1; + } + if (fd[1] != -1) + { + ::close(fd[1]); + fd[1]=-1; + } + if (fd[2] != -1) + { + ::close(fd[2]); + fd[2]=-1; + } eDebug("pipes closed"); + while( outbuf.size() ) // cleanup out buffer + { + queue_data d = outbuf.front(); + outbuf.pop(); + delete [] d.data; + } + pid = -1; } void eConsoleAppContainer::readyRead(int what) { - if (what & POLLPRI|POLLIN) + bool hungup = what & eSocketNotifier::Hungup; + if (what & (eSocketNotifier::Priority|eSocketNotifier::Read)) { - eDebug("what = %d"); - char buf[2048]; - int readed = read(fd[0], buf, 2048); - eDebug("%d bytes read", readed); - if ( readed != -1 && readed ) - /*emit*/ dataAvail( eString( buf ) ); - else if (readed == -1) - eDebug("readerror %d", errno); +// eDebug("what = %d"); + char buf[2049]; + int rd; + while((rd = read(fd[0], buf, 2048)) > 0) + { + buf[rd]=0; + /*emit*/ dataAvail(buf); + stdoutAvail(buf); + if ( filefd[1] > 0 ) + ::write(filefd[1], buf, rd); + if (!hungup) + break; + } } - if (what & eSocketNotifier::Hungup) + readyErrRead(eSocketNotifier::Priority|eSocketNotifier::Read); /* be sure to flush all data which might be already written */ + if (hungup) { eDebug("child has terminated"); closePipes(); - /*emit*/ appClosed(killstate); + int childstatus; + int retval = killstate; + /* + * We have to call 'wait' on the child process, in order to avoid zombies. + * Also, this gives us the chance to provide better exit status info to appClosed. + */ + if (::waitpid(pid, &childstatus, 0) > 0) + { + if (WIFEXITED(childstatus)) + { + retval = WEXITSTATUS(childstatus); + } + } + /*emit*/ appClosed(retval); } } void eConsoleAppContainer::readyErrRead(int what) { - if (what & POLLPRI|POLLIN) + if (what & (eSocketNotifier::Priority|eSocketNotifier::Read)) { - eDebug("what = %d"); - char buf[2048]; - int readed = read(fd[2], buf, 2048); - eDebug("%d bytes read", readed); - if ( readed != -1 && readed ) - /*emit*/ dataAvail( eString( buf ) ); - else if (readed == -1) - eDebug("readerror %d", errno); +// eDebug("what = %d"); + char buf[2049]; + int rd; + while((rd = read(fd[2], buf, 2048)) > 0) + { +/* for ( int i = 0; i < rd; i++ ) + eDebug("%d = %c (%02x)", i, buf[i], buf[i] );*/ + buf[rd]=0; + /*emit*/ dataAvail(buf); + stderrAvail(buf); + } } } -void eConsoleAppContainer::write( const eString & str ) +void eConsoleAppContainer::dumpToFile( PyObject *py_filename ) +{ + char *filename = PyString_AsString(py_filename); + filefd[1] = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0644); + eDebug("eConsoleAppContainer::dumpToFile open(%s, O_WRONLY|O_CREAT|O_TRUNC, 0644)=%i", filename, filefd[1]); +} + +void eConsoleAppContainer::readFromFile( PyObject *py_filename ) +{ + char *filename = PyString_AsString(py_filename); + char readbuf[32*1024]; + filefd[0] = open(filename, O_RDONLY); + int rsize = read(filefd[0], readbuf, 32*1024); + eDebug("eConsoleAppContainer::readFromFile open(%s, O_RDONLY)=%i, read: %i", filename, filefd[0], rsize); + if ( filefd[0] > 0 && rsize > 0 ) + write(readbuf, rsize); +} + +void eConsoleAppContainer::write( const char *data, int len ) +{ + char *tmp = new char[len]; + memcpy(tmp, data, len); + outbuf.push(queue_data(tmp,len)); + if (out) + out->start(); +} + +void eConsoleAppContainer::write( PyObject *data ) { - outbuf = new char[ str.length()]; - strcpy( outbuf, str.c_str() ); + char *buffer; + int length; + if (PyString_AsStringAndSize(data, &buffer, &length)) + return; + if (buffer && length) + write(buffer, length); } void eConsoleAppContainer::readyWrite(int what) { - if (what == 4 && outbuf) + if (what&eSocketNotifier::Write && outbuf.size() ) { - if ( ::write( fd[1], outbuf, strlen(outbuf) ) != (int) strlen(outbuf) ) - { - /* emit */ dataSent(-1); - eDebug("writeError"); - } + queue_data &d = outbuf.front(); + int wr = ::write( fd[1], d.data+d.dataSent, d.len-d.dataSent ); + if (wr < 0) + eDebug("eConsoleAppContainer write failed (%m)"); else + d.dataSent += wr; + if (d.dataSent == d.len) { + outbuf.pop(); + delete [] d.data; + if ( filefd[0] == -1 ) /* emit */ dataSent(0); - eDebug("write ok"); } - - delete outbuf; - outbuf=0; + } + if ( !outbuf.size() ) + { + if ( filefd[0] > 0 ) + { + char readbuf[32*1024]; + int rsize = read(filefd[0], readbuf, 32*1024); + if ( rsize > 0 ) + write(readbuf, rsize); + else + { + close(filefd[0]); + filefd[0] = -1; + ::close(fd[1]); + eDebug("readFromFile done - closing eConsoleAppContainer stdin pipe"); + fd[1]=-1; + dataSent(0); + out->stop(); + } + } + else + out->stop(); } }