From a1664e81dd83e11828909d10b629ed04ea7d3467 Mon Sep 17 00:00:00 2001 From: Andreas Monzner Date: Tue, 21 Feb 2006 16:01:16 +0000 Subject: [PATCH] add eConsoleAppContainer to execute shell scripts or applications from python without blocking the mainloop --- lib/base/Makefile.am | 2 +- lib/base/console.cpp | 339 +++++++++++++++++++++++++++++++++++++ lib/base/console.h | 48 ++++++ lib/python/connections.h | 25 ++- lib/python/enigma_python.i | 32 +++- 5 files changed, 429 insertions(+), 17 deletions(-) create mode 100644 lib/base/console.cpp create mode 100644 lib/base/console.h diff --git a/lib/base/Makefile.am b/lib/base/Makefile.am index d667296a..877de00d 100644 --- a/lib/base/Makefile.am +++ b/lib/base/Makefile.am @@ -7,4 +7,4 @@ libenigma_base_a_SOURCES = \ buffer.cpp ebase.cpp eerror.cpp elock.cpp \ init.cpp message.cpp thread.cpp \ smartptr.cpp estring.cpp connection.cpp \ - filepush.cpp encoding.cpp + filepush.cpp encoding.cpp console.cpp diff --git a/lib/base/console.cpp b/lib/base/console.cpp new file mode 100644 index 00000000..b3fbbf73 --- /dev/null +++ b/lib/base/console.cpp @@ -0,0 +1,339 @@ +#include +#include +#include // for statfs +#include +#include +#include +#include + +int bidirpipe(int pfd[], char *cmd , char *argv[]) +{ + int pfdin[2]; /* from child to parent */ + int pfdout[2]; /* from parent to child */ + int pfderr[2]; /* stderr from child to parent */ + int pid; /* child's pid */ + + if ( pipe(pfdin) == -1 || pipe(pfdout) == -1 || pipe(pfderr) == -1) + return(-1); + + if ( ( pid = fork() ) == -1 ) + return(-1); + else if (pid == 0) /* child process */ + { + setsid(); + if ( close(0) == -1 || close(1) == -1 || close(2) == -1 ) + _exit(0); + + if (dup(pfdout[0]) != 0 || dup(pfdin[1]) != 1 || dup(pfderr[1]) != 2 ) + _exit(0); + + if (close(pfdout[0]) == -1 || close(pfdout[1]) == -1 || + close(pfdin[0]) == -1 || close(pfdin[1]) == -1 || + close(pfderr[0]) == -1 || close(pfderr[1]) == -1 ) + _exit(0); + + for (unsigned int i=3; i < 90; ++i ) + close(i); + + execvp(cmd,argv); + _exit(0); + } + if (close(pfdout[0]) == -1 || close(pfdin[1]) == -1 || close(pfderr[1]) == -1) + return(-1); + + pfd[0] = pfdin[0]; + pfd[1] = pfdout[1]; + pfd[2] = pfderr[0]; + + return(pid); +} + +eConsoleAppContainer::eConsoleAppContainer() +:pid(-1), killstate(0) +{ + for (int i=0; i < 3; ++i) + fd[i]=-1; +} + +int eConsoleAppContainer::execute( const std::string &cmd ) +{ + if (running()) + return -1; + pid=-1; + killstate=0; +// eDebug("cmd = %s", cmd.c_str() ); + int cnt=2; // path to app + terminated 0 + std::string str(cmd.length()?cmd:""); + + // kill spaces at beginning + unsigned int pos = str.find_first_not_of(' '); + if (pos != std::string::npos && pos) + str = str.substr(pos); + + // kill spaces at the end + pos = str.find_last_not_of(' '); + if (pos != std::string::npos && (pos+1) < str.length()) + str = str.erase(pos+1); + + unsigned int slen=str.length(); + if (!slen) + return -2; + + std::map brackets; + brackets.insert(std::pair('\'','\'')); + brackets.insert(std::pair('"','"')); + brackets.insert(std::pair('`','`')); + brackets.insert(std::pair('(',')')); + brackets.insert(std::pair('{','}')); + brackets.insert(std::pair('[',']')); + brackets.insert(std::pair('<','>')); + + unsigned int idx=str.find(' '); + std::string path = str.substr(0, idx != std::string::npos ? idx : slen ); +// eDebug("path = %s", path.c_str() ); + unsigned int plen = path.length(); + + std::string cmds = slen > plen ? str.substr( plen+1 ) : ""; + unsigned int clen = cmds.length(); +// eDebug("cmds = %s", cmds.c_str() ); + + idx = 0; + std::map::iterator it = brackets.find(cmds[idx]); + while ( (idx = cmds.find(' ',idx) ) != std::string::npos ) // count args + { + if (it != brackets.end()) + { + if (cmds[idx-1] == it->second) + it = brackets.end(); + } + if (it == brackets.end()) + { + cnt++; + it = brackets.find(cmds[idx+1]); + } + idx++; + } + +// eDebug("idx = %d, %d counted spaces", idx, cnt-2); + + if ( clen ) + { + cnt++; +// eDebug("increase cnt"); + } + +// eDebug("%d args", cnt-2); + char **argv = new char*[cnt]; // min two args... path and terminating 0 + argv[0] = new char[ plen ]; + 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] + + it = brackets.find(cmds[0]); + idx=0; + while ( (idx = cmds.find(' ',idx)) != std::string::npos ) // parse all args.. + { + bool bracketClosed=false; + if ( it != brackets.end() ) + { + if (cmds[idx-1]==it->second) + { + it = brackets.end(); + bracketClosed=true; + } + } + if ( it == brackets.end() ) + { + std::string tmp = cmds.substr(0, idx); + if (bracketClosed) + { + tmp.erase(0,1); + tmp.erase(tmp.length()-1, 1); + bracketClosed=false; + } + argv[cnt] = new char[ tmp.length()+1 ]; +// eDebug("idx=%d, arg = %s", idx, tmp.c_str() ); + strcpy( argv[cnt++], tmp.c_str() ); + cmds.erase(0, idx+1); +// eDebug("str = %s", cmds.c_str() ); + it = brackets.find(cmds[0]); + idx=0; + } + else + idx++; + } + if ( it != brackets.end() ) + { + cmds.erase(0,1); + cmds.erase(cmds.length()-1, 1); + } + // store the last arg + argv[cnt] = new char[ cmds.length() ]; + strcpy( argv[cnt], cmds.c_str() ); + } + else + cnt=1; + + // get one read ,one write and the err pipe to the prog.. + +// int tmp=0; +// while(argv[tmp]) +// eDebug("%d is %s", tmp, argv[tmp++]); + + pid = bidirpipe(fd, argv[0], argv); + + while ( cnt >= 0 ) // release heap memory + { +// eDebug("delete argv[%d]", cnt); + delete [] argv[cnt--]; + } +// eDebug("delete argv"); + delete [] argv; + + if ( pid == -1 ) + return -3; + + eDebug("pipe in = %d, out = %d, err = %d", fd[0], fd[1], fd[2]); + + in = new eSocketNotifier(eApp, fd[0], POLLIN|POLLPRI|POLLHUP ); + out = new eSocketNotifier(eApp, fd[1], POLLOUT, false); + err = new eSocketNotifier(eApp, fd[2], POLLIN|POLLPRI ); + CONNECT(in->activated, eConsoleAppContainer::readyRead); + CONNECT(out->activated, eConsoleAppContainer::readyWrite); + CONNECT(err->activated, eConsoleAppContainer::readyErrRead); + return 0; +} + +eConsoleAppContainer::~eConsoleAppContainer() +{ + kill(); +} + +void eConsoleAppContainer::kill() +{ + if ( killstate != -1 ) + { + eDebug("user kill(SIGKILL) console App"); + killstate=-1; + ::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; +} + +void eConsoleAppContainer::sendCtrlC() +{ + if ( killstate != -1 ) + { + eDebug("user send SIGINT(Ctrl-C) to console App"); + ::kill(pid, SIGINT); + } +} + +void eConsoleAppContainer::closePipes() +{ + in->stop(); + out->stop(); + err->stop(); + ::close(fd[0]); + fd[0]=-1; + ::close(fd[1]); + fd[1]=-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; + } +} + +void eConsoleAppContainer::readyRead(int what) +{ + if (what & POLLPRI|POLLIN) + { +// eDebug("what = %d"); + char buf[2048]; + int readed = read(fd[0], buf, 2047); +// eDebug("%d bytes read", readed); + if ( readed != -1 && readed ) + { +/* for ( int i = 0; i < readed; i++ ) + eDebug("%d = %c (%02x)", i, buf[i], buf[i] );*/ + buf[readed]=0; + /*emit*/ dataAvail(buf); + } + else if (readed == -1) + eDebug("readerror %d", errno); + } + if (what & eSocketNotifier::Hungup) + { + eDebug("child has terminated"); + closePipes(); + /*emit*/ appClosed(killstate); + } +} + +void eConsoleAppContainer::readyErrRead(int what) +{ + if (what & POLLPRI|POLLIN) + { +// eDebug("what = %d"); + char buf[2048]; + int readed = read(fd[2], buf, 2047); +// eDebug("%d bytes read", readed); + if ( readed != -1 && readed ) + { +/* for ( int i = 0; i < readed; i++ ) + eDebug("%d = %c (%02x)", i, buf[i], buf[i] );*/ + buf[readed]=0; + /*emit*/ dataAvail(buf); + } + else if (readed == -1) + eDebug("readerror %d", errno); + } +} + +void eConsoleAppContainer::write( const char *data, int len ) +{ + char *tmp = new char[len]; + memcpy(tmp, data, len); + outbuf.push(queue_data(tmp,len)); + out->start(); +} + +void eConsoleAppContainer::readyWrite(int what) +{ + if (what&POLLOUT && outbuf.size() ) + { + queue_data d = outbuf.front(); + outbuf.pop(); + if ( ::write( fd[1], d.data, d.len ) != d.len ) + { + /* emit */ dataSent(-1); +// eDebug("writeError"); + } + else + { + /* emit */ dataSent(0); +// eDebug("write ok"); + } + delete [] d.data; + } + if ( !outbuf.size() ) + out->stop(); +} diff --git a/lib/base/console.h b/lib/base/console.h new file mode 100644 index 00000000..bfa02407 --- /dev/null +++ b/lib/base/console.h @@ -0,0 +1,48 @@ +#ifndef __LIB_BASE_CONSOLE_H__ +#define __LIB_BASE_CONSOLE_H__ + +#include +#include +#include +#include + +#ifndef SWIG +struct queue_data +{ + queue_data( char *data, int len ) + :data(data), len(len) + { + } + char *data; + int len; +}; +#endif + +class eConsoleAppContainer: public Object +{ +#ifndef SWIG + int fd[3]; + int pid; + int killstate; + std::queue outbuf; + eSocketNotifier *in, *out, *err; + void readyRead(int what); + void readyErrRead(int what); + void readyWrite(int what); + void closePipes(); +#endif +public: + eConsoleAppContainer(); + int execute( const std::string &str ); + ~eConsoleAppContainer(); + int getPID() { return pid; } + void kill(); + void sendCtrlC(); + void write( const char *data, int len ); + bool running() { return (fd[0]!=-1) && (fd[1]!=-1) && (fd[2]!=-1); } + PSignal1Str dataAvail; + PSignal1 dataSent; + PSignal1 appClosed; +}; + +#endif // __LIB_BASE_CONSOLE_H__ diff --git a/lib/python/connections.h b/lib/python/connections.h index 3df7b565..ac5865e5 100644 --- a/lib/python/connections.h +++ b/lib/python/connections.h @@ -40,11 +40,6 @@ public: PyObject *get() { Py_INCREF(m_list); return m_list; } }; -inline PyObject *PyFrom(int v) -{ - return PyInt_FromLong(v); -} - template class PSignal0: public PSignal, public Signal0 { @@ -65,7 +60,21 @@ public: R operator()(V0 a0) { PyObject *pArgs = PyTuple_New(1); - PyTuple_SET_ITEM(pArgs, 0, PyFrom(a0)); + PyTuple_SET_ITEM(pArgs, 0, PyInt_FromLong(a0)); + callPython(pArgs); + Py_DECREF(pArgs); + return Signal1::operator()(a0); + } +}; + +template +class PSignal1Str: public PSignal, public Signal1 +{ +public: + R operator()(V0 a0) + { + PyObject *pArgs = PyTuple_New(1); + PyTuple_SET_ITEM(pArgs, 0, PyString_FromString(a0)); callPython(pArgs); Py_DECREF(pArgs); return Signal1::operator()(a0); @@ -79,8 +88,8 @@ public: R operator()(V0 a0, V1 a1) { PyObject *pArgs = PyTuple_New(2); - PyTuple_SET_ITEM(pArgs, 0, PyFrom(a0)); - PyTuple_SET_ITEM(pArgs, 1, PyFrom(a1)); + PyTuple_SET_ITEM(pArgs, 0, PyInt_FromLong(a0)); + PyTuple_SET_ITEM(pArgs, 1, PyInt_FromLong(a1)); callPython(pArgs); Py_DECREF(pArgs); return Signal2::operator()(a0, a1); diff --git a/lib/python/enigma_python.i b/lib/python/enigma_python.i index 8261385d..42315ce7 100644 --- a/lib/python/enigma_python.i +++ b/lib/python/enigma_python.i @@ -39,6 +39,7 @@ is usually caused by not marking PSignals as immutable. #include #include #include +#include #include #include #include @@ -138,6 +139,9 @@ typedef long time_t; // TODO: embed these... +%immutable eConsoleAppContainer::appClosed; +%immutable eConsoleAppContainer::dataAvail; +%immutable eConsoleAppContainer::dataSent; %immutable eButton::selected; %immutable eInput::changed; %immutable eComponentScan::statusChanged; @@ -145,6 +149,7 @@ typedef long time_t; %immutable pNavigation::m_event; %immutable eListbox::selectionChanged; +%include %include %include %include @@ -198,13 +203,13 @@ public: PyObject *get(); }; -template class PSignal1 -{ -public: - PyObject *get(); -}; +%template(PSignal0V) PSignal0; -template class PSignal2 +%typemap(out) PSignal0V { + $1 = $input->get(); +} + +template class PSignal1 { public: PyObject *get(); @@ -216,12 +221,23 @@ public: $1 = $input->get(); } -%template(PSignal0V) PSignal0; +template class PSignal1Str +{ +public: + PyObject *get(); +}; -%typemap(out) PSignal0V { +%template(PSignal1VS) PSignal1Str; + +%typemap(out) PSignal1VS { $1 = $input->get(); } +template class PSignal2 +{ +public: + PyObject *get(); +}; /************** debug **************/ -- 2.30.2