#pragma implementation #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "misc.h" #include "popen.h" #include #include #include static DEBUG_KEY keypopen ("popen","Sub-process execution"); 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; } PROTECTED 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; } PROTECTED POPENFD::POPENFD() { bytesent = false; eofout = eoferr = false; fds.in = fds.out = fds.err = fds.ctl = -1; fds.fin = NULL; } PUBLIC POPENFD::POPENFD(int handlein, int handleout) { bytesent = false; setfds (handlein,handleout,-1,-1); eofout = eoferr = false; } PUBLIC VIRTUAL 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 */ PUBLIC VIRTUAL bool POPENFD::isok() { return fds.in != -1; } PUBLIC bool POPENFD::iseof() { return eofout && eoferr; } PRIVATE void POPENFD::readif ( fd_set *in, int fd, SSTRING &buf, bool &ctlmsg, bool &eof) { if (popen_fd_isset(fd,*in)){ char bufread[10000]; int len = read (fd,bufread,sizeof(bufread)-1); if (len > 0){ bufread[len] = '\0'; buf.append (bufread); }else{ // end of process eof = true; ctlmsg = true; } } } PUBLIC 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; } PUBLIC 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 */ PROTECTED int POPENFD::wait(int timeout, int otherfd, bool &ctlmsg) { int ret = -1; 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,&tim); if (ret != -1 || errno != EINTR) break; //fprintf (stderr,"POPEN eintr\n"); } ret = process (ret,in,otherfd,ctlmsg); // fprintf (stderr,"after process %d %d\n",ret,ctlmsg); } 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 */ PUBLIC 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 */ PUBLIC int POPENFD::wait(int timeout) { return wait (timeout,-1); } PUBLIC void POPENFD::seteof() { eofout = eoferr = true; } /* Grab the PIPE handles used by this object to prevent it from closing them. Return the number of handle placed in tb[]. This trickery is needed for startup scripts which goes in background but still try to "talk" on stdout/stderr. This is brain dead. If we close the PIPE too soon, we end up with a dead process (it receives SIGPIPE). The caller of this object grabs the PIPE handles and close it after some reasonnable delay. */ PUBLIC void POPENFD::grabhandles (int &in, int &out, int &err) { out = fds.out; err = fds.err; in = -1; if (fds.in != -1){ in = dup(fds.in); // We need a dup because fdopen does not // duplicate the handle, so fclose below is // also closing fds.in, not only fds.fin if (fds.in == fds.out) out = in; // fds.out may have been opened // with socketpair(), so is the // same as fds.in fclose (fds.fin); fds.fin = NULL; fds.in = -1; } fds.err = fds.out = -1; } /* Read one complete line or up to size byte in "line". If there is no complete line, nothing is read */ PRIVATE int POPENFD::readline (char *line, int size, SSTRING &buf, bool eof) { char *begin = line; const char *pt = buf.get(); while (1){ if (*pt == '\0'){ if (eof){ *line = '\0'; buf.setfrom (""); }else{ // No full line to read, keep the buffer untouched // until we get the \n line = begin; *begin = '\0'; } break; }else{ char carac = *pt++; *line++ = carac; size--; if (size == 0 || carac == '\n'){ buf.setfrom (pt); *line = '\0'; break; } } } return line > begin ? 0 : -1; } /* Read one line of stderr if available (won't block). Return -1 if no more to read. */ PUBLIC int POPENFD::readerr (char *line, int size) { return readline (line,size,errbuf,eoferr); } /* Read one line of stdout if available (won't block). Return -1 if no more to read. */ PUBLIC int POPENFD::readout (char *line, int size) { return readline (line,size,outbuf,eofout); } /* 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. */ PUBLIC 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; } /* Preset the out buffer with some pre-read lines */ PUBLIC void POPENFD::loadout (const char *lines) { outbuf.append (lines); } /* get the FILE handle used to send strings to the process. The application does not have to close it. */ PUBLIC FILE *POPENFD::getfout () { // We assume the application will send commands or message to the // process bytesent = true; return fds.fin; } /* Send a string to the standard input of the process */ PUBLIC void POPENFD::send (const char *msg) { if (fds.fin != NULL){ bytesent = true; fputs (msg,fds.fin); } } /* Send a buffer to the standard input of the process */ PUBLIC void POPENFD::send (const void *msg, int len) { if (fds.fin != NULL){ bytesent = true; fwrite (msg,1,len,fds.fin); } } /* Flush the output buffer */ PUBLIC void POPENFD::flush () { if (fds.fin != NULL) fflush (fds.fin); } /* Send a formatted string to the standard input of the process */ PUBLIC void POPENFD::sendf (const char *ctl, ...) { char line[1000]; va_list list; va_start (list,ctl); vsnprintf (line,sizeof(line)-1,ctl,list); va_end (list); send (line); } 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"); } snprintf (homestr,sizeof(homestr)-1,"HOME=%s",home); tb[2] = homestr; 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]); char newcommand[strlen(command)+1]; if (sockholder == NULL){ dup2 (fdin[0],0); dup2 (fdout[1],1); ::close (fdin[0]); ::close (fdin[1]); ::close (fdout[0]); ::close (fdout[1]); }else{ int offset = (int)(sockholder - command); strcpy (newcommand,command); int len = sprintf (newcommand+offset,"%d",fdinout[1]); strcpy (newcommand+offset+len,command+offset+8); command = newcommand; ::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 (command); 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); } popen_reservpid (pid); ::close (fderr[1]); ::close (fdctl[1]); if (sockholder != NULL){ ::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]); } } } PUBLIC POPEN::POPEN( const char *command, int uid) // Effective and real UID of the new process { init (command,uid,false,false); } PUBLIC POPEN::POPEN( const char *command, int uid, // Effective and real UID of the new process bool keepenv, bool keepcwd) { init (command,uid,keepenv,keepcwd); } PUBLIC POPEN::POPEN(const char *command) { init (command,geteuid(),false,false); } PRIVATE void POPEN::initarg( const char *command, const char *args, int uid) { pid = -1; status = -1; const char *path = command; if (path[0] != '/') path = daemon_findpath (command); if (path != NULL){ SSTRING cmd; cmd.setfromf ("%s %s",path,args); init (cmd.get(),uid,false,false); }else{ seteof(); } } /* 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; } PUBLIC POPEN::POPEN(const char *command, const char *args) { initarg(command,args,geteuid()); } PUBLIC POPEN::POPEN(const char *command, const char *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 */ PUBLIC POPENUSER::POPENUSER(const char *cmd) : POPEN (cmd,getuid(),true,true) { } PUBLIC 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(); } PUBLIC void POPEN::kill() { debug_printf (keypopen,"killing %d\n",pid); if (pid != -1){ int child_pid = process_findchild(pid); debug_printf (keypopen,"kill child_pid %d\n",child_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 */ PUBLIC void POPEN::forget() { popen_forgetpid (pid); pid = -1; } PRIVATE 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 */ PRIVATE void POPEN::waitend () { debug_printf (keypopen,"waitend pid=%d\n",pid); 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){ debug_printf (keypopen,"waitpid1 == -1 pid=%d (%s)\n",pid,strerror(errno)); waitone(); debug_printf (keypopen,"waitpid2 == -1 pid=%d\n",pid); }else{ if ((status & 0xff)==0) status >>= 8; debug_printf (keypopen,"waitpid ok pid=%d\n",pid); } popen_forgetpid (pid); pid = -1; break; } } /* Return != 0 if the pipe is corretly opened */ PUBLIC bool POPEN::isok() { return pid != -1; } /* Check if some signal was received about child death */ PRIVATE 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 */ PUBLIC 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 debug_printf (keypopen,"wait timeout %d otherfd %d pid %d\n",timeout,otherfd,pid); 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 */ PUBLIC int POPEN::wait(int timeout) { return wait (timeout,-1); } /* Return the status code of the ending process */ PUBLIC int POPEN::getstatus() { return status; } /* Closing the PIPE to standard input of the command. */ PUBLIC 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 */ PUBLIC 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(); } /* The SSTREAM_POPEN reads or writes to a POPEN process */ PUBLIC SSTREAM_POPEN::SSTREAM_POPEN(POPENFD &_pop) { offset = 0; pop = &_pop; } PUBLIC SSTREAM_POPEN::~SSTREAM_POPEN() { } PUBLIC void SSTREAM_POPEN::puts(const char *s) { pop->send (s); offset += strlen(s); } PUBLIC char *SSTREAM_POPEN::gets(char *s, int maxsiz) { char *ret = NULL; while (1){ if (pop->readout(s,maxsiz)==0){ offset += strlen(s); ret = s; break; }else if (pop->wait(1)< 0){ break; } } return ret; } PUBLIC long SSTREAM_POPEN::getoffset() { return offset; } /* Object used to wait for several POPEN at once */ PUBLIC POPENWAIT::POPENWAIT ( POPENFD &_po, int _timeout) { po = &_po; fds = NULL; maxfd = 0; timeout = _timeout; retcode = 0; } PUBLIC POPENWAIT::POPENWAIT ( fd_set *_fds, int _maxfd, int _timeout) { po = NULL; fds = _fds; maxfd = _maxfd; timeout = _timeout; retcode = 0; } /* Return the return code of the wait function */ PUBLIC int POPENWAIT::getretcode () { return retcode; } PUBLIC POPENWAIT *POPENWAITS::getitem(int no) const { return (POPENWAIT*)ARRAY::getitem (no); } PUBLIC int POPENWAITS::wait() { int ret = -1; fd_set in; FD_ZERO(&in); int maxfd = 0; long timeout = 1000000; for (int i=0; ipo != NULL){ maxfd = p->po->setup (in,maxfd,-1); }else if (p->fds != NULL){ if (maxfd < p->maxfd) maxfd = p->maxfd; for (unsigned j=0; jfds->fds_bits[j]; } } if (p->timeout < timeout) timeout = p->timeout; } struct timeval tim; tim.tv_usec = 0; tim.tv_sec = timeout; ret = select (maxfd+1,&in,NULL,NULL,&tim); // fprintf (stderr,"waits ret = %d\n",ret); for (int i=0; ipo != NULL){ bool ctlmsg = false; p->retcode = p->po->process (ret,in,-1,ctlmsg); }else if (p->fds != NULL){ // We change p->fds only if some handles have data p->retcode = 0; fd_set newfds; for (unsigned j=0; jfds->fds_bits[j]; newfds.fds_bits[j] = bits; if (bits != 0) p->retcode = 1; } if (p->retcode != 0) *p->fds = newfds; } // fprintf (stderr,"retcode %d\n",p->retcode); } return ret; } /* Return true if at least one POPENWAIT object has some data waiting */ PUBLIC bool POPENWAITS::hasdata() { bool ret = false; for (int i=0; iretcode > 0 || (p->po != NULL && p->po->iseof())){ ret = true; break; } } return ret; } #ifdef TEST int simul_isdemo(){return 0;} int revision; int main (int argc, char *argv[]) { if (argc > 1){ char buf[1000]; char *pt = buf; for (int i=1; i0){ while (p.readout(buf,sizeof(buf)-1)!=-1){ fputs (buf,stdout); } }else{ break; } } } return 0; } #endif