improved launcher
[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/qmlviewer",
66         "-frameless",
67         "-fullscreen",
68         "-script",
69         "%r/%c",
70         NULL
71 };
72
73 static const char *args_for_web_runtime[] = {
74         "/usr/share/qt5/examples/webkitwidgets/browser/browser",
75         "http://localhost:%P/%c?token=%S",
76         NULL
77 };
78
79 static const char *args_for_binary[] = {
80         "%r/%c",
81         NULL
82 };
83
84 struct execdesc {
85         const char *type;
86         const char **master_args;
87         const char **slave_args;
88 };
89
90 static struct execdesc known_launchers[] = {
91         { "text/html",                args_for_afb_daemon, args_for_web_runtime },
92         { "application/x-executable", args_for_binary,     NULL },
93         { "text/vnd.qt.qml",          args_for_qmlviewer,  NULL }
94 };
95
96 struct launchparam {
97         int port;
98         const char *secret;
99         const char *datadir;
100         const char **master_args;
101         const char **slave_args;
102 };
103
104 static char **instantiate_arguments(const char **args, struct afm_launch_desc *desc, struct launchparam *params)
105 {
106         const char **iter, *p, *v;
107         char *data, **result, port[20], width[20], height[20], mini[3], c;
108         int n, s, x;
109
110         /* init */
111         mini[0] = '%';
112         mini[2] = 0;
113
114         /* loop that either compute the size and build the result */
115         n = s = x = 0;
116         for (;;) {
117                 iter = args;
118                 n = 0;
119                 while (*iter) {
120                         p = *iter++;
121                         if (x)
122                                 result[n] = data;
123                         n++;
124                         while((c = *p++) != 0) {
125                                 if (c != '%') {
126                                         if (x)
127                                                 *data++ = c;
128                                         else
129                                                 s++;
130                                 } else {
131                                         c = *p++;
132                                         switch (c) {
133                                         case 'I': v = FWK_ICON_DIR; break;
134                                         case 'P': if(!x) sprintf(port, "%d", params->port); v = port; break;
135                                         case 'S': v = params->secret; break;
136                                         case 'D': v = params->datadir; break;
137                                         case 'r': v = desc->path; break;
138                                         case 'h': v = desc->home; break;
139                                         case 't': v = desc->tag; break;
140                                         case 'a': v = desc->appid; break;
141                                         case 'c': v = desc->content; break;
142                                         case 'm': v = desc->type; break;
143                                         case 'n': v = desc->name; break;
144                                         case 'p': v = "" /*desc->plugins*/; break;
145                                         case 'W': if(!x) sprintf(width, "%d", desc->width); v = width; break;
146                                         case 'H': if(!x) sprintf(height, "%d", desc->height); v = height; break;
147                                         case '%': c = 0;
148                                         default: mini[1] = c; v = mini; break;
149                                         }
150                                         if (x)
151                                                 data = stpcpy(data, v);
152                                         else
153                                                 s += strlen(v);
154                                 }
155                         }
156                         if (x)
157                                 *data++ = 0;
158                         else
159                                 s++;
160                 }
161                 if (x) {
162                         result[n] = NULL;
163                         return result;
164                 }
165                 /* allocation */
166                 result = malloc((n+1)*sizeof(char*) + s);
167                 if (result == NULL) {
168                         errno = ENOMEM;
169                         return NULL;
170                 }
171                 data = (char*)(&result[n + 1]);
172                 x = 1;
173         }
174 }
175
176 static void mksecret(char buffer[9])
177 {
178         snprintf(buffer, 9, "%08lX", (0xffffffff & random()));
179 }
180
181 static int mkport()
182 {
183         static int port_ring = 12345;
184         int port = port_ring;
185         if (port < 12345 || port > 15432)
186                 port = 12345;
187         port_ring = port + 1;
188         return port;
189 }
190
191
192
193 static int launchexec1(struct afm_launch_desc *desc, pid_t children[2], struct launchparam *params)
194 {
195         int rc;
196         char **args;
197
198         /* fork the master child */
199         children[0] = fork();
200         if (children[0] < 0) {
201                 ERROR("master fork failed: %m");
202                 return -1;
203         }
204         if (children[0]) {
205                 /********* in the parent process ************/
206                 return 0;
207         }
208
209         /********* in the master child ************/
210         /* enter the process group */
211         rc = setpgid(0, 0);
212         if (rc) {
213                 ERROR("setpgid failed");
214                 _exit(1);
215         }
216
217         /* enter security mode */
218         rc = secmgr_prepare_exec(desc->tag);
219         if (rc < 0) {
220                 ERROR("call to secmgr_prepare_exec failed: %m");
221                 _exit(1);
222         }
223
224         /* enter the datadirectory */
225         rc = mkdir(params->datadir, 0755);
226         if (rc && errno != EEXIST) {
227                 ERROR("creation of datadir %s failed: %m", params->datadir);
228                 _exit(1);
229         }
230         rc = chdir(params->datadir);
231         if (rc) {
232                 ERROR("can't enter the datadir %s: %m", params->datadir);
233                 _exit(1);
234         }
235
236         args = instantiate_arguments(params->master_args, desc, params);
237         if (args == NULL) {
238                 ERROR("out of memory in master");
239         }
240         else {
241                 rc = execve(args[0], args, environ);
242                 ERROR("failed to exec master %s: %m", args[0]);
243         }
244         _exit(1);
245 }
246
247 static int launchexec2(struct afm_launch_desc *desc, pid_t children[2], struct launchparam *params)
248 {
249         int rc;
250         char message[10];
251         int mpipe[2];
252         int spipe[2];
253         char **args;
254
255         /* prepare the pipes */
256         rc = pipe2(mpipe, O_CLOEXEC);
257         if (rc < 0) {
258                 ERROR("error while calling pipe2: %m");
259                 return -1;
260         }
261         rc = pipe2(spipe, O_CLOEXEC);
262         if (rc < 0) {
263                 ERROR("error while calling pipe2: %m");
264                 close(spipe[0]);
265                 close(spipe[1]);
266                 return -1;
267         }
268
269         /* fork the master child */
270         children[0] = fork();
271         if (children[0] < 0) {
272                 ERROR("master fork failed: %m");
273                 close(mpipe[0]);
274                 close(mpipe[1]);
275                 close(spipe[0]);
276                 close(spipe[1]);
277                 return -1;
278         }
279         if (children[0]) {
280                 /********* in the parent process ************/
281                 close(mpipe[1]);
282                 close(spipe[0]);
283                 /* wait the ready signal (that transmit the slave pid) */
284                 rc = read(mpipe[0], &children[1], sizeof children[1]);
285                 if (rc  < 0) {
286                         ERROR("reading master pipe failed: %m");
287                         close(mpipe[0]);
288                         close(spipe[1]);
289                         return -1;
290                 }
291                 close(mpipe[0]);
292                 assert(rc == sizeof children[1]);
293                 /* start the child */
294                 rc = write(spipe[1], "start", 5);
295                 if (rc < 0) {
296                         ERROR("writing slave pipe failed: %m");
297                         close(spipe[1]);
298                         return -1;
299                 }
300                 assert(rc == 5);
301                 close(spipe[1]);
302                 return 0;
303         }
304
305         /********* in the master child ************/
306         close(mpipe[0]);
307         close(spipe[1]);
308
309         /* enter the process group */
310         rc = setpgid(0, 0);
311         if (rc) {
312                 ERROR("setpgid failed");
313                 _exit(1);
314         }
315
316         /* enter security mode */
317         rc = secmgr_prepare_exec(desc->tag);
318         if (rc < 0) {
319                 ERROR("call to secmgr_prepare_exec failed: %m");
320                 _exit(1);
321         }
322
323         /* enter the datadirectory */
324         rc = mkdir(params->datadir, 0755);
325         if (rc && errno != EEXIST) {
326                 ERROR("creation of datadir %s failed: %m", params->datadir);
327                 _exit(1);
328         }
329         rc = chdir(params->datadir);
330         if (rc) {
331                 ERROR("can't enter the datadir %s: %m", params->datadir);
332                 _exit(1);
333         }
334
335         /* fork the slave child */
336         children[1] = fork();
337         if (children[1] < 0) {
338                 ERROR("slave fork failed: %m");
339                 _exit(1);
340         }
341         if (children[1] == 0) {
342                 /********* in the slave child ************/
343                 close(mpipe[0]);
344                 rc = read(spipe[0], message, sizeof message);
345                 if (rc < 0) {
346                         ERROR("reading slave pipe failed: %m");
347                         _exit(1);
348                 }
349
350                 args = instantiate_arguments(params->slave_args, desc, params);
351                 if (args == NULL) {
352                         ERROR("out of memory in slave");
353                 }
354                 else {
355                         rc = execve(args[0], args, environ);
356                         ERROR("failed to exec slave %s: %m", args[0]);
357                 }
358                 _exit(1);
359         }
360
361         /********* still in the master child ************/
362         close(spipe[1]);
363         args = instantiate_arguments(params->master_args, desc, params);
364         if (args == NULL) {
365                 ERROR("out of memory in master");
366         }
367         else {
368                 rc = write(mpipe[1], &children[1], sizeof children[1]);
369                 if (rc < 0) {
370                         ERROR("can't write master pipe: %m");
371                 }
372                 else {
373                         close(mpipe[1]);
374                         rc = execve(args[0], args, environ);
375                         ERROR("failed to exec master %s: %m", args[0]);
376                 }
377         }
378         _exit(1);
379 }
380
381 int afm_launch(struct afm_launch_desc *desc, pid_t children[2])
382 {
383         char datadir[PATH_MAX];
384         int ikl, nkl, rc;
385         char secret[9];
386         struct launchparam params;
387
388         /* what launcher ? */
389         ikl = 0;
390         if (desc->type != NULL && *desc->type) {
391                 nkl = sizeof known_launchers / sizeof * known_launchers;
392                 while (ikl < nkl && strcmp(desc->type, known_launchers[ikl].type))
393                         ikl++;
394                 if (ikl == nkl) {
395                         ERROR("type %s not found!", desc->type);
396                         errno = ENOENT;
397                         return -1;
398                 }
399         }
400
401         /* prepare paths */
402         rc = snprintf(datadir, sizeof datadir, "%s/%s", desc->home, desc->tag);
403         if (rc < 0 || rc >= sizeof datadir) {
404                 ERROR("overflow for datadir");
405                 errno = EINVAL;
406                 return -1;
407         }
408
409         /* make the secret and port */
410         mksecret(secret);
411         params.port = mkport();
412         params.secret = secret;
413         params.datadir = datadir;
414         params.master_args = known_launchers[ikl].master_args;
415         params.slave_args = known_launchers[ikl].slave_args;
416
417         return params.slave_args ? launchexec2(desc, children, &params) : launchexec1(desc, children, &params);
418 }
419