c55297ac1509ec94ada8097953205995143c3fe4
[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 #define DEFAULT_TYPE "text/html"
56
57 const char separators[] = " \t\n";
58
59 struct execdesc {
60         char *type;
61         char **execs[2];
62 };
63
64 #if 0
65 static const char *args_for_afb_daemon[] = {
66         "/usr/bin/afb-daemon",
67         "--alias=/icons:%I",
68         "--port=%P",
69         "--rootdir=%r",
70         "--token=%S",
71         NULL
72 };
73
74 static const char *args_for_qmlviewer[] = {
75         "/usr/bin/qt5/qmlscene",
76         "-fullscreen",
77         "-I",
78         "%r",
79         "-I",
80         "%r/imports",
81         "%r/%c",
82         NULL
83 };
84
85 static const char *args_for_web_runtime[] = {
86         "/usr/bin/web-runtime",
87         "http://localhost:%P/%c?token=%S",
88         NULL
89 };
90
91 static const char *args_for_binary[] = {
92         "%r/%c",
93         NULL
94 };
95
96 static struct execdesc known_launchers[] = {
97         { "text/html",                args_for_afb_daemon, args_for_web_runtime },
98         { "application/x-executable", args_for_binary,     NULL },
99         { "text/vnd.qt.qml",          args_for_qmlviewer,  NULL },
100         { NULL, NULL, NULL }
101 };
102 #endif
103
104 struct launchers {
105         int count;
106         struct execdesc *descs;
107 };
108
109 static struct launchers launchers = { 0, NULL };
110
111 struct launchparam {
112         int port;
113         const char *secret;
114         const char *datadir;
115         const char **master;
116         const char **slave;
117 };
118
119 static gid_t groupid = 0;
120
121 static int read_type(const char *buffer, const char *filepath, int line)
122 {
123         size_t length;
124         int count;
125         struct execdesc *descs;
126         char *type;
127
128         /* check the type */
129         length = strcspn(buffer, separators);
130         assert(length);
131         if (buffer[length + strspn(buffer + length, separators)] != 0) {
132                 ERROR("%s:%d: extra characters found after type", filepath, line);
133                 errno = EINVAL;
134                 return -1;
135         }
136
137         /* allocates data */
138         type = strndup(buffer, length);
139         count = launchers.count + 1;
140         descs = realloc(launchers.descs, count * sizeof(struct execdesc));
141         if (descs == NULL || type == NULL) {
142                 free(type);
143                 errno = ENOMEM;
144                 return -1;
145         }
146
147         /* fill data */
148         launchers.descs = descs;
149         descs += count - 1;
150         descs->type = type;
151         descs->execs[0] = NULL;
152         descs->execs[1] = NULL;
153         launchers.count = count;
154         return 0;
155 }
156
157 static int read_args(const char *buffer, int bottom, int offset, const char *filepath, int line)
158 {
159         char **vector, *args;
160         size_t index, len, length;
161         int count;
162
163         /* count */
164         count = 0;
165         length = index = 0;
166         while(buffer[index]) {
167                 count++;
168                 /* skips the spaces */
169                 len = strcspn(buffer + index, separators);
170                 length += len;
171                 /* skips the spaces */
172                 index += len;
173                 index += strspn(buffer + index, separators);
174         }
175         /* allocates */
176         while (bottom < launchers.count) {
177                 vector = malloc(length + count + (count + 1) * sizeof(char*));
178                 if (vector == NULL) {
179                         ERROR("%s:%d: out of memory", filepath, line);
180                         return -1;
181                 }
182                 args = (char*)(vector + count + 1);
183                 count = 0;
184                 index = 0;
185                 while(buffer[index]) {
186                         /* skips the spaces */
187                         len = strcspn(buffer + index, separators);
188                         vector[count++] = args;
189                         memcpy(args, buffer + index, len);
190                         args += len;
191                         index += len;
192                         *args++ = 0;
193                         /* skips the spaces */
194                         len = strspn(buffer + index, separators);
195                         index += len;
196                 }
197                 vector[count] = NULL;
198                 launchers.descs[bottom++].execs[offset] = vector;
199         }
200         return 0;
201 }
202
203 static int read_launchers(FILE *file, const char *filepath)
204 {
205         char buffer[4096];
206         int index, line, rc, bottom, offset, typed;
207
208         /* reads the file */
209         line = 0;
210         offset = 0;
211         typed = 0;
212         bottom = launchers.count;
213         while (fgets(buffer, sizeof buffer, file) != NULL) {
214                 line++;
215
216                 /* find start of line */
217                 index = strspn(buffer, separators);
218
219                 /* skip empty lines and comments */
220                 if (buffer[index] == 0 || buffer[index] == '#')
221                         continue;
222
223                 if (index == 0) {
224                         if (!typed)
225                                 bottom = launchers.count;
226                         rc = read_type(buffer, filepath, line);
227                         if (rc)
228                                 return rc;
229                         if (!typed) {
230                                 typed = 1;
231                                 offset = 0;
232                         }
233                 } else if (!typed && !offset) {
234                         ERROR("%s:%d: untyped launcher found", filepath, line);
235                         errno = EINVAL;
236                         return -1;
237                 } else if (offset >= 2) {
238                         ERROR("%s:%d: extra launcher found", filepath, line);
239                         errno = EINVAL;
240                         return -1;
241                 } else {
242                         rc = read_args(buffer + index, bottom, offset, filepath, line);
243                         if (rc)
244                                 return rc;
245                         offset++;
246                         typed = 0;
247                 }
248         }
249         if (ferror(file)) {
250                 ERROR("%s:%d: error while reading, %m", filepath, line);
251                 return -1;
252         }
253         return 0;
254 }
255
256 static int read_configuration_file(const char *filepath)
257 {
258         int rc;
259         FILE *file;
260
261         /* opens the configuration file */
262         file = fopen(filepath, "r");
263         if (file == NULL) {
264                 /* error */
265                 ERROR("can't read file %s: %m", filepath);
266                 rc = -1;
267         } else {
268                 /* reads it */
269                 rc = read_launchers(file, filepath);
270                 fclose(file);
271         }
272         return rc;
273 }
274
275 static char **instantiate_arguments(const char **args, struct afm_launch_desc *desc, struct launchparam *params)
276 {
277         const char **iter, *p, *v;
278         char *data, **result, port[20], width[20], height[20], mini[3], c;
279         int n, s;
280
281         /* init */
282         mini[0] = '%';
283         mini[2] = 0;
284
285         /* loop that either compute the size and build the result */
286         data = NULL;
287         result = NULL;
288         n = s = 0;
289         for (;;) {
290                 iter = args;
291                 n = 0;
292                 while (*iter) {
293                         p = *iter++;
294                         if (data)
295                                 result[n] = data;
296                         n++;
297                         while((c = *p++) != 0) {
298                                 if (c != '%') {
299                                         if (data)
300                                                 *data++ = c;
301                                         else
302                                                 s++;
303                                 } else {
304                                         c = *p++;
305                                         switch (c) {
306                                         case 'I': v = FWK_ICON_DIR; break;
307                                         case 'P': if(!data) sprintf(port, "%d", params->port); v = port; break;
308                                         case 'S': v = params->secret; break;
309                                         case 'D': v = params->datadir; break;
310                                         case 'r': v = desc->path; break;
311                                         case 'h': v = desc->home; break;
312                                         case 't': v = desc->tag; break;
313                                         case 'a': v = desc->appid; break;
314                                         case 'c': v = desc->content; break;
315                                         case 'm': v = desc->type; break;
316                                         case 'n': v = desc->name; break;
317                                         case 'p': v = "" /*desc->plugins*/; break;
318                                         case 'W': if(!data) sprintf(width, "%d", desc->width); v = width; break;
319                                         case 'H': if(!data) sprintf(height, "%d", desc->height); v = height; break;
320                                         case '%': c = 0;
321                                         default: mini[1] = c; v = mini; break;
322                                         }
323                                         if (data)
324                                                 data = stpcpy(data, v);
325                                         else
326                                                 s += strlen(v);
327                                 }
328                         }
329                         if (data)
330                                 *data++ = 0;
331                         else
332                                 s++;
333                 }
334                 if (data) {
335                         result[n] = NULL;
336                         return result;
337                 }
338                 /* allocation */
339                 result = malloc((n+1)*sizeof(char*) + s);
340                 if (result == NULL) {
341                         errno = ENOMEM;
342                         return NULL;
343                 }
344                 data = (char*)(&result[n + 1]);
345         }
346 }
347
348 static void mksecret(char buffer[9])
349 {
350         snprintf(buffer, 9, "%08lX", (0xffffffff & random()));
351 }
352
353 static int mkport()
354 {
355         static int port_ring = 12345;
356         int port = port_ring;
357         if (port < 12345 || port > 15432)
358                 port = 12345;
359         port_ring = port + 1;
360         return port;
361 }
362
363 static int launchexec1(struct afm_launch_desc *desc, pid_t children[2], struct launchparam *params)
364 {
365         int rc;
366         char **args;
367
368         /* fork the master child */
369         children[0] = fork();
370         if (children[0] < 0) {
371                 ERROR("master fork failed: %m");
372                 return -1;
373         }
374         if (children[0]) {
375                 /********* in the parent process ************/
376                 return 0;
377         }
378
379         /********* in the master child ************/
380
381         /* avoid set-gid effect */
382         setresgid(groupid, groupid, groupid);
383
384         /* enter the process group */
385         rc = setpgid(0, 0);
386         if (rc) {
387                 ERROR("setpgid failed");
388                 _exit(1);
389         }
390
391         /* enter security mode */
392         rc = secmgr_prepare_exec(desc->tag);
393         if (rc < 0) {
394                 ERROR("call to secmgr_prepare_exec failed: %m");
395                 _exit(1);
396         }
397
398         /* enter the datadirectory */
399         rc = mkdir(params->datadir, 0755);
400         if (rc && errno != EEXIST) {
401                 ERROR("creation of datadir %s failed: %m", params->datadir);
402                 _exit(1);
403         }
404         rc = chdir(params->datadir);
405         if (rc) {
406                 ERROR("can't enter the datadir %s: %m", params->datadir);
407                 _exit(1);
408         }
409
410         args = instantiate_arguments(params->master, desc, params);
411         if (args == NULL) {
412                 ERROR("out of memory in master");
413         }
414         else {
415                 rc = execve(args[0], args, environ);
416                 ERROR("failed to exec master %s: %m", args[0]);
417         }
418         _exit(1);
419 }
420
421 static int launchexec2(struct afm_launch_desc *desc, pid_t children[2], struct launchparam *params)
422 {
423         int rc;
424         char message[10];
425         int mpipe[2];
426         int spipe[2];
427         char **args;
428
429         /* prepare the pipes */
430         rc = pipe2(mpipe, O_CLOEXEC);
431         if (rc < 0) {
432                 ERROR("error while calling pipe2: %m");
433                 return -1;
434         }
435         rc = pipe2(spipe, O_CLOEXEC);
436         if (rc < 0) {
437                 ERROR("error while calling pipe2: %m");
438                 close(spipe[0]);
439                 close(spipe[1]);
440                 return -1;
441         }
442
443         /* fork the master child */
444         children[0] = fork();
445         if (children[0] < 0) {
446                 ERROR("master fork failed: %m");
447                 close(mpipe[0]);
448                 close(mpipe[1]);
449                 close(spipe[0]);
450                 close(spipe[1]);
451                 return -1;
452         }
453         if (children[0]) {
454                 /********* in the parent process ************/
455                 close(mpipe[1]);
456                 close(spipe[0]);
457                 /* wait the ready signal (that transmit the slave pid) */
458                 rc = read(mpipe[0], &children[1], sizeof children[1]);
459                 close(mpipe[0]);
460                 if (rc  <= 0) {
461                         ERROR("reading master pipe failed: %m");
462                         close(spipe[1]);
463                         return -1;
464                 }
465                 assert(rc == sizeof children[1]);
466                 /* start the child */
467                 rc = write(spipe[1], "start", 5);
468                 if (rc < 0) {
469                         ERROR("writing slave pipe failed: %m");
470                         close(spipe[1]);
471                         return -1;
472                 }
473                 assert(rc == 5);
474                 close(spipe[1]);
475                 return 0;
476         }
477
478         /********* in the master child ************/
479         close(mpipe[0]);
480         close(spipe[1]);
481
482         /* avoid set-gid effect */
483         setresgid(groupid, groupid, groupid);
484
485         /* enter the process group */
486         rc = setpgid(0, 0);
487         if (rc) {
488                 ERROR("setpgid failed");
489                 _exit(1);
490         }
491
492         /* enter security mode */
493         rc = secmgr_prepare_exec(desc->tag);
494         if (rc < 0) {
495                 ERROR("call to secmgr_prepare_exec failed: %m");
496                 _exit(1);
497         }
498
499         /* enter the datadirectory */
500         rc = mkdir(params->datadir, 0755);
501         if (rc && errno != EEXIST) {
502                 ERROR("creation of datadir %s failed: %m", params->datadir);
503                 _exit(1);
504         }
505         rc = chdir(params->datadir);
506         if (rc) {
507                 ERROR("can't enter the datadir %s: %m", params->datadir);
508                 _exit(1);
509         }
510
511         /* fork the slave child */
512         children[1] = fork();
513         if (children[1] < 0) {
514                 ERROR("slave fork failed: %m");
515                 _exit(1);
516         }
517         if (children[1] == 0) {
518                 /********* in the slave child ************/
519                 close(mpipe[0]);
520                 rc = read(spipe[0], message, sizeof message);
521                 if (rc <= 0) {
522                         ERROR("reading slave pipe failed: %m");
523                         _exit(1);
524                 }
525
526                 args = instantiate_arguments(params->slave, desc, params);
527                 if (args == NULL) {
528                         ERROR("out of memory in slave");
529                 }
530                 else {
531                         rc = execve(args[0], args, environ);
532                         ERROR("failed to exec slave %s: %m", args[0]);
533                 }
534                 _exit(1);
535         }
536
537         /********* still in the master child ************/
538         close(spipe[1]);
539         args = instantiate_arguments(params->master, desc, params);
540         if (args == NULL) {
541                 ERROR("out of memory in master");
542         }
543         else {
544                 rc = write(mpipe[1], &children[1], sizeof children[1]);
545                 if (rc <= 0) {
546                         ERROR("can't write master pipe: %m");
547                 }
548                 else {
549                         close(mpipe[1]);
550                         rc = execve(args[0], args, environ);
551                         ERROR("failed to exec master %s: %m", args[0]);
552                 }
553         }
554         _exit(1);
555 }
556
557 int afm_launch_initialize()
558 {
559         gid_t r, e, s;
560         getresgid(&r, &e, &s);
561         if (s && s != e)
562                 groupid = s;
563         else
564                 groupid = -1;
565         return read_configuration_file(FWK_LAUNCH_CONF);
566 }
567
568 int afm_launch(struct afm_launch_desc *desc, pid_t children[2])
569 {
570         char datadir[PATH_MAX];
571         int ikl, rc;
572         char secret[9];
573         struct launchparam params;
574         const char *type;
575
576         /* should be init */
577         assert(groupid != 0);
578
579         /* init */
580         children[0] = 0;
581         children[1] = 0;
582
583         /* what launcher ? */
584         type = desc->type != NULL && *desc->type ? desc->type : DEFAULT_TYPE;
585         ikl = 0;
586         while (ikl < launchers.count && strcmp(type, launchers.descs[ikl].type))
587                 ikl++;
588         if (ikl == launchers.count) {
589                 ERROR("type %s not found!", type);
590                 errno = ENOENT;
591                 return -1;
592         }
593
594         /* prepare paths */
595         rc = snprintf(datadir, sizeof datadir, "%s/%s", desc->home, desc->tag);
596         if (rc < 0 || rc >= sizeof datadir) {
597                 ERROR("overflow for datadir");
598                 errno = EINVAL;
599                 return -1;
600         }
601
602         /* make the secret and port */
603         mksecret(secret);
604         params.port = mkport();
605         params.secret = secret;
606         params.datadir = datadir;
607         params.master = (const char **)launchers.descs[ikl].execs[0];
608         params.slave = (const char **)launchers.descs[ikl].execs[1];
609
610         return params.slave ? launchexec2(desc, children, &params) : launchexec1(desc, children, &params);
611 }
612