/*
- Copyright 2015 IoT.bzh
+ Copyright (C) 2015-2020 IoT.bzh
author: José Bollo <jose.bollo@iot.bzh>
limitations under the License.
*/
+#define _GNU_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include <getopt.h>
#include <string.h>
-#include <json.h>
+#include <systemd/sd-bus.h>
+#include <systemd/sd-event.h>
+#include <json-c/json.h>
+
+#include <afb/afb-ws-client.h>
+#include <afb/afb-proto-ws.h>
#include "verbose.h"
#include "utils-jbus.h"
#include "utils-json.h"
-#include "afm.h"
-#include "afm-db.h"
-#include "afm-launch-mode.h"
-#include "afm-run.h"
+#define AFM_USER_DBUS_PATH "/org/AGL/afm/user"
+
+/*
+ * name of the application
+ */
static const char appname[] = "afm-user-daemon";
-static void usage()
-{
- printf(
- "usage: %s [-q] [-v] [-r rootdir]... [-a appdir]...\n"
- "\n"
- " -a appdir adds an application directory\n"
- " -r rootdir adds a root directory of applications\n"
- " -d run as a daemon\n"
- " -q quiet\n"
- " -v verbose\n"
- "\n",
- appname
- );
-}
+/*
+ * string for printing version
+ */
+static const char versionstr[] =
+ "\n"
+ " %s version="AFM_VERSION"\n"
+ "\n"
+ " Copyright (C) 2015-2020 \"IoT.bzh\"\n"
+ " AFB comes with ABSOLUTELY NO WARRANTY.\n"
+ " Licence Apache 2\n"
+ "\n";
-static struct option options[] = {
- { "root", required_argument, NULL, 'r' },
- { "application", required_argument, NULL, 'a' },
+/*
+ * string for printing usage
+ */
+static const char usagestr[] =
+ "usage: %s [option(s)] afm-main-uri\n"
+ "\n"
+ " -d run as a daemon\n"
+ " -u addr address of user D-Bus to use\n"
+ " -q quiet\n"
+ " -v verbose\n"
+ " -V version\n"
+ "\n";
+
+/*
+ * Option definition for getopt_long
+ */
+static const char options_s[] = "hdqvVu:";
+static struct option options_l[] = {
+ { "user-dbus", required_argument, NULL, 'u' },
{ "daemon", no_argument, NULL, 'd' },
{ "quiet", no_argument, NULL, 'q' },
{ "verbose", no_argument, NULL, 'v' },
{ "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
{ NULL, 0, NULL, 0 }
};
-static struct jbus *jbuses[2];
-static struct afm_db *afdb;
-
-const char error_nothing[] = "[]";
-const char error_bad_request[] = "\"bad request\"";
-const char error_not_found[] = "\"not found\"";
-const char error_cant_start[] = "\"can't start\"";
-const char error_system[] = "\"system error\"";
-
-static const char *getappid(struct json_object *obj)
-{
- return json_type_string == json_object_get_type(obj) ? json_object_get_string(obj) : NULL;
-}
+/*
+ * The methods propagated
+ */
+static const char *methods[] = {
+ "runnables",
+ "detail",
+ "start",
+ "once",
+ "terminate",
+ "pause",
+ "resume",
+ "stop",
+ "continue",
+ "runners",
+ "state",
+ "install",
+ "uninstall",
+ NULL
+};
-static int getrunid(struct json_object *obj)
-{
- return json_type_int == json_object_get_type(obj) ? json_object_get_int(obj) : 0;
-}
+/*
+ * Connections
+ */
+static struct sd_event *evloop;
+static struct jbus *user_bus;
+static struct afb_proto_ws *pws;
+static char *sessionid;
+static const char *uri;
-static void reply(struct jreq *jreq, struct json_object *resp, const char *errstr)
-{
- if (resp)
- jbus_reply_j(jreq, resp);
- else
- jbus_reply_error_s(jreq, errstr);
-}
+/*
+ *
+ */
+static void on_pws_hangup(void *closure);
+static void on_pws_reply(void *closure, void *request, struct json_object *obj, const char *error, const char *info);
+#if !defined(AFB_PROTO_WS_VERSION) || (AFB_PROTO_WS_VERSION < 3)
+static void on_pws_reply_success(void *closure, void *request, struct json_object *result, const char *info)
+ { on_pws_reply(closure, request, result, NULL, info); }
+static void on_pws_reply_fail(void *closure, void *request, const char *error, const char *info)
+ { on_pws_reply(closure, request, NULL, error, info); }
+#endif
+static void on_pws_event_broadcast(void *closure, const char *event_name, struct json_object *data);
+
+/* the callback interface for pws */
+static struct afb_proto_ws_client_itf pws_itf = {
+#if !defined(AFB_PROTO_WS_VERSION) || (AFB_PROTO_WS_VERSION < 3)
+ .on_reply_success = on_pws_reply_success,
+ .on_reply_fail = on_pws_reply_fail,
+#else
+ .on_reply = on_pws_reply,
+#endif
+ .on_event_broadcast = on_pws_event_broadcast,
+};
-static void reply_status(struct jreq *jreq, int status)
+static int try_connect_pws()
{
- if (status)
- jbus_reply_error_s(jreq, error_not_found);
- else
- jbus_reply_s(jreq, "true");
+ pws = afb_ws_client_connect_api(evloop, uri, &pws_itf, NULL);
+ if (pws == NULL) {
+ fprintf(stderr, "connection to %s failed: %m\n", uri);
+ return 0;
+ }
+ afb_proto_ws_on_hangup(pws, on_pws_hangup);
+ return 1;
}
-static void on_runnables(struct jreq *jreq, struct json_object *obj)
-{
- struct json_object *resp;
- INFO("method runnables called");
- resp = afm_db_application_list(afdb);
- jbus_reply_j(jreq, resp);
- json_object_put(resp);
-}
+static void attempt_connect_pws(int count);
-static void on_detail(struct jreq *jreq, struct json_object *obj)
+static int timehand(sd_event_source *s, uint64_t usec, void *userdata)
{
- const char *appid;
- struct json_object *resp;
- appid = getappid(obj);
- INFO("method detail called for %s", appid);
- resp = afm_db_get_application_public(afdb, appid);
- reply(jreq, resp, error_not_found);
- json_object_put(resp);
+ sd_event_source_unref(s);
+ attempt_connect_pws((int)(intptr_t)userdata);
+ return 0;
}
-static void on_start(struct jreq *jreq, struct json_object *obj)
+static void attempt_connect_pws(int count)
{
- const char *appid, *modestr;
- char *uri;
- struct json_object *appli, *resp;
- int runid;
- char runidstr[20];
- enum afm_launch_mode mode;
-
- /* get the parameters */
- mode = invalid_launch_mode;
- if (j_read_string(obj, &appid)) {
- mode = default_launch_mode;
- } else if (j_read_string_at(obj, "id", &appid)) {
- if (j_read_string_at(obj, "mode", &modestr)) {
- mode = launch_mode_of_string(modestr);
- } else {
- mode = default_launch_mode;
+ sd_event_source *s;
+ if (!try_connect_pws()) {
+ if (--count <= 0) {
+ ERROR("Definitely disconnected");
+ exit(1);
}
+ sd_event_add_time(evloop, &s, CLOCK_MONOTONIC, 5000000, 0, timehand, (void*)(intptr_t)count);
}
- if (!launch_mode_is_valid(mode)) {
- jbus_reply_error_s(jreq, error_bad_request);
- return;
- }
-
- /* get the application */
- INFO("method start called for %s mode=%s", appid, mode);
- appli = afm_db_get_application(afdb, appid);
- if (appli == NULL) {
- jbus_reply_error_s(jreq, error_not_found);
- return;
- }
-
- /* launch the application */
- uri = NULL;
- runid = afm_run_start(appli, mode, &uri);
- if (runid <= 0) {
- jbus_reply_error_s(jreq, error_cant_start);
- free(uri);
- return;
- }
-
- if (uri == NULL) {
- /* returns only the runid */
- snprintf(runidstr, sizeof runidstr, "%d", runid);
- runidstr[sizeof runidstr - 1] = 0;
- jbus_reply_s(jreq, runidstr);
- return;
- }
-
- /* returns the runid and its uri */
- resp = json_object_new_object();
- if (resp != NULL && j_add_integer(resp, "runid", runid) && j_add_string(resp, "uri", uri))
- jbus_reply_j(jreq, resp);
- else {
- afm_run_stop(runid);
- jbus_reply_error_s(jreq, error_cant_start);
- }
- json_object_put(resp);
- free(uri);
-}
-
-static void on_stop(struct jreq *jreq, struct json_object *obj)
-{
- int runid, status;
- runid = getrunid(obj);
- INFO("method stop called for %d", runid);
- status = afm_run_stop(runid);
- reply_status(jreq, status);
}
-static void on_continue(struct jreq *jreq, struct json_object *obj)
+static void on_pws_reply(void *closure, void *request, struct json_object *obj, const char *error, const char *info)
{
- int runid, status;
- runid = getrunid(obj);
- INFO("method continue called for %d", runid);
- status = afm_run_continue(runid);
- reply_status(jreq, status);
+ struct sd_bus_message *smsg = request;
+ if (error)
+ jbus_reply_error_s(smsg, error);
+ else
+ jbus_reply_j(smsg, obj);
}
-static void on_terminate(struct jreq *jreq, struct json_object *obj)
+static void on_pws_event_broadcast(void *closure, const char *event_name, struct json_object *data)
{
- int runid, status;
- runid = getrunid(obj);
- INFO("method terminate called for %d", runid);
- status = afm_run_terminate(runid);
- reply_status(jreq, status);
+ jbus_send_signal_j(user_bus, "changed", data);
}
-static void on_runners(struct jreq *jreq, struct json_object *obj)
+/* called when pws hangsup */
+static void on_pws_hangup(void *closure)
{
- struct json_object *resp;
- INFO("method runners called");
- resp = afm_run_list();
- jbus_reply_j(jreq, resp);
- json_object_put(resp);
+ struct afb_proto_ws *apw = pws;
+ pws = NULL;
+ afb_proto_ws_unref(apw);
+ attempt_connect_pws(10);
}
-static void on_state(struct jreq *jreq, struct json_object *obj)
+/* propagate the call to the service */
+static void propagate(struct sd_bus_message *smsg, struct json_object *obj, void *closure)
{
- int runid;
- struct json_object *resp;
- runid = getrunid(obj);
- INFO("method state called for %d", runid);
- resp = afm_run_state(runid);
- reply(jreq, resp, error_not_found);
- json_object_put(resp);
-}
+ int rc;
+ const char *verb = closure;
+ const char *onbehalf = NULL; /* TODO: on behalf of the client */
-static void propagate(struct jreq *jreq, const char *msg, const char *method)
-{
- char *reply;
- INFO("method %s propagated with %s", method, msg);
- reply = jbus_call_ss_sync(jbuses[0], method, msg);
- if (reply) {
- jbus_reply_s(jreq, reply);
- free(reply);
- }
- else
- jbus_reply_error_s(jreq, error_system);
+ INFO("method %s propagated for %s", verb, json_object_to_json_string(obj));
+ if (!pws)
+ jbus_reply_error_s(smsg, "disconnected");
+ else {
+#if defined(AFB_PROTO_WS_VERSION) && (AFB_PROTO_WS_VERSION >= 3)
+ rc = afb_proto_ws_client_call(pws, verb, obj, sessionid, smsg, onbehalf);
+#else
+ rc = afb_proto_ws_client_call(pws, verb, obj, sessionid, smsg);
+#endif
+ if (rc < 0)
+ ERROR("calling %s(%s) failed: %m\n", verb, json_object_to_json_string(obj));
+ }
}
-static void on_install(struct jreq *jreq, const char *msg)
+/*
+ * Tiny routine to daemonize the program
+ * Return 0 on success or -1 on failure.
+ */
+static int daemonize()
{
- return propagate(jreq, msg, "install");
+ int rc = fork();
+ if (rc < 0)
+ return rc;
+ if (rc)
+ _exit(0);
+ return 0;
}
-static void on_uninstall(struct jreq *jreq, const char *msg)
+/*
+ * Opens a sd-bus connection and returns it in 'ret'.
+ * The sd-bus connexion is intended to be for user if 'isuser'
+ * is not null. The adress is the default address when 'address'
+ * is NULL or, otherwise, the given address.
+ * It might be necessary to pass the address as an argument because
+ * library systemd uses secure_getenv to retrieves the default
+ * addresses and secure_getenv might return NULL in some cases.
+ */
+static int open_bus(sd_bus **ret, int isuser, const char *address)
{
- return propagate(jreq, msg, "uninstall");
-}
+ sd_bus *b;
+ int rc;
-static void on_signal_changed(struct json_object *obj)
-{
- /* update the database */
- afm_db_update_applications(afdb);
- /* re-propagate now */
- jbus_send_signal_j(jbuses[1], "changed", obj);
-}
+ if (address == NULL)
+ return (isuser ? sd_bus_default_user : sd_bus_default_system)(ret);
-static int daemonize()
-{
- int rc = fork();
+ rc = sd_bus_new(&b);
if (rc < 0)
return rc;
- if (rc)
- _exit(0);
+
+ rc = sd_bus_set_address(b, address);
+ if (rc < 0)
+ goto fail;
+
+ sd_bus_set_bus_client(b, 1);
+
+ rc = sd_bus_start(b);
+ if (rc < 0)
+ goto fail;
+
+ *ret = b;
return 0;
+
+fail:
+ sd_bus_unref(b);
+ return rc;
}
+/*
+ * ENTRY POINT OF AFM-USER-DAEMON
+ */
int main(int ac, char **av)
{
- int i, daemon = 0;
+ int i, daemon = 0, rc;
+ struct sd_bus *usrbus;
+ const char *usr_bus_addr;
+ const char **iter;
LOGAUTH(appname);
/* first interpretation of arguments */
- while ((i = getopt_long(ac, av, "hdqvr:a:", options, NULL)) >= 0) {
+ usr_bus_addr = NULL;
+ while ((i = getopt_long(ac, av, options_s, options_l, NULL)) >= 0) {
switch (i) {
case 'h':
- usage();
+ printf(usagestr, appname);
+ return 0;
+ case 'V':
+ printf(versionstr, appname);
return 0;
case 'q':
if (verbosity)
case 'd':
daemon = 1;
break;
- case 'r':
- break;
- case 'a':
+ case 'u':
+ usr_bus_addr = optarg;
break;
case ':':
ERROR("missing argument value");
}
}
- /* init random generator */
- srandom((unsigned int)time(NULL));
-
- /* init runners */
- if (afm_run_init()) {
- ERROR("afm_run_init failed");
- return 1;
- }
-
- /* init framework */
- afdb = afm_db_create();
- if (!afdb) {
- ERROR("afm_create failed");
+ /* check argument count */
+ if (optind >= ac) {
+ ERROR("Uri to the framework is missing");
return 1;
}
- if (afm_db_add_root(afdb, FWK_APP_DIR)) {
- ERROR("can't add root %s", FWK_APP_DIR);
+ if (optind + 1 != ac) {
+ ERROR("Extra parameters found");
return 1;
}
+ uri = av[optind];
- /* second interpretation of arguments */
- optind = 1;
- while ((i = getopt_long(ac, av, "hdqvr:a:", options, NULL)) >= 0) {
- switch (i) {
- case 'r':
- if (afm_db_add_root(afdb, optarg)) {
- ERROR("can't add root %s", optarg);
- return 1;
- }
- break;
- case 'a':
- if (afm_db_add_application(afdb, optarg)) {
- ERROR("can't add application %s", optarg);
- return 1;
- }
- break;
- }
- }
-
- /* update the database */
- if (afm_db_update_applications(afdb)) {
- ERROR("afm_update_applications failed");
- return 1;
- }
+ /* init sessionid */
+ asprintf(&sessionid, "%d-%s", (int)getuid(), appname);
+ /* daemonize if requested */
if (daemon && daemonize()) {
ERROR("daemonization failed");
return 1;
}
- /* init observers */
- jbuses[0] = create_jbus(0, AFM_SYSTEM_DBUS_PATH);
- if (!jbuses[0]) {
- ERROR("create_jbus failed for system");
+ /* get systemd objects */
+ rc = sd_event_new(&evloop);
+ if (rc < 0) {
+ ERROR("can't create event loop");
+ return 1;
+ }
+ rc = open_bus(&usrbus, 1, usr_bus_addr);
+ if (rc < 0) {
+ ERROR("can't create user bus");
return 1;
}
- if(jbus_on_signal_j(jbuses[0], "changed", on_signal_changed)) {
- ERROR("adding signal observer failed");
+ rc = sd_bus_attach_event(usrbus, evloop, 0);
+ if (rc < 0) {
+ ERROR("can't attach user bus to event loop");
return 1;
}
- /* init service */
- jbuses[1] = create_jbus(1, AFM_USER_DBUS_PATH);
- if (!jbuses[1]) {
- ERROR("create_jbus failed");
+ /* connect to framework */
+ if (!try_connect_pws()) {
+ ERROR("connection to %s failed: %m\n", uri);
return 1;
}
- if(jbus_add_service_j(jbuses[1], "runnables", on_runnables)
- || jbus_add_service_j(jbuses[1], "detail", on_detail)
- || jbus_add_service_j(jbuses[1], "start", on_start)
- || jbus_add_service_j(jbuses[1], "terminate", on_terminate)
- || jbus_add_service_j(jbuses[1], "stop", on_stop)
- || jbus_add_service_j(jbuses[1], "continue", on_continue)
- || jbus_add_service_j(jbuses[1], "runners", on_runners)
- || jbus_add_service_j(jbuses[1], "state", on_state)
- || jbus_add_service_s(jbuses[1], "install", on_install)
- || jbus_add_service_s(jbuses[1], "uninstall", on_uninstall)) {
- ERROR("adding services failed");
+
+ /* connect to the session bus */
+ user_bus = create_jbus(usrbus, AFM_USER_DBUS_PATH);
+ if (!user_bus) {
+ ERROR("create_jbus failed");
return 1;
}
- /* start and run */
- if (jbus_start_serving(jbuses[1])) {
+ /* init services */
+ for (iter = methods ; *iter ; iter ++) {
+ if (jbus_add_service_j(user_bus, *iter, propagate, (void*)*iter)) {
+ ERROR("adding services failed");
+ return 1;
+ }
+ }
+
+ /* start servicing */
+ if (jbus_start_serving(user_bus) < 0) {
ERROR("can't start server");
return 1;
}
- while (jbus_read_write_dispatch_multiple(jbuses, 2, -1, 20) >= 0);
+
+ /* run until error */
+ for(;;)
+ sd_event_run(evloop, (uint64_t)-1);
return 0;
}