Adds 2017 to copyrights
[src/app-framework-main.git] / src / afm-run.c
index b9e1e7c..55b2daf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- Copyright 2015 IoT.bzh
+ Copyright 2015, 2016, 2017 IoT.bzh
 
  author: José Bollo <jose.bollo@iot.bzh>
 
@@ -16,6 +16,8 @@
  limitations under the License.
 */
 
+#define _GNU_SOURCE
+
 #include <fcntl.h>
 #include <unistd.h>
 #include <signal.h>
 #include <limits.h>
 #include <string.h>
 
-#include <json.h>
+#include <linux/xattr.h>
+#if SIMULATE_LIBSMACK
+#include "simulation/smack.h"
+#else
+#include <sys/smack.h>
+#endif
+
+#include <json-c/json.h>
 
 #include "verbose.h"
 #include "utils-dir.h"
 #include "utils-json.h"
-#include "afm-run.h"
+#include "afm-launch-mode.h"
 #include "afm-launch.h"
+#include "afm-run.h"
 
+/*
+ * State of a launched/running application
+ */
 enum appstate {
-       as_starting,
-       as_running,
-       as_stopped,
-       as_terminating,
-       as_terminated
+       as_starting,    /* start in progress */
+       as_running,     /* started and running */
+       as_paused,      /* paused */
+       as_terminating, /* termination in progress */
+       as_terminated   /* terminated */
 };
 
+/*
+ * Structure for recording a runner
+ */
 struct apprun {
-       struct apprun *next_by_runid;
-       struct apprun *next_by_pgid;
-       int runid;
-       pid_t pids[2]; /* 0: group leader, 1: slave (appli) */
-       enum appstate state;
-       json_object *appli;
+       struct apprun *next_by_runid; /* link for hashing by runid */
+       struct apprun *next_by_pgid;  /* link for hashing by pgid */
+       int runid;           /* runid */
+       pid_t pids[2];       /* pids (0: group leader, 1: slave) */
+       enum appstate state; /* current state of the application */
+       json_object *appli;  /* json object describing the application */
 };
 
+/*
+ * Count of item by hash table
+ */
 #define ROOT_RUNNERS_COUNT  32
+
+/*
+ * Maximum count of simultaneous running application
+ */
 #define MAX_RUNNER_COUNT    32767
 
+/*
+ * Hash tables of runners by runid and by pgid
+ */
 static struct apprun *runners_by_runid[ROOT_RUNNERS_COUNT];
 static struct apprun *runners_by_pgid[ROOT_RUNNERS_COUNT];
+
+/*
+ * List of terminated runners
+ */
+static struct apprun *terminated_runners = NULL;
+
+/*
+ * Count of runners
+ */
 static int runnercount = 0;
+
+/*
+ * Last given runid
+ */
 static int runnerid = 0;
 
+/*
+ * Path name of the directory for applications in the
+ * home directory of the user.
+ */
 static const char fwk_user_app_dir[] = FWK_USER_APP_DIR;
+static const char fwk_user_app_label[] = FWK_USER_APP_DIR_LABEL;
+
+/*
+ * Path of the root directory for applications of the
+ * current user
+ */
 static char *homeappdir;
 
+/****************** manages pids **********************/
+
+/*
+ * Get a runner by its 'pid' (NULL if not found)
+ */
+static struct apprun *runner_of_pid(pid_t pid)
+{
+       int i;
+       struct apprun *result;
+
+       for (i = 0 ; i < ROOT_RUNNERS_COUNT ; i++) {
+               result = runners_by_pgid[i];
+               while (result != NULL) {
+                       if (result->pids[0] == pid || result->pids[1] == pid)
+                               return result;
+                       result = result->next_by_pgid;
+               }
+       }
+       return NULL;
+}
+
 /****************** manages pgids **********************/
 
-/* get a runner by its pgid */
+/*
+ * Get a runner by its 'pgid' (NULL if not found)
+ */
 static struct apprun *runner_of_pgid(pid_t pgid)
 {
-       struct apprun *result = runners_by_pgid[(int)(pgid & (ROOT_RUNNERS_COUNT - 1))];
+       struct apprun *result;
+
+       result = runners_by_pgid[pgid & (ROOT_RUNNERS_COUNT - 1)];
        while (result && result->pids[0] != pgid)
                result = result->next_by_pgid;
        return result;
 }
 
-/* insert a runner for its pgid */
+/*
+ * Insert a 'runner' for its pgid
+ */
 static void pgid_insert(struct apprun *runner)
 {
-       struct apprun **prev = &runners_by_runid[(int)(runner->pids[0] & (ROOT_RUNNERS_COUNT - 1))];
+       struct apprun **prev;
+
+       prev = &runners_by_pgid[runner->pids[0] & (ROOT_RUNNERS_COUNT - 1)];
        runner->next_by_pgid = *prev;
        *prev = runner;
 }
 
-/* remove a runner for its pgid */
+/*
+ * Remove a 'runner' for its pgid
+ */
 static void pgid_remove(struct apprun *runner)
 {
-       struct apprun **prev = &runners_by_runid[(int)(runner->pids[0] & (ROOT_RUNNERS_COUNT - 1))];
-       runner->next_by_pgid = *prev;
-       *prev = runner;
+       struct apprun **prev;
+
+       prev = &runners_by_pgid[runner->pids[0] & (ROOT_RUNNERS_COUNT - 1)];
+       while (*prev) {
+               if (*prev == runner) {
+                       *prev = runner->next_by_pgid;
+                       break;
+               }
+               prev = &(*prev)->next_by_pgid;
+       }
 }
 
-/****************** manages pids **********************/
+/****************** manages runners (by runid) **********************/
 
-/* get a runner by its pid */
-static struct apprun *runner_of_pid(pid_t pid)
+/*
+ * Is a 'runner' alive?
+ */
+static inline int is_alive(struct apprun *runner)
 {
-       /* try avoiding system call */
-       struct apprun *result = runner_of_pgid(pid);
-       if (result == NULL) {
-               result = runner_of_pgid(getpgid(pid));
-               if (result && result->pids[1] != pid)
-                       result = NULL;
+       switch(runner->state) {
+       case as_terminating:
+       case as_terminated:
+               return 0;
+       default:
+               return 1;
        }
-       return result;
 }
 
-/****************** manages runners (by runid) **********************/
+/*
+ * Is a 'runner' dead?
+ */
+static inline int is_dead(struct apprun *runner)
+{
+       switch(runner->state) {
+       case as_terminating:
+       case as_terminated:
+               return 1;
+       default:
+               return 0;
+       }
+}
 
-/* get a runner by its runid */
+/*
+ * Is a 'runner' running?
+ */
+static inline int is_running(struct apprun *runner)
+{
+       switch(runner->state) {
+       case as_starting:
+       case as_running:
+               return 1;
+       default:
+               return 0;
+       }
+}
+
+/*
+ * Is a 'runner' paused?
+ */
+static inline int is_paused(struct apprun *runner)
+{
+       switch(runner->state) {
+       case as_paused:
+               return 1;
+       default:
+               return 0;
+       }
+}
+
+/*
+ * Get a runner by its 'runid'  (NULL if not found)
+ */
 static struct apprun *getrunner(int runid)
 {
-       struct apprun *result = runners_by_runid[runid & (ROOT_RUNNERS_COUNT - 1)];
+       struct apprun *result;
+
+       result = runners_by_runid[runid & (ROOT_RUNNERS_COUNT - 1)];
        while (result && result->runid != runid)
                result = result->next_by_runid;
        return result;
 }
 
-/* free an existing runner */
+/*
+ * Get first runner of 'appli' (NULL if not found)
+ */
+static struct apprun *getrunner_appli(json_object *appli)
+{
+       int i;
+       struct apprun *result;
+
+       for (i = 0 ; i < ROOT_RUNNERS_COUNT ; i++) {
+               result = runners_by_pgid[i];
+               while (result != NULL) {
+                       if (result->appli == appli)
+                               return result;
+                       result = result->next_by_pgid;
+               }
+       }
+       return NULL;
+}
+
+/*
+ * Free an existing 'runner'
+ */
 static void freerunner(struct apprun *runner)
 {
-       struct apprun **prev = &runners_by_runid[runner->runid & (ROOT_RUNNERS_COUNT - 1)];
+       struct apprun **prev;
+
+       /* get previous pointer to runner */
+       prev = &runners_by_runid[runner->runid & (ROOT_RUNNERS_COUNT - 1)];
        assert(*prev);
        while(*prev != runner) {
                prev = &(*prev)->next_by_runid;
                assert(*prev);
        }
+
+       /* unlink */
        *prev = runner->next_by_runid;
+       runnercount--;
+
+       /* release/free */
        json_object_put(runner->appli);
        free(runner);
-       runnercount--;
 }
 
-/* create a new runner */
+/*
+ * Cleans the list of runners from its terminated
+ */
+static void cleanrunners()
+{
+       struct apprun *runner;
+       while (terminated_runners) {
+               runner = terminated_runners;
+               terminated_runners = runner->next_by_pgid;
+               freerunner(runner);
+       }
+}
+
+/*
+ * Create a new runner for the 'appli'
+ *
+ * Returns the created runner or NULL
+ * in case of error.
+ */
 static struct apprun *createrunner(json_object *appli)
 {
        struct apprun *result;
        struct apprun **prev;
 
+       /* cleanup */
+       cleanrunners();
+
+       /* get a runid */
        if (runnercount >= MAX_RUNNER_COUNT) {
                errno = EAGAIN;
                return NULL;
@@ -146,10 +327,13 @@ static struct apprun *createrunner(json_object *appli)
                if (runnerid > MAX_RUNNER_COUNT)
                        runnerid = 1;
        } while(getrunner(runnerid));
+
+       /* create the structure */
        result = calloc(1, sizeof * result);
        if (result == NULL)
                errno = ENOMEM;
        else {
+               /* initialize it linked to the list */
                prev = &runners_by_runid[runnerid & (ROOT_RUNNERS_COUNT - 1)];
                result->next_by_runid = *prev;
                result->next_by_pgid = NULL;
@@ -169,11 +353,11 @@ static void started(int runid)
 {
 }
 
-static void stopped(int runid)
+static void paused(int runid)
 {
 }
 
-static void continued(int runid)
+static void resumed(int runid)
 {
 }
 
@@ -187,6 +371,16 @@ static void removed(int runid)
 #endif
 /**************** running ************************/
 
+/*
+ * Sends (with pgkill) the signal 'sig' to the process group
+ * for 'runid' and put the runner's state to 'tostate'
+ * in case of success.
+ *
+ * Only processes in the state 'as_running' or 'as_paused'
+ * can be signalled.
+ *
+ * Returns 0 in case of success or -1 in case of error.
+ */
 static int killrunner(int runid, int sig, enum appstate tostate)
 {
        int rc;
@@ -195,8 +389,8 @@ static int killrunner(int runid, int sig, enum appstate tostate)
                errno = ENOENT;
                rc = -1;
        }
-       else if (runner->state != as_running && runner->state != as_stopped) {
-               errno = EPERM;
+       else if (is_dead(runner)) {
+               errno = EINVAL;
                rc = -1;
        }
        else if (runner->state == tostate) {
@@ -210,28 +404,41 @@ static int killrunner(int runid, int sig, enum appstate tostate)
        return rc;
 }
 
+/*
+ * Signal callback called on SIGCHLD. This is set using sigaction.
+ */
 static void on_sigchld(int signum, siginfo_t *info, void *uctxt)
 {
        struct apprun *runner;
 
-       runner = runner_of_pgid(info->si_pid);
+       /* retrieves the runner */
+       runner = runner_of_pid(info->si_pid);
        if (!runner)
                return;
 
+       /* known runner, inspect cause of signal */
        switch(info->si_code) {
        case CLD_EXITED:
        case CLD_KILLED:
        case CLD_DUMPED:
        case CLD_TRAPPED:
+               /* update the state */
                runner->state = as_terminated;
+               /* remove it from pgid list */
                pgid_remove(runner);
+               runner->next_by_pgid = terminated_runners;
+               terminated_runners = runner;
+               /* ensures that all the group terminates */
+               killpg(runner->pids[0], SIGKILL);
                break;
 
        case CLD_STOPPED:
-               runner->state = as_stopped;
+               /* update the state */
+               runner->state = as_paused;
                break;
 
        case CLD_CONTINUED:
+               /* update the state */
                runner->state = as_running;
                break;
        }
@@ -239,46 +446,140 @@ static void on_sigchld(int signum, siginfo_t *info, void *uctxt)
 
 /**************** handle afm_launch_desc *********************/
 
-static int fill_launch_desc(struct json_object *appli, struct afm_launch_desc *desc)
+/*
+ * Initialize the data of the launch description 'desc'
+ * for the application 'appli' and the 'mode'.
+ *
+ * Returns 0 in case of success or -1 in case of error.
+ */
+static int fill_launch_desc(struct json_object *appli,
+               enum afm_launch_mode mode, struct afm_launch_desc *desc)
 {
        json_object *pub;
 
+       assert(is_valid_launch_mode(mode));
+
        /* main items */
-       if(!j_object(appli, "public", &pub)
-       || !j_string(appli, "path", &desc->path)
-       || !j_string(appli, "id", &desc->tag)
-       || !j_string(appli, "content", &desc->content)
-       || !j_string(appli, "type", &desc->type)
-       || !j_string(pub, "name", &desc->name)
-       || !j_integer(pub, "width", &desc->width)
-       || !j_integer(pub, "height", &desc->height)) {
+       if(!j_read_object_at(appli, "public", &pub)
+       || !j_read_string_at(appli, "path", &desc->path)
+       || !j_read_string_at(appli, "id", &desc->appid)
+       || !j_read_string_at(appli, "content", &desc->content)
+       || !j_read_string_at(appli, "type", &desc->type)
+       || !j_read_string_at(pub, "name", &desc->name)
+       || !j_read_integer_at(pub, "width", &desc->width)
+       || !j_read_integer_at(pub, "height", &desc->height)) {
                errno = EINVAL;
                return -1;
        }
 
-       /* plugins */
+       /* bindings */
        {
                /* TODO */
                static const char *null = NULL;
-               desc->plugins = &null;
+               desc->bindings = &null;
        }
 
        /* finaly */
        desc->home = homeappdir;
+       desc->mode = mode;
        return 0;
-};
+}
+
+/**************** report state of runner *********************/
+
+/*
+ * Creates a json object that describes the state of 'runner'.
+ *
+ * Returns the created object or NULL in case of error.
+ */
+static json_object *mkstate(struct apprun *runner)
+{
+       const char *state;
+       struct json_object *result, *obj, *pids;
+       int rc;
+
+       /* the structure */
+       result = json_object_new_object();
+       if (result == NULL)
+               goto error;
+
+       /* the runid */
+       if (!j_add_integer(result, "runid", runner->runid))
+               goto error2;
+
+       /* the pids */
+       if (is_alive(runner)) {
+               pids = j_add_new_array(result, "pids");
+               if (!pids)
+                       goto error2;
+               if (!j_add_integer(pids, NULL, runner->pids[0]))
+                       goto error2;
+               if (runner->pids[1] && !j_add_integer(pids, NULL, runner->pids[1]))
+                       goto error2;
+       }
+
+       /* the state */
+       switch(runner->state) {
+       case as_starting:
+       case as_running:
+               state = "running";
+               break;
+       case as_paused:
+               state = "paused";
+               break;
+       default:
+               state = "terminated";
+               break;
+       }
+       if (!j_add_string(result, "state", state))
+               goto error2;
+
+       /* the application id */
+       rc = json_object_object_get_ex(runner->appli, "public", &obj);
+       assert(rc);
+       rc = json_object_object_get_ex(obj, "id", &obj);
+       assert(rc);
+       if (!j_add(result, "id", obj))
+               goto error2;
+       json_object_get(obj);
+
+       /* done */
+       return result;
+
+error2:
+       json_object_put(result);
+error:
+       errno = ENOMEM;
+       return NULL;
+}
 
 /**************** API handling ************************/
 
-int afm_run_start(struct json_object *appli)
+/*
+ * 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_run_start(struct json_object *appli, enum afm_launch_mode mode,
+                                                       char **uri)
 {
-       static struct apprun *runner;
+       struct apprun *runner;
        struct afm_launch_desc desc;
        int rc;
        sigset_t saved, blocked;
 
+       assert(is_valid_launch_mode(mode));
+       assert(mode == mode_local || uri != NULL);
+       assert(uri == NULL || *uri == NULL);
+
        /* prepare to launch */
-       rc = fill_launch_desc(appli, &desc);
+       rc = fill_launch_desc(appli, mode, &desc);
        if (rc)
                return rc;
        runner = createrunner(appli);
@@ -291,7 +592,7 @@ int afm_run_start(struct json_object *appli)
        sigprocmask(SIG_BLOCK, &blocked, &saved);
 
        /* launch now */
-       rc = afm_launch(&desc, runner->pids);
+       rc = afm_launch(&desc, runner->pids, uri);
        if (rc < 0) {
                /* fork failed */
                sigprocmask(SIG_SETMASK, &saved, NULL);
@@ -310,71 +611,58 @@ int afm_run_start(struct json_object *appli)
        return rc;
 }
 
-int afm_run_terminate(int runid)
+/*
+ * 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_run_once(struct json_object *appli)
 {
-       return killrunner(runid, SIGTERM, as_terminating);
+       struct apprun *runner = getrunner_appli(appli);
+       return runner && is_alive(runner) ? runner->runid : afm_run_start(appli, mode_local, NULL);
 }
 
-int afm_run_stop(int runid)
+/*
+ * Terminates the runner of 'runid'
+ *
+ * Returns 0 in case of success or -1 in case of error
+ */
+int afm_run_terminate(int runid)
 {
-       return killrunner(runid, SIGSTOP, as_stopped);
+       return killrunner(runid, SIGTERM, as_terminating);
 }
 
-int afm_run_continue(int runid)
+/*
+ * Stops (aka pause) the runner of 'runid'
+ *
+ * Returns 0 in case of success or -1 in case of error
+ */
+int afm_run_pause(int runid)
 {
-       return killrunner(runid, SIGCONT, as_running);
+       return killrunner(runid, SIGSTOP, as_paused);
 }
 
-static json_object *mkstate(struct apprun *runner)
+/*
+ * Continue (aka resume) the runner of 'runid'
+ *
+ * Returns 0 in case of success or -1 in case of error
+ */
+int afm_run_resume(int runid)
 {
-       const char *state;
-       struct json_object *result, *obj;
-       int rc;
-
-       /* the structure */
-       result = json_object_new_object();
-       if (result == NULL)
-               goto error;
-
-       /* the runid */
-       if (!j_add_integer(result, "runid", runner->runid))
-               goto error2;
-
-       /* the state */
-       switch(runner->state) {
-       case as_starting:
-       case as_running:
-               state = "running";
-               break;
-       case as_stopped:
-               state = "stopped";
-               break;
-       default:
-               state = "terminated";
-               break;
-       }
-       if (!j_add_string(result, "state", state))
-               goto error2;
-
-       /* the application id */
-       rc = json_object_object_get_ex(runner->appli, "public", &obj);
-       assert(rc);
-       rc = json_object_object_get_ex(obj, "id", &obj);
-       assert(rc);
-       if (!j_add(result, "id", obj))
-               goto error2;
-       json_object_get(obj);
-
-       /* done */
-       return result;
-
-error2:
-       json_object_put(result);
-error:
-       errno = ENOMEM;
-       return NULL;
+       return killrunner(runid, SIGCONT, as_running);
 }
 
+/*
+ * Get the list of the runners.
+ *
+ * Returns the list or NULL in case of error.
+ */
 struct json_object *afm_run_list()
 {
        struct json_object *result, *obj;
@@ -386,9 +674,12 @@ struct json_object *afm_run_list()
        if (result == NULL)
                goto error;
 
+       /* iterate over runners */
        for (i = 0 ; i < ROOT_RUNNERS_COUNT ; i++) {
-               for (runner = runners_by_runid[i] ; runner ; runner = runner->next_by_runid) {
-                       if (runner->state != as_terminating && runner->state != as_terminated) {
+               runner = runners_by_runid[i];
+               while (runner) {
+                       if (is_alive(runner)) {
+                               /* adds the living runner */
                                obj = mkstate(runner);
                                if (obj == NULL)
                                        goto error2;
@@ -397,6 +688,7 @@ struct json_object *afm_run_list()
                                        goto error2;
                                }
                        }
+                       runner = runner->next_by_runid;
                }
        }
        return result;
@@ -408,10 +700,15 @@ error:
        return NULL;
 }
 
+/*
+ * Get the state of the runner of 'runid'.
+ *
+ * Returns the state or NULL in case of success
+ */
 struct json_object *afm_run_state(int runid)
 {
        struct apprun *runner = getrunner(runid);
-       if (runner == NULL || runner->state == as_terminating || runner->state == as_terminated) {
+       if (runner == NULL || is_dead(runner)) {
                errno = ENOENT;
                return NULL;
        }
@@ -420,10 +717,12 @@ struct json_object *afm_run_state(int runid)
 
 /**************** INITIALISATION **********************/
 
+/*
+ * Initialize the module
+ */
 int afm_run_init()
 {
        char buf[2048];
-       char dir[PATH_MAX];
        int rc;
        uid_t me;
        struct passwd passwd, *pw;
@@ -442,23 +741,34 @@ int afm_run_init()
                ERROR("getpwuid_r failed for uid=%d: %m",(int)me);
                return -1;
        }
-       rc = snprintf(dir, sizeof dir, "%s/%s", passwd.pw_dir, fwk_user_app_dir);
-       if (rc >= sizeof dir) {
-               ERROR("buffer overflow in user_app_dir for uid=%d",(int)me);
+       rc = asprintf(&homeappdir, "%s/%s", passwd.pw_dir, fwk_user_app_dir);
+       if (rc < 0) {
+               errno = ENOMEM;
+               ERROR("allocating homeappdir for uid=%d failed", (int)me);
                return -1;
        }
-       rc = create_directory(dir, 0755, 1);
+       rc = create_directory(homeappdir, 0755, 1);
        if (rc && errno != EEXIST) {
-               ERROR("creation of directory %s failed in user_app_dir: %m", dir);
+               ERROR("creation of directory %s failed: %m", homeappdir);
+               free(homeappdir);
                return -1;
        }
-       homeappdir = strdup(dir);
-       if (homeappdir == NULL) {
-               errno = ENOMEM;
-               ERROR("out of memory in user_app_dir for %s : %m", dir);
+       rc = smack_remove_label_for_path(homeappdir,
+                                               XATTR_NAME_SMACKTRANSMUTE, 0);
+       if (rc < 0 && errno != ENODATA) {
+               ERROR("can't remove smack transmutation of directory %s: %m",
+                                                               homeappdir);
+               free(homeappdir);
+               return -1;
+       }
+       rc = smack_set_label_for_path(homeappdir, XATTR_NAME_SMACK, 0,
+                                                       fwk_user_app_label);
+       if (rc < 0) {
+               ERROR("can't set smack label %s to directory %s: %m",
+                                       fwk_user_app_label, homeappdir);
+               free(homeappdir);
                return -1;
        }
-
        /* install signal handlers */
        siga.sa_flags = SA_SIGINFO | SA_NOCLDWAIT;
        sigemptyset(&siga.sa_mask);