2615cf58c29fc9e6fc6311f41fa5b7bc7129bcd9
[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)
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)
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)
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)
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)
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)
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)
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)
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 /*
338  * On query "install" from 'jreq' with parameters of 'msg'.
339  */
340 static void on_install(struct jreq *jreq, const char *msg)
341 {
342         return propagate(jreq, msg, "install");
343 }
344
345 /*
346  * On query "uninstall" from 'jreq' with parameters of 'msg'.
347  */
348 static void on_uninstall(struct jreq *jreq, const char *msg)
349 {
350         return propagate(jreq, msg, "uninstall");
351 }
352
353 /*
354  * On system signaling that applications list changed
355  */
356 static void on_signal_changed(struct json_object *obj)
357 {
358         /* update the database */
359         afm_db_update_applications(afdb);
360         /* re-propagate now */
361         jbus_send_signal_j(user_bus, "changed", obj);
362 }
363
364 /*
365  * Tiny routine to daemonize the program
366  * Return 0 on success or -1 on failure.
367  */
368 static int daemonize()
369 {
370         int rc = fork();
371         if (rc < 0)
372                 return rc;
373         if (rc)
374                 _exit(0);
375         return 0;
376 }
377
378 /*
379  * ENTRY POINT OF AFM-USER-DAEMON
380  */
381 int main(int ac, char **av)
382 {
383         int i, daemon = 0;
384         enum afm_launch_mode mode;
385
386         LOGAUTH(appname);
387
388         /* first interpretation of arguments */
389         while ((i = getopt_long(ac, av, options_s, options_l, NULL)) >= 0) {
390                 switch (i) {
391                 case 'h':
392                         printf(usagestr, appname);
393                         return 0;
394                 case 'q':
395                         if (verbosity)
396                                 verbosity--;
397                         break;
398                 case 'v':
399                         verbosity++;
400                         break;
401                 case 'd':
402                         daemon = 1;
403                         break;
404                 case 'r':
405                         break;
406                 case 'a':
407                         break;
408                 case 'm':
409                         mode = launch_mode_of_name(optarg);
410                         if (!is_valid_launch_mode(mode)) {
411                                 ERROR("invalid mode '%s'", optarg);
412                                 return 1;
413                         }
414                         set_default_launch_mode(mode);
415                         break;
416                 case ':':
417                         ERROR("missing argument value");
418                         return 1;
419                 default:
420                         ERROR("unrecognized option");
421                         return 1;
422                 }
423         }
424
425         /* init random generator */
426         srandom((unsigned int)time(NULL));
427
428         /* init runners */
429         if (afm_run_init()) {
430                 ERROR("afm_run_init failed");
431                 return 1;
432         }
433
434         /* init framework */
435         afdb = afm_db_create();
436         if (!afdb) {
437                 ERROR("afm_create failed");
438                 return 1;
439         }
440         if (afm_db_add_root(afdb, FWK_APP_DIR)) {
441                 ERROR("can't add root %s", FWK_APP_DIR);
442                 return 1;
443         }
444
445         /* second interpretation of arguments */
446         optind = 1;
447         while ((i = getopt_long(ac, av, options_s, options_l, NULL)) >= 0) {
448                 switch (i) {
449                 case 'r':
450                         if (afm_db_add_root(afdb, optarg)) {
451                                 ERROR("can't add root %s", optarg);
452                                 return 1;
453                         }
454                         break;
455                 case 'a':
456                         if (afm_db_add_application(afdb, optarg)) {
457                                 ERROR("can't add application %s", optarg);
458                                 return 1;
459                         }
460                         break;
461                 }
462         }
463
464         /* update the database */
465         if (afm_db_update_applications(afdb)) {
466                 ERROR("afm_update_applications failed");
467                 return 1;
468         }
469
470         /* daemonize if requested */
471         if (daemon && daemonize()) {
472                 ERROR("daemonization failed");
473                 return 1;
474         }
475
476         /* connects to the system bus */
477         system_bus = create_jbus_system(AFM_SYSTEM_DBUS_PATH);
478         if (!system_bus) {
479                 ERROR("create_jbus failed for system");
480                 return 1;
481         }
482
483         /* observe signals of system */
484         if(jbus_on_signal_j(system_bus, "changed", on_signal_changed)) {
485                 ERROR("adding signal observer failed");
486                 return 1;
487         }
488
489         /* connect to the session bus */
490         user_bus = create_jbus_session(AFM_USER_DBUS_PATH);
491         if (!user_bus) {
492                 ERROR("create_jbus failed");
493                 return 1;
494         }
495
496         /* init services */
497         if (jbus_add_service_j(user_bus, "runnables", on_runnables)
498          || jbus_add_service_j(user_bus, "detail",    on_detail)
499          || jbus_add_service_j(user_bus, "start",     on_start)
500          || jbus_add_service_j(user_bus, "terminate", on_terminate)
501          || jbus_add_service_j(user_bus, "stop",      on_stop)
502          || jbus_add_service_j(user_bus, "continue",  on_continue)
503          || jbus_add_service_j(user_bus, "runners",   on_runners)
504          || jbus_add_service_j(user_bus, "state",     on_state)
505          || jbus_add_service_s(user_bus, "install",   on_install)
506          || jbus_add_service_s(user_bus, "uninstall", on_uninstall)) {
507                 ERROR("adding services failed");
508                 return 1;
509         }
510
511         /* start servicing */
512         if (jbus_start_serving(user_bus)) {
513                 ERROR("can't start server");
514                 return 1;
515         }
516
517         /* run until error */
518         while (jbus_read_write_dispatch_multiple(jbuses, 2, -1, 20) >= 0);
519         return 0;
520 }
521