Start user units at the system level
[src/app-framework-main.git] / src / afm-urun.c
1 /*
2  Copyright 2015, 2016, 2017 IoT.bzh
3
4  author: José Bollo <jose.bollo@iot.bzh>
5
6  Licensed under the Apache License, Version 2.0 (the "License");
7  you may not use this file except in compliance with the License.
8  You may obtain a copy of the License at
9
10      http://www.apache.org/licenses/LICENSE-2.0
11
12  Unless required by applicable law or agreed to in writing, software
13  distributed under the License is distributed on an "AS IS" BASIS,
14  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  See the License for the specific language governing permissions and
16  limitations under the License.
17 */
18
19 #define _GNU_SOURCE
20
21 #include <fcntl.h>
22 #include <unistd.h>
23 #include <sys/types.h>
24 #include <errno.h>
25 #include <assert.h>
26 #include <stdio.h>
27 #include <limits.h>
28 #include <string.h>
29
30 #include <json-c/json.h>
31
32 #include "verbose.h"
33 #include "utils-dir.h"
34 #include "utils-json.h"
35 #include "utils-systemd.h"
36 #include "afm-udb.h"
37 #include "afm-urun.h"
38
39 static const char key_unit_d_path[] = "-unit-dpath-";
40
41 /**************** get appli basis *********************/
42
43 static int get_basis(struct json_object *appli, int *isuser, const char **dpath, int load)
44 {
45         char *dp;
46         const char *uname, *uscope;
47
48         /* get the scope */
49         if (!j_read_string_at(appli, "unit-scope", &uscope)) {
50                 ERROR("'unit-scope' missing in appli description %s", json_object_get_string(appli));
51                 goto inval;
52         }
53         *isuser = strcmp(uscope, "system") != 0;
54
55         /* get dpath */
56         if (!j_read_string_at(appli, key_unit_d_path, dpath)) {
57                 if (!load) {
58                         errno = ENOENT;
59                         goto error;
60                 }
61                 if (!j_read_string_at(appli, "unit-name", &uname)) {
62                         ERROR("'unit-name' missing in appli description %s", json_object_get_string(appli));
63                         goto inval;
64                 }
65                 dp = systemd_unit_dpath_by_name(*isuser, uname, 1);
66                 if (dp == NULL) {
67                         ERROR("Can't load unit of name %s for %s: %m", uname, uscope);
68                         goto error;
69                 }
70                 if (!j_add_string(appli, key_unit_d_path, dp)) {
71                         free(dp);
72                         goto nomem;
73                 }
74                 free(dp);
75                 j_read_string_at(appli, key_unit_d_path, dpath);
76         }
77
78         return 0;
79
80 nomem:
81         ERROR("out of memory");
82         errno = ENOMEM;
83         goto error;
84
85 inval:
86         errno = EINVAL;
87 error:
88         return -1;
89 }
90
91 static const char *wait_state_stable(int isuser, const char *dpath)
92 {
93         const char *state;
94
95         for (;;) {
96                 state = systemd_unit_state_of_dpath(isuser, dpath);
97                 if (state == NULL || state == SysD_State_Active
98                  || state == SysD_State_Failed)
99                         return state;
100                 /* TODO: sleep */
101         }
102 }
103
104 /*
105  * Creates a json object that describes the state for:
106  *  - 'id', the id of the application
107  *  - 'runid', its runid
108  *  - 'pid', its pid
109  *  - 'state', its systemd state
110  *
111  * Returns the created object or NULL in case of error.
112  */
113 static json_object *mkstate(const char *id, int runid, int pid, const char *state)
114 {
115         struct json_object *result, *pids;
116
117         /* the structure */
118         result = json_object_new_object();
119         if (result == NULL)
120                 goto error;
121
122         /* the runid */
123         if (!j_add_integer(result, "runid", runid))
124                 goto error;
125
126         /* the pids */
127         if (pid > 0) {
128                 pids = j_add_new_array(result, "pids");
129                 if (!pids)
130                         goto error;
131                 if (!j_add_integer(pids, NULL, pid))
132                         goto error;
133         }
134
135         /* the state */
136         if (!j_add_string(result, "state", state == SysD_State_Active ? "running" : "terminated"))
137                 goto error;
138
139         /* the application id */
140         if (!j_add_string(result, "id", id))
141                 goto error;
142
143         /* done */
144         return result;
145
146 error:
147         json_object_put(result);
148         errno = ENOMEM;
149         return NULL;
150 }
151
152 /**************** API handling ************************/
153
154 /*
155  * Starts the application described by 'appli' for the 'mode'.
156  * In case of remote start, it returns in uri the uri to
157  * connect to.
158  *
159  * A reference to 'appli' is kept during the live of the
160  * runner. This is made using json_object_get. Thus be aware
161  * that further modifications to 'appli' might create errors.
162  *
163  * Returns the runid in case of success or -1 in case of error
164  */
165 int afm_urun_start(struct json_object *appli)
166 {
167         return afm_urun_once(appli);
168 }
169
170 /*
171  * Returns the runid of a previously started application 'appli'
172  * or if none is running, starts the application described by 'appli'
173  * in local mode.
174  *
175  * A reference to 'appli' is kept during the live of the
176  * runner. This is made using json_object_get. Thus be aware
177  * that further modifications to 'appli' might create errors.
178  *
179  * Returns the runid in case of success or -1 in case of error
180  */
181 int afm_urun_once(struct json_object *appli)
182 {
183         const char *udpath, *state, *uscope, *uname;
184         int rc, isuser;
185
186         /* retrieve basis */
187         rc = get_basis(appli, &isuser, &udpath, 1);
188         if (rc < 0)
189                 goto error;
190
191         /* start the unit */
192         rc = systemd_unit_start_dpath(isuser, udpath);
193         if (rc < 0) {
194                 j_read_string_at(appli, "unit-scope", &uscope);
195                 j_read_string_at(appli, "unit-name", &uname);
196                 ERROR("can't start %s unit %s", uscope, uname);
197                 goto error;
198         }
199
200         state = wait_state_stable(isuser, udpath);
201         if (state == NULL) {
202                 j_read_string_at(appli, "unit-scope", &uscope);
203                 j_read_string_at(appli, "unit-name", &uname);
204                 ERROR("can't wait %s unit %s: %m", uscope, uname);
205                 goto error;
206         }
207         if (state != SysD_State_Active) {
208                 j_read_string_at(appli, "unit-scope", &uscope);
209                 j_read_string_at(appli, "unit-name", &uname);
210                 ERROR("start error %s unit %s: %s", uscope, uname, state);
211                 goto error;
212         }
213
214         rc = systemd_unit_pid_of_dpath(isuser, udpath);
215         if (rc < 0) {
216                 j_read_string_at(appli, "unit-scope", &uscope);
217                 j_read_string_at(appli, "unit-name", &uname);
218                 ERROR("can't getpid of %s unit %s: %m", uscope, uname);
219                 goto error;
220         }
221                 
222         return rc;
223
224 error:
225         return -1;
226 }
227
228 static int not_yet_implemented(const char *what)
229 {
230         ERROR("%s isn't yet implemented", what);
231         errno = ENOTSUP;
232         return -1;
233 }
234
235 /*
236  * Terminates the runner of 'runid'
237  *
238  * Returns 0 in case of success or -1 in case of error
239  */
240 int afm_urun_terminate(int runid)
241 {
242         int rc = systemd_unit_stop_pid(1 /* TODO: isuser? */, (unsigned)runid);
243         if (rc < 0)
244                 rc = systemd_unit_stop_pid(0 /* TODO: isuser? */, (unsigned)runid);
245         return rc < 0 ? rc : 0;
246 }
247
248 /*
249  * Stops (aka pause) the runner of 'runid'
250  *
251  * Returns 0 in case of success or -1 in case of error
252  */
253 int afm_urun_pause(int runid)
254 {
255         return not_yet_implemented("pause");
256 }
257
258 /*
259  * Continue (aka resume) the runner of 'runid'
260  *
261  * Returns 0 in case of success or -1 in case of error
262  */
263 int afm_urun_resume(int runid)
264 {
265         return not_yet_implemented("resume");
266 }
267
268 /*
269  * Get the list of the runners.
270  *
271  * Returns the list or NULL in case of error.
272  */
273 struct json_object *afm_urun_list(struct afm_udb *db)
274 {
275         int i, n, isuser, pid;
276         const char *udpath;
277         const char *id;
278         const char *state;
279         struct json_object *desc;
280         struct json_object *appli;
281         struct json_object *apps;
282         struct json_object *result;
283
284         apps = NULL;
285         result = json_object_new_array();
286         if (result == NULL)
287                 goto error;
288
289         apps = afm_udb_applications_private(db);
290         n = json_object_array_length(apps);
291         for (i = 0 ; i < n ; i++) {
292                 appli = json_object_array_get_idx(apps, i);
293                 if (appli && get_basis(appli, &isuser, &udpath, 0) >= 0) {
294                         pid = systemd_unit_pid_of_dpath(isuser, udpath);
295                         if (pid > 0 && j_read_string_at(appli, "id", &id)) {
296                                 state = systemd_unit_state_of_dpath(isuser, udpath);
297                                 if (state == SysD_State_Active) {
298                                         desc = mkstate(id, pid, pid, state);
299                                         if (desc && json_object_array_add(result, desc) == -1) {
300                                                 ERROR("can't add desc %s to result", json_object_get_string(desc));
301                                                 json_object_put(desc);
302                                         }
303                                 }
304                         }
305                 }
306         }
307
308 error:
309         json_object_put(apps);
310         return result;
311 }
312
313 /*
314  * Get the state of the runner of 'runid'.
315  *
316  * Returns the state or NULL in case of success
317  */
318 struct json_object *afm_urun_state(struct afm_udb *db, int runid)
319 {
320         int i, n, isuser, pid, wasuser;
321         char *dpath;
322         const char *udpath;
323         const char *id;
324         const char *state;
325         struct json_object *appli;
326         struct json_object *apps;
327         struct json_object *result;
328
329         result = NULL;
330
331         /* get the dpath */
332         dpath = systemd_unit_dpath_by_pid(wasuser = 1, (unsigned)runid);
333         if (!dpath)
334                 dpath = systemd_unit_dpath_by_pid(wasuser = 0, (unsigned)runid);
335         if (!dpath) {
336                 errno = EINVAL;
337                 WARNING("searched runid %d not found", runid);
338         } else {
339                 /* search in the base */
340                 apps = afm_udb_applications_private(db);
341                 n = json_object_array_length(apps);
342                 for (i = 0 ; i < n ; i++) {
343                         appli = json_object_array_get_idx(apps, i);
344                         if (appli
345                          && get_basis(appli, &isuser, &udpath, 0) >= 0
346                          && !strcmp(dpath, udpath)
347                          && j_read_string_at(appli, "id", &id)) {
348                                 pid = systemd_unit_pid_of_dpath(isuser, udpath);
349                                 state = systemd_unit_state_of_dpath(isuser, dpath);
350                                 if (state == SysD_State_Active)
351                                         result = mkstate(id, runid, pid, state);
352                                 goto end;
353                         }
354                 }
355                 errno = ENOENT;
356                 WARNING("searched runid %d of dpath %s isn't an applications", runid, dpath);
357 end:
358                 json_object_put(apps);
359                 free(dpath);    
360         }
361
362         return result;
363 }
364