/* Pipestream: A simple C++ interface to UNIX pipes Copyright (C) 2005-2014 John C. Bowman, with contributions from Mojca Miklavec This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #if !defined(_WIN32) #include #include #include #include #include #include #include "pipestream.h" #include "common.h" #include "errormsg.h" #include "settings.h" #include "util.h" #include "interact.h" #include "lexical.h" #include "camperror.h" #include "pen.h" iopipestream *instance; void pipeHandler(int) { Signal(SIGPIPE,SIG_DFL); instance->pipeclose(); } void iopipestream::open(const mem::vector &command, const char *hint, const char *application, int out_fileno) { if(pipe(in) == -1) { ostringstream buf; buf << "in pipe failed: "; for(size_t i=0; i < command.size(); ++i) buf << command[i]; camp::reportError(buf); } if(pipe(out) == -1) { ostringstream buf; buf << "out pipe failed: "; for(size_t i=0; i < command.size(); ++i) buf << command[i]; camp::reportError(buf); } cout.flush(); // Flush stdout to avoid duplicate output. if((pid=fork()) < 0) { ostringstream buf; buf << "fork failed: "; for(size_t i=0; i < command.size(); ++i) buf << command[i]; camp::reportError(buf); } if(pid == 0) { if(interact::interactive) signal(SIGINT,SIG_IGN); close(in[1]); close(out[0]); close(STDIN_FILENO); close(out_fileno); dup2(in[0],STDIN_FILENO); dup2(out[1],out_fileno); close(in[0]); close(out[1]); char **argv=args(command); if(argv) execvp(argv[0],argv); execError(argv[0],hint,application); kill(0,SIGPIPE); _exit(-1); } instance=this; Signal(SIGPIPE,pipeHandler); close(out[1]); close(in[0]); *buffer=0; Running=true; pipeopen=true; pipein=true; block(false,true); } void iopipestream::eof() { if(pipeopen && pipein) { close(in[1]); pipein=false; } } void iopipestream::pipeclose() { if(pipeopen) { kill(pid,SIGHUP); eof(); close(out[0]); Running=false; pipeopen=false; waitpid(pid,NULL,0); // Avoid zombies. } } void iopipestream::block(bool write, bool read) { if(pipeopen) { int w=fcntl(in[1],F_GETFL); int r=fcntl(out[0],F_GETFL); fcntl(in[1],F_SETFL,write ? w & ~O_NONBLOCK : w | O_NONBLOCK); fcntl(out[0],F_SETFL,read ? r & ~O_NONBLOCK : r | O_NONBLOCK); } } ssize_t iopipestream::readbuffer() { ssize_t nc; char *p=buffer; ssize_t size=BUFSIZE-1; errno=0; for(;;) { if((nc=read(out[0],p,size)) < 0) { if(errno == EAGAIN || errno == EINTR) {p[0]=0; break;} else { ostringstream buf; buf << "read from pipe failed: errno=" << errno; camp::reportError(buf); } nc=0; } p[nc]=0; if(nc == 0) { if(waitpid(pid,NULL,WNOHANG) == pid) Running=false; break; } if(nc > 0) { if(settings::verbose > 2) cerr << p; break; } } return nc; } bool iopipestream::tailequals(const char *buf, size_t len, const char *prompt, size_t plen) { const char *a=buf+len; const char *b=prompt+plen; while(b >= prompt) { if(a < buf) return false; if(*a != *b) return false; // Handle MSDOS incompatibility: if(a > buf && *a == '\n' && *(a-1) == '\r') --a; --a; --b; } return true; } string iopipestream::readline() { sbuffer.clear(); int nc; do { nc=readbuffer(); sbuffer.append(buffer); } while(buffer[nc-1] != '\n' && Running); return sbuffer; } void iopipestream::wait(const char *prompt) { sbuffer.clear(); size_t plen=strlen(prompt); do { readbuffer(); if(*buffer == 0) camp::reportError(sbuffer); sbuffer.append(buffer); if(tailequals(sbuffer.c_str(),sbuffer.size(),prompt,plen)) break; } while(true); } int iopipestream::wait() { for(;;) { int status; if (waitpid(pid,&status,0) == -1) { if (errno == ECHILD) return 0; if (errno != EINTR) { ostringstream buf; buf << "Process " << pid << " failed"; camp::reportError(buf); } } else { if(WIFEXITED(status)) return WEXITSTATUS(status); else { ostringstream buf; buf << "Process " << pid << " exited abnormally"; camp::reportError(buf); } } } } void iopipestream::Write(const string &s) { ssize_t size=s.length(); if(settings::verbose > 2) cerr << s; if(write(in[1],s.c_str(),size) != size) { camp::reportFatal("write to pipe failed"); } } #endif