2b7ec7a6ccc472ed29fd2ce04748bdf06b3b70ab
[src/app-framework-main.git] / src / afm-user-daemon.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 #include <unistd.h>
20 #include <stdio.h>
21 #include <time.h>
22 #include <getopt.h>
23 #include <string.h>
24
25 #include <json.h>
26
27 #include "verbose.h"
28 #include "utils-jbus.h"
29 #include "utils-json.h"
30 #include "afm.h"
31 #include "afm-db.h"
32 #include "afm-launch-mode.h"
33 #include "afm-run.h"
34
35 /*
36  * name of the application
37  */
38 static const char appname[] = "afm-user-daemon";
39
40 /*
41  * string for printing usage
42  */
43 static const char usagestr[] =
44         "usage: %s [-q] [-v] [-m mode] [-r rootdir]... [-a appdir]...\n"
45         "\n"
46         "   -a appdir    adds an application directory\n"
47         "   -r rootdir   adds a root directory of applications\n"
48         "   -m mode      set default launch mode (local or remote)\n"
49         "   -d           run as a daemon\n"
50         "   -q           quiet\n"
51         "   -v           verbose\n"
52         "\n";
53
54 /*
55  * Option definition for getopt_long
56  */
57 static const char options_s[] = "hdqvr:a:m:";
58 static struct option options_l[] = {
59         { "root",        required_argument, NULL, 'r' },
60         { "application", required_argument, NULL, 'a' },
61         { "mode",        required_argument, NULL, 'm' },
62         { "daemon",      no_argument,       NULL, 'd' },
63         { "quiet",       no_argument,       NULL, 'q' },
64         { "verbose",     no_argument,       NULL, 'v' },
65         { "help",        no_argument,       NULL, 'h' },
66         { NULL, 0, NULL, 0 }
67 };
68
69 /*
70  * Connections to D-Bus
71  * This is an array for using the function
72  *    jbus_read_write_dispatch_multiple
73  * directly without transformations.
74  */
75 static struct jbus *jbuses[2];
76 #define system_bus  jbuses[0]
77 #define user_bus    jbuses[1]
78
79 /*
80  * Handle to the database of applications
81  */
82 static struct afm_db *afdb;
83
84 /*
85  * Returned error strings
86  */
87 const char error_nothing[] = "[]";
88 const char error_bad_request[] = "\"bad request\"";
89 const char error_not_found[] = "\"not found\"";
90 const char error_cant_start[] = "\"can't start\"";
91 const char error_system[] = "\"system error\"";
92
93
94 /*
95  * retrieves the 'runid' in 'obj' parameters received with the
96  * request 'jreq' for the 'method'.
97  *
98  * Returns 1 in case of success.
99  * Otherwise, if the 'runid' can't be retrived, an error stating
100  * the bad request is replied for 'jreq' and 0 is returned.
101  */
102 static int onrunid(struct jreq *jreq, struct json_object *obj,
103                                                 const char *method, int *runid)
104 {
105         if (!j_read_integer(obj, runid)
106                                 && !j_read_integer_at(obj, "runid", runid)) {
107                 INFO("bad request method %s: %s", method,
108                                         json_object_to_json_string(obj));
109                 jbus_reply_error_s(jreq, error_bad_request);
110                 return 0;
111         }
112
113         INFO("method %s called for %d", method, *runid);
114         return 1;
115 }
116
117 /*
118  * Sends the reply 'resp' to the request 'jreq' if 'resp' is not NULL.
119  * Otherwise, when 'resp' is NULL replies the error string 'errstr'.
120  */
121 static void reply(struct jreq *jreq, struct json_object *resp,
122                                                 const char *errstr)
123 {
124         if (resp)
125                 jbus_reply_j(jreq, resp);
126         else
127                 jbus_reply_error_s(jreq, errstr);
128 }
129
130 /*
131  * Sends the reply "true" to the request 'jreq' if 'status' is zero.
132  * Otherwise, when 'status' is not zero replies the error string 'errstr'.
133  */
134 static void reply_status(struct jreq *jreq, int status, const char *errstr)
135 {
136         if (status)
137                 jbus_reply_error_s(jreq, errstr);
138         else
139                 jbus_reply_s(jreq, "true");
140 }
141
142 /*
143  * On query "runnables" from 'jreq' with parameters of 'obj'.
144  *
145  * Nothing is expected in 'obj' that can be anything.
146  */
147 static void on_runnables(struct jreq *jreq, struct json_object *obj, void *unused)
148 {
149         struct json_object *resp;
150         INFO("method runnables called");
151         resp = afm_db_application_list(afdb);
152         jbus_reply_j(jreq, resp);
153         json_object_put(resp);
154 }
155
156 /*
157  * On query "detail" from 'jreq' with parameters of 'obj'.
158  */
159 static void on_detail(struct jreq *jreq, struct json_object *obj, void *unused)
160 {
161         const char *appid;
162         struct json_object *resp;
163
164         /* get the parameters */
165         if (j_read_string(obj, &appid))
166                 ; /* appid as a string */
167         else if (j_read_string_at(obj, "id", &appid))
168                 ; /* appid as obj.id string */
169         else {
170                 INFO("method detail called but bad request!");
171                 jbus_reply_error_s(jreq, error_bad_request);
172                 return;
173         }
174
175         /* wants details for appid */
176         INFO("method detail called for %s", appid);
177         resp = afm_db_get_application_public(afdb, appid);
178         reply(jreq, resp, error_not_found);
179         json_object_put(resp);
180 }
181
182
183 /*
184  * On query "start" from 'jreq' with parameters of 'obj'.
185  */
186 static void on_start(struct jreq *jreq, struct json_object *obj, void *unused)
187 {
188         const char *appid, *modestr;
189         char *uri;
190         struct json_object *appli, *resp;
191         int runid;
192         char runidstr[20];
193         enum afm_launch_mode mode;
194
195         /* get the parameters */
196         mode = invalid_launch_mode;
197         if (j_read_string(obj, &appid)) {
198                 mode = get_default_launch_mode();
199         } else if (j_read_string_at(obj, "id", &appid)) {
200                 if (j_read_string_at(obj, "mode", &modestr)) {
201                         mode = launch_mode_of_name(modestr);
202                 } else {
203                         mode = get_default_launch_mode();
204                 }
205         }
206         if (!is_valid_launch_mode(mode)) {
207                 jbus_reply_error_s(jreq, error_bad_request);
208                 return;
209         }
210
211         /* get the application */
212         INFO("method start called for %s mode=%s", appid,
213                                                 name_of_launch_mode(mode));
214         appli = afm_db_get_application(afdb, appid);
215         if (appli == NULL) {
216                 jbus_reply_error_s(jreq, error_not_found);
217                 return;
218         }
219
220         /* launch the application */
221         uri = NULL;
222         runid = afm_run_start(appli, mode, &uri);
223         if (runid <= 0) {
224                 jbus_reply_error_s(jreq, error_cant_start);
225                 free(uri);
226                 return;
227         }
228
229         if (uri == NULL) {
230                 /* returns only the runid */
231                 snprintf(runidstr, sizeof runidstr, "%d", runid);
232                 runidstr[sizeof runidstr - 1] = 0;
233                 jbus_reply_s(jreq, runidstr);
234                 return;
235         }
236
237         /* returns the runid and its uri */
238         resp = json_object_new_object();
239         if (resp != NULL && j_add_integer(resp, "runid", runid)
240                                         && j_add_string(resp, "uri", uri))
241                 jbus_reply_j(jreq, resp);
242         else {
243                 afm_run_stop(runid);
244                 jbus_reply_error_s(jreq, error_system);
245         }
246         json_object_put(resp);
247         free(uri);
248 }
249
250 /*
251  * On query "stop" from 'jreq' with parameters of 'obj'.
252  */
253 static void on_stop(struct jreq *jreq, struct json_object *obj, void *unused)
254 {
255         int runid, status;
256         if (onrunid(jreq, obj, "stop", &runid)) {
257                 status = afm_run_stop(runid);
258                 reply_status(jreq, status, error_not_found);
259         }
260 }
261
262 /*
263  * On query "continue" from 'jreq' with parameters of 'obj'.
264  */
265 static void on_continue(struct jreq *jreq, struct json_object *obj, void *unused)
266 {
267         int runid, status;
268         if (onrunid(jreq, obj, "continue", &runid)) {
269                 status = afm_run_continue(runid);
270                 reply_status(jreq, status, error_not_found);
271         }
272 }
273
274 /*
275  * On query "terminate" from 'jreq' with parameters of 'obj'.
276  */
277 static void on_terminate(struct jreq *jreq, struct json_object *obj, void *unused)
278 {
279         int runid, status;
280         if (onrunid(jreq, obj, "terminate", &runid)) {
281                 status = afm_run_terminate(runid);
282                 reply_status(jreq, status, error_not_found);
283         }
284 }
285
286 /*
287  * On query "runners" from 'jreq' with parameters of 'obj'.
288  */
289 static void on_runners(struct jreq *jreq, struct json_object *obj, void *unused)
290 {
291         struct json_object *resp;
292         INFO("method runners called");
293         resp = afm_run_list();
294         jbus_reply_j(jreq, resp);
295         json_object_put(resp);
296 }
297
298 /*
299  * On query "state" from 'jreq' with parameters of 'obj'.
300  */
301 static void on_state(struct jreq *jreq, struct json_object *obj, void *unused)
302 {
303         int runid;
304         struct json_object *resp;
305         if (onrunid(jreq, obj, "state", &runid)) {
306                 resp = afm_run_state(runid);
307                 reply(jreq, resp, error_not_found);
308                 json_object_put(resp);
309         }
310 }
311
312 /*
313  * Calls the system daemon to achieve application management of
314  * the 'method' gotten from 'jreq' with the parameter's string 'msg'.
315  *
316  * The principle is very simple: call the corresponding system method
317  * and reply its response to the caller.
318  *
319  * The request and reply is synchronous and is blocking.
320  * It is possible to implment it in an asynchrounous way but it
321  * would brake the common behaviour. It would be a call like
322  * jbus_call_ss(system_bus, method, msg, callback, jreq)
323  */
324 static void propagate(struct jreq *jreq, const char *msg, const char *method)
325 {
326         char *reply;
327         INFO("method %s propagated with %s", method, msg);
328         reply = jbus_call_ss_sync(system_bus, method, msg);
329         if (reply) {
330                 jbus_reply_s(jreq, reply);
331                 free(reply);
332         }
333         else
334                 jbus_reply_error_s(jreq, error_system);
335 }
336
337 #if defined(EXPLICIT_CALL)
338 /*
339  * On query "install" from 'jreq' with parameters of 'msg'.
340  */
341 static void on_install(struct jreq *jreq, const char *msg, void *unused)
342 {
343         return propagate(jreq, msg, "install");
344 }
345
346 /*
347  * On query "uninstall" from 'jreq' with parameters of 'msg'.
348  */
349 static void on_uninstall(struct jreq *jreq, const char *msg, void *unused)
350 {
351         return propagate(jreq, msg, "uninstall");
352 }
353 #endif
354
355 /*
356  * On system signaling that applications list changed
357  */
358 static void on_signal_changed(struct json_object *obj, void *unused)
359 {
360         /* update the database */
361         afm_db_update_applications(afdb);
362         /* re-propagate now */
363         jbus_send_signal_j(user_bus, "changed", obj);
364 }
365
366 /*
367  * Tiny routine to daemonize the program
368  * Return 0 on success or -1 on failure.
369  */
370 static int daemonize()
371 {
372         int rc = fork();
373         if (rc < 0)
374                 return rc;
375         if (rc)
376                 _exit(0);
377         return 0;
378 }
379
380 /*
381  * ENTRY POINT OF AFM-USER-DAEMON
382  */
383 int main(int ac, char **av)
384 {
385         int i, daemon = 0;
386         enum afm_launch_mode mode;
387
388         LOGAUTH(appname);
389
390         /* first interpretation of arguments */
391         while ((i = getopt_long(ac, av, options_s, options_l, NULL)) >= 0) {
392                 switch (i) {
393                 case 'h':
394                         printf(usagestr, appname);
395                         return 0;
396                 case 'q':
397                         if (verbosity)
398                                 verbosity--;
399                         break;
400                 case 'v':
401                         verbosity++;
402                         break;
403                 case 'd':
404                         daemon = 1;
405                         break;
406                 case 'r':
407                         break;
408                 case 'a':
409                         break;
410                 case 'm':
411                         mode = launch_mode_of_name(optarg);
412                         if (!is_valid_launch_mode(mode)) {
413                                 ERROR("invalid mode '%s'", optarg);
414                                 return 1;
415                         }
416                         set_default_launch_mode(mode);
417                         break;
418                 case ':':
419                         ERROR("missing argument value");
420                         return 1;
421                 default:
422                         ERROR("unrecognized option");
423                         return 1;
424                 }
425         }
426
427         /* init random generator */
428         srandom((unsigned int)time(NULL));
429
430         /* init runners */
431         if (afm_run_init()) {
432                 ERROR("afm_run_init failed");
433                 return 1;
434         }
435
436         /* init framework */
437         afdb = afm_db_create();
438         if (!afdb) {
439                 ERROR("afm_create failed");
440                 return 1;
441         }
442         if (afm_db_add_root(afdb, FWK_APP_DIR)) {
443                 ERROR("can't add root %s", FWK_APP_DIR);
444                 return 1;
445         }
446
447         /* second interpretation of arguments */
448         optind = 1;
449         while ((i = getopt_long(ac, av, options_s, options_l, NULL)) >= 0) {
450                 switch (i) {
451                 case 'r':
452                         if (afm_db_add_root(afdb, optarg)) {
453                                 ERROR("can't add root %s", optarg);
454                                 return 1;
455                         }
456                         break;
457                 case 'a':
458                         if (afm_db_add_application(afdb, optarg)) {
459                                 ERROR("can't add application %s", optarg);
460                                 return 1;
461                         }
462                         break;
463                 }
464         }
465
466         /* update the database */
467         if (afm_db_update_applications(afdb)) {
468                 ERROR("afm_update_applications failed");
469                 return 1;
470         }
471
472         /* daemonize if requested */
473         if (daemon && daemonize()) {
474                 ERROR("daemonization failed");
475                 return 1;
476         }
477
478         /* connects to the system bus */
479         system_bus = create_jbus_system(AFM_SYSTEM_DBUS_PATH);
480         if (!system_bus) {
481                 ERROR("create_jbus failed for system");
482                 return 1;
483         }
484
485         /* observe signals of system */
486         if(jbus_on_signal_j(system_bus, "changed", on_signal_changed, NULL)) {
487                 ERROR("adding signal observer failed");
488                 return 1;
489         }
490
491         /* connect to the session bus */
492         user_bus = create_jbus_session(AFM_USER_DBUS_PATH);
493         if (!user_bus) {
494                 ERROR("create_jbus failed");
495                 return 1;
496         }
497
498         /* init services */
499         if (jbus_add_service_j(user_bus, "runnables", on_runnables, NULL)
500          || jbus_add_service_j(user_bus, "detail",    on_detail, NULL)
501          || jbus_add_service_j(user_bus, "start",     on_start, NULL)
502          || jbus_add_service_j(user_bus, "terminate", on_terminate, NULL)
503          || jbus_add_service_j(user_bus, "stop",      on_stop, NULL)
504          || jbus_add_service_j(user_bus, "continue",  on_continue, NULL)
505          || jbus_add_service_j(user_bus, "runners",   on_runners, NULL)
506          || jbus_add_service_j(user_bus, "state",     on_state, NULL)
507 #if defined(EXPLICIT_CALL)
508          || jbus_add_service_s(user_bus, "install",   on_install, NULL)
509          || jbus_add_service_s(user_bus, "uninstall", on_uninstall, NULL)) {
510 #else
511          || jbus_add_service_s(user_bus, "install",   (void (*)(struct jreq *, const char *, void *))propagate, "install")
512          || jbus_add_service_s(user_bus, "uninstall", (void (*)(struct jreq *, const char *, void *))propagate, "uninstall")) {
513 #endif
514                 ERROR("adding services failed");
515                 return 1;
516         }
517
518         /* start servicing */
519         if (jbus_start_serving(user_bus)) {
520                 ERROR("can't start server");
521                 return 1;
522         }
523
524         /* run until error */
525         while (jbus_read_write_dispatch_multiple(jbuses, 2, -1, 20) >= 0);
526         return 0;
527 }
528