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