add command to manually close input pipe (send CTRL-D / EOF)
[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::sendEOF()
272 {
273         if (out)
274                 out->stop();
275         if (fd[1] != -1)
276         {
277                 ::close(fd[1]);
278                 fd[1]=-1;
279         }
280 }
281
282 void eConsoleAppContainer::closePipes()
283 {
284         if (in)
285                 in->stop();
286         if (out)
287                 out->stop();
288         if (err)
289                 err->stop();
290         if (fd[0] != -1)
291         {
292                 ::close(fd[0]);
293                 fd[0]=-1;
294         }
295         if (fd[1] != -1)
296         {
297                 ::close(fd[1]);
298                 fd[1]=-1;
299         }
300         if (fd[2] != -1)
301         {
302                 ::close(fd[2]);
303                 fd[2]=-1;
304         }
305         eDebug("pipes closed");
306         while( outbuf.size() ) // cleanup out buffer
307         {
308                 queue_data d = outbuf.front();
309                 outbuf.pop();
310                 delete [] d.data;
311         }
312         pid = -1;
313 }
314
315 void eConsoleAppContainer::readyRead(int what)
316 {
317         bool hungup = what & eSocketNotifier::Hungup;
318         if (what & (eSocketNotifier::Priority|eSocketNotifier::Read))
319         {
320 //              eDebug("what = %d");
321                 char buf[2049];
322                 int rd;
323                 while((rd = read(fd[0], buf, 2048)) > 0)
324                 {
325                         buf[rd]=0;
326                         /*emit*/ dataAvail(buf);
327                         stdoutAvail(buf);
328                         if ( filefd[1] > 0 )
329                                 ::write(filefd[1], buf, rd);
330                         if (!hungup)
331                                 break;
332                 }
333         }
334         readyErrRead(eSocketNotifier::Priority|eSocketNotifier::Read); /* be sure to flush all data which might be already written */
335         if (hungup)
336         {
337                 eDebug("child has terminated");
338                 closePipes();
339                 int childstatus;
340                 int retval = killstate;
341                 /*
342                  * We have to call 'wait' on the child process, in order to avoid zombies.
343                  * Also, this gives us the chance to provide better exit status info to appClosed.
344                  */
345                 if (::waitpid(pid, &childstatus, 0) > 0)
346                 {
347                         if (WIFEXITED(childstatus))
348                         {
349                                 retval = WEXITSTATUS(childstatus);
350                         }
351                 }
352                 /*emit*/ appClosed(retval);
353         }
354 }
355
356 void eConsoleAppContainer::readyErrRead(int what)
357 {
358         if (what & (eSocketNotifier::Priority|eSocketNotifier::Read))
359         {
360 //              eDebug("what = %d");
361                 char buf[2049];
362                 int rd;
363                 while((rd = read(fd[2], buf, 2048)) > 0)
364                 {
365 /*                      for ( int i = 0; i < rd; i++ )
366                                 eDebug("%d = %c (%02x)", i, buf[i], buf[i] );*/
367                         buf[rd]=0;
368                         /*emit*/ dataAvail(buf);
369                         stderrAvail(buf);
370                 }
371         }
372 }
373
374 void eConsoleAppContainer::dumpToFile( PyObject *py_filename )
375 {
376         char *filename = PyString_AsString(py_filename);
377         filefd[1] = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0644);
378         eDebug("eConsoleAppContainer::dumpToFile open(%s, O_WRONLY|O_CREAT|O_TRUNC, 0644)=%i", filename, filefd[1]);
379 }
380
381 void eConsoleAppContainer::readFromFile( PyObject *py_filename )
382 {
383         char *filename = PyString_AsString(py_filename);
384         char readbuf[32*1024];
385         filefd[0] = open(filename, O_RDONLY);
386         int rsize = read(filefd[0], readbuf, 32*1024);
387         eDebug("eConsoleAppContainer::readFromFile open(%s, O_RDONLY)=%i, read: %i", filename, filefd[0], rsize);
388         if ( filefd[0] > 0 && rsize > 0 )
389                 write(readbuf, rsize);
390 }
391
392 void eConsoleAppContainer::write( const char *data, int len )
393 {
394         char *tmp = new char[len];
395         memcpy(tmp, data, len);
396         outbuf.push(queue_data(tmp,len));
397         if (out)
398                 out->start();
399 }
400
401 void eConsoleAppContainer::write( PyObject *data )
402 {
403         char *buffer;
404         int length;
405         if (PyString_AsStringAndSize(data, &buffer, &length))
406                 return;
407         if (buffer && length)
408                 write(buffer, length);
409 }
410
411 void eConsoleAppContainer::readyWrite(int what)
412 {
413         if (what&eSocketNotifier::Write && outbuf.size() )
414         {
415                 queue_data &d = outbuf.front();
416                 int wr = ::write( fd[1], d.data+d.dataSent, d.len-d.dataSent );
417                 if (wr < 0)
418                         eDebug("eConsoleAppContainer write failed (%m)");
419                 else
420                         d.dataSent += wr;
421                 if (d.dataSent == d.len)
422                 {
423                         outbuf.pop();
424                         delete [] d.data;
425                         if ( filefd[0] == -1 )
426                         /* emit */ dataSent(0);
427                 }
428         }
429         if ( !outbuf.size() )
430         {
431                 if ( filefd[0] > 0 )
432                 {
433                         char readbuf[32*1024];
434                         int rsize = read(filefd[0], readbuf, 32*1024);
435                         if ( rsize > 0 )
436                                 write(readbuf, rsize);
437                         else
438                         {
439                                 close(filefd[0]);
440                                 filefd[0] = -1;
441                                 ::close(fd[1]);
442                                 eDebug("readFromFile done - closing eConsoleAppContainer stdin pipe");
443                                 fd[1]=-1;
444                                 dataSent(0);
445                                 out->stop();
446                         }
447                 }
448                 else
449                         out->stop();
450         }
451 }