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