From 550ca0ae2bb9138c85fda941b67d4db1da1609ed Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jos=C3=A9=20Bollo?= Date: Thu, 16 Mar 2017 09:36:13 +0100 Subject: [PATCH] Switch to use systemd database MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Installation now creates unit files. This commits use these created unit files to fill the application database of afm-user-daemon. Change-Id: Ice39d3ff51b6afe41609f3ce4ff0e89b2f3a0ca7 Signed-off-by: José Bollo --- conf/afm-unit.conf | 55 +++++-- src/CMakeLists.txt | 1 + src/afm-udb.c | 444 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/afm-udb.h | 30 ++++ src/afm-user-daemon.c | 54 +++++- 5 files changed, 570 insertions(+), 14 deletions(-) create mode 100644 src/afm-udb.c create mode 100644 src/afm-udb.h diff --git a/conf/afm-unit.conf b/conf/afm-unit.conf index 86ab626..4cbfc87 100644 --- a/conf/afm-unit.conf +++ b/conf/afm-unit.conf @@ -49,6 +49,27 @@ ; ; tells to install a link to unit in the wants of NAME ; +; Setting variables: +; +; AFM uses the feature of systemd that completely ignores options prefixed +; with X- +; +; Consequently, options starting with X-AFM- are recorded as public data +; about the application and options starting starting with X-AFM-- are +; recorded as private data. +; +; Examples: +; +; X-AFM-description={{description}} +; +; Records the descritpion of the unit in the field "description" +; of both the public and private object describing the unit. +; +; X-AFM--wgtdir={{:#metadata.install-dir}} +; +; Records the installation directory path in the field "wgtdir" +; of the private object only. +; ;--------------------------------------------------------------------------------- {{#targets}} %begin systemd-unit @@ -56,16 +77,23 @@ # auto generated by wgtpkg-unit for {{id}} version {{version}} target {{:#target}} %nl -[unit] +[Unit] Description={{description}} -X-AGL-Name={{name.content}} -X-AGL-Name-Short={{name.short}} -X-AGL-Id={{id}} -X-AGL-Idaver={{idaver}} -X-AGL-Target-Name={{:#target}} -X-AGL-Author={{author.content}} -X-AGL-Author-email={{author.email}} -X-AGL-HTTP-port={{:#metadata.http-port}} +X-AFM-description={{description}} +X-AFM-name={{name.content}} +X-AFM-shortname={{name.short}} +X-AFM-id={{idaver}} +X-AFM-version={{version}} +X-AFM-author={{author.content}} +X-AFM-author-email={{author.email}} +X-AFM-width={{width}} +X-AFM-height={{height}} +X-AFM--ID={{id}} +X-AFM--target-name={{:#target}} +X-AFM--content={{content.src}} +X-AFM--type={{content.type}} +X-AFM--wgtdir={{:#metadata.install-dir}} +X-AFM--workdir={{&#metadata.app-data-dir}}/{{id}} %nl # Adds check to smack @@ -92,7 +120,8 @@ SuccessExitStatus=0 SIGKILL {{/required-permission}} %nl -WorkingDirectory={{&#metadata.app-data-dir}} +WorkingDirectory=-{{&#metadata.app-data-dir}}/{{id}} +ExecStartPre=/bin/mkdir -p {{&#metadata.app-data-dir}}/{{id}} Environment=AFM_APP_INSTALL_DIR={{:#metadata.install-dir}} @@ -103,6 +132,7 @@ Environment=AFM_APP_INSTALL_DIR={{:#metadata.install-dir}} %systemd-unit service afm-appli-{{idaver}}{{^#target=main}}@{{:#target}}{{/#target=main}} +X-AFM--http-port={{:#metadata.http-port}} ExecStart=/usr/bin/afb-daemon --port={{:#metadata.http-port}} --random-token \ --rootdir={{:#metadata.install-dir}} \ --workdir={{&#metadata.app-data-dir}}/{{id}} \ @@ -141,6 +171,7 @@ ExecStart=/usr/bin/afb-daemon --port={{:#metadata.http-port}} --random-token \ %systemd-unit user %systemd-unit service afm-appli-{{idaver}}{{^#target=main}}@{{:#target}}{{/#target=main}} + Environment=LD_LIBRARY_PATH=$ORIGIN/$LIB ExecStart={{:#metadata.install-dir}}/{{content.src}} @@ -174,7 +205,7 @@ ExecStart=/usr/bin/afb-daemon \ %systemd-unit user %systemd-unit socket afm-service-{{:#target}} -[socket] +[Socket] SmackLabel=* ListenStream=%t/bindings/{{:#target}} FileDescriptorName={{:#target}} @@ -185,7 +216,7 @@ FileDescriptorName={{:#target}} ;--------------------------------------------------------------------------------- {{#required-permission.urn:AGL:permission::system:run-by-default}} -[install] +[Install] WantedBy=default.target %systemd-unit wanted-by default.target {{/required-permission.urn:AGL:permission::system:run-by-default}} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 41c2b1e..b4894b6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -119,6 +119,7 @@ add_library(secwrp STATIC add_library(afm STATIC afm-db.c + afm-udb.c afm-launch.c afm-launch-mode.c afm-run.c diff --git a/src/afm-udb.c b/src/afm-udb.c new file mode 100644 index 0000000..9b1a354 --- /dev/null +++ b/src/afm-udb.c @@ -0,0 +1,444 @@ +/* + Copyright 2015, 2016, 2017 IoT.bzh + + author: José Bollo + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "utils-json.h" +#include "utils-systemd.h" +#include "utils-file.h" + +#include "afm-udb.h" + + +static const char x_afm_prefix[] = "X-AFM-"; +static const char service_extension[] = ".service"; +static const char key_unit_path[] = "-unit-path"; +static const char key_unit_name[] = "-unit-name"; +static const char key_unit_scope[] = "-unit-scope"; +static const char scope_user[] = "user"; +static const char scope_system[] = "system"; +static const char key_id[] = "id"; + +#define x_afm_prefix_length (sizeof x_afm_prefix - 1) +#define service_extension_length (sizeof service_extension - 1) + +/* + * The structure afm_apps records the data about applications + * for several accesses. + */ +struct afm_apps { + struct json_object *prvarr; /* array of the private data of apps */ + struct json_object *pubarr; /* array of the public data of apps */ + struct json_object *pubobj; /* hash of application's publics */ + struct json_object *prvobj; /* hash of application's privates */ +}; + +/* + * The structure afm_udb records the applications + * for a set of directories recorded as a linked list + */ +struct afm_udb { + struct afm_apps applications; /* the data about applications */ + int refcount; /* count of references to the structure */ + int system; /* is managing system units? */ + int user; /* is managing user units? */ + size_t prefixlen; /* length of the prefix */ + char prefix[1]; /* filtering prefix */ +}; + +/* + * The structure afm_updt is internally used for updates + */ +struct afm_updt { + struct afm_udb *afudb; + struct afm_apps applications; +}; + +/* + * Release the data of the afm_apps object 'apps'. + */ +static void apps_put(struct afm_apps *apps) +{ + json_object_put(apps->prvarr); + json_object_put(apps->pubarr); + json_object_put(apps->pubobj); + json_object_put(apps->prvobj); +} + +/* + * Adds the field of 'name' and 'value' in 'priv' and also if possible in 'pub' + * Returns 0 on success or -1 on error. + */ +static int add_field( + struct json_object *priv, + struct json_object *pub, + const char *name, + const char *value +) +{ + long int ival; + char *end; + struct json_object *v; + + /* try to adapt the value to its type */ + errno = 0; + ival = strtol(value, &end, 10); + if (*value && !*end && !errno) { + /* integer value */ + v = json_object_new_int64(ival); + } else { + /* string value */ + v = json_object_new_string(value); + } + if (!v) { + errno = ENOMEM; + return -1; + } + + /* add the value */ + if (name[0] == '-') { + json_object_object_add(priv, &name[1], v); + } else { + json_object_object_add(priv, name, json_object_get(v)); + json_object_object_add(pub, name, v); + } + return 0; +} + +/* + * Adds the field of 'name' and 'value' in 'priv' and also if possible in 'pub' + * Returns 0 on success or -1 on error. + */ +static int add_fields_of_content( + struct json_object *priv, + struct json_object *pub, + char *content, + size_t length +) +{ + char *name, *value, *read, *write; + + read = strstr(content, x_afm_prefix); + while (read) { + name = read + x_afm_prefix_length; + value = strchr(name, '='); + if (value == NULL) + read = strstr(name, x_afm_prefix); + else { + *value++ = 0; + read = write = value; + while(*read && *read != '\n') { + if (read[0] != '\\') + *write++ = *read++; + else { + switch(*++read) { + case 'n': *write++ = '\n'; break; + case '\n': *write++ = ' '; break; + default: *write++ = '\\'; *write++ = *read; break; + } + read += !!*read; + } + } + read = strstr(read, x_afm_prefix); + *write = 0; + if (add_field(priv, pub, name, value) < 0) + return -1; + } + } + return 0; +} + +/* + * Adds the application widget 'desc' of the directory 'path' to the + * afm_apps object 'apps'. + * Returns 0 in case of success. + * Returns -1 and set errno in case of error + */ +static int addunit( + struct afm_apps *apps, + int isuser, + const char *unitpath, + const char *unitname, + char *content, + size_t length +) +{ + struct json_object *priv, *pub, *id; + const char *strid; + + /* create the application structure */ + priv = json_object_new_object(); + if (!priv) + return -1; + + pub = json_object_new_object(); + if (!pub) + goto error; + + /* adds the values */ + if (add_fields_of_content(priv, pub, content, length) + || add_field(priv, pub, key_unit_path, unitpath) + || add_field(priv, pub, key_unit_name, unitname) + || add_field(priv, pub, key_unit_scope, isuser ? scope_user : scope_system)) + goto error; + + /* get the id */ + if (!json_object_object_get_ex(pub, key_id, &id)) { + errno = EINVAL; + goto error; + } + strid = json_object_get_string(id); + + /* record the application structure */ + json_object_get(pub); + json_object_array_add(apps->pubarr, pub); + json_object_object_add(apps->pubobj, strid, pub); + json_object_get(priv); + json_object_array_add(apps->prvarr, priv); + json_object_object_add(apps->prvobj, strid, priv); + return 0; + +error: + json_object_put(pub); + json_object_put(priv); + return -1; +} + +/* + * called for each unit + */ +static int update_cb(void *closure, const char *name, const char *path, int isuser) +{ + struct afm_updt *updt = closure; + char *content; + size_t length; + int rc; + + /* prefix filtering */ + length = updt->afudb->prefixlen; + if (length && strncmp(updt->afudb->prefix, name, length)) + return 0; + + /* only services */ + length = strlen(name); + if (length < service_extension_length || strcmp(service_extension, name + length - service_extension_length)) + return 0; + + /* reads the file */ + rc = getfile(path, &content, &length); + if (rc < 0) + return rc; + + /* process the file */ + rc = addunit(&updt->applications, isuser, path, name, content, length); + free(content); + return rc; +} + +/* + * Creates an afm_udb object and returns it with one reference added. + * Return NULL with errno = ENOMEM if memory exhausted. + */ +struct afm_udb *afm_udb_create(int sys, int usr, const char *prefix) +{ + size_t length; + struct afm_udb *afudb; + + length = prefix ? strlen(prefix) : 0; + afudb = malloc(length + sizeof * afudb); + if (afudb == NULL) + errno = ENOMEM; + else { + afudb->refcount = 1; + afudb->applications.prvarr = NULL; + afudb->applications.pubarr = NULL; + afudb->applications.pubobj = NULL; + afudb->applications.prvobj = NULL; + afudb->system = sys; + afudb->user = usr; + afudb->prefixlen = length; + if (length) + memcpy(afudb->prefix, prefix, length); + afudb->prefix[length] = 0; + if (afm_udb_update(afudb) < 0) { + afm_udb_unref(afudb); + afudb = NULL; + } + } + return afudb; +} + +/* + * Adds a reference to an existing afm_udb. + */ +void afm_udb_addref(struct afm_udb *afudb) +{ + assert(afudb); + afudb->refcount++; +} + +/* + * Removes a reference to an existing afm_udb object. + * Removes the objet if there no more reference to it. + */ +void afm_udb_unref(struct afm_udb *afudb) +{ + assert(afudb); + if (!--afudb->refcount) { + /* no more reference, clean the memory used by the object */ + apps_put(&afudb->applications); + free(afudb); + } +} + +/* + * Regenerate the list of applications of the afm_bd object 'afudb'. + * Returns 0 in case of success. + * Returns -1 and set errno in case of error + */ +int afm_udb_update(struct afm_udb *afudb) +{ + struct afm_updt updt; + struct afm_apps tmp; + + /* lock the db */ + afm_udb_addref(afudb); + updt.afudb = afudb; + + /* create the result */ + updt.applications.prvarr = json_object_new_array(); + updt.applications.pubarr = json_object_new_array(); + updt.applications.pubobj = json_object_new_object(); + updt.applications.prvobj = json_object_new_object(); + if (updt.applications.pubarr == NULL + || updt.applications.prvarr == NULL + || updt.applications.pubobj == NULL + || updt.applications.prvobj == NULL) { + errno = ENOMEM; + goto error; + } + + /* scan the units */ + if (afudb->user) + if (systemd_unit_list(1, update_cb, &updt) < 0) + goto error; + if (afudb->system) + if (systemd_unit_list(0, update_cb, &updt) < 0) + goto error; + + /* commit the result */ + tmp = afudb->applications; + afudb->applications = updt.applications; + apps_put(&tmp); + afm_udb_addref(afudb); + return 0; + +error: + apps_put(&updt.applications); + afm_udb_addref(afudb); + return -1; +} + +/* + * Get the list of the applications private data of the afm_udb object 'afudb'. + * The list is returned as a JSON-array that must be released using + * 'json_object_put'. + * Returns NULL in case of error. + */ +struct json_object *afm_udb_applications_private(struct afm_udb *afudb) +{ + return json_object_get(afudb->applications.prvarr); +} + +/* + * Get the list of the applications public data of the afm_udb object 'afudb'. + * The list is returned as a JSON-array that must be released using + * 'json_object_put'. + * Returns NULL in case of error. + */ +struct json_object *afm_udb_applications_public(struct afm_udb *afudb) +{ + return json_object_get(afudb->applications.pubarr); +} + +/* + * Get the private data of the applications of 'id' in the afm_udb object 'afudb'. + * It returns a JSON-object that must be released using 'json_object_put'. + * Returns NULL in case of error. + */ +static struct json_object *get_no_case(struct json_object *object, const char *id) +{ + struct json_object *result; + struct json_object_iter i; + + /* search case sensitively */ + if (json_object_object_get_ex(object, id, &result)) + return json_object_get(result); + + /* fallback to a case insensitive search */ + json_object_object_foreachC(object, i) { + if (!strcasecmp(i.key, id)) + return json_object_get(i.val); + } + return NULL; +} + +/* + * Get the private data of the applications of 'id' in the afm_udb object 'afudb'. + * It returns a JSON-object that must be released using 'json_object_put'. + * Returns NULL in case of error. + */ +struct json_object *afm_udb_get_application_private(struct afm_udb *afudb, const char *id) +{ + return get_no_case(afudb->applications.prvobj, id); +} + +/* + * Get the public data of the applications of 'id' in the afm_udb object 'afudb'. + * It returns a JSON-object that must be released using 'json_object_put'. + * Returns NULL in case of error. + */ +struct json_object *afm_udb_get_application_public(struct afm_udb *afudb, + const char *id) +{ + return get_no_case(afudb->applications.pubobj, id); +} + + + +#if defined(TESTAPPFWK) +#include +int main() +{ +struct afm_udb *afudb = afm_udb_create(1, 1, NULL); +printf("array = %s\n", json_object_to_json_string_ext(afudb->applications.pubarr, 3)); +printf("pubobj = %s\n", json_object_to_json_string_ext(afudb->applications.pubobj, 3)); +printf("prvobj = %s\n", json_object_to_json_string_ext(afudb->applications.prvobj, 3)); +return 0; +} +#endif + diff --git a/src/afm-udb.h b/src/afm-udb.h new file mode 100644 index 0000000..672a311 --- /dev/null +++ b/src/afm-udb.h @@ -0,0 +1,30 @@ +/* + Copyright 2015, 2016, 2017 IoT.bzh + + author: José Bollo + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +struct afm_udb; +struct json_object; + +extern struct afm_udb *afm_udb_create(int sys, int usr, const char *prefix); +extern void afm_udb_addref(struct afm_udb *afdb); +extern void afm_udb_unref(struct afm_udb *afdb); +extern int afm_udb_update(struct afm_udb *afdb); +extern struct json_object *afm_udb_applications_private(struct afm_udb *afdb); +extern struct json_object *afm_udb_applications_public(struct afm_udb *afdb); +extern struct json_object *afm_udb_get_application_private(struct afm_udb *afdb, const char *id); +extern struct json_object *afm_udb_get_application_public(struct afm_udb *afdb, const char *id); + diff --git a/src/afm-user-daemon.c b/src/afm-user-daemon.c index a180be6..7c7824d 100644 --- a/src/afm-user-daemon.c +++ b/src/afm-user-daemon.c @@ -30,7 +30,11 @@ #include "utils-jbus.h" #include "utils-json.h" #include "afm.h" -#include "afm-db.h" +#ifdef LEGACY_MODE_WITHOUT_SYSTEMD +# include "afm-db.h" +#else +# include "afm-udb.h" +#endif #include "afm-launch-mode.h" #include "afm-run.h" @@ -43,11 +47,16 @@ static const char appname[] = "afm-user-daemon"; * string for printing usage */ static const char usagestr[] = +#ifdef LEGACY_MODE_WITHOUT_SYSTEMD "usage: %s [-q] [-v] [-m mode] [-r rootdir]... [-a appdir]...\n" "\n" " -a appdir adds an application directory\n" " -r rootdir adds a root directory of applications\n" " -m mode set default launch mode (local or remote)\n" +#else + "usage: %s [option(s)]\n" + "\n" +#endif " -d run as a daemon\n" " -u addr address of user D-Bus to use\n" " -s addr address of system D-Bus to use\n" @@ -58,11 +67,16 @@ static const char usagestr[] = /* * Option definition for getopt_long */ +#ifdef LEGACY_MODE_WITHOUT_SYSTEMD static const char options_s[] = "hdqvr:a:m:"; static struct option options_l[] = { { "root", required_argument, NULL, 'r' }, { "application", required_argument, NULL, 'a' }, { "mode", required_argument, NULL, 'm' }, +#else +static const char options_s[] = "hdqv"; +static struct option options_l[] = { +#endif { "user-dbus", required_argument, NULL, 'u' }, { "system-dbus", required_argument, NULL, 's' }, { "daemon", no_argument, NULL, 'd' }, @@ -85,7 +99,11 @@ static struct jbus *jbuses[2]; /* * Handle to the database of applications */ +#ifdef LEGACY_MODE_WITHOUT_SYSTEMD static struct afm_db *afdb; +#else +static struct afm_udb *afudb; +#endif /* * Returned error strings @@ -154,7 +172,11 @@ static void on_runnables(struct sd_bus_message *smsg, struct json_object *obj, v { struct json_object *resp; INFO("method runnables called"); +#ifdef LEGACY_MODE_WITHOUT_SYSTEMD resp = afm_db_application_list(afdb); +#else + resp = afm_udb_applications_public(afudb); +#endif jbus_reply_j(smsg, resp); json_object_put(resp); } @@ -180,7 +202,11 @@ static void on_detail(struct sd_bus_message *smsg, struct json_object *obj, void /* wants details for appid */ INFO("method detail called for %s", appid); +#ifdef LEGACY_MODE_WITHOUT_SYSTEMD resp = afm_db_get_application_public(afdb, appid); +#else + resp = afm_udb_get_application_public(afudb, appid); +#endif reply(smsg, resp, error_not_found); json_object_put(resp); } @@ -216,7 +242,11 @@ static void on_start(struct sd_bus_message *smsg, struct json_object *obj, void /* get the application */ INFO("method start called for %s mode=%s", appid, name_of_launch_mode(mode)); +#ifdef LEGACY_MODE_WITHOUT_SYSTEMD appli = afm_db_get_application(afdb, appid); +#else + appli = afm_udb_get_application_private(afudb, appid); +#endif if (appli == NULL) { jbus_reply_error_s(smsg, error_not_found); return; @@ -269,7 +299,11 @@ static void on_once(struct sd_bus_message *smsg, struct json_object *obj, void * /* get the application */ INFO("method once called for %s", appid); +#ifdef LEGACY_MODE_WITHOUT_SYSTEMD appli = afm_db_get_application(afdb, appid); +#else + appli = afm_udb_get_application_private(afudb, appid); +#endif if (appli == NULL) { jbus_reply_error_s(smsg, error_not_found); return; @@ -417,7 +451,11 @@ static void on_uninstall(struct sd_bus_message *smsg, const char *msg, void *unu static void on_signal_changed(struct json_object *obj, void *unused) { /* update the database */ +#ifdef LEGACY_MODE_WITHOUT_SYSTEMD afm_db_update_applications(afdb); +#else + afm_udb_update(afudb); +#endif /* re-propagate now */ jbus_send_signal_j(user_bus, "changed", obj); } @@ -481,7 +519,9 @@ fail: int main(int ac, char **av) { int i, daemon = 0, rc; +#ifdef LEGACY_MODE_WITHOUT_SYSTEMD enum afm_launch_mode mode; +#endif struct sd_event *evloop; struct sd_bus *sysbus, *usrbus; const char *sys_bus_addr, *usr_bus_addr; @@ -506,6 +546,7 @@ int main(int ac, char **av) case 'd': daemon = 1; break; +#ifdef LEGACY_MODE_WITHOUT_SYSTEMD case 'r': break; case 'a': @@ -518,6 +559,7 @@ int main(int ac, char **av) } set_default_launch_mode(mode); break; +#endif case 'u': usr_bus_addr = optarg; break; @@ -543,9 +585,10 @@ int main(int ac, char **av) } /* init framework */ +#ifdef LEGACY_MODE_WITHOUT_SYSTEMD afdb = afm_db_create(); if (!afdb) { - ERROR("afm_create failed"); + ERROR("afm_db_create failed"); return 1; } if (afm_db_add_root(afdb, FWK_APP_DIR)) { @@ -577,6 +620,13 @@ int main(int ac, char **av) ERROR("afm_update_applications failed"); return 1; } +#else + afudb = afm_udb_create(0, 1, "afm-appli-"); + if (!afudb) { + ERROR("afm_udb_create failed"); + return 1; + } +#endif /* daemonize if requested */ if (daemon && daemonize()) { -- 2.16.6