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