Improve use of systemd's states
[src/app-framework-main.git] / src / afm-urun.c
index 48f8436..051ce7e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- Copyright 2015, 2016, 2017 IoT.bzh
+ Copyright (C) 2015-2020 IoT.bzh
 
  author: José Bollo <jose.bollo@iot.bzh>
 
@@ -26,6 +26,7 @@
 #include <stdio.h>
 #include <limits.h>
 #include <string.h>
+#include <time.h>
 
 #include <json-c/json.h>
 
 #include "afm-udb.h"
 #include "afm-urun.h"
 
+static const char key_unit_d_path[] = "-unit-dpath-";
+
 /**************** get appli basis *********************/
 
-static int get_basis(struct json_object *appli, int *isuser, const char **dpath, int load)
+static int get_basis(struct json_object *appli, int *isuser, const char **dpath, int uid)
 {
-       char *dp;
+       char userid[40];
+       char *dp, *arodot, *nun;
        const char *uname, *uscope;
+       struct json_object *odp;
+       int rc;
 
        /* get the scope */
        if (!j_read_string_at(appli, "unit-scope", &uscope)) {
@@ -50,29 +56,82 @@ static int get_basis(struct json_object *appli, int *isuser, const char **dpath,
        }
        *isuser = strcmp(uscope, "system") != 0;
 
-       /* get dpath */
-       if (!j_read_string_at(appli, "-unit-dpath-", dpath)) {
-               if (!load) {
-                       errno = ENOENT;
-                       goto error;
+       /* get dpaths of known users */
+       odp = NULL;
+       if (json_object_object_get_ex(appli, key_unit_d_path, &odp)) {
+               /* try not parametric dpath */
+               if (json_object_get_type(odp) == json_type_string) {
+                       *dpath = json_object_get_string(odp);
+                       return 0;
                }
-               if (!j_read_string_at(appli, "unit-name", &uname)) {
-                       ERROR("'unit-name' missing in appli description %s", json_object_get_string(appli));
+               assert(json_object_get_type(odp) == json_type_object);
+               /* get userid */
+               if (uid < 0) {
+                       ERROR("unexpected uid %d", uid);
                        goto inval;
                }
+               rc = snprintf(userid, sizeof userid, "%d", uid);
+               assert(rc < (int)(sizeof userid));
+               /* try dpath for the user */
+               if (j_read_string_at(odp, userid, dpath))
+                       return 0;
+       }
+
+       /* get uname */
+       if (!j_read_string_at(appli, "unit-name", &uname)) {
+               ERROR("'unit-name' missing in appli description %s", json_object_get_string(appli));
+               goto inval;
+       }
+
+       /* is user parametric? */
+       arodot = strchr(uname, '@');
+       if (arodot && *++arodot == '.') {
+               if (!odp) {
+                       /* get userid */
+                       if (uid < 0) {
+                               ERROR("unexpected uid %d", uid);
+                               goto inval;
+                       }
+                       rc = snprintf(userid, sizeof userid, "%d", uid);
+                       assert(rc < (int)(sizeof userid));
+
+                       /* create the dpaths of known users */
+                       odp = json_object_new_object();
+                       if (!odp)
+                               goto nomem;
+                       json_object_object_add(appli, key_unit_d_path, odp);
+               }
+
+               /* get dpath of userid */
+               nun = alloca((size_t)(arodot - uname) + strlen(userid) + strlen(arodot) + 1);
+               stpcpy(stpcpy(stpncpy(nun, uname, (size_t)(arodot - uname)), userid), arodot);
+               dp = systemd_unit_dpath_by_name(*isuser, nun, 1);
+               if (dp == NULL) {
+                       ERROR("Can't load unit of name %s for %s: %m", nun, uscope);
+                       goto error;
+               }
+               /* record the dpath */
+               if (!j_add_string(odp, userid, dp)) {
+                       free(dp);
+                       goto nomem;
+               }
+               free(dp);
+               j_read_string_at(odp, userid, dpath);
+       } else {
+               /* get dpath */
                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)) {
+               /* record the dpath */
+               if (!j_add_string(appli, key_unit_d_path, dp)) {
                        free(dp);
                        goto nomem;
                }
                free(dp);
-               j_read_string_at(appli, "-unit-dpath-", dpath);
+               j_read_string_at(appli, key_unit_d_path, dpath);
        }
-
        return 0;
 
 nomem:
@@ -86,17 +145,31 @@ error:
        return -1;
 }
 
-static const char *wait_state_stable(int isuser, const char *dpath)
+static enum SysD_State wait_state_stable(int isuser, const char *dpath)
 {
-       const char *state;
-
-       for (;;) {
+       int trial;
+       enum SysD_State state = SysD_State_INVALID;
+       struct timespec tispec;
+       const int period_ms = 10;
+       const int trial_s = 10;
+       const int trial_count = (trial_s * 1000) / period_ms;
+       const int period_ns = period_ms * 1000000;
+
+       for (trial = 1 ; trial <= trial_count ; trial++) {
                state = systemd_unit_state_of_dpath(isuser, dpath);
-               if (state == NULL || state == SysD_State_Active
-                || state == SysD_State_Failed || state == SysD_State_Inactive)
+               switch (state) {
+               case SysD_State_Active:
+               case SysD_State_Failed:
+               case SysD_State_Inactive:
                        return state;
-               /* TODO: sleep */
+               default:
+                       tispec.tv_sec = 0;
+                       tispec.tv_nsec = period_ns;
+                       nanosleep(&tispec, NULL);
+                       break;
+               }
        }
+       return state;
 }
 
 /*
@@ -108,7 +181,7 @@ static const char *wait_state_stable(int isuser, const char *dpath)
  *
  * Returns the created object or NULL in case of error.
  */
-static json_object *mkstate(const char *id, int runid, int pid, const char *state)
+static json_object *mkstate(const char *id, int runid, int pid, enum SysD_State state)
 {
        struct json_object *result, *pids;
 
@@ -160,9 +233,9 @@ error:
  *
  * Returns the runid in case of success or -1 in case of error
  */
-int afm_urun_start(struct json_object *appli)
+int afm_urun_start(struct json_object *appli, int uid)
 {
-       return afm_urun_once(appli);
+       return afm_urun_once(appli, uid);
 }
 
 /*
@@ -176,13 +249,14 @@ int afm_urun_start(struct json_object *appli)
  *
  * Returns the runid in case of success or -1 in case of error
  */
-int afm_urun_once(struct json_object *appli)
+int afm_urun_once(struct json_object *appli, int uid)
 {
-       const char *udpath, *state, *uscope, *uname;
+       const char *udpath, *uscope, *uname;
+       enum SysD_State state;
        int rc, isuser;
 
        /* retrieve basis */
-       rc = get_basis(appli, &isuser, &udpath, 1);
+       rc = get_basis(appli, &isuser, &udpath, uid);
        if (rc < 0)
                goto error;
 
@@ -191,21 +265,25 @@ int afm_urun_once(struct json_object *appli)
        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);
+               ERROR("can't start %s unit %s for uid %d", uscope, uname, uid);
                goto error;
        }
 
        state = wait_state_stable(isuser, udpath);
-       if (state == NULL) {
+       switch (state) {
+       case SysD_State_Active:
+       case SysD_State_Inactive:
+               break;
+       case SysD_State_Failed:
                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);
+               ERROR("start error %s unit %s for uid %d: %s", uscope, uname, uid,
+                                                       systemd_state_name(state));
                goto error;
-       }
-       if (state != SysD_State_Active) {
+       default:
                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);
+               ERROR("can't wait %s unit %s for uid %d: %m", uscope, uname, uid);
                goto error;
        }
 
@@ -213,10 +291,10 @@ int afm_urun_once(struct json_object *appli)
        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);
+               ERROR("can't get pid of %s unit %s for uid %d: %m", uscope, uname, uid);
                goto error;
        }
-               
+
        return rc;
 
 error:
@@ -235,9 +313,11 @@ static int not_yet_implemented(const char *what)
  *
  * Returns 0 in case of success or -1 in case of error
  */
-int afm_urun_terminate(int runid)
+int afm_urun_terminate(int runid, int uid)
 {
        int rc = systemd_unit_stop_pid(1 /* TODO: isuser? */, (unsigned)runid);
+       if (rc < 0)
+               rc = systemd_unit_stop_pid(0 /* TODO: isuser? */, (unsigned)runid);
        return rc < 0 ? rc : 0;
 }
 
@@ -246,7 +326,7 @@ int afm_urun_terminate(int runid)
  *
  * Returns 0 in case of success or -1 in case of error
  */
-int afm_urun_pause(int runid)
+int afm_urun_pause(int runid, int uid)
 {
        return not_yet_implemented("pause");
 }
@@ -256,7 +336,7 @@ int afm_urun_pause(int runid)
  *
  * Returns 0 in case of success or -1 in case of error
  */
-int afm_urun_resume(int runid)
+int afm_urun_resume(int runid, int uid)
 {
        return not_yet_implemented("resume");
 }
@@ -266,12 +346,12 @@ int afm_urun_resume(int runid)
  *
  * Returns the list or NULL in case of error.
  */
-struct json_object *afm_urun_list(struct afm_udb *db)
+struct json_object *afm_urun_list(struct afm_udb *db, int all, int uid)
 {
        int i, n, isuser, pid;
        const char *udpath;
        const char *id;
-       const char *state;
+       enum SysD_State state;
        struct json_object *desc;
        struct json_object *appli;
        struct json_object *apps;
@@ -282,18 +362,20 @@ struct json_object *afm_urun_list(struct afm_udb *db)
        if (result == NULL)
                goto error;
 
-       apps = afm_udb_applications_private(db);
+       apps = afm_udb_applications_private(db, all, uid);
        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) {
+               if (appli && get_basis(appli, &isuser, &udpath, uid) >= 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);
+                               if (state == SysD_State_Active) {
+                                       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);
+                                       }
                                }
                        }
                }
@@ -309,13 +391,13 @@ error:
  *
  * Returns the state or NULL in case of success
  */
-struct json_object *afm_urun_state(struct afm_udb *db, int runid)
+struct json_object *afm_urun_state(struct afm_udb *db, int runid, int uid)
 {
-       int i, n, isuser, pid;
+       int i, n, isuser, pid, wasuser;
        char *dpath;
        const char *udpath;
        const char *id;
-       const char *state;
+       enum SysD_State state;
        struct json_object *appli;
        struct json_object *apps;
        struct json_object *result;
@@ -323,35 +405,63 @@ struct json_object *afm_urun_state(struct afm_udb *db, int runid)
        result = NULL;
 
        /* get the dpath */
-       dpath = systemd_unit_dpath_by_pid(1 /* TODO: isuser? */, (unsigned)runid);
+       dpath = systemd_unit_dpath_by_pid(wasuser = 1, (unsigned)runid);
+       if (!dpath)
+               dpath = systemd_unit_dpath_by_pid(wasuser = 0, (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);
+               apps = afm_udb_applications_private(db, 1, uid);
                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
+                        && get_basis(appli, &isuser, &udpath, uid) >= 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);
+                               if (pid > 0 && state == SysD_State_Active)
+                                       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);    
+               free(dpath);
        }
 
        return result;
 }
 
+/*
+ * Search the runid, if any, of the application of 'id' for the user 'uid'.
+ * Returns the pid (a positive not null number) or -1 in case of error.
+ */
+int afm_urun_search_runid(struct afm_udb *db, const char *id, int uid)
+{
+       int isuser, pid;
+       const char *udpath;
+       struct json_object *appli;
+
+       appli = afm_udb_get_application_private(db, id, uid);
+       if (!appli) {
+               NOTICE("Unknown appid %s", id);
+               errno = ENOENT;
+               pid = -1;
+       } else if (get_basis(appli, &isuser, &udpath, uid) < 0) {
+               pid = -1;
+       } else {
+               pid = systemd_unit_pid_of_dpath(isuser, udpath);
+               if (pid == 0) {
+                       errno = ESRCH;
+                       pid = -1;
+               }
+       }
+       return pid;
+}
+