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