emit seperate signals for stdout and stderr pipes, allow cat'ing file content into...
[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 #include <fcntl.h>
11
12 int bidirpipe(int pfd[], const char *cmd , const char * const argv[], const char *cwd )
13 {
14         int pfdin[2];  /* from child to parent */
15         int pfdout[2]; /* from parent to child */
16         int pfderr[2]; /* stderr from child to parent */
17         int pid;       /* child's pid */
18
19         if ( pipe(pfdin) == -1 || pipe(pfdout) == -1 || pipe(pfderr) == -1)
20                 return(-1);
21
22         if ( ( pid = vfork() ) == -1 )
23                 return(-1);
24         else if (pid == 0) /* child process */
25         {
26                 setsid();
27                 if ( close(0) == -1 || close(1) == -1 || close(2) == -1 )
28                         _exit(0);
29
30                 if (dup(pfdout[0]) != 0 || dup(pfdin[1]) != 1 || dup(pfderr[1]) != 2 )
31                         _exit(0);
32
33                 if (close(pfdout[0]) == -1 || close(pfdout[1]) == -1 ||
34                                 close(pfdin[0]) == -1 || close(pfdin[1]) == -1 ||
35                                 close(pfderr[0]) == -1 || close(pfderr[1]) == -1 )
36                         _exit(0);
37
38                 for (unsigned int i=3; i < 90; ++i )
39                         close(i);
40
41                 if (cwd)
42                         chdir(cwd);
43
44                 execvp(cmd, (char * const *)argv); 
45                                 /* the vfork will actually suspend the parent thread until execvp is called. thus it's ok to use the shared arg/cmdline pointers here. */
46                 _exit(0);
47         }
48         if (close(pfdout[0]) == -1 || close(pfdin[1]) == -1 || close(pfderr[1]) == -1)
49                         return(-1);
50
51         pfd[0] = pfdin[0];
52         pfd[1] = pfdout[1];
53         pfd[2] = pfderr[0];
54
55         return(pid);
56 }
57
58 eConsoleAppContainer::eConsoleAppContainer()
59 :pid(-1), killstate(0), in(0), out(0), err(0)
60 {
61         for (int i=0; i < 3; ++i)
62         {
63                 fd[i]=-1;
64                 filefd[i]=-1;
65         }
66 }
67
68 static char brakets[][2] = {
69         { '\'','\'' },
70         {'"','"'},
71         {'`','`'},
72         {'(',')'},
73         {'{','}'},
74         {'[',']'},
75         {'<','>'}
76 };
77
78 static char *find_bracket(char ch)
79 {
80         size_t idx=0;
81         while (idx < sizeof(brakets)/2) {
82                 if (brakets[idx][0] == ch)
83                         return &brakets[idx][0];
84                 ++idx;
85         }
86         return NULL;
87 }
88
89 int eConsoleAppContainer::setCWD( const char *path )
90 {
91         struct stat dir_stat;
92
93         if (stat(path, &dir_stat) == -1)
94                 return -1;
95
96         if (!S_ISDIR(dir_stat.st_mode))
97                 return -2;
98
99         m_cwd = path;
100         return 0;
101 }
102
103 int eConsoleAppContainer::execute( const char *cmd )
104 {
105         int cnt=0, slen=strlen(cmd);
106         char buf[slen+1];
107         char *tmp=0, *argv[64], *path=buf, *cmds = buf;
108         memcpy(buf, cmd, slen+1);
109
110 //      printf("cmd = %s, len %d\n", cmd, slen);
111
112         // kill spaces at beginning
113         while(path[0] == ' ') {
114                 ++path;
115                 ++cmds;
116                 --slen;
117         }
118
119         // kill spaces at the end
120         while(slen && path[slen-1] == ' ') {
121                 path[slen-1] = 0;
122                 --slen;
123         }
124
125         if (!slen)
126                 return -2;
127
128         tmp = strchr(path, ' ');
129         if (tmp) {
130                 *tmp = 0;
131                 cmds = tmp+1;
132                 while(*cmds && *cmds == ' ')
133                         ++cmds;
134         }
135         else
136                 cmds = path+slen;
137
138         memset(argv, 0, sizeof(argv));
139         argv[cnt++] = path;
140
141         if (*cmds) {
142                 char *argb=NULL, *it=NULL;
143                 while ( (tmp = strchr(cmds, ' ')) ) {
144                         if (!it && *cmds && (it = find_bracket(*cmds)) )
145                                 *cmds = 'X'; // replace open braket...
146                         if (!argb) // not arg begin
147                                 argb = cmds;
148                         if (it && *(tmp-1) == it[1]) {
149                                 *argb = it[0]; // set old char for open braket
150                                 it = 0;
151                         }
152                         if (!it) { // end of arg
153                                 *tmp = 0;
154                                 argv[cnt++] = argb;
155                                 argb=0; // reset arg begin
156                         }
157                         cmds = tmp+1;
158                         while (*cmds && *cmds == ' ')
159                                 ++cmds;
160                 }
161                 argv[cnt++] = argb ? argb : cmds;
162                 if (it)
163                     *argv[cnt-1] = it[0]; // set old char for open braket
164         }
165
166 //      int tmp=0;
167 //      while(argv[tmp])
168 //              eDebug("%d is %s", tmp, argv[tmp++]);
169         return execute(argv[0], argv);
170 }
171
172 int eConsoleAppContainer::execute(const char *cmdline, const char * const argv[])
173 {
174         if (running())
175                 return -1;
176
177         pid=-1;
178         killstate=0;
179
180         // get one read ,one write and the err pipe to the prog..
181         pid = bidirpipe(fd, cmdline, argv, m_cwd.length() ? m_cwd.c_str() : 0);
182
183         if ( pid == -1 )
184                 return -3;
185
186 //      eDebug("pipe in = %d, out = %d, err = %d", fd[0], fd[1], fd[2]);
187
188         ::fcntl(fd[1], F_SETFL, O_NONBLOCK);
189         ::fcntl(fd[2], F_SETFL, O_NONBLOCK);
190         in = new eSocketNotifier(eApp, fd[0], eSocketNotifier::Read|eSocketNotifier::Priority|eSocketNotifier::Hungup );
191         out = new eSocketNotifier(eApp, fd[1], eSocketNotifier::Write, false);  
192         err = new eSocketNotifier(eApp, fd[2], eSocketNotifier::Read|eSocketNotifier::Priority );
193         CONNECT(in->activated, eConsoleAppContainer::readyRead);
194         CONNECT(out->activated, eConsoleAppContainer::readyWrite);
195         CONNECT(err->activated, eConsoleAppContainer::readyErrRead);
196
197         return 0;
198 }
199
200 int eConsoleAppContainer::execute( PyObject *cmdline, PyObject *args )
201 {
202         if (!PyString_Check(cmdline))
203                 return -1;
204         if (!PyList_Check(args))
205                 return -1;
206         const char *argv[PyList_Size(args) + 1];
207         int i;
208         for (i = 0; i < PyList_Size(args); ++i)
209         {
210                 PyObject *arg = PyList_GetItem(args, i); /* borrowed ref */
211                 if (!arg)
212                         return -1;
213                 if (!PyString_Check(arg))
214                         return -1;
215                 argv[i] = PyString_AsString(arg); /* borrowed pointer */
216         }
217         argv[i] = 0;
218
219         return execute(PyString_AsString(cmdline), argv); /* borrowed pointer */
220 }
221
222 eConsoleAppContainer::~eConsoleAppContainer()
223 {
224         kill();
225 }
226
227 void eConsoleAppContainer::kill()
228 {
229         if ( killstate != -1 && pid != -1 )
230         {
231                 eDebug("user kill(SIGKILL) console App");
232                 killstate=-1;
233                 /*
234                  * Use a negative pid value, to signal the whole process group
235                  * ('pid' might not even be running anymore at this point)
236                  */
237                 ::kill(-pid, SIGKILL);
238                 closePipes();
239         }
240         while( outbuf.size() ) // cleanup out buffer
241         {
242                 queue_data d = outbuf.front();
243                 outbuf.pop();
244                 delete [] d.data;
245         }
246         delete in;
247         delete out;
248         delete err;
249         in=out=err=0;
250
251         for (int i=0; i < 3; ++i)
252         {
253                 if ( filefd[i] > 0 )
254                         close(filefd[i]);
255         }
256 }
257
258 void eConsoleAppContainer::sendCtrlC()
259 {
260         if ( killstate != -1 && pid != -1 )
261         {
262                 eDebug("user send SIGINT(Ctrl-C) to console App");
263                 /*
264                  * Use a negative pid value, to signal the whole process group
265                  * ('pid' might not even be running anymore at this point)
266                  */
267                 ::kill(-pid, SIGINT);
268         }
269 }
270
271 void eConsoleAppContainer::closePipes()
272 {
273         if (in)
274                 in->stop();
275         if (out)
276                 out->stop();
277         if (err)
278                 err->stop();
279         if (fd[0] != -1)
280         {
281                 ::close(fd[0]);
282                 fd[0]=-1;
283         }
284         if (fd[1] != -1)
285         {
286                 ::close(fd[1]);
287                 fd[1]=-1;
288         }
289         if (fd[2] != -1)
290         {
291                 ::close(fd[2]);
292                 fd[2]=-1;
293         }
294         eDebug("pipes closed");
295         while( outbuf.size() ) // cleanup out buffer
296         {
297                 queue_data d = outbuf.front();
298                 outbuf.pop();
299                 delete [] d.data;
300         }
301         pid = -1;
302 }
303
304 void eConsoleAppContainer::readyRead(int what)
305 {
306         bool hungup = what & eSocketNotifier::Hungup;
307         if (what & (eSocketNotifier::Priority|eSocketNotifier::Read))
308         {
309 //              eDebug("what = %d");
310                 char buf[2049];
311                 int rd;
312                 while((rd = read(fd[0], buf, 2048)) > 0)
313                 {
314                         buf[rd]=0;
315                         /*emit*/ dataAvail(buf);
316                         stdoutAvail(buf);
317                         if ( filefd[1] > 0 )
318                                 ::write(filefd[1], buf, rd);
319                         if (!hungup)
320                                 break;
321                 }
322         }
323         readyErrRead(eSocketNotifier::Priority|eSocketNotifier::Read); /* be sure to flush all data which might be already written */
324         if (hungup)
325         {
326                 eDebug("child has terminated");
327                 closePipes();
328                 int childstatus;
329                 int retval = killstate;
330                 /*
331                  * We have to call 'wait' on the child process, in order to avoid zombies.
332                  * Also, this gives us the chance to provide better exit status info to appClosed.
333                  */
334                 if (::waitpid(pid, &childstatus, 0) > 0)
335                 {
336                         if (WIFEXITED(childstatus))
337                         {
338                                 retval = WEXITSTATUS(childstatus);
339                         }
340                 }
341                 /*emit*/ appClosed(retval);
342         }
343 }
344
345 void eConsoleAppContainer::readyErrRead(int what)
346 {
347         if (what & (eSocketNotifier::Priority|eSocketNotifier::Read))
348         {
349 //              eDebug("what = %d");
350                 char buf[2049];
351                 int rd;
352                 while((rd = read(fd[2], buf, 2048)) > 0)
353                 {
354 /*                      for ( int i = 0; i < rd; i++ )
355                                 eDebug("%d = %c (%02x)", i, buf[i], buf[i] );*/
356                         buf[rd]=0;
357                         /*emit*/ dataAvail(buf);
358                         stderrAvail(buf);
359                 }
360         }
361 }
362
363 void eConsoleAppContainer::dumpToFile( PyObject *py_filename )
364 {
365         char *filename = PyString_AsString(py_filename);
366         filefd[1] = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0644);
367         eDebug("eConsoleAppContainer::dumpToFile open(%s, O_WRONLY|O_CREAT|O_TRUNC, 0644)=%i", filename, filefd[1]);
368 }
369
370 void eConsoleAppContainer::readFromFile( PyObject *py_filename )
371 {
372         char *filename = PyString_AsString(py_filename);
373         char readbuf[32*1024];
374         filefd[0] = open(filename, O_RDONLY);
375         int rsize = read(filefd[0], readbuf, 32*1024);
376         eDebug("eConsoleAppContainer::readFromFile open(%s, O_RDONLY)=%i, read: %i", filename, filefd[0], rsize);
377         if ( filefd[0] > 0 && rsize > 0 )
378                 write(readbuf, rsize);
379 }
380
381 void eConsoleAppContainer::write( const char *data, int len )
382 {
383         char *tmp = new char[len];
384         memcpy(tmp, data, len);
385         outbuf.push(queue_data(tmp,len));
386         if (out)
387                 out->start();
388 }
389
390 void eConsoleAppContainer::write( PyObject *data )
391 {
392         char *buffer;
393         int length;
394         if (PyString_AsStringAndSize(data, &buffer, &length))
395                 return;
396         if (buffer && length)
397                 write(buffer, length);
398 }
399
400 void eConsoleAppContainer::readyWrite(int what)
401 {
402         if (what&eSocketNotifier::Write && outbuf.size() )
403         {
404                 queue_data &d = outbuf.front();
405                 int wr = ::write( fd[1], d.data+d.dataSent, d.len-d.dataSent );
406                 if (wr < 0)
407                         eDebug("eConsoleAppContainer write failed (%m)");
408                 else
409                         d.dataSent += wr;
410                 if (d.dataSent == d.len)
411                 {
412                         outbuf.pop();
413                         delete [] d.data;
414                         if ( filefd[0] == -1 )
415                         /* emit */ dataSent(0);
416                 }
417         }
418         if ( !outbuf.size() )
419         {
420                 if ( filefd[0] > 0 )
421                 {
422                         char readbuf[32*1024];
423                         int rsize = read(filefd[0], readbuf, 32*1024);
424                         if ( rsize > 0 )
425                                 write(readbuf, rsize);
426                         else
427                         {
428                                 close(filefd[0]);
429                                 filefd[0] = -1;
430                                 ::close(fd[1]);
431                                 eDebug("readFromFile done - closing eConsoleAppContainer stdin pipe");
432                                 fd[1]=-1;
433                                 dataSent(0);
434                                 out->stop();
435                         }
436                 }
437                 else
438                         out->stop();
439         }
440 }