add support for writing python strings directly
[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[], const char *cmd , const char * const 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 = vfork() ) == -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, (char * const *)argv); 
41                                 /* the vfork will actually suspend the parent thread until execvp is called. thus it's ok to use the shared arg/cmdline pointers here. */
42                 _exit(0);
43         }
44         if (close(pfdout[0]) == -1 || close(pfdin[1]) == -1 || close(pfderr[1]) == -1)
45                         return(-1);
46
47         pfd[0] = pfdin[0];
48         pfd[1] = pfdout[1];
49         pfd[2] = pfderr[0];
50
51         return(pid);
52 }
53
54 eConsoleAppContainer::eConsoleAppContainer()
55 :pid(-1), killstate(0), in(0), out(0), err(0)
56 {
57         for (int i=0; i < 3; ++i)
58                 fd[i]=-1;
59 }
60
61 static char brakets[][2] = {
62         { '\'','\'' },
63         {'"','"'},
64         {'`','`'},
65         {'(',')'},
66         {'{','}'},
67         {'[',']'},
68         {'<','>'}
69 };
70
71 static char *find_bracket(char ch)
72 {
73         size_t idx=0;
74         while (idx < sizeof(brakets)/2) {
75                 if (brakets[idx][0] == ch)
76                         return &brakets[idx][0];
77                 ++idx;
78         }
79         return NULL;
80 }
81
82 int eConsoleAppContainer::execute( const char *cmd )
83 {
84         int cnt=0, slen=strlen(cmd);
85         char buf[slen+1];
86         char *tmp=0, *argv[64], *path=buf, *cmds = buf;
87         memcpy(buf, cmd, slen+1);
88
89 //      printf("cmd = %s, len %d\n", cmd, slen);
90
91         // kill spaces at beginning
92         while(path[0] == ' ') {
93                 ++path;
94                 ++cmds;
95                 --slen;
96         }
97
98         // kill spaces at the end
99         while(slen && path[slen-1] == ' ') {
100                 path[slen-1] = 0;
101                 --slen;
102         }
103
104         if (!slen)
105                 return -2;
106
107         tmp = strchr(path, ' ');
108         if (tmp) {
109                 *tmp = 0;
110                 cmds = tmp+1;
111                 while(*cmds && *cmds == ' ')
112                         ++cmds;
113         }
114         else
115                 cmds = path+slen;
116
117         memset(argv, 0, sizeof(argv));
118         argv[cnt++] = path;
119
120         if (*cmds) {
121                 char *argb=NULL, *it=NULL;
122                 while ( (tmp = strchr(cmds, ' ')) ) {
123                         if (!it && *cmds && (it = find_bracket(*cmds)) )
124                                 *cmds = 'X'; // replace open braket...
125                         if (!argb) // not arg begin
126                                 argb = cmds;
127                         if (it && *(tmp-1) == it[1]) {
128                                 *argb = it[0]; // set old char for open braket
129                                 it = 0;
130                         }
131                         if (!it) { // end of arg
132                                 *tmp = 0;
133                                 argv[cnt++] = argb;
134                                 argb=0; // reset arg begin
135                         }
136                         cmds = tmp+1;
137                         while (*cmds && *cmds == ' ')
138                                 ++cmds;
139                 }
140                 argv[cnt++] = argb ? argb : cmds;
141                 if (it)
142                     *argv[cnt-1] = it[0]; // set old char for open braket
143         }
144
145 //      int tmp=0;
146 //      while(argv[tmp])
147 //              eDebug("%d is %s", tmp, argv[tmp++]);
148         return execute(argv[0], argv);
149 }
150
151 int eConsoleAppContainer::execute(const char *cmdline, const char * const argv[])
152 {
153         if (running())
154                 return -1;
155
156         pid=-1;
157         killstate=0;
158
159         // get one read ,one write and the err pipe to the prog..
160         pid = bidirpipe(fd, cmdline, argv);
161
162         if ( pid == -1 )
163                 return -3;
164
165 //      eDebug("pipe in = %d, out = %d, err = %d", fd[0], fd[1], fd[2]);
166
167         in = new eSocketNotifier(eApp, fd[0], eSocketNotifier::Read|eSocketNotifier::Priority|eSocketNotifier::Hungup );
168         out = new eSocketNotifier(eApp, fd[1], eSocketNotifier::Write, false);  
169         err = new eSocketNotifier(eApp, fd[2], eSocketNotifier::Read|eSocketNotifier::Priority );
170         CONNECT(in->activated, eConsoleAppContainer::readyRead);
171         CONNECT(out->activated, eConsoleAppContainer::readyWrite);
172         CONNECT(err->activated, eConsoleAppContainer::readyErrRead);
173
174         return 0;
175 }
176
177 int eConsoleAppContainer::execute( PyObject *cmdline, PyObject *args )
178 {
179         if (!PyString_Check(cmdline))
180                 return -1;
181         if (!PyList_Check(args))
182                 return -1;
183         const char *argv[PyList_Size(args) + 1];
184         int i;
185         for (i = 0; i < PyList_Size(args); ++i)
186         {
187                 PyObject *arg = PyList_GetItem(args, i); /* borrowed ref */
188                 if (!arg)
189                         return -1;
190                 if (!PyString_Check(arg))
191                         return -1;
192                 argv[i] = PyString_AsString(arg); /* borrowed pointer */
193         }
194         argv[i] = 0;
195
196         return execute(PyString_AsString(cmdline), argv); /* borrowed pointer */
197 }
198
199 eConsoleAppContainer::~eConsoleAppContainer()
200 {
201         kill();
202 }
203
204 void eConsoleAppContainer::kill()
205 {
206         if ( killstate != -1 && pid != -1 )
207         {
208                 eDebug("user kill(SIGKILL) console App");
209                 killstate=-1;
210                 /*
211                  * Use a negative pid value, to signal the whole process group
212                  * ('pid' might not even be running anymore at this point)
213                  */
214                 ::kill(-pid, SIGKILL);
215                 closePipes();
216         }
217         while( outbuf.size() ) // cleanup out buffer
218         {
219                 queue_data d = outbuf.front();
220                 outbuf.pop();
221                 delete [] d.data;
222         }
223         delete in;
224         delete out;
225         delete err;
226         in=out=err=0;
227 }
228
229 void eConsoleAppContainer::sendCtrlC()
230 {
231         if ( killstate != -1 && pid != -1 )
232         {
233                 eDebug("user send SIGINT(Ctrl-C) to console App");
234                 /*
235                  * Use a negative pid value, to signal the whole process group
236                  * ('pid' might not even be running anymore at this point)
237                  */
238                 ::kill(-pid, SIGINT);
239         }
240 }
241
242 void eConsoleAppContainer::closePipes()
243 {
244         if (in)
245                 in->stop();
246         if (out)
247                 out->stop();
248         if (err)
249                 err->stop();
250         if (fd[0] != -1)
251         {
252                 ::close(fd[0]);
253                 fd[0]=-1;
254         }
255         if (fd[1] != -1)
256         {
257                 ::close(fd[1]);
258                 fd[1]=-1;
259         }
260         if (fd[2] != -1)
261         {
262                 ::close(fd[2]);
263                 fd[2]=-1;
264         }
265         eDebug("pipes closed");
266         while( outbuf.size() ) // cleanup out buffer
267         {
268                 queue_data d = outbuf.front();
269                 outbuf.pop();
270                 delete [] d.data;
271         }
272         pid = -1;
273 }
274
275 void eConsoleAppContainer::readyRead(int what)
276 {
277         bool hungup = what & eSocketNotifier::Hungup;
278         if (what & (eSocketNotifier::Priority|eSocketNotifier::Read))
279         {
280 //              eDebug("what = %d");
281                 char buf[2048];
282                 int rd;
283                 while((rd = read(fd[0], buf, 2047)) > 0)
284                 {
285 /*                      for ( int i = 0; i < rd; i++ )
286                                 eDebug("%d = %c (%02x)", i, buf[i], buf[i] );*/
287                         buf[rd]=0;
288                         /*emit*/ dataAvail(buf);
289                         if (!hungup)
290                                 break;
291                 }
292         }
293         if (hungup)
294         {
295                 eDebug("child has terminated");
296                 closePipes();
297                 int childstatus;
298                 int retval = killstate;
299                 /*
300                  * We have to call 'wait' on the child process, in order to avoid zombies.
301                  * Also, this gives us the chance to provide better exit status info to appClosed.
302                  */
303                 if (::waitpid(pid, &childstatus, 0) > 0)
304                 {
305                         if (WIFEXITED(childstatus))
306                         {
307                                 retval = WEXITSTATUS(childstatus);
308                         }
309                 }
310                 /*emit*/ appClosed(retval);
311         }
312 }
313
314 void eConsoleAppContainer::readyErrRead(int what)
315 {
316         if (what & (eSocketNotifier::Priority|eSocketNotifier::Read))
317         {
318 //              eDebug("what = %d");
319                 char buf[2048];
320                 int rd;
321                 while((rd = read(fd[2], buf, 2047)) > 0)
322                 {
323 /*                      for ( int i = 0; i < rd; i++ )
324                                 eDebug("%d = %c (%02x)", i, buf[i], buf[i] );*/
325                         buf[rd]=0;
326                         /*emit*/ dataAvail(buf);
327                 }
328         }
329 }
330
331 void eConsoleAppContainer::write( const char *data, int len )
332 {
333         char *tmp = new char[len];
334         memcpy(tmp, data, len);
335         outbuf.push(queue_data(tmp,len));
336         if (out)
337                 out->start();
338 }
339
340 void eConsoleAppContainer::write( PyObject *data )
341 {
342         char *buffer;
343         int length;
344         if (PyString_AsStringAndSize(data, &buffer, &length))
345                 return;
346         if (buffer && length)
347                 write(buffer, length);
348 }
349
350 void eConsoleAppContainer::readyWrite(int what)
351 {
352         if (what&eSocketNotifier::Write && outbuf.size() )
353         {
354                 queue_data d = outbuf.front();
355                 outbuf.pop();
356                 if ( ::write( fd[1], d.data, d.len ) != d.len )
357                 {
358                         /* emit */ dataSent(-1);
359 //                      eDebug("writeError");
360                 }
361                 else
362                 {
363                         /* emit */ dataSent(0);
364 //                      eDebug("write ok");
365                 }
366                 delete [] d.data;
367         }
368         if ( !outbuf.size() )
369                 out->stop();
370 }