bdd5ab1384a0b5ca229248f9153b703e5a5df16f
[src/app-framework-main.git] / src / afm-launch.c
1 /*
2  Copyright 2015 IoT.bzh
3
4  author: José Bollo <jose.bollo@iot.bzh>
5
6  Licensed under the Apache License, Version 2.0 (the "License");
7  you may not use this file except in compliance with the License.
8  You may obtain a copy of the License at
9
10      http://www.apache.org/licenses/LICENSE-2.0
11
12  Unless required by applicable law or agreed to in writing, software
13  distributed under the License is distributed on an "AS IS" BASIS,
14  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  See the License for the specific language governing permissions and
16  limitations under the License.
17 */
18
19 #define _GNU_SOURCE
20
21 #include <unistd.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <limits.h>
25 #include <errno.h>
26 #include <string.h>
27 #include <fcntl.h>
28 #include <assert.h>
29 #include <sys/stat.h>
30 #include <sys/types.h>
31
32 extern char **environ;
33
34 #include "verbose.h"
35 #include "afm-launch.h"
36 #include "secmgr-wrap.h"
37
38 /*
39 %I icondir                      FWK_ICON_DIR
40 %P port                         params->port
41 %S secret                       params->secret
42 %D datadir                      params->datadir
43 %r rootdir                      desc->path
44 %h homedir                      desc->home
45 %t tag (smack label)            desc->tag
46 %a appid                        desc->appid
47 %c content                      desc->content
48 %m mime-type                    desc->type
49 %n name                         desc->name
50 %p plugins                      desc->plugins
51 %W width                        desc->width
52 %H height                       desc->height
53 */
54
55 static const char *args_for_afb_daemon[] = {
56         "/usr/bin/afb-daemon",
57         "--alias=/icons:%I",
58         "--port=%P",
59         "--rootdir=%D",
60         "--token=%S",
61         NULL
62 };
63
64 static const char *args_for_qmlviewer[] = {
65         "/usr/bin/qt5/qmlscene",
66         "-fullscreen",
67         "-I",
68         "%r",
69         "-I",
70         "%r/imports",
71         "%r/%c",
72         NULL
73 };
74
75 static const char *args_for_web_runtime[] = {
76         "/usr/share/qt5/examples/webkitwidgets/browser/browser",
77         "http://localhost:%P/%c?token=%S",
78         NULL
79 };
80
81 static const char *args_for_binary[] = {
82         "%r/%c",
83         NULL
84 };
85
86 struct execdesc {
87         const char *type;
88         const char **master_args;
89         const char **slave_args;
90 };
91
92 static struct execdesc known_launchers[] = {
93         { "text/html",                args_for_afb_daemon, args_for_web_runtime },
94         { "application/x-executable", args_for_binary,     NULL },
95         { "text/vnd.qt.qml",          args_for_qmlviewer,  NULL }
96 };
97
98 struct launchparam {
99         int port;
100         const char *secret;
101         const char *datadir;
102         const char **master_args;
103         const char **slave_args;
104 };
105
106 static char **instantiate_arguments(const char **args, struct afm_launch_desc *desc, struct launchparam *params)
107 {
108         const char **iter, *p, *v;
109         char *data, **result, port[20], width[20], height[20], mini[3], c;
110         int n, s;
111
112         /* init */
113         mini[0] = '%';
114         mini[2] = 0;
115
116         /* loop that either compute the size and build the result */
117         data = NULL;
118         n = s = 0;
119         for (;;) {
120                 iter = args;
121                 n = 0;
122                 while (*iter) {
123                         p = *iter++;
124                         if (data)
125                                 result[n] = data;
126                         n++;
127                         while((c = *p++) != 0) {
128                                 if (c != '%') {
129                                         if (data)
130                                                 *data++ = c;
131                                         else
132                                                 s++;
133                                 } else {
134                                         c = *p++;
135                                         switch (c) {
136                                         case 'I': v = FWK_ICON_DIR; break;
137                                         case 'P': if(!data) sprintf(port, "%d", params->port); v = port; break;
138                                         case 'S': v = params->secret; break;
139                                         case 'D': v = params->datadir; break;
140                                         case 'r': v = desc->path; break;
141                                         case 'h': v = desc->home; break;
142                                         case 't': v = desc->tag; break;
143                                         case 'a': v = desc->appid; break;
144                                         case 'c': v = desc->content; break;
145                                         case 'm': v = desc->type; break;
146                                         case 'n': v = desc->name; break;
147                                         case 'p': v = "" /*desc->plugins*/; break;
148                                         case 'W': if(!data) sprintf(width, "%d", desc->width); v = width; break;
149                                         case 'H': if(!data) sprintf(height, "%d", desc->height); v = height; break;
150                                         case '%': c = 0;
151                                         default: mini[1] = c; v = mini; break;
152                                         }
153                                         if (data)
154                                                 data = stpcpy(data, v);
155                                         else
156                                                 s += strlen(v);
157                                 }
158                         }
159                         if (data)
160                                 *data++ = 0;
161                         else
162                                 s++;
163                 }
164                 if (data) {
165                         result[n] = NULL;
166                         return result;
167                 }
168                 /* allocation */
169                 result = malloc((n+1)*sizeof(char*) + s);
170                 if (result == NULL) {
171                         errno = ENOMEM;
172                         return NULL;
173                 }
174                 data = (char*)(&result[n + 1]);
175         }
176 }
177
178 static void mksecret(char buffer[9])
179 {
180         snprintf(buffer, 9, "%08lX", (0xffffffff & random()));
181 }
182
183 static int mkport()
184 {
185         static int port_ring = 12345;
186         int port = port_ring;
187         if (port < 12345 || port > 15432)
188                 port = 12345;
189         port_ring = port + 1;
190         return port;
191 }
192
193
194
195 static int launchexec1(struct afm_launch_desc *desc, pid_t children[2], struct launchparam *params)
196 {
197         int rc;
198         char **args;
199
200         /* fork the master child */
201         children[0] = fork();
202         if (children[0] < 0) {
203                 ERROR("master fork failed: %m");
204                 return -1;
205         }
206         if (children[0]) {
207                 /********* in the parent process ************/
208                 return 0;
209         }
210
211         /********* in the master child ************/
212         /* enter the process group */
213         rc = setpgid(0, 0);
214         if (rc) {
215                 ERROR("setpgid failed");
216                 _exit(1);
217         }
218
219         /* enter security mode */
220         rc = secmgr_prepare_exec(desc->tag);
221         if (rc < 0) {
222                 ERROR("call to secmgr_prepare_exec failed: %m");
223                 _exit(1);
224         }
225
226         /* enter the datadirectory */
227         rc = mkdir(params->datadir, 0755);
228         if (rc && errno != EEXIST) {
229                 ERROR("creation of datadir %s failed: %m", params->datadir);
230                 _exit(1);
231         }
232         rc = chdir(params->datadir);
233         if (rc) {
234                 ERROR("can't enter the datadir %s: %m", params->datadir);
235                 _exit(1);
236         }
237
238         args = instantiate_arguments(params->master_args, desc, params);
239         if (args == NULL) {
240                 ERROR("out of memory in master");
241         }
242         else {
243                 rc = execve(args[0], args, environ);
244                 ERROR("failed to exec master %s: %m", args[0]);
245         }
246         _exit(1);
247 }
248
249 static int launchexec2(struct afm_launch_desc *desc, pid_t children[2], struct launchparam *params)
250 {
251         int rc;
252         char message[10];
253         int mpipe[2];
254         int spipe[2];
255         char **args;
256
257         /* prepare the pipes */
258         rc = pipe2(mpipe, O_CLOEXEC);
259         if (rc < 0) {
260                 ERROR("error while calling pipe2: %m");
261                 return -1;
262         }
263         rc = pipe2(spipe, O_CLOEXEC);
264         if (rc < 0) {
265                 ERROR("error while calling pipe2: %m");
266                 close(spipe[0]);
267                 close(spipe[1]);
268                 return -1;
269         }
270
271         /* fork the master child */
272         children[0] = fork();
273         if (children[0] < 0) {
274                 ERROR("master fork failed: %m");
275                 close(mpipe[0]);
276                 close(mpipe[1]);
277                 close(spipe[0]);
278                 close(spipe[1]);
279                 return -1;
280         }
281         if (children[0]) {
282                 /********* in the parent process ************/
283                 close(mpipe[1]);
284                 close(spipe[0]);
285                 /* wait the ready signal (that transmit the slave pid) */
286                 rc = read(mpipe[0], &children[1], sizeof children[1]);
287                 close(mpipe[0]);
288                 if (rc  <= 0) {
289                         ERROR("reading master pipe failed: %m");
290                         close(spipe[1]);
291                         return -1;
292                 }
293                 assert(rc == sizeof children[1]);
294                 /* start the child */
295                 rc = write(spipe[1], "start", 5);
296                 if (rc < 0) {
297                         ERROR("writing slave pipe failed: %m");
298                         close(spipe[1]);
299                         return -1;
300                 }
301                 assert(rc == 5);
302                 close(spipe[1]);
303                 return 0;
304         }
305
306         /********* in the master child ************/
307         close(mpipe[0]);
308         close(spipe[1]);
309
310         /* enter the process group */
311         rc = setpgid(0, 0);
312         if (rc) {
313                 ERROR("setpgid failed");
314                 _exit(1);
315         }
316
317         /* enter security mode */
318         rc = secmgr_prepare_exec(desc->tag);
319         if (rc < 0) {
320                 ERROR("call to secmgr_prepare_exec failed: %m");
321                 _exit(1);
322         }
323
324         /* enter the datadirectory */
325         rc = mkdir(params->datadir, 0755);
326         if (rc && errno != EEXIST) {
327                 ERROR("creation of datadir %s failed: %m", params->datadir);
328                 _exit(1);
329         }
330         rc = chdir(params->datadir);
331         if (rc) {
332                 ERROR("can't enter the datadir %s: %m", params->datadir);
333                 _exit(1);
334         }
335
336         /* fork the slave child */
337         children[1] = fork();
338         if (children[1] < 0) {
339                 ERROR("slave fork failed: %m");
340                 _exit(1);
341         }
342         if (children[1] == 0) {
343                 /********* in the slave child ************/
344                 close(mpipe[0]);
345                 rc = read(spipe[0], message, sizeof message);
346                 if (rc <= 0) {
347                         ERROR("reading slave pipe failed: %m");
348                         _exit(1);
349                 }
350
351                 args = instantiate_arguments(params->slave_args, desc, params);
352                 if (args == NULL) {
353                         ERROR("out of memory in slave");
354                 }
355                 else {
356                         rc = execve(args[0], args, environ);
357                         ERROR("failed to exec slave %s: %m", args[0]);
358                 }
359                 _exit(1);
360         }
361
362         /********* still in the master child ************/
363         close(spipe[1]);
364         args = instantiate_arguments(params->master_args, desc, params);
365         if (args == NULL) {
366                 ERROR("out of memory in master");
367         }
368         else {
369                 rc = write(mpipe[1], &children[1], sizeof children[1]);
370                 if (rc <= 0) {
371                         ERROR("can't write master pipe: %m");
372                 }
373                 else {
374                         close(mpipe[1]);
375                         rc = execve(args[0], args, environ);
376                         ERROR("failed to exec master %s: %m", args[0]);
377                 }
378         }
379         _exit(1);
380 }
381
382 int afm_launch(struct afm_launch_desc *desc, pid_t children[2])
383 {
384         char datadir[PATH_MAX];
385         int ikl, nkl, rc;
386         char secret[9];
387         struct launchparam params;
388
389         /* what launcher ? */
390         ikl = 0;
391         if (desc->type != NULL && *desc->type) {
392                 nkl = sizeof known_launchers / sizeof * known_launchers;
393                 while (ikl < nkl && strcmp(desc->type, known_launchers[ikl].type))
394                         ikl++;
395                 if (ikl == nkl) {
396                         ERROR("type %s not found!", desc->type);
397                         errno = ENOENT;
398                         return -1;
399                 }
400         }
401
402         /* prepare paths */
403         rc = snprintf(datadir, sizeof datadir, "%s/%s", desc->home, desc->tag);
404         if (rc < 0 || rc >= sizeof datadir) {
405                 ERROR("overflow for datadir");
406                 errno = EINVAL;
407                 return -1;
408         }
409
410         /* make the secret and port */
411         mksecret(secret);
412         params.port = mkport();
413         params.secret = secret;
414         params.datadir = datadir;
415         params.master_args = known_launchers[ikl].master_args;
416         params.slave_args = known_launchers[ikl].slave_args;
417
418         return params.slave_args ? launchexec2(desc, children, &params) : launchexec1(desc, children, &params);
419 }
420