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