Improve use of systemd's states
[src/app-framework-main.git] / src / afm-urun.c
1 /*
2  Copyright (C) 2015-2020 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 #include <time.h>
30
31 #include <json-c/json.h>
32
33 #include "verbose.h"
34 #include "utils-dir.h"
35 #include "utils-json.h"
36 #include "utils-systemd.h"
37 #include "afm-udb.h"
38 #include "afm-urun.h"
39
40 static const char key_unit_d_path[] = "-unit-dpath-";
41
42 /**************** get appli basis *********************/
43
44 static int get_basis(struct json_object *appli, int *isuser, const char **dpath, int uid)
45 {
46         char userid[40];
47         char *dp, *arodot, *nun;
48         const char *uname, *uscope;
49         struct json_object *odp;
50         int rc;
51
52         /* get the scope */
53         if (!j_read_string_at(appli, "unit-scope", &uscope)) {
54                 ERROR("'unit-scope' missing in appli description %s", json_object_get_string(appli));
55                 goto inval;
56         }
57         *isuser = strcmp(uscope, "system") != 0;
58
59         /* get dpaths of known users */
60         odp = NULL;
61         if (json_object_object_get_ex(appli, key_unit_d_path, &odp)) {
62                 /* try not parametric dpath */
63                 if (json_object_get_type(odp) == json_type_string) {
64                         *dpath = json_object_get_string(odp);
65                         return 0;
66                 }
67                 assert(json_object_get_type(odp) == json_type_object);
68                 /* get userid */
69                 if (uid < 0) {
70                         ERROR("unexpected uid %d", uid);
71                         goto inval;
72                 }
73                 rc = snprintf(userid, sizeof userid, "%d", uid);
74                 assert(rc < (int)(sizeof userid));
75                 /* try dpath for the user */
76                 if (j_read_string_at(odp, userid, dpath))
77                         return 0;
78         }
79
80         /* get uname */
81         if (!j_read_string_at(appli, "unit-name", &uname)) {
82                 ERROR("'unit-name' missing in appli description %s", json_object_get_string(appli));
83                 goto inval;
84         }
85
86         /* is user parametric? */
87         arodot = strchr(uname, '@');
88         if (arodot && *++arodot == '.') {
89                 if (!odp) {
90                         /* get userid */
91                         if (uid < 0) {
92                                 ERROR("unexpected uid %d", uid);
93                                 goto inval;
94                         }
95                         rc = snprintf(userid, sizeof userid, "%d", uid);
96                         assert(rc < (int)(sizeof userid));
97
98                         /* create the dpaths of known users */
99                         odp = json_object_new_object();
100                         if (!odp)
101                                 goto nomem;
102                         json_object_object_add(appli, key_unit_d_path, odp);
103                 }
104
105                 /* get dpath of userid */
106                 nun = alloca((size_t)(arodot - uname) + strlen(userid) + strlen(arodot) + 1);
107                 stpcpy(stpcpy(stpncpy(nun, uname, (size_t)(arodot - uname)), userid), arodot);
108                 dp = systemd_unit_dpath_by_name(*isuser, nun, 1);
109                 if (dp == NULL) {
110                         ERROR("Can't load unit of name %s for %s: %m", nun, uscope);
111                         goto error;
112                 }
113                 /* record the dpath */
114                 if (!j_add_string(odp, userid, dp)) {
115                         free(dp);
116                         goto nomem;
117                 }
118                 free(dp);
119                 j_read_string_at(odp, userid, dpath);
120         } else {
121                 /* get dpath */
122                 dp = systemd_unit_dpath_by_name(*isuser, uname, 1);
123                 if (dp == NULL) {
124                         ERROR("Can't load unit of name %s for %s: %m", uname, uscope);
125                         goto error;
126                 }
127                 /* record the dpath */
128                 if (!j_add_string(appli, key_unit_d_path, dp)) {
129                         free(dp);
130                         goto nomem;
131                 }
132                 free(dp);
133                 j_read_string_at(appli, key_unit_d_path, dpath);
134         }
135         return 0;
136
137 nomem:
138         ERROR("out of memory");
139         errno = ENOMEM;
140         goto error;
141
142 inval:
143         errno = EINVAL;
144 error:
145         return -1;
146 }
147
148 static enum SysD_State wait_state_stable(int isuser, const char *dpath)
149 {
150         int trial;
151         enum SysD_State state = SysD_State_INVALID;
152         struct timespec tispec;
153         const int period_ms = 10;
154         const int trial_s = 10;
155         const int trial_count = (trial_s * 1000) / period_ms;
156         const int period_ns = period_ms * 1000000;
157
158         for (trial = 1 ; trial <= trial_count ; trial++) {
159                 state = systemd_unit_state_of_dpath(isuser, dpath);
160                 switch (state) {
161                 case SysD_State_Active:
162                 case SysD_State_Failed:
163                 case SysD_State_Inactive:
164                         return state;
165                 default:
166                         tispec.tv_sec = 0;
167                         tispec.tv_nsec = period_ns;
168                         nanosleep(&tispec, NULL);
169                         break;
170                 }
171         }
172         return state;
173 }
174
175 /*
176  * Creates a json object that describes the state for:
177  *  - 'id', the id of the application
178  *  - 'runid', its runid
179  *  - 'pid', its pid
180  *  - 'state', its systemd state
181  *
182  * Returns the created object or NULL in case of error.
183  */
184 static json_object *mkstate(const char *id, int runid, int pid, enum SysD_State state)
185 {
186         struct json_object *result, *pids;
187
188         /* the structure */
189         result = json_object_new_object();
190         if (result == NULL)
191                 goto error;
192
193         /* the runid */
194         if (!j_add_integer(result, "runid", runid))
195                 goto error;
196
197         /* the pids */
198         if (pid > 0) {
199                 pids = j_add_new_array(result, "pids");
200                 if (!pids)
201                         goto error;
202                 if (!j_add_integer(pids, NULL, pid))
203                         goto error;
204         }
205
206         /* the state */
207         if (!j_add_string(result, "state", state == SysD_State_Active ? "running" : "terminated"))
208                 goto error;
209
210         /* the application id */
211         if (!j_add_string(result, "id", id))
212                 goto error;
213
214         /* done */
215         return result;
216
217 error:
218         json_object_put(result);
219         errno = ENOMEM;
220         return NULL;
221 }
222
223 /**************** API handling ************************/
224
225 /*
226  * Starts the application described by 'appli' for the 'mode'.
227  * In case of remote start, it returns in uri the uri to
228  * connect to.
229  *
230  * A reference to 'appli' is kept during the live of the
231  * runner. This is made using json_object_get. Thus be aware
232  * that further modifications to 'appli' might create errors.
233  *
234  * Returns the runid in case of success or -1 in case of error
235  */
236 int afm_urun_start(struct json_object *appli, int uid)
237 {
238         return afm_urun_once(appli, uid);
239 }
240
241 /*
242  * Returns the runid of a previously started application 'appli'
243  * or if none is running, starts the application described by 'appli'
244  * in local mode.
245  *
246  * A reference to 'appli' is kept during the live of the
247  * runner. This is made using json_object_get. Thus be aware
248  * that further modifications to 'appli' might create errors.
249  *
250  * Returns the runid in case of success or -1 in case of error
251  */
252 int afm_urun_once(struct json_object *appli, int uid)
253 {
254         const char *udpath, *uscope, *uname;
255         enum SysD_State state;
256         int rc, isuser;
257
258         /* retrieve basis */
259         rc = get_basis(appli, &isuser, &udpath, uid);
260         if (rc < 0)
261                 goto error;
262
263         /* start the unit */
264         rc = systemd_unit_start_dpath(isuser, udpath);
265         if (rc < 0) {
266                 j_read_string_at(appli, "unit-scope", &uscope);
267                 j_read_string_at(appli, "unit-name", &uname);
268                 ERROR("can't start %s unit %s for uid %d", uscope, uname, uid);
269                 goto error;
270         }
271
272         state = wait_state_stable(isuser, udpath);
273         switch (state) {
274         case SysD_State_Active:
275         case SysD_State_Inactive:
276                 break;
277         case SysD_State_Failed:
278                 j_read_string_at(appli, "unit-scope", &uscope);
279                 j_read_string_at(appli, "unit-name", &uname);
280                 ERROR("start error %s unit %s for uid %d: %s", uscope, uname, uid,
281                                                         systemd_state_name(state));
282                 goto error;
283         default:
284                 j_read_string_at(appli, "unit-scope", &uscope);
285                 j_read_string_at(appli, "unit-name", &uname);
286                 ERROR("can't wait %s unit %s for uid %d: %m", uscope, uname, uid);
287                 goto error;
288         }
289
290         rc = systemd_unit_pid_of_dpath(isuser, udpath);
291         if (rc < 0) {
292                 j_read_string_at(appli, "unit-scope", &uscope);
293                 j_read_string_at(appli, "unit-name", &uname);
294                 ERROR("can't get pid of %s unit %s for uid %d: %m", uscope, uname, uid);
295                 goto error;
296         }
297
298         return rc;
299
300 error:
301         return -1;
302 }
303
304 static int not_yet_implemented(const char *what)
305 {
306         ERROR("%s isn't yet implemented", what);
307         errno = ENOTSUP;
308         return -1;
309 }
310
311 /*
312  * Terminates the runner of 'runid'
313  *
314  * Returns 0 in case of success or -1 in case of error
315  */
316 int afm_urun_terminate(int runid, int uid)
317 {
318         int rc = systemd_unit_stop_pid(1 /* TODO: isuser? */, (unsigned)runid);
319         if (rc < 0)
320                 rc = systemd_unit_stop_pid(0 /* TODO: isuser? */, (unsigned)runid);
321         return rc < 0 ? rc : 0;
322 }
323
324 /*
325  * Stops (aka pause) the runner of 'runid'
326  *
327  * Returns 0 in case of success or -1 in case of error
328  */
329 int afm_urun_pause(int runid, int uid)
330 {
331         return not_yet_implemented("pause");
332 }
333
334 /*
335  * Continue (aka resume) the runner of 'runid'
336  *
337  * Returns 0 in case of success or -1 in case of error
338  */
339 int afm_urun_resume(int runid, int uid)
340 {
341         return not_yet_implemented("resume");
342 }
343
344 /*
345  * Get the list of the runners.
346  *
347  * Returns the list or NULL in case of error.
348  */
349 struct json_object *afm_urun_list(struct afm_udb *db, int all, int uid)
350 {
351         int i, n, isuser, pid;
352         const char *udpath;
353         const char *id;
354         enum SysD_State state;
355         struct json_object *desc;
356         struct json_object *appli;
357         struct json_object *apps;
358         struct json_object *result;
359
360         apps = NULL;
361         result = json_object_new_array();
362         if (result == NULL)
363                 goto error;
364
365         apps = afm_udb_applications_private(db, all, uid);
366         n = json_object_array_length(apps);
367         for (i = 0 ; i < n ; i++) {
368                 appli = json_object_array_get_idx(apps, i);
369                 if (appli && get_basis(appli, &isuser, &udpath, uid) >= 0) {
370                         pid = systemd_unit_pid_of_dpath(isuser, udpath);
371                         if (pid > 0 && j_read_string_at(appli, "id", &id)) {
372                                 state = systemd_unit_state_of_dpath(isuser, udpath);
373                                 if (state == SysD_State_Active) {
374                                         desc = mkstate(id, pid, pid, state);
375                                         if (desc && json_object_array_add(result, desc) == -1) {
376                                                 ERROR("can't add desc %s to result", json_object_get_string(desc));
377                                                 json_object_put(desc);
378                                         }
379                                 }
380                         }
381                 }
382         }
383
384 error:
385         json_object_put(apps);
386         return result;
387 }
388
389 /*
390  * Get the state of the runner of 'runid'.
391  *
392  * Returns the state or NULL in case of success
393  */
394 struct json_object *afm_urun_state(struct afm_udb *db, int runid, int uid)
395 {
396         int i, n, isuser, pid, wasuser;
397         char *dpath;
398         const char *udpath;
399         const char *id;
400         enum SysD_State state;
401         struct json_object *appli;
402         struct json_object *apps;
403         struct json_object *result;
404
405         result = NULL;
406
407         /* get the dpath */
408         dpath = systemd_unit_dpath_by_pid(wasuser = 1, (unsigned)runid);
409         if (!dpath)
410                 dpath = systemd_unit_dpath_by_pid(wasuser = 0, (unsigned)runid);
411         if (!dpath) {
412                 errno = EINVAL;
413                 WARNING("searched runid %d not found", runid);
414         } else {
415                 /* search in the base */
416                 apps = afm_udb_applications_private(db, 1, uid);
417                 n = json_object_array_length(apps);
418                 for (i = 0 ; i < n ; i++) {
419                         appli = json_object_array_get_idx(apps, i);
420                         if (appli
421                          && get_basis(appli, &isuser, &udpath, uid) >= 0
422                          && !strcmp(dpath, udpath)
423                          && j_read_string_at(appli, "id", &id)) {
424                                 pid = systemd_unit_pid_of_dpath(isuser, udpath);
425                                 state = systemd_unit_state_of_dpath(isuser, dpath);
426                                 if (pid > 0 && state == SysD_State_Active)
427                                         result = mkstate(id, runid, pid, state);
428                                 goto end;
429                         }
430                 }
431                 errno = ENOENT;
432                 WARNING("searched runid %d of dpath %s isn't an applications", runid, dpath);
433 end:
434                 json_object_put(apps);
435                 free(dpath);
436         }
437
438         return result;
439 }
440
441 /*
442  * Search the runid, if any, of the application of 'id' for the user 'uid'.
443  * Returns the pid (a positive not null number) or -1 in case of error.
444  */
445 int afm_urun_search_runid(struct afm_udb *db, const char *id, int uid)
446 {
447         int isuser, pid;
448         const char *udpath;
449         struct json_object *appli;
450
451         appli = afm_udb_get_application_private(db, id, uid);
452         if (!appli) {
453                 NOTICE("Unknown appid %s", id);
454                 errno = ENOENT;
455                 pid = -1;
456         } else if (get_basis(appli, &isuser, &udpath, uid) < 0) {
457                 pid = -1;
458         } else {
459                 pid = systemd_unit_pid_of_dpath(isuser, udpath);
460                 if (pid == 0) {
461                         errno = ESRCH;
462                         pid = -1;
463                 }
464         }
465         return pid;
466 }
467