Merge branch 'bug_533_fix_crash_on_satconfig_close'
[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 DEFINE_REF(eConsoleAppContainer);
59
60 eConsoleAppContainer::eConsoleAppContainer()
61 :pid(-1), killstate(0)
62 {
63         for (int i=0; i < 3; ++i)
64         {
65                 fd[i]=-1;
66                 filefd[i]=-1;
67         }
68 }
69
70 int eConsoleAppContainer::setCWD( const char *path )
71 {
72         struct stat dir_stat;
73
74         if (stat(path, &dir_stat) == -1)
75                 return -1;
76
77         if (!S_ISDIR(dir_stat.st_mode))
78                 return -2;
79
80         m_cwd = path;
81         return 0;
82 }
83
84 int eConsoleAppContainer::execute( const char *cmd )
85 {
86         int argc = 3;
87         const char *argv[argc + 1];
88         argv[0] = "/bin/sh";
89         argv[1] = "-c";
90         argv[2] = cmd;
91         argv[argc] = NULL;
92
93         return execute(argv[0], argv);
94 }
95
96 int eConsoleAppContainer::execute(const char *cmdline, const char * const argv[])
97 {
98         if (running())
99                 return -1;
100
101         pid=-1;
102         killstate=0;
103
104         // get one read ,one write and the err pipe to the prog..
105         pid = bidirpipe(fd, cmdline, argv, m_cwd.length() ? m_cwd.c_str() : 0);
106
107         if ( pid == -1 )
108                 return -3;
109
110 //      eDebug("pipe in = %d, out = %d, err = %d", fd[0], fd[1], fd[2]);
111
112         ::fcntl(fd[0], F_SETFL, O_NONBLOCK);
113         ::fcntl(fd[1], F_SETFL, O_NONBLOCK);
114         ::fcntl(fd[2], F_SETFL, O_NONBLOCK);
115         in = eSocketNotifier::create(eApp, fd[0], eSocketNotifier::Read|eSocketNotifier::Priority|eSocketNotifier::Hungup );
116         out = eSocketNotifier::create(eApp, fd[1], eSocketNotifier::Write, false);  
117         err = eSocketNotifier::create(eApp, fd[2], eSocketNotifier::Read|eSocketNotifier::Priority );
118         CONNECT(in->activated, eConsoleAppContainer::readyRead);
119         CONNECT(out->activated, eConsoleAppContainer::readyWrite);
120         CONNECT(err->activated, eConsoleAppContainer::readyErrRead);
121         in->m_clients.push_back(this);
122         out->m_clients.push_back(this);
123         err->m_clients.push_back(this);
124
125         return 0;
126 }
127
128 eConsoleAppContainer::~eConsoleAppContainer()
129 {
130         kill();
131 }
132
133 void eConsoleAppContainer::kill()
134 {
135         if ( killstate != -1 && pid != -1 )
136         {
137                 eDebug("user kill(SIGKILL) console App");
138                 killstate=-1;
139                 /*
140                  * Use a negative pid value, to signal the whole process group
141                  * ('pid' might not even be running anymore at this point)
142                  */
143                 ::kill(-pid, SIGKILL);
144                 closePipes();
145         }
146         while( outbuf.size() ) // cleanup out buffer
147         {
148                 queue_data d = outbuf.front();
149                 outbuf.pop();
150                 delete [] d.data;
151         }
152         in = 0;
153         out = 0;
154         err = 0;
155
156         for (int i=0; i < 3; ++i)
157         {
158                 if ( filefd[i] > 0 )
159                         close(filefd[i]);
160         }
161 }
162
163 void eConsoleAppContainer::sendCtrlC()
164 {
165         if ( killstate != -1 && pid != -1 )
166         {
167                 eDebug("user send SIGINT(Ctrl-C) to console App");
168                 /*
169                  * Use a negative pid value, to signal the whole process group
170                  * ('pid' might not even be running anymore at this point)
171                  */
172                 ::kill(-pid, SIGINT);
173         }
174 }
175
176 void eConsoleAppContainer::sendEOF()
177 {
178         if (out)
179                 out->stop();
180         if (fd[1] != -1)
181         {
182                 ::close(fd[1]);
183                 fd[1]=-1;
184         }
185 }
186
187 void eConsoleAppContainer::closePipes()
188 {
189         if (in)
190                 in->stop();
191         if (out)
192                 out->stop();
193         if (err)
194                 err->stop();
195         if (fd[0] != -1)
196         {
197                 ::close(fd[0]);
198                 fd[0]=-1;
199         }
200         if (fd[1] != -1)
201         {
202                 ::close(fd[1]);
203                 fd[1]=-1;
204         }
205         if (fd[2] != -1)
206         {
207                 ::close(fd[2]);
208                 fd[2]=-1;
209         }
210         eDebug("pipes closed");
211         while( outbuf.size() ) // cleanup out buffer
212         {
213                 queue_data d = outbuf.front();
214                 outbuf.pop();
215                 delete [] d.data;
216         }
217         in = 0; out = 0; err = 0;
218         pid = -1;
219 }
220
221 void eConsoleAppContainer::readyRead(int what)
222 {
223         bool hungup = what & eSocketNotifier::Hungup;
224         if (what & (eSocketNotifier::Priority|eSocketNotifier::Read))
225         {
226 //              eDebug("what = %d");
227                 char buf[2049];
228                 int rd;
229                 while((rd = read(fd[0], buf, 2048)) > 0)
230                 {
231                         buf[rd]=0;
232                         /*emit*/ dataAvail(buf);
233                         stdoutAvail(buf);
234                         if ( filefd[1] > 0 )
235                                 ::write(filefd[1], buf, rd);
236                         if (!hungup)
237                                 break;
238                 }
239         }
240         readyErrRead(eSocketNotifier::Priority|eSocketNotifier::Read); /* be sure to flush all data which might be already written */
241         if (hungup)
242         {
243                 eDebug("child has terminated");
244                 closePipes();
245                 int childstatus;
246                 int retval = killstate;
247                 /*
248                  * We have to call 'wait' on the child process, in order to avoid zombies.
249                  * Also, this gives us the chance to provide better exit status info to appClosed.
250                  */
251                 if (::waitpid(pid, &childstatus, 0) > 0)
252                 {
253                         if (WIFEXITED(childstatus))
254                         {
255                                 retval = WEXITSTATUS(childstatus);
256                         }
257                 }
258                 /*emit*/ appClosed(retval);
259         }
260 }
261
262 void eConsoleAppContainer::readyErrRead(int what)
263 {
264         if (what & (eSocketNotifier::Priority|eSocketNotifier::Read))
265         {
266 //              eDebug("what = %d");
267                 char buf[2049];
268                 int rd;
269                 while((rd = read(fd[2], buf, 2048)) > 0)
270                 {
271 /*                      for ( int i = 0; i < rd; i++ )
272                                 eDebug("%d = %c (%02x)", i, buf[i], buf[i] );*/
273                         buf[rd]=0;
274                         /*emit*/ dataAvail(buf);
275                         stderrAvail(buf);
276                 }
277         }
278 }
279
280 void eConsoleAppContainer::write( const char *data, int len )
281 {
282         char *tmp = new char[len];
283         memcpy(tmp, data, len);
284         outbuf.push(queue_data(tmp,len));
285         if (out)
286                 out->start();
287 }
288
289 void eConsoleAppContainer::readyWrite(int what)
290 {
291         if (what&eSocketNotifier::Write && outbuf.size() )
292         {
293                 queue_data &d = outbuf.front();
294                 int wr = ::write( fd[1], d.data+d.dataSent, d.len-d.dataSent );
295                 if (wr < 0)
296                         eDebug("eConsoleAppContainer write failed (%m)");
297                 else
298                         d.dataSent += wr;
299                 if (d.dataSent == d.len)
300                 {
301                         outbuf.pop();
302                         delete [] d.data;
303                         if ( filefd[0] == -1 )
304                         /* emit */ dataSent(0);
305                 }
306         }
307         if ( !outbuf.size() )
308         {
309                 if ( filefd[0] > 0 )
310                 {
311                         char readbuf[32*1024];
312                         int rsize = read(filefd[0], readbuf, 32*1024);
313                         if ( rsize > 0 )
314                                 write(readbuf, rsize);
315                         else
316                         {
317                                 close(filefd[0]);
318                                 filefd[0] = -1;
319                                 ::close(fd[1]);
320                                 eDebug("readFromFile done - closing eConsoleAppContainer stdin pipe");
321                                 fd[1]=-1;
322                                 dataSent(0);
323                                 out->stop();
324                         }
325                 }
326                 else
327                         out->stop();
328         }
329 }
330
331 #include "structmember.h"
332
333 extern "C" {
334
335 struct eConsolePy
336 {
337         PyObject_HEAD
338         eConsoleAppContainer *cont;
339         PyObject *in_weakreflist; /* List of weak references */
340 };
341
342 static PyObject *
343 eConsolePy_dataAvail(eConsolePy *self, void *closure)
344 {
345         return self->cont->dataAvail.get();
346 }
347
348 static PyObject *
349 eConsolePy_stdoutAvail(eConsolePy *self, void *closure)
350 {
351         return self->cont->stdoutAvail.get();
352 }
353
354 static PyObject *
355 eConsolePy_stderrAvail(eConsolePy *self, void *closure)
356 {
357         return self->cont->stderrAvail.get();
358 }
359
360 static PyObject *
361 eConsolePy_dataSent(eConsolePy *self, void *closure)
362 {
363         return self->cont->dataSent.get();
364 }
365
366 static PyObject *
367 eConsolePy_appClosed(eConsolePy *self, void *closure)
368 {
369         return self->cont->appClosed.get();
370 }
371
372 static PyGetSetDef eConsolePy_getseters[] = {
373         {"dataAvail",
374          (getter)eConsolePy_dataAvail, (setter)0,
375          "dataAvail callback list",
376          NULL},
377         {"stdoutAvail",
378          (getter)eConsolePy_stdoutAvail, (setter)0,
379          "stdoutAvail callback list",
380          NULL},
381         {"stderrAvail",
382          (getter)eConsolePy_stderrAvail, (setter)0,
383          "stderrAvail callback list",
384          NULL},
385         {"dataSent",
386          (getter)eConsolePy_dataSent, (setter)0,
387          "dataSent callback list",
388          NULL},
389         {"appClosed",
390          (getter)eConsolePy_appClosed, (setter)0,
391          "appClosed callback list",
392          NULL},
393         {NULL} /* Sentinel */
394 };
395
396 static int
397 eConsolePy_traverse(eConsolePy *self, visitproc visit, void *arg)
398 {
399         PyObject *obj = self->cont->dataAvail.getSteal();
400         if (obj) {
401                 Py_VISIT(obj);
402         }
403         obj = self->cont->stdoutAvail.getSteal();
404         if (obj) {
405                 Py_VISIT(obj);
406         }
407         obj = self->cont->stderrAvail.getSteal();
408         if (obj) {
409                 Py_VISIT(obj);
410         }
411         obj = self->cont->dataSent.getSteal();
412         if (obj) {
413                 Py_VISIT(obj);
414         }
415         obj = self->cont->appClosed.getSteal();
416         if (obj) {
417                 Py_VISIT(obj);
418         }
419         return 0;
420 }
421
422 static int
423 eConsolePy_clear(eConsolePy *self)
424 {
425         PyObject *obj = self->cont->dataAvail.getSteal(true);
426         if (obj) {
427                 Py_CLEAR(obj);
428         }
429         obj = self->cont->stdoutAvail.getSteal(true);
430         if (obj) {
431                 Py_CLEAR(obj);
432         }
433         obj = self->cont->stderrAvail.getSteal(true);
434         if (obj) {
435                 Py_CLEAR(obj);
436         }
437         obj = self->cont->dataSent.getSteal(true);
438         if (obj) {
439                 Py_CLEAR(obj);
440         }
441         obj = self->cont->appClosed.getSteal(true);
442         if (obj) {
443                 Py_CLEAR(obj);
444         }
445         return 0;
446 }
447
448 static void
449 eConsolePy_dealloc(eConsolePy* self)
450 {
451         if (self->in_weakreflist != NULL)
452                 PyObject_ClearWeakRefs((PyObject *) self);
453         eConsolePy_clear(self);
454         self->cont->Release();
455         self->ob_type->tp_free((PyObject*)self);
456 }
457
458 static PyObject *
459 eConsolePy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
460 {
461         eConsolePy *self = (eConsolePy *)type->tp_alloc(type, 0);
462         self->cont = new eConsoleAppContainer();
463         self->cont->AddRef();
464         self->in_weakreflist = NULL;
465         return (PyObject *)self;
466 }
467
468 static PyObject *
469 eConsolePy_running(eConsolePy* self)
470 {
471         PyObject *ret = NULL;
472         ret = self->cont->running() ? Py_True : Py_False;
473         Org_Py_INCREF(ret);
474         return ret;
475 }
476
477 static PyObject *
478 eConsolePy_execute(eConsolePy* self, PyObject *argt)
479 {
480         Py_ssize_t argc = PyTuple_Size(argt);
481         if (argc > 1)
482         {
483                 const char *argv[argc + 1];
484                 int argpos=0;
485                 while(argpos < argc)
486                 {
487                         PyObject *arg = PyTuple_GET_ITEM(argt, argpos);
488                         if (!PyString_Check(arg))
489                         {
490                                 char err[255];
491                                 if (argpos)
492                                         snprintf(err, 255, "arg %d is not a string", argpos);
493                                 else
494                                         snprintf(err, 255, "cmd is not a string!");
495                                 PyErr_SetString(PyExc_TypeError, err);
496                                 return NULL;
497                         }
498                         argv[argpos++] = PyString_AsString(arg);
499                 }
500                 argv[argpos] = 0;
501                 return PyInt_FromLong(self->cont->execute(argv[0], argv+1));
502         }
503         else
504         {
505                 const char *str;
506                 if (PyArg_ParseTuple(argt, "s", &str))
507                         return PyInt_FromLong(self->cont->execute(str));
508                 PyErr_SetString(PyExc_TypeError,
509                         "cmd is not a string!");
510         }
511         return NULL;
512 }
513
514 static PyObject *
515 eConsolePy_write(eConsolePy* self, PyObject *args)
516 {
517         int len;
518         char *data;
519         int ret = -1;
520         Py_ssize_t argc = PyTuple_Size(args);
521         if (argc > 1)
522                 ret = PyArg_ParseTuple(args, "si", &data, &len);
523         else if (argc == 1)
524         {
525                 PyObject *ob;
526                 ret = !PyArg_ParseTuple(args, "O", &ob) || !PyString_Check(ob);
527                 if (!ret)
528                 {
529                         Py_ssize_t length;
530                         if (!PyString_AsStringAndSize(ob, &data, &length))
531                                 len = length;
532                         else
533                                 len = 0;
534                 }
535         }
536         if (ret)
537         {
538                 PyErr_SetString(PyExc_TypeError,
539                         "1st arg must be a string, optionaly 2nd arg can be the string length");
540                 return NULL;
541         }
542         self->cont->write(data, len);
543         Py_RETURN_NONE;
544 }
545
546 static PyObject *
547 eConsolePy_getPID(eConsolePy* self)
548 {
549         return PyInt_FromLong(self->cont->getPID());
550 }
551
552 static PyObject *
553 eConsolePy_setCWD(eConsolePy* self, PyObject *args)
554 {
555         const char *path=0;
556         if (!PyArg_ParseTuple(args, "s", &path))
557                 return NULL;
558         self->cont->setCWD(path);
559         Py_RETURN_NONE;
560 }
561
562 static PyObject *
563 eConsolePy_kill(eConsolePy* self)
564 {
565         self->cont->kill();
566         Py_RETURN_NONE;
567 }
568
569 static PyObject *
570 eConsolePy_sendCtrlC(eConsolePy* self)
571 {
572         self->cont->sendCtrlC();
573         Py_RETURN_NONE;
574 }
575
576 static PyObject *
577 eConsolePy_sendEOF(eConsolePy* self)
578 {
579         self->cont->sendEOF();
580         Py_RETURN_NONE;
581 }
582
583 static PyObject *
584 eConsolePy_dumpToFile(eConsolePy* self, PyObject *args)
585 {
586         char *filename;
587         if (!PyArg_ParseTuple(args, "s", &filename))
588         {
589                 PyErr_SetString(PyExc_TypeError,
590                         "arg must be a string (filename)");
591                 return NULL;
592         }
593         else
594         {
595                 int fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0644);
596                 self->cont->setFileFD(1, fd);
597                 eDebug("eConsoleAppContainer::dumpToFile open(%s, O_WRONLY|O_CREAT|O_TRUNC, 0644)=%d", filename, fd);
598         }
599         Py_RETURN_NONE;
600 }
601
602 static PyObject *
603 eConsolePy_readFromFile(eConsolePy* self, PyObject *args)
604 {
605         char *filename;
606         if (!PyArg_ParseTuple(args, "s", &filename))
607         {
608                 PyErr_SetString(PyExc_TypeError,
609                         "arg must be a string (filename)");
610                 return NULL;
611         }
612         else
613         {
614                 int fd = open(filename, O_RDONLY);
615                 if (fd >= 0)
616                 {
617                         char readbuf[32*1024];
618                         int rsize = read(fd, readbuf, 32*1024);
619                         self->cont->setFileFD(0, fd);
620                         eDebug("eConsoleAppContainer::readFromFile open(%s, O_RDONLY)=%d, read: %d", filename, fd, rsize);
621                         self->cont->write(readbuf, rsize);
622                 }
623                 else
624                 {
625                         eDebug("eConsoleAppContainer::readFromFile %s not exist!", filename);
626                         self->cont->setFileFD(0, -1);
627                 }
628         }
629         Py_RETURN_NONE;
630 }
631
632 static PyMethodDef eConsolePy_methods[] = {
633         {"setCWD", (PyCFunction)eConsolePy_setCWD, METH_VARARGS,
634          "set working dir"
635         },
636         {"execute", (PyCFunction)eConsolePy_execute, METH_VARARGS,
637          "execute command"
638         },
639         {"dumpToFile", (PyCFunction)eConsolePy_dumpToFile, METH_VARARGS,
640          "set output file"
641         },
642         {"readFromFile", (PyCFunction)eConsolePy_readFromFile, METH_VARARGS,
643          "set input file"
644         },
645         {"getPID", (PyCFunction)eConsolePy_getPID, METH_NOARGS,
646          "execute command"
647         },
648         {"kill", (PyCFunction)eConsolePy_kill, METH_NOARGS,
649          "kill application"
650         },
651         {"sendCtrlC", (PyCFunction)eConsolePy_sendCtrlC, METH_NOARGS,
652          "send Ctrl-C to application"
653         },
654         {"sendEOF", (PyCFunction)eConsolePy_sendEOF, METH_NOARGS,
655          "send EOF to application"
656         },
657         {"write", (PyCFunction)eConsolePy_write, METH_VARARGS,
658          "write data to application"
659         },
660         {"running", (PyCFunction)eConsolePy_running, METH_NOARGS,
661          "returns the running state"
662         },
663         {NULL}  /* Sentinel */
664 };
665
666 static PyTypeObject eConsolePyType = {
667         PyObject_HEAD_INIT(NULL)
668         0, /*ob_size*/
669         "eConsoleImpl.eConsoleAppContainer", /*tp_name*/
670         sizeof(eConsolePy), /*tp_basicsize*/
671         0, /*tp_itemsize*/
672         (destructor)eConsolePy_dealloc, /*tp_dealloc*/
673         0, /*tp_print*/
674         0, /*tp_getattr*/
675         0, /*tp_setattr*/
676         0, /*tp_compare*/
677         0, /*tp_repr*/
678         0, /*tp_as_number*/
679         0, /*tp_as_sequence*/
680         0, /*tp_as_mapping*/
681         0, /*tp_hash */
682         0, /*tp_call*/
683         0, /*tp_str*/
684         0, /*tp_getattro*/
685         0, /*tp_setattro*/
686         0, /*tp_as_buffer*/
687         Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /*tp_flags*/
688         "eConsoleAppContainer objects", /* tp_doc */
689         (traverseproc)eConsolePy_traverse, /* tp_traverse */
690         (inquiry)eConsolePy_clear, /* tp_clear */
691         0, /* tp_richcompare */
692         offsetof(eConsolePy, in_weakreflist), /* tp_weaklistoffset */
693         0, /* tp_iter */
694         0, /* tp_iternext */
695         eConsolePy_methods, /* tp_methods */
696         0, /* tp_members */
697         eConsolePy_getseters, /* tp_getset */
698         0, /* tp_base */
699         0, /* tp_dict */
700         0, /* tp_descr_get */
701         0, /* tp_descr_set */
702         0, /* tp_dictoffset */
703         0, /* tp_init */
704         0, /* tp_alloc */
705         eConsolePy_new, /* tp_new */
706 };
707
708 static PyMethodDef module_methods[] = {
709         {NULL}  /* Sentinel */
710 };
711
712 void eConsoleInit(void)
713 {
714         PyObject* m = Py_InitModule3("eConsoleImpl", module_methods,
715                 "Module that implements eConsoleAppContainer with working cyclic garbage collection.");
716
717         if (m == NULL)
718                 return;
719
720         if (!PyType_Ready(&eConsolePyType))
721         {
722                 Org_Py_INCREF((PyObject*)&eConsolePyType);
723                 PyModule_AddObject(m, "eConsoleAppContainer", (PyObject*)&eConsolePyType);
724         }
725 }
726 }