patch by Pieter Grimmerink: -call waitpid on the child, after it completes. This...
[vuplus_dvbapp] / lib / base / console.cpp
1 #include <lib/base/console.h>
2 #include <lib/base/eerror.h>
3 #include <sys/vfs.h> // for statfs
4 #include <unistd.h>
5 #include <signal.h>
6 #include <errno.h>
7 #include <poll.h>
8 #include <sys/types.h>
9 #include <sys/wait.h>
10
11 int bidirpipe(int pfd[], char *cmd , char *argv[])
12 {
13         int pfdin[2];  /* from child to parent */
14         int pfdout[2]; /* from parent to child */
15         int pfderr[2]; /* stderr from child to parent */
16         int pid;       /* child's pid */
17
18         if ( pipe(pfdin) == -1 || pipe(pfdout) == -1 || pipe(pfderr) == -1)
19                 return(-1);
20
21         if ( ( pid = fork() ) == -1 )
22                 return(-1);
23         else if (pid == 0) /* child process */
24         {
25                 setsid();
26                 if ( close(0) == -1 || close(1) == -1 || close(2) == -1 )
27                         _exit(0);
28
29                 if (dup(pfdout[0]) != 0 || dup(pfdin[1]) != 1 || dup(pfderr[1]) != 2 )
30                         _exit(0);
31
32                 if (close(pfdout[0]) == -1 || close(pfdout[1]) == -1 ||
33                                 close(pfdin[0]) == -1 || close(pfdin[1]) == -1 ||
34                                 close(pfderr[0]) == -1 || close(pfderr[1]) == -1 )
35                         _exit(0);
36
37                 for (unsigned int i=3; i < 90; ++i )
38                         close(i);
39
40                 execvp(cmd,argv);
41                 _exit(0);
42         }
43         if (close(pfdout[0]) == -1 || close(pfdin[1]) == -1 || close(pfderr[1]) == -1)
44                         return(-1);
45
46         pfd[0] = pfdin[0];
47         pfd[1] = pfdout[1];
48         pfd[2] = pfderr[0];
49
50         return(pid);
51 }
52
53 eConsoleAppContainer::eConsoleAppContainer()
54 :pid(-1), killstate(0), in(0), out(0), err(0)
55 {
56         for (int i=0; i < 3; ++i)
57                 fd[i]=-1;
58 }
59
60 int eConsoleAppContainer::execute( const std::string &cmd )
61 {
62         if (running())
63                 return -1;
64         pid=-1;
65         killstate=0;
66 //      eDebug("cmd = %s", cmd.c_str() );
67         int cnt=2; // path to app + terminated 0
68         std::string str(cmd.length()?cmd:"");
69
70         // kill spaces at beginning
71         unsigned int pos = str.find_first_not_of(' ');
72         if (pos != std::string::npos && pos)
73                 str = str.substr(pos);
74
75         // kill spaces at the end
76         pos = str.find_last_not_of(' ');
77         if (pos != std::string::npos && (pos+1) < str.length())
78                 str = str.erase(pos+1);
79
80         unsigned int slen=str.length();
81         if (!slen)
82                 return -2;
83
84         std::map<char,char> brackets;
85         brackets.insert(std::pair<char,char>('\'','\''));
86         brackets.insert(std::pair<char,char>('"','"'));
87         brackets.insert(std::pair<char,char>('`','`'));
88         brackets.insert(std::pair<char,char>('(',')'));
89         brackets.insert(std::pair<char,char>('{','}'));
90         brackets.insert(std::pair<char,char>('[',']'));
91         brackets.insert(std::pair<char,char>('<','>'));
92
93         unsigned int idx=str.find(' ');
94         std::string path = str.substr(0, idx != std::string::npos ? idx : slen );
95 //      eDebug("path = %s", path.c_str() );
96         unsigned int plen = path.length();
97
98         std::string cmds = slen > plen ? str.substr( plen+1 ) : "";
99         unsigned int clen = cmds.length();
100 //      eDebug("cmds = %s", cmds.c_str() );
101
102         idx = 0;
103         std::map<char,char>::iterator it = brackets.find(cmds[idx]);
104         while ( (idx = cmds.find(' ',idx) ) != std::string::npos )  // count args
105         {
106                 if (it != brackets.end())
107                 {
108                         if (cmds[idx-1] == it->second)
109                                 it = brackets.end();
110                 }
111                 if (it == brackets.end())
112                 {
113                         cnt++;
114                         it = brackets.find(cmds[idx+1]);
115                 }
116                 idx++;
117         }
118
119 //      eDebug("idx = %d, %d counted spaces", idx, cnt-2);
120
121         if ( clen )
122         {
123                 cnt++;
124 //              eDebug("increase cnt");
125         }
126
127 //      eDebug("%d args", cnt-2);
128         char **argv = new char*[cnt];  // min two args... path and terminating 0
129 //      eDebug("%d args", cnt);
130         argv[0] = new char[ plen+1 ];
131 //      eDebug("new argv[0] %d bytes (%s)", plen+1, path.c_str());
132         strcpy( argv[0], path.c_str() );
133         argv[cnt-1] = 0;               // set terminating null
134
135         if ( cnt > 2 )  // more then default args?
136         {
137                 cnt=1;  // do not overwrite path in argv[0]
138
139                 it = brackets.find(cmds[0]);
140                 idx=0;
141                 while ( (idx = cmds.find(' ',idx)) != std::string::npos )  // parse all args..
142                 {
143                         bool bracketClosed=false;
144                         if ( it != brackets.end() )
145                         {
146                                 if (cmds[idx-1]==it->second)
147                                 {
148                                         it = brackets.end();
149                                         bracketClosed=true;
150                                 }
151                         }
152                         if ( it == brackets.end() )
153                         {
154                                 std::string tmp = cmds.substr(0, idx);
155                                 if (bracketClosed)
156                                 {
157                                         tmp.erase(0,1);
158                                         tmp.erase(tmp.length()-1, 1);
159                                         bracketClosed=false;
160                                 }
161 //                              eDebug("new argv[%d] %d bytes (%s)", cnt, tmp.length()+1, tmp.c_str());
162                                 argv[cnt] = new char[ tmp.length()+1 ];
163 //                              eDebug("idx=%d, arg = %s", idx, tmp.c_str() );
164                                 strcpy( argv[cnt++], tmp.c_str() );
165                                 cmds.erase(0, idx+1);
166 //                              eDebug("str = %s", cmds.c_str() );
167                                 it = brackets.find(cmds[0]);
168                                 idx=0;
169                         }
170                         else
171                                 idx++;
172                 }
173                 if ( it != brackets.end() )
174                 {
175                         cmds.erase(0,1);
176                         cmds.erase(cmds.length()-1, 1);
177                 }
178                 // store the last arg
179 //              eDebug("new argv[%d] %d bytes (%s)", cnt, cmds.length()+1, cmds.c_str());
180                 argv[cnt] = new char[ cmds.length()+1 ];
181                 strcpy( argv[cnt], cmds.c_str() );
182         }
183         else
184                 cnt=1;
185
186   // get one read ,one write and the err pipe to the prog..
187
188 //      int tmp=0;
189 //      while(argv[tmp])
190 //              eDebug("%d is %s", tmp, argv[tmp++]);
191   
192         pid = bidirpipe(fd, argv[0], argv);
193
194         while ( cnt >= 0 )  // release heap memory
195         {
196 //              eDebug("delete argv[%d]", cnt);
197                 delete [] argv[cnt--];
198         }
199 //      eDebug("delete argv");
200         delete [] argv;
201         
202         if ( pid == -1 )
203                 return -3;
204
205 //      eDebug("pipe in = %d, out = %d, err = %d", fd[0], fd[1], fd[2]);
206
207         in = new eSocketNotifier(eApp, fd[0], eSocketNotifier::Read|eSocketNotifier::Priority|eSocketNotifier::Hungup );
208         out = new eSocketNotifier(eApp, fd[1], eSocketNotifier::Write, false);  
209         err = new eSocketNotifier(eApp, fd[2], eSocketNotifier::Read|eSocketNotifier::Priority );
210         CONNECT(in->activated, eConsoleAppContainer::readyRead);
211         CONNECT(out->activated, eConsoleAppContainer::readyWrite);
212         CONNECT(err->activated, eConsoleAppContainer::readyErrRead);
213         return 0;
214 }
215
216 eConsoleAppContainer::~eConsoleAppContainer()
217 {
218         kill();
219 }
220
221 void eConsoleAppContainer::kill()
222 {
223         if ( killstate != -1 && pid != -1 )
224         {
225                 eDebug("user kill(SIGKILL) console App");
226                 killstate=-1;
227                 /*
228                  * Use a negative pid value, to signal the whole process group
229                  * ('pid' might not even be running anymore at this point)
230                  */
231                 ::kill(-pid, SIGKILL);
232                 closePipes();
233         }
234         while( outbuf.size() ) // cleanup out buffer
235         {
236                 queue_data d = outbuf.front();
237                 outbuf.pop();
238                 delete [] d.data;
239         }
240         delete in;
241         delete out;
242         delete err;
243         in=out=err=0;
244 }
245
246 void eConsoleAppContainer::sendCtrlC()
247 {
248         if ( killstate != -1 && pid != -1 )
249         {
250                 eDebug("user send SIGINT(Ctrl-C) to console App");
251                 /*
252                  * Use a negative pid value, to signal the whole process group
253                  * ('pid' might not even be running anymore at this point)
254                  */
255                 ::kill(-pid, SIGINT);
256         }
257 }
258
259 void eConsoleAppContainer::closePipes()
260 {
261         if (in)
262                 in->stop();
263         if (out)
264                 out->stop();
265         if (err)
266                 err->stop();
267         if (fd[0] != -1)
268         {
269                 ::close(fd[0]);
270                 fd[0]=-1;
271         }
272         if (fd[1] != -1)
273         {
274                 ::close(fd[1]);
275                 fd[1]=-1;
276         }
277         if (fd[2] != -1)
278         {
279                 ::close(fd[2]);
280                 fd[2]=-1;
281         }
282         eDebug("pipes closed");
283         while( outbuf.size() ) // cleanup out buffer
284         {
285                 queue_data d = outbuf.front();
286                 outbuf.pop();
287                 delete [] d.data;
288         }
289         pid = -1;
290 }
291
292 void eConsoleAppContainer::readyRead(int what)
293 {
294         bool hungup = what & eSocketNotifier::Hungup;
295         if (what & (eSocketNotifier::Priority|eSocketNotifier::Read))
296         {
297 //              eDebug("what = %d");
298                 char buf[2048];
299                 int rd;
300                 while((rd = read(fd[0], buf, 2047)) > 0)
301                 {
302 /*                      for ( int i = 0; i < rd; i++ )
303                                 eDebug("%d = %c (%02x)", i, buf[i], buf[i] );*/
304                         buf[rd]=0;
305                         /*emit*/ dataAvail(buf);
306                         if (!hungup)
307                                 break;
308                 }
309         }
310         if (hungup)
311         {
312                 eDebug("child has terminated");
313                 closePipes();
314                 int childstatus;
315                 int retval = killstate;
316                 /*
317                  * We have to call 'wait' on the child process, in order to avoid zombies.
318                  * Also, this gives us the chance to provide better exit status info to appClosed.
319                  */
320                 if (::waitpid(pid, &childstatus, 0) > 0)
321                 {
322                         if (WIFEXITED(childstatus))
323                         {
324                                 retval = WEXITSTATUS(childstatus);
325                         }
326                 }
327                 /*emit*/ appClosed(retval);
328         }
329 }
330
331 void eConsoleAppContainer::readyErrRead(int what)
332 {
333         if (what & (eSocketNotifier::Priority|eSocketNotifier::Read))
334         {
335 //              eDebug("what = %d");
336                 char buf[2048];
337                 int rd;
338                 while((rd = read(fd[2], buf, 2047)) > 0)
339                 {
340 /*                      for ( int i = 0; i < rd; i++ )
341                                 eDebug("%d = %c (%02x)", i, buf[i], buf[i] );*/
342                         buf[rd]=0;
343                         /*emit*/ dataAvail(buf);
344                 }
345         }
346 }
347
348 void eConsoleAppContainer::write( const char *data, int len )
349 {
350         char *tmp = new char[len];
351         memcpy(tmp, data, len);
352         outbuf.push(queue_data(tmp,len));
353         out->start();
354 }
355
356 void eConsoleAppContainer::readyWrite(int what)
357 {
358         if (what&eSocketNotifier::Write && outbuf.size() )
359         {
360                 queue_data d = outbuf.front();
361                 outbuf.pop();
362                 if ( ::write( fd[1], d.data, d.len ) != d.len )
363                 {
364                         /* emit */ dataSent(-1);
365 //                      eDebug("writeError");
366                 }
367                 else
368                 {
369                         /* emit */ dataSent(0);
370 //                      eDebug("write ok");
371                 }
372                 delete [] d.data;
373         }
374         if ( !outbuf.size() )
375                 out->stop();
376 }