change eConsoleAppcontainers to using standard system shell 'sh' as suggested by...
[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 #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 int eConsoleAppContainer::setCWD( const char *path )
69 {
70         struct stat dir_stat;
71
72         if (stat(path, &dir_stat) == -1)
73                 return -1;
74
75         if (!S_ISDIR(dir_stat.st_mode))
76                 return -2;
77
78         m_cwd = path;
79         return 0;
80 }
81
82 int eConsoleAppContainer::execute( const char *cmd )
83 {
84         int argc = 3;
85         const char *argv[argc + 1];
86         argv[0] = "/bin/sh";
87         argv[1] = "-c";
88         argv[2] = cmd;
89         argv[argc] = NULL;
90
91         return execute(argv[0], argv);
92 }
93
94 int eConsoleAppContainer::execute(const char *cmdline, const char * const argv[])
95 {
96         if (running())
97                 return -1;
98
99         pid=-1;
100         killstate=0;
101
102         // get one read ,one write and the err pipe to the prog..
103         pid = bidirpipe(fd, cmdline, argv, m_cwd.length() ? m_cwd.c_str() : 0);
104
105         if ( pid == -1 )
106                 return -3;
107
108 //      eDebug("pipe in = %d, out = %d, err = %d", fd[0], fd[1], fd[2]);
109
110         ::fcntl(fd[1], F_SETFL, O_NONBLOCK);
111         ::fcntl(fd[2], F_SETFL, O_NONBLOCK);
112         in = new eSocketNotifier(eApp, fd[0], eSocketNotifier::Read|eSocketNotifier::Priority|eSocketNotifier::Hungup );
113         out = new eSocketNotifier(eApp, fd[1], eSocketNotifier::Write, false);  
114         err = new eSocketNotifier(eApp, fd[2], eSocketNotifier::Read|eSocketNotifier::Priority );
115         CONNECT(in->activated, eConsoleAppContainer::readyRead);
116         CONNECT(out->activated, eConsoleAppContainer::readyWrite);
117         CONNECT(err->activated, eConsoleAppContainer::readyErrRead);
118
119         return 0;
120 }
121
122 int eConsoleAppContainer::execute( PyObject *cmdline, PyObject *args )
123 {
124         if (!PyString_Check(cmdline))
125                 return -1;
126         if (!PyList_Check(args))
127                 return -1;
128         const char *argv[PyList_Size(args) + 1];
129         int i;
130         for (i = 0; i < PyList_Size(args); ++i)
131         {
132                 PyObject *arg = PyList_GetItem(args, i); /* borrowed ref */
133                 if (!arg)
134                         return -1;
135                 if (!PyString_Check(arg))
136                         return -1;
137                 argv[i] = PyString_AsString(arg); /* borrowed pointer */
138         }
139         argv[i] = 0;
140
141         return execute(PyString_AsString(cmdline), argv); /* borrowed pointer */
142 }
143
144 eConsoleAppContainer::~eConsoleAppContainer()
145 {
146         kill();
147 }
148
149 void eConsoleAppContainer::kill()
150 {
151         if ( killstate != -1 && pid != -1 )
152         {
153                 eDebug("user kill(SIGKILL) console App");
154                 killstate=-1;
155                 /*
156                  * Use a negative pid value, to signal the whole process group
157                  * ('pid' might not even be running anymore at this point)
158                  */
159                 ::kill(-pid, SIGKILL);
160                 closePipes();
161         }
162         while( outbuf.size() ) // cleanup out buffer
163         {
164                 queue_data d = outbuf.front();
165                 outbuf.pop();
166                 delete [] d.data;
167         }
168         delete in;
169         delete out;
170         delete err;
171         in=out=err=0;
172
173         for (int i=0; i < 3; ++i)
174         {
175                 if ( filefd[i] > 0 )
176                         close(filefd[i]);
177         }
178 }
179
180 void eConsoleAppContainer::sendCtrlC()
181 {
182         if ( killstate != -1 && pid != -1 )
183         {
184                 eDebug("user send SIGINT(Ctrl-C) to console App");
185                 /*
186                  * Use a negative pid value, to signal the whole process group
187                  * ('pid' might not even be running anymore at this point)
188                  */
189                 ::kill(-pid, SIGINT);
190         }
191 }
192
193 void eConsoleAppContainer::sendEOF()
194 {
195         if (out)
196                 out->stop();
197         if (fd[1] != -1)
198         {
199                 ::close(fd[1]);
200                 fd[1]=-1;
201         }
202 }
203
204 void eConsoleAppContainer::closePipes()
205 {
206         if (in)
207                 in->stop();
208         if (out)
209                 out->stop();
210         if (err)
211                 err->stop();
212         if (fd[0] != -1)
213         {
214                 ::close(fd[0]);
215                 fd[0]=-1;
216         }
217         if (fd[1] != -1)
218         {
219                 ::close(fd[1]);
220                 fd[1]=-1;
221         }
222         if (fd[2] != -1)
223         {
224                 ::close(fd[2]);
225                 fd[2]=-1;
226         }
227         eDebug("pipes closed");
228         while( outbuf.size() ) // cleanup out buffer
229         {
230                 queue_data d = outbuf.front();
231                 outbuf.pop();
232                 delete [] d.data;
233         }
234         pid = -1;
235 }
236
237 void eConsoleAppContainer::readyRead(int what)
238 {
239         bool hungup = what & eSocketNotifier::Hungup;
240         if (what & (eSocketNotifier::Priority|eSocketNotifier::Read))
241         {
242 //              eDebug("what = %d");
243                 char buf[2049];
244                 int rd;
245                 while((rd = read(fd[0], buf, 2048)) > 0)
246                 {
247                         buf[rd]=0;
248                         /*emit*/ dataAvail(buf);
249                         stdoutAvail(buf);
250                         if ( filefd[1] > 0 )
251                                 ::write(filefd[1], buf, rd);
252                         if (!hungup)
253                                 break;
254                 }
255         }
256         readyErrRead(eSocketNotifier::Priority|eSocketNotifier::Read); /* be sure to flush all data which might be already written */
257         if (hungup)
258         {
259                 eDebug("child has terminated");
260                 closePipes();
261                 int childstatus;
262                 int retval = killstate;
263                 /*
264                  * We have to call 'wait' on the child process, in order to avoid zombies.
265                  * Also, this gives us the chance to provide better exit status info to appClosed.
266                  */
267                 if (::waitpid(pid, &childstatus, 0) > 0)
268                 {
269                         if (WIFEXITED(childstatus))
270                         {
271                                 retval = WEXITSTATUS(childstatus);
272                         }
273                 }
274                 /*emit*/ appClosed(retval);
275         }
276 }
277
278 void eConsoleAppContainer::readyErrRead(int what)
279 {
280         if (what & (eSocketNotifier::Priority|eSocketNotifier::Read))
281         {
282 //              eDebug("what = %d");
283                 char buf[2049];
284                 int rd;
285                 while((rd = read(fd[2], buf, 2048)) > 0)
286                 {
287 /*                      for ( int i = 0; i < rd; i++ )
288                                 eDebug("%d = %c (%02x)", i, buf[i], buf[i] );*/
289                         buf[rd]=0;
290                         /*emit*/ dataAvail(buf);
291                         stderrAvail(buf);
292                 }
293         }
294 }
295
296 void eConsoleAppContainer::dumpToFile( PyObject *py_filename )
297 {
298         char *filename = PyString_AsString(py_filename);
299         filefd[1] = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0644);
300         eDebug("eConsoleAppContainer::dumpToFile open(%s, O_WRONLY|O_CREAT|O_TRUNC, 0644)=%i", filename, filefd[1]);
301 }
302
303 void eConsoleAppContainer::readFromFile( PyObject *py_filename )
304 {
305         char *filename = PyString_AsString(py_filename);
306         char readbuf[32*1024];
307         filefd[0] = open(filename, O_RDONLY);
308         int rsize = read(filefd[0], readbuf, 32*1024);
309         eDebug("eConsoleAppContainer::readFromFile open(%s, O_RDONLY)=%i, read: %i", filename, filefd[0], rsize);
310         if ( filefd[0] > 0 && rsize > 0 )
311                 write(readbuf, rsize);
312 }
313
314 void eConsoleAppContainer::write( const char *data, int len )
315 {
316         char *tmp = new char[len];
317         memcpy(tmp, data, len);
318         outbuf.push(queue_data(tmp,len));
319         if (out)
320                 out->start();
321 }
322
323 void eConsoleAppContainer::write( PyObject *data )
324 {
325         char *buffer;
326         Py_ssize_t length;
327         if (PyString_AsStringAndSize(data, &buffer, &length))
328                 return;
329         if (buffer && length)
330                 write(buffer, length);
331 }
332
333 void eConsoleAppContainer::readyWrite(int what)
334 {
335         if (what&eSocketNotifier::Write && outbuf.size() )
336         {
337                 queue_data &d = outbuf.front();
338                 int wr = ::write( fd[1], d.data+d.dataSent, d.len-d.dataSent );
339                 if (wr < 0)
340                         eDebug("eConsoleAppContainer write failed (%m)");
341                 else
342                         d.dataSent += wr;
343                 if (d.dataSent == d.len)
344                 {
345                         outbuf.pop();
346                         delete [] d.data;
347                         if ( filefd[0] == -1 )
348                         /* emit */ dataSent(0);
349                 }
350         }
351         if ( !outbuf.size() )
352         {
353                 if ( filefd[0] > 0 )
354                 {
355                         char readbuf[32*1024];
356                         int rsize = read(filefd[0], readbuf, 32*1024);
357                         if ( rsize > 0 )
358                                 write(readbuf, rsize);
359                         else
360                         {
361                                 close(filefd[0]);
362                                 filefd[0] = -1;
363                                 ::close(fd[1]);
364                                 eDebug("readFromFile done - closing eConsoleAppContainer stdin pipe");
365                                 fd[1]=-1;
366                                 dataSent(0);
367                                 out->stop();
368                         }
369                 }
370                 else
371                         out->stop();
372         }
373 }