afm-launch: add readyfd and improves typing
[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-mode.h"
36 #include "afm-launch.h"
37 #include "secmgr-wrap.h"
38
39 #define DEFAULT_TYPE "text/html"
40
41 struct type_list {
42         struct type_list *next;
43         char type[1];
44 };
45
46 struct exec_vector {
47         int has_readyfd;
48         const char **args;
49 };
50
51 struct desc_list {
52         struct desc_list *next;
53         enum afm_launch_mode mode;
54         struct type_list *types;
55         struct exec_vector execs[2];
56 };
57
58 struct launchparam {
59         int port;
60         int readyfd;
61         char **uri;
62         const char *secret;
63         const char *datadir;
64         struct exec_vector *master;
65         struct exec_vector *slave;
66 };
67
68 struct confread {
69         const char *filepath;
70         FILE *file;
71         int lineno;
72         int index;
73         int length;
74         char buffer[4096];
75 };
76
77 struct desc_list *launchers = NULL;
78
79 static gid_t groupid = 0;
80
81 const char separators[] = " \t\n";
82
83 static void dump_launchers()
84 {
85         int j, k;
86         struct desc_list *desc;
87         struct type_list *type;
88
89         for (desc = launchers ; desc != NULL ; desc = desc->next) {
90                 printf("mode %s\n", name_of_launch_mode(desc->mode));
91                 for (type = desc->types ; type != NULL ; type = type->next)
92                         printf("%s\n", type->type);
93                 for ( j = 0 ; j < 2 ; j++)
94                         if (desc->execs[j].args != NULL) {
95                                 for (k = 0 ; desc->execs[j].args[k] != NULL ; k++)
96                                         printf("  %s", desc->execs[j].args[k]);
97                                 printf("\n");
98                         }
99                 printf("\n");
100         }
101 }
102
103 static int next_token(struct confread *cread)
104 {
105         int idx = cread->index + cread->length;
106         cread->index = idx + strspn(&cread->buffer[idx], separators);
107         cread->length = strcspn(&cread->buffer[cread->index], separators);
108         return cread->length;
109 }
110
111 static int read_line(struct confread *cread)
112 {
113         while (fgets(cread->buffer, sizeof cread->buffer, cread->file) != NULL) {
114                 cread->lineno++;
115                 cread->index = strspn(cread->buffer, separators);
116                 if (cread->buffer[cread->index] && cread->buffer[cread->index] != '#') {
117                         cread->length = strcspn(&cread->buffer[cread->index], separators);
118                         assert(cread->length > 0);
119                         return cread->length;
120                 }
121         }
122         if (ferror(cread->file)) {
123                 ERROR("%s:%d: error while reading, %m", cread->filepath, cread->lineno);
124                 return -1;
125         }
126         return 0;
127 }
128
129 static const char **read_vector(struct confread *cread)
130 {
131         int index0, length0;
132         const char **vector;
133         char *args;
134         int count, length;
135
136         /* record origin */
137         index0 = cread->index;
138         length0 = cread->length;
139
140         /* count */
141         count = 0;
142         length = 0;
143         while(cread->length) {
144                 count++;
145                 length += cread->length;
146                 next_token(cread);
147         }
148
149         /* allocates */
150         cread->index = index0;
151         cread->length = length0;
152         vector = malloc(length + count + (count + 1) * sizeof(char*));
153         if (vector == NULL)
154                 return NULL;
155
156         /* copies */
157         args = (char*)(vector + count + 1);
158         count = 0;
159         while(cread->length) {
160                 vector[count++] = args;
161                 memcpy(args, &cread->buffer[cread->index], cread->length);
162                 args += cread->length;
163                 *args++ = 0;
164                 next_token(cread);
165         }
166         vector[count] = NULL;
167         cread->index = index0;
168         cread->length = length0;
169         return vector;
170 }
171
172 static struct type_list *read_type(struct confread *cread)
173 {
174         int index, length;
175         struct type_list *result;
176
177         /* record index and length */
178         index = cread->index;
179         length = cread->length;
180
181         /* check no extra characters */
182         if (next_token(cread)) {
183                 ERROR("%s:%d: extra characters found after type %.*s",
184                         cread->filepath, cread->lineno, length, &cread->buffer[index]);
185                 errno = EINVAL;
186                 return NULL;
187         }
188
189         /* allocate structure */
190         result = malloc(sizeof(struct type_list) + length);
191         if (result == NULL) {
192                 ERROR("%s:%d: out of memory", cread->filepath, cread->lineno);
193                 errno = ENOMEM;
194                 return NULL;
195         }
196
197         /* fill the structure */
198         memcpy(result->type, &cread->buffer[index], length);
199         result->type[length] = 0;
200         return result;
201 }
202
203 static enum afm_launch_mode read_mode(struct confread *cread)
204 {
205         int index, length;
206         enum afm_launch_mode result;
207
208         assert(cread->index == 0);
209         assert(!strncmp(&cread->buffer[cread->index], "mode", 4));
210
211         /* get the next token: the mode string */
212         if (!next_token(cread)) {
213                 ERROR("%s:%d: no mode value set", cread->filepath, cread->lineno);
214                 errno = EINVAL;
215                 return invalid_launch_mode;
216         }
217
218         /* record index and length */
219         index = cread->index;
220         length = cread->length;
221
222         /* check no extra characters */
223         if (next_token(cread)) {
224                 ERROR("%s:%d: extra characters found after mode %.*s",
225                         cread->filepath, cread->lineno, length, &cread->buffer[index]);
226                 errno = EINVAL;
227                 return invalid_launch_mode;
228         }
229
230         /* get the mode */
231         cread->buffer[index + length] = 0;
232         result = launch_mode_of_string(&cread->buffer[index]);
233         if (result == invalid_launch_mode) {
234                 ERROR("%s:%d: invalid mode value %s",
235                         cread->filepath, cread->lineno, &cread->buffer[index]);
236                 errno = EINVAL;
237         }
238         return result;
239 }
240
241 static void free_type_list(struct type_list *types)
242 {
243         while (types != NULL) {
244                 struct type_list *next = types->next;
245                 free(types);
246                 types = next;
247         }
248 }
249
250 static int read_launchers(struct confread *cread)
251 {
252         int rc, has_readyfd;
253         struct type_list *types, *lt;
254         struct desc_list *desc;
255         enum afm_launch_mode mode;
256         const char **vector;
257
258         /* reads the file */
259         lt = NULL;
260         types = NULL;
261         desc = NULL;
262         mode = invalid_launch_mode;
263         rc = read_line(cread);
264         while (rc > 0) {
265                 if (cread->index == 0) {
266                         if (cread->length == 4
267                         && !memcmp(&cread->buffer[cread->index], "mode", 4)) {
268                                 /* check if allowed */
269                                 if (types != NULL) {
270                                         ERROR("%s:%d: mode found before launch vector",
271                                                 cread->filepath, cread->lineno);
272                                         errno = EINVAL;
273                                         free_type_list(types);
274                                         return -1;
275                                 }
276
277                                 /* read the mode */
278                                 mode = read_mode(cread);
279                                 if (mode == invalid_launch_mode)
280                                         return -1;
281                         } else {
282                                 if (mode == invalid_launch_mode) {
283                                         ERROR("%s:%d: mode not found before type",
284                                                         cread->filepath, cread->lineno);
285                                         errno = EINVAL;
286                                         assert(types == NULL);
287                                         return -1;
288                                 }
289                                 /* read a type */
290                                 lt = read_type(cread);
291                                 if (lt == NULL) {
292                                         free_type_list(types);
293                                         return -1;
294                                 }
295                                 lt->next = types;
296                                 types = lt;
297                         }
298                         desc = NULL;
299                 } else if (types == NULL && desc == NULL) {
300                         if (lt == NULL)
301                                 ERROR("%s:%d: untyped launch vector found",
302                                         cread->filepath, cread->lineno);
303                         else
304                                 ERROR("%s:%d: extra launch vector found (2 max)",
305                                         cread->filepath, cread->lineno);
306                         errno = EINVAL;
307                         return -1;
308                 } else {
309                         has_readyfd = NULL != strstr(&cread->buffer[cread->index], "%R");
310                         vector = read_vector(cread);
311                         if (vector == NULL) {
312                                 ERROR("%s:%d: out of memory",
313                                         cread->filepath, cread->lineno);
314                                 free_type_list(types);
315                                 errno = ENOMEM;
316                                 return -1;
317                         }
318                         if (types) {
319                                 assert(desc == NULL);
320                                 desc = malloc(sizeof * desc);
321                                 if (desc == NULL) {
322                                         ERROR("%s:%d: out of memory",
323                                                 cread->filepath, cread->lineno);
324                                         free_type_list(types);
325                                         errno = ENOMEM;
326                                         return -1;
327                                 }
328                                 desc->next = launchers;
329                                 desc->mode = mode;
330                                 desc->types = types;
331                                 desc->execs[0].has_readyfd = has_readyfd;
332                                 desc->execs[0].args = vector;
333                                 desc->execs[1].has_readyfd = 0;
334                                 desc->execs[1].args = NULL;
335                                 types = NULL;
336                                 launchers = desc;
337                         } else {
338                                 desc->execs[1].has_readyfd = has_readyfd;
339                                 desc->execs[1].args = vector;
340                                 desc = NULL;
341                         }
342                 }
343                 rc = read_line(cread);
344         }
345         if (types != NULL) {
346                 ERROR("%s:%d: end of file found before launch vector",
347                         cread->filepath, cread->lineno);
348                 free_type_list(types);
349                 errno = EINVAL;
350                 return -1;
351         }
352         return rc;
353 }
354
355 static int read_configuration_file(const char *filepath)
356 {
357         int rc;
358         struct confread cread;
359
360         /* opens the configuration file */
361         cread.file = fopen(filepath, "r");
362         if (cread.file == NULL) {
363                 /* error */
364                 ERROR("can't read file %s: %m", filepath);
365                 rc = -1;
366         } else {
367                 /* reads it */
368                 cread.filepath = filepath;
369                 cread.lineno = 0;
370                 rc = read_launchers(&cread);
371                 fclose(cread.file);
372         }
373         return rc;
374 }
375
376 /*
377 %% %
378 %a appid                        desc->appid
379 %c content                      desc->content
380 %D datadir                      params->datadir
381 %H height                       desc->height
382 %h homedir                      desc->home
383 %I icondir                      FWK_ICON_DIR
384 %m mime-type                    desc->type
385 %n name                         desc->name
386 %p plugins                      desc->plugins
387 %P port                         params->port
388 %r rootdir                      desc->path
389 %R readyfd                      params->readyfd
390 %S secret                       params->secret
391 %t tag (smack label)            desc->tag
392 %W width                        desc->width
393 */
394
395 union arguments {
396         char *scalar;
397         char **vector;
398 };
399
400 static union arguments instantiate_arguments(
401         const char * const     *args,
402         struct afm_launch_desc *desc,
403         struct launchparam     *params,
404         int                     wants_vector
405 )
406 {
407         const char * const *iter;
408         const char *p, *v;
409         char *data, port[20], width[20], height[20], readyfd[20], mini[3], c, sep;
410         int n, s;
411         union arguments result;
412
413         /* init */
414         sep = wants_vector ? 0 : ' ';
415         mini[0] = '%';
416         mini[2] = 0;
417
418         /* loop that either compute the size and build the result */
419         result.vector = NULL;
420         result.scalar = NULL;
421         data = NULL;
422         n = s = 0;
423         for (;;) {
424                 iter = args;
425                 n = 0;
426                 while (*iter) {
427                         p = *iter++;
428                         if (data && !sep)
429                                 result.vector[n] = data;
430                         n++;
431                         while((c = *p++) != 0) {
432                                 if (c != '%') {
433                                         if (data)
434                                                 *data++ = c;
435                                         else
436                                                 s++;
437                                 } else {
438                                         c = *p++;
439                                         switch (c) {
440                                         case 'a': v = desc->appid; break;
441                                         case 'c': v = desc->content; break;
442                                         case 'D': v = params->datadir; break;
443                                         case 'H':
444                                                 if(!data)
445                                                         sprintf(height, "%d", desc->height);
446                                                 v = height;
447                                                 break;
448                                         case 'h': v = desc->home; break;
449                                         case 'I': v = FWK_ICON_DIR; break;
450                                         case 'm': v = desc->type; break;
451                                         case 'n': v = desc->name; break;
452                                         case 'P':
453                                                 if(!data)
454                                                         sprintf(port, "%d", params->port);
455                                                 v = port;
456                                                 break;
457                                         case 'p': v = "" /*desc->plugins*/; break;
458                                         case 'R':
459                                                 if(!data)
460                                                         sprintf(readyfd, "%d", params->readyfd);
461                                                 v = readyfd;
462                                                 break;
463                                         case 'r': v = desc->path; break;
464                                         case 'S': v = params->secret; break;
465                                         case 't': v = desc->tag; break;
466                                         case 'W':
467                                                 if(!data)
468                                                         sprintf(width, "%d", desc->width);
469                                                 v = width;
470                                                 break;
471                                         case '%':
472                                                 c = 0;
473                                         default:
474                                                 mini[1] = c;
475                                                 v = mini;
476                                                 break;
477                                         }
478                                         if (data)
479                                                 data = stpcpy(data, v);
480                                         else
481                                                 s += strlen(v);
482                                 }
483                         }
484                         if (data)
485                                 *data++ = sep;
486                         else
487                                 s++;
488                 }
489                 if (sep) {
490                         assert(!wants_vector);
491                         if (data) {
492                                 *--data = 0;
493                                 return result;
494                         }
495                         /* allocation */
496                         result.scalar = malloc(s);
497                         if (result.scalar == NULL) {
498                                 errno = ENOMEM;
499                                 return result;
500                         }
501                         data = result.scalar;
502                 } else {
503                         assert(wants_vector);
504                         if (data) {
505                                 result.vector[n] = NULL;
506                                 return result;
507                         }
508                         /* allocation */
509                         result.vector = malloc((n+1)*sizeof(char*) + s);
510                         if (result.vector == NULL) {
511                                 errno = ENOMEM;
512                                 return result;
513                         }
514                         data = (char*)(&result.vector[n + 1]);
515                 }
516         }
517 }
518
519 static void mksecret(char buffer[9])
520 {
521         snprintf(buffer, 9, "%08lX", (0xffffffff & random()));
522 }
523
524 static int mkport()
525 {
526         static int port_ring = 12345;
527         int port = port_ring;
528         if (port < 12345 || port > 15432)
529                 port = 12345;
530         port_ring = port + 1;
531         return port;
532 }
533
534 static int launch_local_1(
535         struct afm_launch_desc *desc,
536         pid_t                   children[2],
537         struct launchparam     *params
538 )
539 {
540         int rc;
541         char **args;
542
543         /* fork the master child */
544         children[0] = fork();
545         if (children[0] < 0) {
546                 ERROR("master fork failed: %m");
547                 return -1;
548         }
549         if (children[0]) {
550                 /********* in the parent process ************/
551                 return 0;
552         }
553
554         /********* in the master child ************/
555
556         /* avoid set-gid effect */
557         setresgid(groupid, groupid, groupid);
558
559         /* enter the process group */
560         rc = setpgid(0, 0);
561         if (rc) {
562                 ERROR("setpgid failed");
563                 _exit(1);
564         }
565
566         /* enter security mode */
567         rc = secmgr_prepare_exec(desc->tag);
568         if (rc < 0) {
569                 ERROR("call to secmgr_prepare_exec failed: %m");
570                 _exit(1);
571         }
572
573         /* enter the datadirectory */
574         rc = mkdir(params->datadir, 0755);
575         if (rc && errno != EEXIST) {
576                 ERROR("creation of datadir %s failed: %m", params->datadir);
577                 _exit(1);
578         }
579         rc = chdir(params->datadir);
580         if (rc) {
581                 ERROR("can't enter the datadir %s: %m", params->datadir);
582                 _exit(1);
583         }
584
585         args = instantiate_arguments(params->master->args, desc, params, 1).vector;
586         if (args == NULL) {
587                 ERROR("out of memory in master");
588         }
589         else {
590                 rc = execve(args[0], args, environ);
591                 ERROR("failed to exec master %s: %m", args[0]);
592         }
593         _exit(1);
594 }
595
596 static int launch_local_2(
597         struct afm_launch_desc *desc,
598         pid_t                   children[2],
599         struct launchparam     *params
600 )
601 {
602         int rc;
603         char message[10];
604         int mpipe[2];
605         int spipe[2];
606         char **args;
607
608         /* prepare the pipes */
609         rc = pipe2(mpipe, O_CLOEXEC);
610         if (rc < 0) {
611                 ERROR("error while calling pipe2: %m");
612                 return -1;
613         }
614         rc = pipe2(spipe, O_CLOEXEC);
615         if (rc < 0) {
616                 ERROR("error while calling pipe2: %m");
617                 close(spipe[0]);
618                 close(spipe[1]);
619                 return -1;
620         }
621
622         /* fork the master child */
623         children[0] = fork();
624         if (children[0] < 0) {
625                 ERROR("master fork failed: %m");
626                 close(mpipe[0]);
627                 close(mpipe[1]);
628                 close(spipe[0]);
629                 close(spipe[1]);
630                 return -1;
631         }
632         if (children[0]) {
633                 /********* in the parent process ************/
634                 close(mpipe[1]);
635                 close(spipe[0]);
636                 /* wait the ready signal (that transmit the slave pid) */
637                 rc = read(mpipe[0], &children[1], sizeof children[1]);
638                 close(mpipe[0]);
639                 if (rc  <= 0) {
640                         ERROR("reading master pipe failed: %m");
641                         close(spipe[1]);
642                         return -1;
643                 }
644                 assert(rc == sizeof children[1]);
645                 /* start the child */
646                 rc = write(spipe[1], "start", 5);
647                 if (rc < 0) {
648                         ERROR("writing slave pipe failed: %m");
649                         close(spipe[1]);
650                         return -1;
651                 }
652                 assert(rc == 5);
653                 close(spipe[1]);
654                 return 0;
655         }
656
657         /********* in the master child ************/
658         close(mpipe[0]);
659         close(spipe[1]);
660
661         /* avoid set-gid effect */
662         setresgid(groupid, groupid, groupid);
663
664         /* enter the process group */
665         rc = setpgid(0, 0);
666         if (rc) {
667                 ERROR("setpgid failed");
668                 _exit(1);
669         }
670
671         /* enter security mode */
672         rc = secmgr_prepare_exec(desc->tag);
673         if (rc < 0) {
674                 ERROR("call to secmgr_prepare_exec failed: %m");
675                 _exit(1);
676         }
677
678         /* enter the datadirectory */
679         rc = mkdir(params->datadir, 0755);
680         if (rc && errno != EEXIST) {
681                 ERROR("creation of datadir %s failed: %m", params->datadir);
682                 _exit(1);
683         }
684         rc = chdir(params->datadir);
685         if (rc) {
686                 ERROR("can't enter the datadir %s: %m", params->datadir);
687                 _exit(1);
688         }
689
690         /* fork the slave child */
691         children[1] = fork();
692         if (children[1] < 0) {
693                 ERROR("slave fork failed: %m");
694                 _exit(1);
695         }
696         if (children[1] == 0) {
697                 /********* in the slave child ************/
698                 close(mpipe[0]);
699                 rc = read(spipe[0], message, sizeof message);
700                 if (rc <= 0) {
701                         ERROR("reading slave pipe failed: %m");
702                         _exit(1);
703                 }
704
705                 args = instantiate_arguments(params->slave->args, desc, params, 1).vector;
706                 if (args == NULL) {
707                         ERROR("out of memory in slave");
708                 }
709                 else {
710                         rc = execve(args[0], args, environ);
711                         ERROR("failed to exec slave %s: %m", args[0]);
712                 }
713                 _exit(1);
714         }
715
716         /********* still in the master child ************/
717         close(spipe[1]);
718         args = instantiate_arguments(params->master->args, desc, params, 1).vector;
719         if (args == NULL) {
720                 ERROR("out of memory in master");
721         }
722         else {
723                 rc = write(mpipe[1], &children[1], sizeof children[1]);
724                 if (rc <= 0) {
725                         ERROR("can't write master pipe: %m");
726                 }
727                 else {
728                         close(mpipe[1]);
729                         rc = execve(args[0], args, environ);
730                         ERROR("failed to exec master %s: %m", args[0]);
731                 }
732         }
733         _exit(1);
734 }
735
736 static int launch_local(
737         struct afm_launch_desc *desc,
738         pid_t                   children[2],
739         struct launchparam     *params
740 )
741 {
742         if (params->slave == NULL)
743                 return launch_local_1(desc, children, params);
744         return launch_local_2(desc, children, params);
745 }
746
747 static int launch_remote(
748         struct afm_launch_desc *desc,
749         pid_t                   children[2],
750         struct launchparam     *params
751 )
752 {
753         int rc;
754         char *uri;
755
756         /* instanciate the uri */
757         if (params->slave == NULL)
758                 uri = NULL;
759         else
760                 uri = instantiate_arguments(params->slave->args, desc, params, 0).scalar;
761         if (uri == NULL) {
762                 ERROR("out of memory for remote uri");
763                 errno = ENOMEM;
764                 return -1;
765         }
766
767         /* launch the command */
768         rc = launch_local_1(desc, children, params);
769         if (rc)
770                 free(uri);
771         else
772                 *params->uri = uri;
773         return rc;
774 }
775
776 static struct desc_list *search_launcher(const char *type, enum afm_launch_mode mode)
777 {
778         struct desc_list *dl;
779         struct type_list *tl;
780
781         for (dl = launchers ; dl ; dl = dl->next)
782                 if (dl->mode == mode)
783                         for (tl = dl->types ; tl != NULL ; tl = tl->next)
784                                 if (!strcmp(tl->type, type))
785                                         return dl;
786         return NULL;
787 }
788
789 int afm_launch(struct afm_launch_desc *desc, pid_t children[2], char **uri)
790 {
791         int rc;
792         char datadir[PATH_MAX];
793         char secret[9];
794         struct launchparam params;
795         const char *type;
796         struct desc_list *dl;
797
798         /* should be init */
799         assert(groupid != 0);
800         assert(launch_mode_is_valid(desc->mode));
801         assert(desc->mode == mode_local || uri != NULL);
802         assert(uri == NULL || *uri == NULL);
803
804         /* init */
805         children[0] = 0;
806         children[1] = 0;
807
808         /* what launcher ? */
809         type = desc->type != NULL && *desc->type ? desc->type : DEFAULT_TYPE;
810         dl = search_launcher(type, desc->mode);
811         if (dl == NULL) {
812                 ERROR("type %s not found for mode %s!", type, name_of_launch_mode(desc->mode));
813                 errno = ENOENT;
814                 return -1;
815         }
816
817         /* prepare paths */
818         rc = snprintf(datadir, sizeof datadir, "%s/%s", desc->home, desc->tag);
819         if (rc < 0 || rc >= sizeof datadir) {
820                 ERROR("overflow for datadir");
821                 errno = EINVAL;
822                 return -1;
823         }
824
825         /* make the secret and port */
826         mksecret(secret);
827         params.uri = uri;
828         params.port = mkport();
829         params.secret = secret;
830         params.datadir = datadir;
831         params.master = &dl->execs[0];
832         params.slave = &dl->execs[1];
833
834         switch (desc->mode) {
835         case mode_local:
836                 return launch_local(desc, children, &params);
837         case mode_remote:
838                 return launch_remote(desc, children, &params);
839         default:
840                 assert(0);
841                 return -1;
842         }
843 }
844
845 int afm_launch_initialize()
846 {
847         int rc;
848         gid_t r, e, s;
849
850         getresgid(&r, &e, &s);
851         if (s && s != e)
852                 groupid = s;
853         else
854                 groupid = -1;
855
856         rc = read_configuration_file(FWK_LAUNCH_CONF);
857         dump_launchers();
858         return rc;
859 }
860