#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 8 process concurently static RETCOD tbcod[8]; static void fchild(int ) { 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; // fprintf (stderr,"fchild %d\n",pid); 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 = 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; sleep(10); // Should be interrupted when the child exited } } /* 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