1 /* Pipestream: A simple C++ interface to UNIX pipes
2    Copyright (C) 2005-2014 John C. Bowman,
3    with contributions from Mojca Miklavec
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU Lesser General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
18 
19 #include <iostream>
20 #include <cstring>
21 #include <cerrno>
22 #include <sstream>
23 #include <signal.h>
24 
25 #include "pipestream.h"
26 #include "common.h"
27 #include "errormsg.h"
28 #include "settings.h"
29 #include "util.h"
30 #include "interact.h"
31 #include "lexical.h"
32 #include "camperror.h"
33 #include "pen.h"
34 
open(const mem::vector<string> & command,const char * hint,const char * application,int out_fileno)35 void iopipestream::open(const mem::vector<string> &command, const char *hint,
36                         const char *application, int out_fileno)
37 {
38   if(pipe(in) == -1) {
39     ostringstream buf;
40     buf << "in pipe failed: ";
41     for(size_t i=0; i < command.size(); ++i) buf << command[i];
42     camp::reportError(buf);
43   }
44 
45   if(pipe(out) == -1) {
46     ostringstream buf;
47     buf << "out pipe failed: ";
48     for(size_t i=0; i < command.size(); ++i) buf << command[i];
49     camp::reportError(buf);
50   }
51   cout.flush(); // Flush stdout to avoid duplicate output.
52 
53   if((pid=fork()) < 0) {
54     ostringstream buf;
55     buf << "fork failed: ";
56     for(size_t i=0; i < command.size(); ++i) buf << command[i];
57     camp::reportError(buf);
58   }
59 
60   if(pid == 0) {
61     if(interact::interactive) signal(SIGINT,SIG_IGN);
62     close(in[1]);
63     close(out[0]);
64     close(STDIN_FILENO);
65     close(out_fileno);
66     dup2(in[0],STDIN_FILENO);
67     dup2(out[1],out_fileno);
68     close(in[0]);
69     close(out[1]);
70     char **argv=args(command);
71     if(argv) execvp(argv[0],argv);
72     execError(argv[0],hint,application);
73     kill(0,SIGTERM);
74     _exit(-1);
75   }
76   close(out[1]);
77   close(in[0]);
78   *buffer=0;
79   pipeopen=true;
80   pipein=true;
81   Running=true;
82   block(false,true);
83 }
84 
eof()85 void iopipestream::eof()
86 {
87   if(pipeopen && pipein) {
88     close(in[1]);
89     pipein=false;
90   }
91 }
92 
pipeclose()93 void iopipestream::pipeclose()
94 {
95   if(pipeopen) {
96     kill(pid,SIGTERM);
97     eof();
98     close(out[0]);
99     Running=false;
100     pipeopen=false;
101     waitpid(pid,NULL,0); // Avoid zombies.
102   }
103 }
104 
block(bool write,bool read)105 void iopipestream::block(bool write, bool read)
106 {
107   if(pipeopen) {
108     int w=fcntl(in[1],F_GETFL);
109     int r=fcntl(out[0],F_GETFL);
110     fcntl(in[1],F_SETFL,write ? w & ~O_NONBLOCK : w | O_NONBLOCK);
111     fcntl(out[0],F_SETFL,read ? r & ~O_NONBLOCK : r | O_NONBLOCK);
112   }
113 }
114 
readbuffer()115 ssize_t iopipestream::readbuffer()
116 {
117   ssize_t nc;
118   char *p=buffer;
119   ssize_t size=BUFSIZE-1;
120   errno=0;
121   for(;;) {
122     if((nc=read(out[0],p,size)) < 0) {
123       if(errno == EAGAIN) {p[0]=0; break;}
124       else camp::reportError("read from pipe failed");
125       nc=0;
126     }
127     p[nc]=0;
128     if(nc == 0) {
129       if(waitpid(pid,NULL,WNOHANG) == pid)
130         Running=false;
131       break;
132     }
133     if(nc > 0) {
134       if(settings::verbose > 2) cerr << p;
135       break;
136     }
137   }
138   return nc;
139 }
140 
tailequals(const char * buf,size_t len,const char * prompt,size_t plen)141 bool iopipestream::tailequals(const char *buf, size_t len, const char *prompt,
142                               size_t plen)
143 {
144   const char *a=buf+len;
145   const char *b=prompt+plen;
146   while(b >= prompt) {
147     if(a < buf) return false;
148     if(*a != *b) return false;
149     // Handle MSDOS incompatibility:
150     if(a > buf && *a == '\n' && *(a-1) == '\r') --a;
151     --a; --b;
152   }
153   return true;
154 }
155 
readline()156 string iopipestream::readline()
157 {
158   sbuffer.clear();
159   int nc;
160   do {
161     nc=readbuffer();
162     sbuffer.append(buffer);
163   } while(buffer[nc-1] != '\n' && Running);
164   return sbuffer;
165 }
166 
wait(const char * prompt)167 void iopipestream::wait(const char *prompt)
168 {
169   sbuffer.clear();
170   size_t plen=strlen(prompt);
171 
172   do {
173     readbuffer();
174     sbuffer.append(buffer);
175   } while(!tailequals(sbuffer.c_str(),sbuffer.size(),prompt,plen));
176 }
177 
wait()178 int iopipestream::wait()
179 {
180   for(;;) {
181     int status;
182     if (waitpid(pid,&status,0) == -1) {
183       if (errno == ECHILD) return 0;
184       if (errno != EINTR) {
185         ostringstream buf;
186         buf << "Process " << pid << " failed";
187         camp::reportError(buf);
188       }
189     } else {
190       if(WIFEXITED(status)) return WEXITSTATUS(status);
191       else {
192         ostringstream buf;
193         buf << "Process " << pid << " exited abnormally";
194         camp::reportError(buf);
195       }
196     }
197   }
198 }
199 
Write(const string & s)200 void iopipestream::Write(const string &s)
201 {
202   ssize_t size=s.length();
203   if(settings::verbose > 2) cerr << s;
204   if(write(in[1],s.c_str(),size) != size) {
205     camp::reportFatal("write to pipe failed");
206   }
207 }
208