937366d67ab084d4fb7f3aac918a2c76bc768cdb
[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, x;
111
112         /* init */
113         mini[0] = '%';
114         mini[2] = 0;
115
116         /* loop that either compute the size and build the result */
117         n = s = x = 0;
118         for (;;) {
119                 iter = args;
120                 n = 0;
121                 while (*iter) {
122                         p = *iter++;
123                         if (x)
124                                 result[n] = data;
125                         n++;
126                         while((c = *p++) != 0) {
127                                 if (c != '%') {
128                                         if (x)
129                                                 *data++ = c;
130                                         else
131                                                 s++;
132                                 } else {
133                                         c = *p++;
134                                         switch (c) {
135                                         case 'I': v = FWK_ICON_DIR; break;
136                                         case 'P': if(!x) sprintf(port, "%d", params->port); v = port; break;
137                                         case 'S': v = params->secret; break;
138                                         case 'D': v = params->datadir; break;
139                                         case 'r': v = desc->path; break;
140                                         case 'h': v = desc->home; break;
141                                         case 't': v = desc->tag; break;
142                                         case 'a': v = desc->appid; break;
143                                         case 'c': v = desc->content; break;
144                                         case 'm': v = desc->type; break;
145                                         case 'n': v = desc->name; break;
146                                         case 'p': v = "" /*desc->plugins*/; break;
147                                         case 'W': if(!x) sprintf(width, "%d", desc->width); v = width; break;
148                                         case 'H': if(!x) sprintf(height, "%d", desc->height); v = height; break;
149                                         case '%': c = 0;
150                                         default: mini[1] = c; v = mini; break;
151                                         }
152                                         if (x)
153                                                 data = stpcpy(data, v);
154                                         else
155                                                 s += strlen(v);
156                                 }
157                         }
158                         if (x)
159                                 *data++ = 0;
160                         else
161                                 s++;
162                 }
163                 if (x) {
164                         result[n] = NULL;
165                         return result;
166                 }
167                 /* allocation */
168                 result = malloc((n+1)*sizeof(char*) + s);
169                 if (result == NULL) {
170                         errno = ENOMEM;
171                         return NULL;
172                 }
173                 data = (char*)(&result[n + 1]);
174                 x = 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                 if (rc  < 0) {
288                         ERROR("reading master pipe failed: %m");
289                         close(mpipe[0]);
290                         close(spipe[1]);
291                         return -1;
292                 }
293                 close(mpipe[0]);
294                 assert(rc == sizeof children[1]);
295                 /* start the child */
296                 rc = write(spipe[1], "start", 5);
297                 if (rc < 0) {
298                         ERROR("writing slave pipe failed: %m");
299                         close(spipe[1]);
300                         return -1;
301                 }
302                 assert(rc == 5);
303                 close(spipe[1]);
304                 return 0;
305         }
306
307         /********* in the master child ************/
308         close(mpipe[0]);
309         close(spipe[1]);
310
311         /* enter the process group */
312         rc = setpgid(0, 0);
313         if (rc) {
314                 ERROR("setpgid failed");
315                 _exit(1);
316         }
317
318         /* enter security mode */
319         rc = secmgr_prepare_exec(desc->tag);
320         if (rc < 0) {
321                 ERROR("call to secmgr_prepare_exec failed: %m");
322                 _exit(1);
323         }
324
325         /* enter the datadirectory */
326         rc = mkdir(params->datadir, 0755);
327         if (rc && errno != EEXIST) {
328                 ERROR("creation of datadir %s failed: %m", params->datadir);
329                 _exit(1);
330         }
331         rc = chdir(params->datadir);
332         if (rc) {
333                 ERROR("can't enter the datadir %s: %m", params->datadir);
334                 _exit(1);
335         }
336
337         /* fork the slave child */
338         children[1] = fork();
339         if (children[1] < 0) {
340                 ERROR("slave fork failed: %m");
341                 _exit(1);
342         }
343         if (children[1] == 0) {
344                 /********* in the slave child ************/
345                 close(mpipe[0]);
346                 rc = read(spipe[0], message, sizeof message);
347                 if (rc < 0) {
348                         ERROR("reading slave pipe failed: %m");
349                         _exit(1);
350                 }
351
352                 args = instantiate_arguments(params->slave_args, desc, params);
353                 if (args == NULL) {
354                         ERROR("out of memory in slave");
355                 }
356                 else {
357                         rc = execve(args[0], args, environ);
358                         ERROR("failed to exec slave %s: %m", args[0]);
359                 }
360                 _exit(1);
361         }
362
363         /********* still in the master child ************/
364         close(spipe[1]);
365         args = instantiate_arguments(params->master_args, desc, params);
366         if (args == NULL) {
367                 ERROR("out of memory in master");
368         }
369         else {
370                 rc = write(mpipe[1], &children[1], sizeof children[1]);
371                 if (rc < 0) {
372                         ERROR("can't write master pipe: %m");
373                 }
374                 else {
375                         close(mpipe[1]);
376                         rc = execve(args[0], args, environ);
377                         ERROR("failed to exec master %s: %m", args[0]);
378                 }
379         }
380         _exit(1);
381 }
382
383 int afm_launch(struct afm_launch_desc *desc, pid_t children[2])
384 {
385         char datadir[PATH_MAX];
386         int ikl, nkl, rc;
387         char secret[9];
388         struct launchparam params;
389
390         /* what launcher ? */
391         ikl = 0;
392         if (desc->type != NULL && *desc->type) {
393                 nkl = sizeof known_launchers / sizeof * known_launchers;
394                 while (ikl < nkl && strcmp(desc->type, known_launchers[ikl].type))
395                         ikl++;
396                 if (ikl == nkl) {
397                         ERROR("type %s not found!", desc->type);
398                         errno = ENOENT;
399                         return -1;
400                 }
401         }
402
403         /* prepare paths */
404         rc = snprintf(datadir, sizeof datadir, "%s/%s", desc->home, desc->tag);
405         if (rc < 0 || rc >= sizeof datadir) {
406                 ERROR("overflow for datadir");
407                 errno = EINVAL;
408                 return -1;
409         }
410
411         /* make the secret and port */
412         mksecret(secret);
413         params.port = mkport();
414         params.secret = secret;
415         params.datadir = datadir;
416         params.master_args = known_launchers[ikl].master_args;
417         params.slave_args = known_launchers[ikl].slave_args;
418
419         return params.slave_args ? launchexec2(desc, children, &params) : launchexec1(desc, children, &params);
420 }
421