a66004b158d58a3a68bff36a6fc18beab93d94fe
[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         "--daemon",
58         "--alias=/icons:%I",
59         "--port=%P",
60         "--rootdir=%D",
61         "--token=%S",
62         NULL
63 };
64
65 static const char *args_for_qmlviewer[] = {
66         "/usr/bin/qt5/qmlscene",
67         "-fullscreen",
68         "-I",
69         "%r",
70         "-I",
71         "%r/imports",
72         "%r/%c",
73         NULL
74 };
75
76 static const char *args_for_web_runtime[] = {
77         "/usr/bin/web-runtime",
78         "http://localhost:%P/%c?token=%S",
79         NULL
80 };
81
82 static const char *args_for_binary[] = {
83         "%r/%c",
84         NULL
85 };
86
87 struct execdesc {
88         const char *type;
89         const char **master_args;
90         const char **slave_args;
91 };
92
93 static struct execdesc known_launchers[] = {
94         { "text/html",                args_for_afb_daemon, args_for_web_runtime },
95         { "application/x-executable", args_for_binary,     NULL },
96         { "text/vnd.qt.qml",          args_for_qmlviewer,  NULL }
97 };
98
99 struct launchparam {
100         int port;
101         const char *secret;
102         const char *datadir;
103         const char **master_args;
104         const char **slave_args;
105 };
106
107 static char **instantiate_arguments(const char **args, struct afm_launch_desc *desc, struct launchparam *params)
108 {
109         const char **iter, *p, *v;
110         char *data, **result, port[20], width[20], height[20], mini[3], c;
111         int n, s;
112
113         /* init */
114         mini[0] = '%';
115         mini[2] = 0;
116
117         /* loop that either compute the size and build the result */
118         data = NULL;
119         n = s = 0;
120         for (;;) {
121                 iter = args;
122                 n = 0;
123                 while (*iter) {
124                         p = *iter++;
125                         if (data)
126                                 result[n] = data;
127                         n++;
128                         while((c = *p++) != 0) {
129                                 if (c != '%') {
130                                         if (data)
131                                                 *data++ = c;
132                                         else
133                                                 s++;
134                                 } else {
135                                         c = *p++;
136                                         switch (c) {
137                                         case 'I': v = FWK_ICON_DIR; break;
138                                         case 'P': if(!data) sprintf(port, "%d", params->port); v = port; break;
139                                         case 'S': v = params->secret; break;
140                                         case 'D': v = params->datadir; break;
141                                         case 'r': v = desc->path; break;
142                                         case 'h': v = desc->home; break;
143                                         case 't': v = desc->tag; break;
144                                         case 'a': v = desc->appid; break;
145                                         case 'c': v = desc->content; break;
146                                         case 'm': v = desc->type; break;
147                                         case 'n': v = desc->name; break;
148                                         case 'p': v = "" /*desc->plugins*/; break;
149                                         case 'W': if(!data) sprintf(width, "%d", desc->width); v = width; break;
150                                         case 'H': if(!data) sprintf(height, "%d", desc->height); v = height; break;
151                                         case '%': c = 0;
152                                         default: mini[1] = c; v = mini; break;
153                                         }
154                                         if (data)
155                                                 data = stpcpy(data, v);
156                                         else
157                                                 s += strlen(v);
158                                 }
159                         }
160                         if (data)
161                                 *data++ = 0;
162                         else
163                                 s++;
164                 }
165                 if (data) {
166                         result[n] = NULL;
167                         return result;
168                 }
169                 /* allocation */
170                 result = malloc((n+1)*sizeof(char*) + s);
171                 if (result == NULL) {
172                         errno = ENOMEM;
173                         return NULL;
174                 }
175                 data = (char*)(&result[n + 1]);
176         }
177 }
178
179 static void mksecret(char buffer[9])
180 {
181         snprintf(buffer, 9, "%08lX", (0xffffffff & random()));
182 }
183
184 static int mkport()
185 {
186         static int port_ring = 12345;
187         int port = port_ring;
188         if (port < 12345 || port > 15432)
189                 port = 12345;
190         port_ring = port + 1;
191         return port;
192 }
193
194
195
196 static int launchexec1(struct afm_launch_desc *desc, pid_t children[2], struct launchparam *params)
197 {
198         int rc;
199         char **args;
200
201         /* fork the master child */
202         children[0] = fork();
203         if (children[0] < 0) {
204                 ERROR("master fork failed: %m");
205                 return -1;
206         }
207         if (children[0]) {
208                 /********* in the parent process ************/
209                 return 0;
210         }
211
212         /********* in the master child ************/
213         /* enter the process group */
214         rc = setpgid(0, 0);
215         if (rc) {
216                 ERROR("setpgid failed");
217                 _exit(1);
218         }
219
220         /* enter security mode */
221         rc = secmgr_prepare_exec(desc->tag);
222         if (rc < 0) {
223                 ERROR("call to secmgr_prepare_exec failed: %m");
224                 _exit(1);
225         }
226
227         /* enter the datadirectory */
228         rc = mkdir(params->datadir, 0755);
229         if (rc && errno != EEXIST) {
230                 ERROR("creation of datadir %s failed: %m", params->datadir);
231                 _exit(1);
232         }
233         rc = chdir(params->datadir);
234         if (rc) {
235                 ERROR("can't enter the datadir %s: %m", params->datadir);
236                 _exit(1);
237         }
238
239         args = instantiate_arguments(params->master_args, desc, params);
240         if (args == NULL) {
241                 ERROR("out of memory in master");
242         }
243         else {
244                 rc = execve(args[0], args, environ);
245                 ERROR("failed to exec master %s: %m", args[0]);
246         }
247         _exit(1);
248 }
249
250 static int launchexec2(struct afm_launch_desc *desc, pid_t children[2], struct launchparam *params)
251 {
252         int rc;
253         char message[10];
254         int mpipe[2];
255         int spipe[2];
256         char **args;
257
258         /* prepare the pipes */
259         rc = pipe2(mpipe, O_CLOEXEC);
260         if (rc < 0) {
261                 ERROR("error while calling pipe2: %m");
262                 return -1;
263         }
264         rc = pipe2(spipe, O_CLOEXEC);
265         if (rc < 0) {
266                 ERROR("error while calling pipe2: %m");
267                 close(spipe[0]);
268                 close(spipe[1]);
269                 return -1;
270         }
271
272         /* fork the master child */
273         children[0] = fork();
274         if (children[0] < 0) {
275                 ERROR("master fork failed: %m");
276                 close(mpipe[0]);
277                 close(mpipe[1]);
278                 close(spipe[0]);
279                 close(spipe[1]);
280                 return -1;
281         }
282         if (children[0]) {
283                 /********* in the parent process ************/
284                 close(mpipe[1]);
285                 close(spipe[0]);
286                 /* wait the ready signal (that transmit the slave pid) */
287                 rc = read(mpipe[0], &children[1], sizeof children[1]);
288                 close(mpipe[0]);
289                 if (rc  <= 0) {
290                         ERROR("reading master pipe failed: %m");
291                         close(spipe[1]);
292                         return -1;
293                 }
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