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