utils-systemd: implement start/stop of units
[src/app-framework-main.git] / src / afm-urun.c
diff --git a/src/afm-urun.c b/src/afm-urun.c
new file mode 100644 (file)
index 0000000..48f8436
--- /dev/null
@@ -0,0 +1,357 @@
+/*
+ Copyright 2015, 2016, 2017 IoT.bzh
+
+ author: José Bollo <jose.bollo@iot.bzh>
+
+ 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.
+*/
+
+#define _GNU_SOURCE
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <assert.h>
+#include <stdio.h>
+#include <limits.h>
+#include <string.h>
+
+#include <json-c/json.h>
+
+#include "verbose.h"
+#include "utils-dir.h"
+#include "utils-json.h"
+#include "utils-systemd.h"
+#include "afm-udb.h"
+#include "afm-urun.h"
+
+/**************** get appli basis *********************/
+
+static int get_basis(struct json_object *appli, int *isuser, const char **dpath, int load)
+{
+       char *dp;
+       const char *uname, *uscope;
+
+       /* get the scope */
+       if (!j_read_string_at(appli, "unit-scope", &uscope)) {
+               ERROR("'unit-scope' missing in appli description %s", json_object_get_string(appli));
+               goto inval;
+       }
+       *isuser = strcmp(uscope, "system") != 0;
+
+       /* get dpath */
+       if (!j_read_string_at(appli, "-unit-dpath-", dpath)) {
+               if (!load) {
+                       errno = ENOENT;
+                       goto error;
+               }
+               if (!j_read_string_at(appli, "unit-name", &uname)) {
+                       ERROR("'unit-name' missing in appli description %s", json_object_get_string(appli));
+                       goto inval;
+               }
+               dp = systemd_unit_dpath_by_name(*isuser, uname, 1);
+               if (dp == NULL) {
+                       ERROR("Can't load unit of name %s for %s: %m", uname, uscope);
+                       goto error;
+               }
+               if (!j_add_string(appli, "-unit-dpath-", dp)) {
+                       free(dp);
+                       goto nomem;
+               }
+               free(dp);
+               j_read_string_at(appli, "-unit-dpath-", dpath);
+       }
+
+       return 0;
+
+nomem:
+       ERROR("out of memory");
+       errno = ENOMEM;
+       goto error;
+
+inval:
+       errno = EINVAL;
+error:
+       return -1;
+}
+
+static const char *wait_state_stable(int isuser, const char *dpath)
+{
+       const char *state;
+
+       for (;;) {
+               state = systemd_unit_state_of_dpath(isuser, dpath);
+               if (state == NULL || state == SysD_State_Active
+                || state == SysD_State_Failed || state == SysD_State_Inactive)
+                       return state;
+               /* TODO: sleep */
+       }
+}
+
+/*
+ * Creates a json object that describes the state for:
+ *  - 'id', the id of the application
+ *  - 'runid', its runid
+ *  - 'pid', its pid
+ *  - 'state', its systemd state
+ *
+ * Returns the created object or NULL in case of error.
+ */
+static json_object *mkstate(const char *id, int runid, int pid, const char *state)
+{
+       struct json_object *result, *pids;
+
+       /* the structure */
+       result = json_object_new_object();
+       if (result == NULL)
+               goto error;
+
+       /* the runid */
+       if (!j_add_integer(result, "runid", runid))
+               goto error;
+
+       /* the pids */
+       if (pid > 0) {
+               pids = j_add_new_array(result, "pids");
+               if (!pids)
+                       goto error;
+               if (!j_add_integer(pids, NULL, pid))
+                       goto error;
+       }
+
+       /* the state */
+       if (!j_add_string(result, "state", state == SysD_State_Active ? "running" : "terminated"))
+               goto error;
+
+       /* the application id */
+       if (!j_add_string(result, "id", id))
+               goto error;
+
+       /* done */
+       return result;
+
+error:
+       json_object_put(result);
+       errno = ENOMEM;
+       return NULL;
+}
+
+/**************** API handling ************************/
+
+/*
+ * Starts the application described by 'appli' for the 'mode'.
+ * In case of remote start, it returns in uri the uri to
+ * connect to.
+ *
+ * A reference to 'appli' is kept during the live of the
+ * runner. This is made using json_object_get. Thus be aware
+ * that further modifications to 'appli' might create errors.
+ *
+ * Returns the runid in case of success or -1 in case of error
+ */
+int afm_urun_start(struct json_object *appli)
+{
+       return afm_urun_once(appli);
+}
+
+/*
+ * Returns the runid of a previously started application 'appli'
+ * or if none is running, starts the application described by 'appli'
+ * in local mode.
+ *
+ * A reference to 'appli' is kept during the live of the
+ * runner. This is made using json_object_get. Thus be aware
+ * that further modifications to 'appli' might create errors.
+ *
+ * Returns the runid in case of success or -1 in case of error
+ */
+int afm_urun_once(struct json_object *appli)
+{
+       const char *udpath, *state, *uscope, *uname;
+       int rc, isuser;
+
+       /* retrieve basis */
+       rc = get_basis(appli, &isuser, &udpath, 1);
+       if (rc < 0)
+               goto error;
+
+       /* start the unit */
+       rc = systemd_unit_start_dpath(isuser, udpath);
+       if (rc < 0) {
+               j_read_string_at(appli, "unit-scope", &uscope);
+               j_read_string_at(appli, "unit-name", &uname);
+               ERROR("can't start %s unit %s", uscope, uname);
+               goto error;
+       }
+
+       state = wait_state_stable(isuser, udpath);
+       if (state == NULL) {
+               j_read_string_at(appli, "unit-scope", &uscope);
+               j_read_string_at(appli, "unit-name", &uname);
+               ERROR("can't wait %s unit %s: %m", uscope, uname);
+               goto error;
+       }
+       if (state != SysD_State_Active) {
+               j_read_string_at(appli, "unit-scope", &uscope);
+               j_read_string_at(appli, "unit-name", &uname);
+               ERROR("start error %s unit %s: %s", uscope, uname, state);
+               goto error;
+       }
+
+       rc = systemd_unit_pid_of_dpath(isuser, udpath);
+       if (rc < 0) {
+               j_read_string_at(appli, "unit-scope", &uscope);
+               j_read_string_at(appli, "unit-name", &uname);
+               ERROR("can't getpid of %s unit %s: %m", uscope, uname);
+               goto error;
+       }
+               
+       return rc;
+
+error:
+       return -1;
+}
+
+static int not_yet_implemented(const char *what)
+{
+       ERROR("%s isn't yet implemented", what);
+       errno = ENOTSUP;
+       return -1;
+}
+
+/*
+ * Terminates the runner of 'runid'
+ *
+ * Returns 0 in case of success or -1 in case of error
+ */
+int afm_urun_terminate(int runid)
+{
+       int rc = systemd_unit_stop_pid(1 /* TODO: isuser? */, (unsigned)runid);
+       return rc < 0 ? rc : 0;
+}
+
+/*
+ * Stops (aka pause) the runner of 'runid'
+ *
+ * Returns 0 in case of success or -1 in case of error
+ */
+int afm_urun_pause(int runid)
+{
+       return not_yet_implemented("pause");
+}
+
+/*
+ * Continue (aka resume) the runner of 'runid'
+ *
+ * Returns 0 in case of success or -1 in case of error
+ */
+int afm_urun_resume(int runid)
+{
+       return not_yet_implemented("resume");
+}
+
+/*
+ * Get the list of the runners.
+ *
+ * Returns the list or NULL in case of error.
+ */
+struct json_object *afm_urun_list(struct afm_udb *db)
+{
+       int i, n, isuser, pid;
+       const char *udpath;
+       const char *id;
+       const char *state;
+       struct json_object *desc;
+       struct json_object *appli;
+       struct json_object *apps;
+       struct json_object *result;
+
+       apps = NULL;
+       result = json_object_new_array();
+       if (result == NULL)
+               goto error;
+
+       apps = afm_udb_applications_private(db);
+       n = json_object_array_length(apps);
+       for (i = 0 ; i < n ; i++) {
+               appli = json_object_array_get_idx(apps, i);
+               if (appli && get_basis(appli, &isuser, &udpath, 0) >= 0) {
+                       pid = systemd_unit_pid_of_dpath(isuser, udpath);
+                       if (pid > 0 && j_read_string_at(appli, "id", &id)) {
+                               state = systemd_unit_state_of_dpath(isuser, udpath);
+                               desc = mkstate(id, pid, pid, state);
+                               if (desc && json_object_array_add(result, desc) == -1) {
+                                       ERROR("can't add desc %s to result", json_object_get_string(desc));
+                                       json_object_put(desc);
+                               }
+                       }
+               }
+       }
+
+error:
+       json_object_put(apps);
+       return result;
+}
+
+/*
+ * Get the state of the runner of 'runid'.
+ *
+ * Returns the state or NULL in case of success
+ */
+struct json_object *afm_urun_state(struct afm_udb *db, int runid)
+{
+       int i, n, isuser, pid;
+       char *dpath;
+       const char *udpath;
+       const char *id;
+       const char *state;
+       struct json_object *appli;
+       struct json_object *apps;
+       struct json_object *result;
+
+       result = NULL;
+
+       /* get the dpath */
+       dpath = systemd_unit_dpath_by_pid(1 /* TODO: isuser? */, (unsigned)runid);
+       if (!dpath) {
+               result = NULL;
+               errno = EINVAL;
+               WARNING("searched runid %d not found", runid);
+       } else {
+               /* search in the base */
+               apps = afm_udb_applications_private(db);
+               n = json_object_array_length(apps);
+               for (i = 0 ; i < n ; i++) {
+                       appli = json_object_array_get_idx(apps, i);
+                       if (appli
+                        && get_basis(appli, &isuser, &udpath, 0) >= 0
+                        && !strcmp(dpath, udpath)
+                        && j_read_string_at(appli, "id", &id)) {
+                               pid = systemd_unit_pid_of_dpath(isuser, udpath);
+                               state = systemd_unit_state_of_dpath(isuser, dpath);
+                               result = mkstate(id, runid, pid, state);
+                               goto end;
+                       }
+               }
+               result = NULL;
+               errno = ENOENT;
+               WARNING("searched runid %d of dpath %s isn't an applications", runid, dpath);
+end:
+               json_object_put(apps);
+               free(dpath);    
+       }
+
+       return result;
+}
+