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