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