227fd1deb9de8c849e0e5404a2c135e6a7f2c5df
[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         const char *state;
150
151         for (;;) {
152                 state = systemd_unit_state_of_dpath(isuser, dpath);
153                 if (state == NULL || state == SysD_State_Active
154                  || state == SysD_State_Failed)
155                         return state;
156                 /* TODO: sleep */
157         }
158 }
159
160 /*
161  * Creates a json object that describes the state for:
162  *  - 'id', the id of the application
163  *  - 'runid', its runid
164  *  - 'pid', its pid
165  *  - 'state', its systemd state
166  *
167  * Returns the created object or NULL in case of error.
168  */
169 static json_object *mkstate(const char *id, int runid, int pid, const char *state)
170 {
171         struct json_object *result, *pids;
172
173         /* the structure */
174         result = json_object_new_object();
175         if (result == NULL)
176                 goto error;
177
178         /* the runid */
179         if (!j_add_integer(result, "runid", runid))
180                 goto error;
181
182         /* the pids */
183         if (pid > 0) {
184                 pids = j_add_new_array(result, "pids");
185                 if (!pids)
186                         goto error;
187                 if (!j_add_integer(pids, NULL, pid))
188                         goto error;
189         }
190
191         /* the state */
192         if (!j_add_string(result, "state", state == SysD_State_Active ? "running" : "terminated"))
193                 goto error;
194
195         /* the application id */
196         if (!j_add_string(result, "id", id))
197                 goto error;
198
199         /* done */
200         return result;
201
202 error:
203         json_object_put(result);
204         errno = ENOMEM;
205         return NULL;
206 }
207
208 /**************** API handling ************************/
209
210 /*
211  * Starts the application described by 'appli' for the 'mode'.
212  * In case of remote start, it returns in uri the uri to
213  * connect to.
214  *
215  * A reference to 'appli' is kept during the live of the
216  * runner. This is made using json_object_get. Thus be aware
217  * that further modifications to 'appli' might create errors.
218  *
219  * Returns the runid in case of success or -1 in case of error
220  */
221 int afm_urun_start(struct json_object *appli, int uid)
222 {
223         return afm_urun_once(appli, uid);
224 }
225
226 /*
227  * Returns the runid of a previously started application 'appli'
228  * or if none is running, starts the application described by 'appli'
229  * in local mode.
230  *
231  * A reference to 'appli' is kept during the live of the
232  * runner. This is made using json_object_get. Thus be aware
233  * that further modifications to 'appli' might create errors.
234  *
235  * Returns the runid in case of success or -1 in case of error
236  */
237 int afm_urun_once(struct json_object *appli, int uid)
238 {
239         const char *udpath, *state, *uscope, *uname;
240         int rc, isuser;
241
242         /* retrieve basis */
243         rc = get_basis(appli, &isuser, &udpath, uid);
244         if (rc < 0)
245                 goto error;
246
247         /* start the unit */
248         rc = systemd_unit_start_dpath(isuser, udpath);
249         if (rc < 0) {
250                 j_read_string_at(appli, "unit-scope", &uscope);
251                 j_read_string_at(appli, "unit-name", &uname);
252                 ERROR("can't start %s unit %s for uid %d", uscope, uname, uid);
253                 goto error;
254         }
255
256         state = wait_state_stable(isuser, udpath);
257         if (state == NULL) {
258                 j_read_string_at(appli, "unit-scope", &uscope);
259                 j_read_string_at(appli, "unit-name", &uname);
260                 ERROR("can't wait %s unit %s for uid %d: %m", uscope, uname, uid);
261                 goto error;
262         }
263         if (state != SysD_State_Active) {
264                 j_read_string_at(appli, "unit-scope", &uscope);
265                 j_read_string_at(appli, "unit-name", &uname);
266                 ERROR("start error %s unit %s for uid %d: %s", uscope, uname, uid, state);
267                 goto error;
268         }
269
270         rc = systemd_unit_pid_of_dpath(isuser, udpath);
271         if (rc <= 0) {
272                 j_read_string_at(appli, "unit-scope", &uscope);
273                 j_read_string_at(appli, "unit-name", &uname);
274                 ERROR("can't getpid of %s unit %s for uid %d: %m", uscope, uname, uid);
275                 goto error;
276         }
277
278         return rc;
279
280 error:
281         return -1;
282 }
283
284 static int not_yet_implemented(const char *what)
285 {
286         ERROR("%s isn't yet implemented", what);
287         errno = ENOTSUP;
288         return -1;
289 }
290
291 /*
292  * Terminates the runner of 'runid'
293  *
294  * Returns 0 in case of success or -1 in case of error
295  */
296 int afm_urun_terminate(int runid, int uid)
297 {
298         int rc = systemd_unit_stop_pid(1 /* TODO: isuser? */, (unsigned)runid);
299         if (rc < 0)
300                 rc = systemd_unit_stop_pid(0 /* TODO: isuser? */, (unsigned)runid);
301         return rc < 0 ? rc : 0;
302 }
303
304 /*
305  * Stops (aka pause) the runner of 'runid'
306  *
307  * Returns 0 in case of success or -1 in case of error
308  */
309 int afm_urun_pause(int runid, int uid)
310 {
311         return not_yet_implemented("pause");
312 }
313
314 /*
315  * Continue (aka resume) the runner of 'runid'
316  *
317  * Returns 0 in case of success or -1 in case of error
318  */
319 int afm_urun_resume(int runid, int uid)
320 {
321         return not_yet_implemented("resume");
322 }
323
324 /*
325  * Get the list of the runners.
326  *
327  * Returns the list or NULL in case of error.
328  */
329 struct json_object *afm_urun_list(struct afm_udb *db, int all, int uid)
330 {
331         int i, n, isuser, pid;
332         const char *udpath;
333         const char *id;
334         const char *state;
335         struct json_object *desc;
336         struct json_object *appli;
337         struct json_object *apps;
338         struct json_object *result;
339
340         apps = NULL;
341         result = json_object_new_array();
342         if (result == NULL)
343                 goto error;
344
345         apps = afm_udb_applications_private(db, all, uid);
346         n = json_object_array_length(apps);
347         for (i = 0 ; i < n ; i++) {
348                 appli = json_object_array_get_idx(apps, i);
349                 if (appli && get_basis(appli, &isuser, &udpath, uid) >= 0) {
350                         pid = systemd_unit_pid_of_dpath(isuser, udpath);
351                         if (pid > 0 && j_read_string_at(appli, "id", &id)) {
352                                 state = systemd_unit_state_of_dpath(isuser, udpath);
353                                 if (state == SysD_State_Active) {
354                                         desc = mkstate(id, pid, pid, state);
355                                         if (desc && json_object_array_add(result, desc) == -1) {
356                                                 ERROR("can't add desc %s to result", json_object_get_string(desc));
357                                                 json_object_put(desc);
358                                         }
359                                 }
360                         }
361                 }
362         }
363
364 error:
365         json_object_put(apps);
366         return result;
367 }
368
369 /*
370  * Get the state of the runner of 'runid'.
371  *
372  * Returns the state or NULL in case of success
373  */
374 struct json_object *afm_urun_state(struct afm_udb *db, int runid, int uid)
375 {
376         int i, n, isuser, pid, wasuser;
377         char *dpath;
378         const char *udpath;
379         const char *id;
380         const char *state;
381         struct json_object *appli;
382         struct json_object *apps;
383         struct json_object *result;
384
385         result = NULL;
386
387         /* get the dpath */
388         dpath = systemd_unit_dpath_by_pid(wasuser = 1, (unsigned)runid);
389         if (!dpath)
390                 dpath = systemd_unit_dpath_by_pid(wasuser = 0, (unsigned)runid);
391         if (!dpath) {
392                 errno = EINVAL;
393                 WARNING("searched runid %d not found", runid);
394         } else {
395                 /* search in the base */
396                 apps = afm_udb_applications_private(db, 1, uid);
397                 n = json_object_array_length(apps);
398                 for (i = 0 ; i < n ; i++) {
399                         appli = json_object_array_get_idx(apps, i);
400                         if (appli
401                          && get_basis(appli, &isuser, &udpath, uid) >= 0
402                          && !strcmp(dpath, udpath)
403                          && j_read_string_at(appli, "id", &id)) {
404                                 pid = systemd_unit_pid_of_dpath(isuser, udpath);
405                                 state = systemd_unit_state_of_dpath(isuser, dpath);
406                                 if (pid > 0 && state == SysD_State_Active)
407                                         result = mkstate(id, runid, pid, state);
408                                 goto end;
409                         }
410                 }
411                 errno = ENOENT;
412                 WARNING("searched runid %d of dpath %s isn't an applications", runid, dpath);
413 end:
414                 json_object_put(apps);
415                 free(dpath);
416         }
417
418         return result;
419 }
420
421 /*
422  * Search the runid, if any, of the application of 'id' for the user 'uid'.
423  * Returns the pid (a positive not null number) or -1 in case of error.
424  */
425 int afm_urun_search_runid(struct afm_udb *db, const char *id, int uid)
426 {
427         int isuser, pid;
428         const char *udpath;
429         struct json_object *appli;
430
431         appli = afm_udb_get_application_private(db, id, uid);
432         if (!appli) {
433                 NOTICE("Unknown appid %s", id);
434                 errno = ENOENT;
435                 pid = -1;
436         } else if (get_basis(appli, &isuser, &udpath, uid) < 0) {
437                 pid = -1;
438         } else {
439                 pid = systemd_unit_pid_of_dpath(isuser, udpath);
440                 if (pid == 0) {
441                         errno = ESRCH;
442                         pid = -1;
443                 }
444         }
445         return pid;
446 }
447