Improve use of systemd's states
[src/app-framework-main.git] / src / afm-binding.c
1 /*
2  * Copyright (C) 2015-2020 "IoT.bzh"
3  * Author José Bollo <jose.bollo@iot.bzh>
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #define _GNU_SOURCE         /* See feature_test_macros(7) */
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <assert.h>
23 #include <signal.h>
24 #include <errno.h>
25
26 #include <json-c/json.h>
27
28 #define AFB_BINDING_VERSION 3
29 #include <afb/afb-binding.h>
30
31 #include "verbose.h"
32 #include "utils-systemd.h"
33 #include "afm-udb.h"
34 #include "afm-urun.h"
35 #include "wgt-info.h"
36 #include "wgtpkg-install.h"
37 #include "wgtpkg-uninstall.h"
38 #include "wrap-json.h"
39
40 /*
41  * constant strings
42  */
43 static const char _added_[]     = "added";
44 static const char _all_[]       = "all";
45 static const char _a_l_c_[]     = "application-list-changed";
46 static const char _bad_request_[] = "bad-request";
47 static const char _cannot_start_[] = "cannot-start";
48 static const char _detail_[]    = "detail";
49 static const char _id_[]        = "id";
50 static const char _install_[]   = "install";
51 static const char _lang_[]      = "lang";
52 static const char _not_found_[] = "not-found";
53 static const char _not_running_[] = "not-running";
54 static const char _once_[]      = "once";
55 static const char _pause_[]     = "pause";
56 static const char _resume_[]    = "resume";
57 static const char _runid_[]     = "runid";
58 static const char _runnables_[] = "runnables";
59 static const char _runners_[]   = "runners";
60 static const char _start_[]     = "start";
61 static const char _state_[]     = "state";
62 static const char _terminate_[] = "terminate";
63 static const char _uninstall_[] = "uninstall";
64 static const char _update_[]    = "update";
65
66 /*
67  * the permissions
68  */
69 static const struct afb_auth
70         auth_perm_widget = {
71                 .type = afb_auth_Permission,
72                 .text = FWK_PREFIX"permission:afm:system:widget"
73         },
74         auth_perm_widget_install = {
75                 .type = afb_auth_Permission,
76                 .text = FWK_PREFIX"permission:afm:system:widget:install"
77         },
78         auth_perm_widget_uninstall = {
79                 .type = afb_auth_Permission,
80                 .text = FWK_PREFIX"permission:afm:system:widget:uninstall"
81         },
82         auth_perm_widget_preinstall = {
83                 .type = afb_auth_Permission,
84                 .text = FWK_PREFIX"permission:afm:system:widget:preinstall"
85         },
86         auth_perm_widget_detail = {
87                 .type = afb_auth_Permission,
88                 .text = FWK_PREFIX"permission:afm:system:widget:detail"
89         },
90         auth_perm_widget_start = {
91                 .type = afb_auth_Permission,
92                 .text = FWK_PREFIX"permission:afm:system:widget:start"
93         },
94         auth_perm_widget_view_all = {
95                 .type = afb_auth_Permission,
96                 .text = FWK_PREFIX"permission:afm:system:widget:view-all"
97         },
98         auth_perm_runner = {
99                 .type = afb_auth_Permission,
100                 .text = FWK_PREFIX"permission:afm:system:runner"
101         },
102         auth_perm_runner_state = {
103                 .type = afb_auth_Permission,
104                 .text = FWK_PREFIX"permission:afm:system:runner:state"
105         },
106         auth_perm_runner_kill = {
107                 .type = afb_auth_Permission,
108                 .text = FWK_PREFIX"permission:afm:system:runner:kill"
109         },
110
111         auth_install = {
112                 .type = afb_auth_Or,
113                 .first = &auth_perm_widget,
114                 .next = &auth_perm_widget_install
115         },
116         auth_uninstall = {
117                 .type = afb_auth_Or,
118                 .first = &auth_perm_widget,
119                 .next = &auth_perm_widget_uninstall
120         },
121         auth_preinstall = {
122                 .type = afb_auth_Or,
123                 .first = &auth_perm_widget,
124                 .next = &auth_perm_widget_preinstall
125         },
126         auth_detail = {
127                 .type = afb_auth_Or,
128                 .first = &auth_perm_widget,
129                 .next = &auth_perm_widget_detail
130         },
131         auth_start = {
132                 .type = afb_auth_Or,
133                 .first = &auth_perm_widget,
134                 .next = &auth_perm_widget_start
135         },
136         auth_view_all = {
137                 .type = afb_auth_Or,
138                 .first = &auth_perm_widget,
139                 .next = &auth_perm_widget_view_all
140         },
141         auth_state = {
142                 .type = afb_auth_Or,
143                 .first = &auth_perm_runner,
144                 .next = &auth_perm_runner_state
145         },
146         auth_kill = {
147                 .type = afb_auth_Or,
148                 .first = &auth_perm_runner,
149                 .next = &auth_perm_runner_kill
150         }
151 ;
152
153 /*
154  * default root
155  */
156 static const char *rootdir = FWK_APP_DIR;
157
158 /*
159  * the internal application database
160  */
161 static struct afm_udb *afudb;
162
163 /*
164  * the event signaling that application list changed
165  */
166 static afb_event_t applist_changed_event;
167
168 /*
169  * the preallocated true json_object
170  */
171 static struct json_object *json_true;
172
173 /* enforce daemon reload */
174 static void do_reloads()
175 {
176         systemd_daemon_reload(0);
177         systemd_unit_restart_name(0, "sockets.target");
178 }
179
180 /* common bad request reply */
181 static void bad_request(afb_req_t req)
182 {
183         afb_req_fail(req, _bad_request_, NULL);
184 }
185
186 /* common not found reply */
187 static void not_found(afb_req_t req)
188 {
189         afb_req_fail(req, _not_found_, NULL);
190 }
191
192 /* common not running reply */
193 static void not_running(afb_req_t req)
194 {
195         afb_req_fail(req, _not_running_, NULL);
196 }
197
198 /* common can't start reply */
199 static void cant_start(afb_req_t req)
200 {
201         afb_req_fail(req, _cannot_start_, NULL);
202 }
203
204 /*
205  * Broadcast the event "application-list-changed".
206  * This event is sent was the event "changed" is received from dbus.
207  */
208 static void application_list_changed(const char *operation, const char *data)
209 {
210         struct json_object *e = NULL;
211         wrap_json_pack(&e, "{ss ss}", "operation", operation, "data", data);
212         afb_event_broadcast(applist_changed_event, e);
213 }
214
215 /*
216  * Retrieve the required language from 'req'.
217  */
218 static const char *get_lang(afb_req_t req)
219 {
220         const char *lang;
221
222         /* get the optional language */
223         lang = afb_req_value(req, _lang_);
224
225         /* TODO use the req to get the lang of the session (if any) */
226
227         return lang;
228 }
229
230 /*
231  * Retrieve whether all is required from 'req'.
232  */
233 static int get_all(afb_req_t req)
234 {
235         struct json_object *val;
236
237         /* get the optional language */
238         return json_object_object_get_ex(afb_req_json(req), _all_, &val)
239                 && json_object_get_boolean(val);
240 }
241
242
243 /*
244  * retrieves the 'appid' in parameters received with the
245  * request 'req' for the 'method'.
246  *
247  * Returns 1 in case of success.
248  * Otherwise, if the 'appid' can't be retrieved, an error stating
249  * the bad request is replied for 'req' and 0 is returned.
250  */
251 static int onappid(afb_req_t req, const char *method, const char **appid)
252 {
253         struct json_object *json;
254
255         /* get the paramaters of the request */
256         json = afb_req_json(req);
257
258         /* get the appid if any */
259         if (!wrap_json_unpack(json, "s", appid)
260          || !wrap_json_unpack(json, "{ss}", _id_, appid)) {
261                 /* found */
262                 INFO("method %s called for %s", method, *appid);
263                 return 1;
264         }
265
266         /* nothing appropriate */
267         INFO("bad request method %s: %s", method,
268                                         json_object_to_json_string(json));
269         bad_request(req);
270         return 0;
271 }
272
273 /*
274  * retrieves the 'runid' in parameters received with the
275  * request 'req' for the 'method'.
276  *
277  * Returns 1 in case of success.
278  * Otherwise, if the 'runid' can't be retrieved, an error stating
279  * the bad request is replied for 'req' and 0 is returned.
280  */
281 static int onrunid(afb_req_t req, const char *method, int *runid)
282 {
283         struct json_object *json;
284         const char *appid;
285
286         /* get the paramaters of the request */
287         json = afb_req_json(req);
288
289         /* get the runid if any */
290         if (!wrap_json_unpack(json, "i", runid)
291          || !wrap_json_unpack(json, "{si}", _runid_, runid)) {
292                 INFO("method %s called for %d", method, *runid);
293                 return 1;
294         }
295
296         /* get the appid if any */
297         if (!onappid(req, method, &appid))
298                 return 0;
299
300         /* search the runid of the appid */
301         *runid = afm_urun_search_runid(afudb, appid, afb_req_get_uid(req));
302         if (*runid < 0) {
303                 /* nothing appropriate */
304                 INFO("method %s can't get runid for %s: %m", method,
305                                                         appid);
306                 if (errno == ESRCH)
307                         not_running(req);
308                 else
309                         not_found(req);
310                 return 0;
311         }
312
313         /* found */
314         INFO("method %s called for %s -> %d", method, appid, *runid);
315         return 1;
316 }
317
318 /*
319  * Sends the reply 'resp' to the request 'req' if 'resp' is not NULLzero.
320  * Otherwise, when 'resp' is NULL replies the error string 'errstr'.
321  */
322 static void reply(afb_req_t req, struct json_object *resp)
323 {
324         if (resp)
325                 afb_req_reply(req, resp, NULL, NULL);
326         else
327                 afb_req_reply(req, NULL, "failed", strerror(errno));
328 }
329
330 /*
331  * Sends the reply "true" to the request 'req' if 'status' is zero.
332  * Otherwise, when 'status' is not zero replies the error string 'errstr'.
333  */
334 static void reply_status(afb_req_t req, int status)
335 {
336         reply(req, status ? NULL : json_object_get(json_true));
337 }
338
339 /*
340  * On query "runnables"
341  */
342 static void runnables(afb_req_t req)
343 {
344         int all;
345         const char *lang;
346         struct json_object *resp;
347
348         /* get the language */
349         lang = get_lang(req);
350
351         /* get the all */
352         all = get_all(req);
353
354         /* get the details */
355         resp = afm_udb_applications_public(afudb, all, afb_req_get_uid(req), lang);
356         afb_req_success(req, resp, NULL);
357 }
358
359 /*
360  * On query "detail"
361  */
362 static void detail(afb_req_t req)
363 {
364         const char *lang;
365         const char *appid;
366         struct json_object *resp;
367
368         /* scan the request */
369         if (!onappid(req, _detail_, &appid))
370                 return;
371
372         /* get the language */
373         lang = get_lang(req);
374
375         /* wants details for appid */
376         resp = afm_udb_get_application_public(afudb, appid, afb_req_get_uid(req), lang);
377         if (resp)
378                 afb_req_success(req, resp, NULL);
379         else
380                 not_found(req);
381 }
382
383 /*
384  * On query "start"
385  */
386 static void start(afb_req_t req)
387 {
388         const char *appid;
389         struct json_object *appli, *resp;
390         int runid;
391
392         /* scan the request */
393         if (!onappid(req, _start_, &appid))
394                 return;
395
396         /* get the application */
397         appli = afm_udb_get_application_private(afudb, appid, afb_req_get_uid(req));
398         if (appli == NULL) {
399                 not_found(req);
400                 return;
401         }
402
403         /* launch the application */
404         runid = afm_urun_start(appli, afb_req_get_uid(req));
405         if (runid < 0) {
406                 cant_start(req);
407                 return;
408         }
409
410         /* returns */
411         resp = NULL;
412 #if 0
413         wrap_json_pack(&resp, "{si}", _runid_, runid);
414 #else
415         if (runid)
416                 wrap_json_pack(&resp, "i", runid);
417 #endif
418         afb_req_success(req, resp, NULL);
419 }
420
421 /*
422  * On query "once"
423  */
424 static void once(afb_req_t req)
425 {
426         const char *appid;
427         struct json_object *appli, *resp;
428         int runid;
429
430         /* scan the request */
431         if (!onappid(req, _once_, &appid))
432                 return;
433
434         /* get the application */
435         appli = afm_udb_get_application_private(afudb, appid, afb_req_get_uid(req));
436         if (appli == NULL) {
437                 not_found(req);
438                 return;
439         }
440
441         /* launch the application */
442         runid = afm_urun_once(appli, afb_req_get_uid(req));
443         if (runid < 0) {
444                 cant_start(req);
445                 return;
446         }
447
448         /* returns the state */
449         resp = runid ? afm_urun_state(afudb, runid, afb_req_get_uid(req)) : NULL;
450         afb_req_success(req, resp, NULL);
451 }
452
453 /*
454  * On query "pause"
455  */
456 static void pause(afb_req_t req)
457 {
458         int runid, status;
459         if (onrunid(req, "pause", &runid)) {
460                 status = afm_urun_pause(runid, afb_req_get_uid(req));
461                 reply_status(req, status);
462         }
463 }
464
465 /*
466  * On query "resume" from 'smsg' with parameters of 'obj'.
467  */
468 static void resume(afb_req_t req)
469 {
470         int runid, status;
471         if (onrunid(req, "resume", &runid)) {
472                 status = afm_urun_resume(runid, afb_req_get_uid(req));
473                 reply_status(req, status);
474         }
475 }
476
477 /*
478  * On query "terminate"
479  */
480 static void terminate(afb_req_t req)
481 {
482         int runid, status;
483         if (onrunid(req, "terminate", &runid)) {
484                 status = afm_urun_terminate(runid, afb_req_get_uid(req));
485                 reply_status(req, status);
486         }
487 }
488
489 /*
490  * On query "runners"
491  */
492 static void runners(afb_req_t req)
493 {
494         int all;
495         struct json_object *resp;
496         all = get_all(req);
497         resp = afm_urun_list(afudb, all, afb_req_get_uid(req));
498         afb_req_success(req, resp, NULL);
499 }
500
501 /*
502  * On query "state"
503  */
504 static void state(afb_req_t req)
505 {
506         int runid;
507         struct json_object *resp;
508         if (onrunid(req, "state", &runid)) {
509                 resp = afm_urun_state(afudb, runid, afb_req_get_uid(req));
510                 reply(req, resp);
511         }
512 }
513
514 /*
515  * On querying installation of widget(s)
516  */
517 static void install(afb_req_t req)
518 {
519         const char *wgtfile;
520         const char *root;
521         int force;
522         int reload;
523         struct wgt_info *ifo;
524         struct json_object *json;
525         struct json_object *resp;
526
527         /* default settings */
528         root = rootdir;
529         force = 0;
530         reload = 1;
531
532         /* scan the request */
533         json = afb_req_json(req);
534         if (wrap_json_unpack(json, "s", &wgtfile)
535                 && wrap_json_unpack(json, "{ss s?s s?b s?b}",
536                                 "wgt", &wgtfile,
537                                 "root", &root,
538                                 "force", &force,
539                                 "reload", &reload)) {
540                 return bad_request(req);
541         }
542
543         /* install the widget */
544         ifo = install_widget(wgtfile, root, force);
545         if (ifo == NULL)
546                 afb_req_fail_f(req, "failed", "installation failed: %m");
547         else {
548                 afm_udb_update(afudb);
549                 /* reload if needed */
550                 if (reload)
551                         do_reloads();
552
553                 /* build the response */
554                 wrap_json_pack(&resp, "{ss}", _added_, wgt_info_desc(ifo)->idaver);
555                 afb_req_success(req, resp, NULL);
556                 application_list_changed(_install_, wgt_info_desc(ifo)->idaver);
557
558                 /* clean-up */
559                 wgt_info_unref(ifo);
560         }
561 }
562
563 /*
564  * On querying uninstallation of widget(s)
565  */
566 static void uninstall(afb_req_t req)
567 {
568         const char *idaver;
569         const char *root;
570         struct json_object *json;
571         int rc;
572
573         /* default settings */
574         root = rootdir;
575
576         /* scan the request */
577         json = afb_req_json(req);
578         if (wrap_json_unpack(json, "s", &idaver)
579                 && wrap_json_unpack(json, "{ss s?s}",
580                                 _id_, &idaver,
581                                 "root", &root)) {
582                 return bad_request(req);
583         }
584
585         /* install the widget */
586         rc = uninstall_widget(idaver, root);
587         if (rc)
588                 afb_req_fail_f(req, "failed", "uninstallation failed: %m");
589         else {
590                 afm_udb_update(afudb);
591                 afb_req_success(req, NULL, NULL);
592                 application_list_changed(_uninstall_, idaver);
593         }
594 }
595
596 static void onsighup(int signal)
597 {
598         afm_udb_update(afudb);
599         application_list_changed(_update_, _update_);
600 }
601
602 static int init(afb_api_t api)
603 {
604         /* create TRUE */
605         json_true = json_object_new_boolean(1);
606
607         /* init database */
608         afudb = afm_udb_create(1, 0, "afm-");
609         if (!afudb) {
610                 ERROR("afm_udb_create failed");
611                 return -1;
612         }
613
614         signal(SIGHUP, onsighup);
615
616         /* create the event */
617         applist_changed_event = afb_api_make_event(api, _a_l_c_);
618         return -!afb_event_is_valid(applist_changed_event);
619 }
620
621 static const afb_verb_t verbs[] =
622 {
623         {.verb=_runnables_, .callback=runnables, .auth=&auth_detail,    .info="Get list of runnable applications",          .session=AFB_SESSION_CHECK },
624         {.verb=_detail_   , .callback=detail,    .auth=&auth_detail,    .info="Get the details for one application",        .session=AFB_SESSION_CHECK },
625         {.verb=_start_    , .callback=start,     .auth=&auth_start,     .info="Start an application",                       .session=AFB_SESSION_CHECK },
626         {.verb=_once_     , .callback=once,      .auth=&auth_start,     .info="Start once an application",                  .session=AFB_SESSION_CHECK },
627         {.verb=_terminate_, .callback=terminate, .auth=&auth_kill,      .info="Terminate a running application",            .session=AFB_SESSION_CHECK },
628         {.verb=_pause_    , .callback=pause,     .auth=&auth_kill,      .info="Pause a running application",                .session=AFB_SESSION_CHECK },
629         {.verb=_resume_   , .callback=resume,    .auth=&auth_kill,      .info="Resume a paused application",                .session=AFB_SESSION_CHECK },
630         {.verb=_runners_  , .callback=runners,   .auth=&auth_state,     .info="Get the list of running applications",       .session=AFB_SESSION_CHECK },
631         {.verb=_state_    , .callback=state,     .auth=&auth_state,     .info="Get the state of a running application",     .session=AFB_SESSION_CHECK },
632         {.verb=_install_  , .callback=install,   .auth=&auth_install,   .info="Install an application using a widget file", .session=AFB_SESSION_CHECK },
633         {.verb=_uninstall_, .callback=uninstall, .auth=&auth_uninstall, .info="Uninstall an application",                   .session=AFB_SESSION_CHECK },
634         {.verb=NULL }
635 };
636
637 const afb_binding_t afbBindingExport = {
638         .api = "afm-main",
639         .specification = NULL,
640         .info = "Application Framework Master Service",
641         .verbs = verbs,
642         .preinit = NULL,
643         .init = init,
644         .onevent = NULL,
645         .noconcurrency = 1 /* relies on binder for serialization of requests */
646 };
647