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