/* This file is part of c++script. c++script 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. c++script 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 c++script. If not, see . */ /* Execution of sub-program. Replacement for system() with more control on the output of the sub-program (handle 1 and 2). This is very old code used by the COMMAND object.. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "c++script.h" using namespace std; static bool debug = false; namespace cpps{ // Use to split input buffer into lines struct INPUT_BUF{ string buf; size_t pos=0; unsigned nblines=0; // Number of complete line (with \n) in the buffer void clear(){ buf.clear(); pos = 0; nblines = 0; } void clear_start(){ if (pos > 0){ buf.erase(0,pos); pos = 0; } } // Find the number of complete line in the buffer void set_nblines(){ nblines = 0; for (auto c:buf|views::drop(pos)){ //size_t i = pos; i 0 || (eof && pos < buf.size()); } }; class POPENFD{ friend class POPENWAITS; protected: struct { int in; // stdin of the process FILE *fin; // Used to send buffered output int out; // stdout of the process int err; // error int ctl; // Special channel used to send a message // indicating the end of the process (avoid some races) }fds; INPUT_BUF outbuf; INPUT_BUF errbuf; bool bytesent; // Flag telling us if at least one message // was sent to the application, so we must give // it some time to process it at close time. bool eofout; bool eoferr; public: POPENFD (int handlein, int handleout); protected: POPENFD (void); public: void flush (void); FILE *getfout (void); void grabhandles (int &in, int &out, int &err); bool iseof (void); virtual bool isok (void); void loadout (const char *lines); int process (int ret_select, fd_set&in, int otherfd, bool&ctlmsg); int readerr (string &line); private: void readif (fd_set *in, int fd, INPUT_BUF &buf, bool &ctlmsg, bool &eof); int readline (string &line, INPUT_BUF &buf, bool eof); public: int readout (string &line); int readoutraw (char *data, int size); void send (string_view msg); void send (const void *msg, int len); void sendf (const char *ctl, ...); void seteof (void); protected: void setfds (int fdin, int fdout, int fderr, int fdctl); public: int setup (fd_set&in, int maxfd, int otherfd); int wait (int timeout); int wait (int timeout, int otherfd); protected: int wait (int timeout, int otherfd, bool&ctlmsg); public: virtual ~POPENFD (void); bool has_lines(){ if (debug){ cerr << format ("has_lines eofout={} eoferr={} output.nb={} outbuf.pos={} output.size={} errbuf.nb={} errbuf.pos={} errbuf.size={}\n" ,eofout,eoferr,outbuf.nblines,outbuf.pos,outbuf.buf.size(),errbuf.nblines,errbuf.pos,errbuf.buf.size()); cerr << format ("has_lines {}\n",outbuf.has_lines(eofout) || errbuf.has_lines(eoferr)); } return outbuf.has_lines(eofout) || errbuf.has_lines(eoferr); } }; class POPEN: public POPENFD{ int pid = -1; int status = -1; int cur_dead; // Current level of child_counter public: POPEN (std::string_view command); POPEN (std::string_view command, int uid, bool keepenv, bool keepcwd); POPEN (std::string_view command, std::string_view args); POPEN (std::string_view command, std::string_view args, int uid); POPEN (std::string_view command, int uid); private: void checksignal (void); public: int close (void); void closepipe (void); void forget (void); int getstatus (void); private: void init (std::string_view command, int uid, bool keepenv, bool keepcwd); void initarg (std::string_view command, std::string_view args, int uid); public: bool isok (void); void kill (void); int wait (int timeout); int wait (int timeout, int otherfd); private: void waitend (void); void waitone (void); public: ~POPEN (void); }; class POPENUSER: public POPEN{ public: POPENUSER (std::string_view cmd); }; static int popen_fdset (int fd, fd_set &in, int maxfd) { if (fd != -1){ FD_SET (fd,&in); if (fd > maxfd) maxfd = fd; } return maxfd; } static bool popen_fd_isset (int fd, fd_set &in) { bool ret = false; if (fd != -1){ ret = FD_ISSET (fd,&in); } return ret; } void POPENFD::setfds ( int fdin, int fdout, int fderr, int fdctl) { fds.in = fdin; fds.fin = fdopen (fds.in,"w"); fds.out = fdout; fds.err = fderr; fds.ctl = fdctl; } POPENFD::POPENFD() { bytesent = false; eofout = eoferr = false; fds.in = fds.out = fds.err = fds.ctl = -1; fds.fin = NULL; } POPENFD::POPENFD(int handlein, int handleout) { bytesent = false; setfds (handlein,handleout,-1,-1); eofout = eoferr = false; } POPENFD::~POPENFD() { if (fds.in != -1){ if (fds.fin != NULL){ fclose (fds.fin); }else{ ::close (fds.in); } } ::close (fds.out); ::close (fds.err); ::close (fds.ctl); } /* Return != 0 if the pipe is corretly opened */ bool POPENFD::isok() { return fds.in != -1; } bool POPENFD::iseof() { return eofout && eoferr; } void POPENFD::readif ( fd_set *in, int fd, INPUT_BUF &buf, bool &ctlmsg, bool &eof) { if (popen_fd_isset(fd,*in)){ // Clear the start of the input buffer buf.clear_start(); char bufread[10000]; int len = read (fd,bufread,sizeof(bufread)-1); if (len > 0){ buf.append (bufread,len); }else{ // end of process eof = true; ctlmsg = true; } } } int POPENFD::setup (fd_set &in, int maxfd, int otherfd) { if (!iseof()){ maxfd = popen_fdset (fds.out,in,maxfd); maxfd = popen_fdset (fds.err,in,maxfd); maxfd = popen_fdset (fds.ctl,in,maxfd); maxfd = popen_fdset (otherfd,in,maxfd); if (fds.fin != NULL) fflush (fds.fin); } return maxfd; } int POPENFD::process ( int ret_select, fd_set &in, int otherfd, bool &ctlmsg) { int ret = -1; if (!iseof()){ #if 0 printf ("ret=%d errno=%d otherfd=%d %d %d %d %d\n" ,ret,errno,otherfd ,popen_fd_isset(fds.err,in) ,popen_fd_isset(fds.out,in) ,popen_fd_isset(otherfd,in) ,popen_fd_isset(fds.ctl,in)); #endif ret = 0; if (ret_select > 0){ if (popen_fd_isset(fds.err,in) || popen_fd_isset(fds.out,in)){ ret = 1; readif (&in,fds.out,outbuf,ctlmsg,eofout); readif (&in,fds.err,errbuf,ctlmsg,eoferr); } if (popen_fd_isset(otherfd,in)) ret |= 2; if (popen_fd_isset(fds.ctl,in)){ char buf[10]; read (fds.ctl,buf,10); // Just remove the message // fprintf (stderr,"ctlmsg ret=%d\n",ret); ctlmsg = true; ::close (fds.ctl); fds.ctl = -1; } }else if (ret_select == -1 && errno == EINTR){ ret = 0; } } return ret; } /* Wait for anything to be available from the child process Return -1 if any error. Return 0 if the timeout has elapsed or a signal has been received. Return 1 if there is some data to read Return 2 if there is some data on otherfd Return 3 if there is some data on otherfd and on POPEN file handle */ int POPENFD::wait(int timeout, int otherfd, bool &ctlmsg) { int ret = -1; if (debug) cerr << format ("wait iseof()={}\n",iseof()); if (!iseof()){ fd_set in; FD_ZERO(&in); int maxfd = setup (in,0,otherfd); struct timeval tim; tim.tv_usec = 0; tim.tv_sec = timeout; while (1){ ret = select (maxfd+1,&in,NULL,NULL,timeout != -1 ? &tim: nullptr); if (ret != -1 || errno != EINTR) break; } ret = process (ret,in,otherfd,ctlmsg); if (debug) cerr << format ("POPENFD::wait ret={} errno={} {} eofout={} eoferr={}\n",ret,errno,strerror(errno),eofout,eoferr); } return ret; } /* Wait for anything to be available from the child process Return -1 if any error. Return 0 if the timeout has elapsed. Return 1 if there is some data to read Return 2 if there is some data on otherfd Return 3 if there is some data on otherfd and on POPEN file handle */ int POPENFD::wait(int timeout, int otherfd) { bool dummy=false; int ret = wait (timeout,otherfd,dummy); // fprintf (stderr,"wait %d %d\n",ret,dummy); if (ret == 0 && dummy) ret = -1; return ret; } /* Wait for anything to be available from the child process Return -1 if any error. Return 0 if the timeout has elapsed. Return 1 if there is some data to read */ int POPENFD::wait(int timeout) { return wait (timeout,-1); } void POPENFD::seteof() { eofout = eoferr = true; } /* Read one complete line or up to size byte in "line". If there is no complete line, nothing is read */ int POPENFD::readline (string &line, INPUT_BUF &buf, bool eof) { line.clear(); bool eol = false; for (size_t i=buf.pos; i 0 ? 0 : -1; } /* Read one line of stderr if available (won't block). Return -1 if no more to read. */ int POPENFD::readerr (string &line) { return readline (line,errbuf,eoferr); } /* Read one line of stdout if available (won't block). Return -1 if no more to read. */ int POPENFD::readout (string &line) { return readline (line,outbuf,eofout); } #if 0 /* Read some bytes of stdout if available (won't block). Return the number of bytes written in buf Return -1 if no more to read. */ int POPENFD::readoutraw (char *data, int size) { int ret = -1; int len = outbuf.getlen(); if (len < size){ outbuf.copy (data); outbuf.setfrom (""); ret = len; }else{ ret = size - 1; strncpy (data,outbuf.get(),ret); data[ret] = '\0'; outbuf.setfrom (outbuf.get()+ret); } return ret; } /* get the FILE handle used to send strings to the process. The application does not have to close it. */ FILE *POPENFD::getfout () { // We assume the application will send commands or message to the // process bytesent = true; return fds.fin; } #endif /* Send a string to the standard input of the process */ void POPENFD::send (string_view msg) { if (fds.fin != NULL){ bytesent = true; for (auto c:msg) fputc (c,fds.fin); } } #if 0 /* Send a buffer to the standard input of the process */ void POPENFD::send (const void *msg, int len) { if (fds.fin != NULL){ bytesent = true; fwrite (msg,1,len,fds.fin); } } #endif /* Flush the output buffer */ void POPENFD::flush () { if (fds.fin != NULL) fflush (fds.fin); } static int child_counter = 0; // Pid of a process we are currently waiting for struct RETCOD { int pid; int status; bool done; }; // We wait for at most 20 process concurently static RETCOD tbcod[20]; static void fchild(int ) { /* A long time ago, they changed something in the linux schedular. A newly forked process was scheduled before its parent. A fix was done in this function to cope with this. Not much later they returned to the original behavior. The new behavior could create something like this A parent fork the child executes and dies the parent, immediatly after the fork tries to install its pid in tbcod[] (using popen_reservpid below). But it is too late because the child is already dead and the code below has already been executed. To solve this, a second loop (adding in tbcod[]) was created. So when the parent finally execute, it will find the pid in tbcod and know the child is already done. Now, so far so good. But what happen if your application fork() on its own (or use libc popen()). Then the pid of the ending process will be added here (unless you installed another signal handler). And it will never be removed. Now, the kernel seems to behave to old way, but you never know if the new way will prevail. This is why we have this cleanup in removepid. In removepid, we know that no POPEN object has fork() recently. So in popen_forgetpid, we have a special cleanup of the tbcod[] array. Using debug_printf in fchild may create a deadlock because it tries to allocate memory or something. */ signal (SIGCHLD,fchild); child_counter++; int status,pid; // Several process may be dying at the same time while ((pid=waitpid(-1,&status,WNOHANG)) > 0){ unsigned i; for (i=0; ipw_dir; }else{ home = getenv ("HOME"); } homestr = format("HOME={}",home); tb[2] = homestr.c_str(); int nbenv = 3; popen_add2env ("DISPLAY",displaystr,tb,nbenv); popen_add2env ("LANG",langstr,tb,nbenv); popen_add2env ("XAUTHORITY",authstr,tb,nbenv); tb[nbenv] = NULL; environ = (char**)tb; } ::close (fderr[0]); ::close (fdctl[0]); string newcommand(command); if (pos == string_view::npos){ dup2 (fdin[0],0); dup2 (fdout[1],1); ::close (fdin[0]); ::close (fdin[1]); ::close (fdout[0]); ::close (fdout[1]); }else{ newcommand = newcommand.substr(0,pos)+format("{}",fdinout[1])+newcommand.substr(pos+8); ::close (fdinout[0]); } dup2 (fderr[1],2); ::close (fderr[1]); setuid (uid); seteuid(uid); // Closing unnecessary file handle for (int i=3; i<100; i++){ if (i != fdctl[1] && i != fdinout[1]) ::close (i); } if (!keepcwd) chdir ("/"); int ret = system (newcommand.c_str()); write (fdctl[1],"done\n",5); #if 0 FILE *fout = fopen ("/tmp/popen.log","a"); fprintf (fout,"%s -> %d\n",command,ret); fclose (fout); #endif if ((ret & 0xff)==0) ret >>= 8; _exit (ret); }else if (pid == -1){ ::close (fderr[0]); ::close (fderr[1]); ::close (fdctl[0]); ::close (fdctl[1]); ::close (fdin[0]); ::close (fdin[1]); ::close (fdout[0]); ::close (fdout[1]); ::close (fdinout[0]); ::close (fdinout[1]); }else{ popen_reservpid (pid); ::close (fderr[1]); ::close (fdctl[1]); if (pos != string_view::npos){ ::close (fdinout[1]); setfds (fdinout[0],fdinout[0],fderr[0],fdctl[0]); }else{ ::close (fdin[0]); ::close (fdout[1]); setfds (fdin[1],fdout[0],fderr[0],fdctl[0]); } } } } POPEN::POPEN( string_view command, int uid) // Effective and real UID of the new process { init (command,uid,false,false); } POPEN::POPEN( string_view command, int uid, // Effective and real UID of the new process bool keepenv, bool keepcwd) { init (command,uid,keepenv,keepcwd); } POPEN::POPEN(string_view command) { init (command,geteuid(),false,false); } void POPEN::initarg( string_view command, string_view args, int uid) { string cmd = format ("{} {}",command,args); init (cmd,uid,false,false); } /* Get the UID of the user who is login on this terminal. Even if this user has done a "su" command, we can find out who he really is. */ int popen_getloginuid() { int uid = getuid(); const char *tname = ttyname(0); if (tname != NULL){ struct utmp ut; strcpy (ut.ut_line,tname+5); struct utmp *u = getutline (&ut); if (u != NULL){ struct passwd *p = getpwnam (u->ut_user); if (p != NULL) uid = p->pw_uid; } } return uid; } POPEN::POPEN(string_view command, string_view args) { initarg(command,args,geteuid()); } POPEN::POPEN(string_view command, string_view args, int uid) { initarg(command,args,uid); } /* Execute a command for a user. Unlike POPEN which make sure tne environment is secure, POPENUSER is really trusting the user. This is never called by Linuxconf */ POPENUSER::POPENUSER(string_view cmd) : POPEN (cmd,getuid(),true,true) { } POPEN::~POPEN() { if (bytesent){ // We have sent some message to the process. // we must close the link and let the process complete by itself close(); } kill(); } static int process_findchild(int pid) { return -1; } void POPEN::kill() { if (pid != -1){ int child_pid = process_findchild(pid); if (child_pid != -1) ::kill (child_pid,SIGTERM); ::kill (pid,SIGTERM); waitend(); } } /* Forget about the child process. It won't be killed by the destructor of POPEN */ void POPEN::forget() { popen_forgetpid (pid); pid = -1; } void POPEN::waitone() { if (pid != -1){ for (unsigned i=0; i>= 8; popen_forgetpid (pid); pid = -1; } break; } } } } /* Wait until the process is really dead and recover its end status */ void POPEN::waitend () { while (1){ waitone(); if (pid == -1) break; if (::kill(pid,0)==-1){ fprintf (stderr,"POPEN::waitend waiting for a gone process, odd: %d (%s)\n",pid,strerror(errno)); //pid = -1; //break; } //sleep(10); // Should be interrupted when the child exited if(waitpid (pid,&status,0)==-1){ waitone(); }else{ if ((status & 0xff)==0) status >>= 8; } popen_forgetpid (pid); pid = -1; break; } } /* Return != 0 if the pipe is corretly opened */ bool POPEN::isok() { if (debug) cerr << format ("isok pid={}\n",pid); return pid != -1; } /* Check if some signal was received about child death */ void POPEN::checksignal() { if (child_counter > cur_dead){ cur_dead = child_counter; waitone(); } } /* Wait for anything to be available from the child process Return -1 if any error (or end of session). Return 0 if the timeout has elapsed. Return 1 if there is some data to read Return 2 if there is some data on otherfd Return 3 if there is some data on otherfd and on POPEN file handle */ int POPEN::wait(int timeout, int otherfd) { int ret = -1; #if 0 FILE *fout = fopen ("/tmp/popen.log","a"); fprintf (fout,"wait pid=%d fds=[%d %d %d %d] eofout=%d eoferr=%d\n",pid ,fds.in,fds.out,fds.err,fds.ctl,eofout,eoferr); fclose (fout); #endif if (pid != -1){ checksignal(); bool ctlmsg = false; ret = POPENFD::wait (timeout,otherfd,ctlmsg); checksignal(); if (ctlmsg){ waitend(); if (ret == 0) ret = -1; } }else if(!iseof()){ ret = POPENFD::wait (timeout,-1); } return ret; } /* Wait for anything to be available from the child process Return -1 if any error. Return 0 if the timeout has elapsed. Return 1 if there is some data to read */ int POPEN::wait(int timeout) { return wait (timeout,-1); } /* Return the status code of the ending process */ int POPEN::getstatus() { return status; } /* Closing the PIPE to standard input of the command. */ void POPEN::closepipe() { if (fds.fin != NULL){ fclose (fds.fin); fds.fin = NULL; } if (fds.in != -1){ ::close (fds.in); fds.in = -1; } } /* Closing the PIPE to standard input of the command. Expect the command to terminate soon (20 seconds timeout). Return the exit code */ int POPEN::close() { closepipe(); time_t end = time(NULL) + 20; //printf ("outbuf size %d eof %d pid %d\n",outbuf.getlen(),eof,pid); while (time(NULL) < end && !iseof() && pid != -1){ wait(20); //printf ("loop outbuf size %d eof %d pid %d code = %d\n",outbuf.getlen(),eof,pid,code); } //printf ("fin outbuf size %d eof %d pid %d\n",outbuf.getlen(),eof,pid); return getstatus(); } void COMMANDEXEC_iterator::fill_line() { while (!pop->has_lines() && !pop->iseof()){ int ret = readnext(); if (debug) cerr << format ("opeation ++ readnext={}\n",ret); //if (ret == -1) break; } if (pop->iseof()){ if (debug) cerr << format ("status={}\n",pop->getstatus()); } if (pop->readerr(content.line)!=-1){ content.is_err = true; }else if (pop->readout(content.line)!=-1){ content.is_err = false; } } int COMMANDEXEC_iterator::readnext() const { int ret = pop->wait(-1); if (debug) cerr << format("readnext ret={}\n",ret); return ret; } COMMANDEXEC_iterator::COMMANDEXEC_iterator(POPENUSER *_pop) :pop(_pop) { fill_line(); } COMMANDEXEC_iterator::COMMANDEXEC_iterator(const COMMANDEXEC_iterator &n) : pop(n.pop),content(n.content) { } COMMANDEXEC_iterator & COMMANDEXEC_iterator::operator = (const COMMANDEXEC_iterator &n) { content = n.content; pop = n.pop; return *this; } COMMANDEXEC_iterator::~COMMANDEXEC_iterator() { } COMMANDEXEC_iterator & COMMANDEXEC_iterator::operator ++() { if (debug) cerr << "operator ++\n"; fill_line(); return *this; } COMMANDEXEC_iterator COMMANDEXEC_iterator::operator ++(int) { if (debug) cerr << "operator ++(int)\n"; return *this; } COMMANDEXEC_line &COMMANDEXEC_iterator::operator *() const { if (debug) cerr << format ("operator * {} {}\n",content.is_err,content.line.size()); return (COMMANDEXEC_line&)content; //(COMMANDEXEC_iterator&)*this; } bool COMMANDEXEC_iterator::operator == (const COMMANDEXEC_iterator &d) const { if (debug) cerr << "operator ==\n"; return true; } bool COMMANDEXEC_iterator::operator == (const COMMANDEXEC_end &d) const { if (debug) cerr << format("operator == end isok()={} iseof={}\n",pop->isok(),pop->iseof()); return pop->iseof(); } COMMANDEXEC::COMMANDEXEC(std::string_view _command) :pop(make_shared(_command)),command(_command) { } COMMANDEXEC::COMMANDEXEC(const COMMANDEXEC &n) :pop(n.pop),command(n.command) { } COMMANDEXEC::~COMMANDEXEC() { } COMMANDEXEC_iterator COMMANDEXEC::begin() noexcept { return COMMANDEXEC_iterator (pop.get()); } COMMANDEXEC_iterator COMMANDEXEC::begin() const noexcept { return COMMANDEXEC_iterator (pop.get()); } COMMANDEXEC_end COMMANDEXEC::end() { return COMMANDEXEC_end(); } } #ifdef TEST using namespace cpps; int main (int argc, char *argv[]) { if (argc > 1){ cpps::option optdebug ('d',"debug","Debug messages",false,false); cpps::option arg ('m',"match","match parameter","something",false); cpps::option sequence ('s',"sequence","Do many tests",false,false); cpps::endoptions(argc,argv); debug = optdebug.val; string buf; bool first = true; for (auto &a:cpps::args){ if (!first) buf += ' '; first = false; buf += a; } cout << format ("exec command :{}:\n",buf); #if 0 POPENUSER p(buf); while (p.isok()){ if (p.wait(10)>0){ string line; while (p.readout(line)!=-1){ cout << line << endl; } }else{ break; } } #else cpps::COMMAND cmd (buf); cout << "==================== cmd.run()\n"; for (auto &&l:cmd.run()){ cout << format ("{}{}-{}\n",l.is_error() ? "* " : "+ ",l.line.size(),l.line); } if (sequence.val){ cout << "==================== dir.ls|take(2)\n"; cpps::DIRNAME dir("/tmp"); for (auto &&l: dir.ls()|views::take(2)){ cout << format ("file {}\n",l.name); } cout << "==================== cmd.run()|take(2)\n"; for (auto &&l:cmd.run() | views::take(2)){ cout << format ("{}{}-{}\n",l.is_error() ? "* " : "+ ",l.line.size(),l.line); } #if 1 cout << "==================== cmd.run()|match(arg)\n"; for (auto &&l:cmd.run() | cpps::match (arg.val)){ cout << format ("{}{}-{}\n",l.is_error() ? "* " : "+ ",l.line.size(),l.line); } #endif } #endif } return 0; } #endif