Don't list terminated processes
[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 /**************** get appli basis *********************/
40
41 static int get_basis(struct json_object *appli, int *isuser, const char **dpath, int load)
42 {
43         char *dp;
44         const char *uname, *uscope;
45
46         /* get the scope */
47         if (!j_read_string_at(appli, "unit-scope", &uscope)) {
48                 ERROR("'unit-scope' missing in appli description %s", json_object_get_string(appli));
49                 goto inval;
50         }
51         *isuser = strcmp(uscope, "system") != 0;
52
53         /* get dpath */
54         if (!j_read_string_at(appli, "-unit-dpath-", dpath)) {
55                 if (!load) {
56                         errno = ENOENT;
57                         goto error;
58                 }
59                 if (!j_read_string_at(appli, "unit-name", &uname)) {
60                         ERROR("'unit-name' missing in appli description %s", json_object_get_string(appli));
61                         goto inval;
62                 }
63                 dp = systemd_unit_dpath_by_name(*isuser, uname, 1);
64                 if (dp == NULL) {
65                         ERROR("Can't load unit of name %s for %s: %m", uname, uscope);
66                         goto error;
67                 }
68                 if (!j_add_string(appli, "-unit-dpath-", dp)) {
69                         free(dp);
70                         goto nomem;
71                 }
72                 free(dp);
73                 j_read_string_at(appli, "-unit-dpath-", dpath);
74         }
75
76         return 0;
77
78 nomem:
79         ERROR("out of memory");
80         errno = ENOMEM;
81         goto error;
82
83 inval:
84         errno = EINVAL;
85 error:
86         return -1;
87 }
88
89 static const char *wait_state_stable(int isuser, const char *dpath)
90 {
91         const char *state;
92
93         for (;;) {
94                 state = systemd_unit_state_of_dpath(isuser, dpath);
95                 if (state == NULL || state == SysD_State_Active
96                  || state == SysD_State_Failed)
97                         return state;
98                 /* TODO: sleep */
99         }
100 }
101
102 /*
103  * Creates a json object that describes the state for:
104  *  - 'id', the id of the application
105  *  - 'runid', its runid
106  *  - 'pid', its pid
107  *  - 'state', its systemd state
108  *
109  * Returns the created object or NULL in case of error.
110  */
111 static json_object *mkstate(const char *id, int runid, int pid, const char *state)
112 {
113         struct json_object *result, *pids;
114
115         /* the structure */
116         result = json_object_new_object();
117         if (result == NULL)
118                 goto error;
119
120         /* the runid */
121         if (!j_add_integer(result, "runid", runid))
122                 goto error;
123
124         /* the pids */
125         if (pid > 0) {
126                 pids = j_add_new_array(result, "pids");
127                 if (!pids)
128                         goto error;
129                 if (!j_add_integer(pids, NULL, pid))
130                         goto error;
131         }
132
133         /* the state */
134         if (!j_add_string(result, "state", state == SysD_State_Active ? "running" : "terminated"))
135                 goto error;
136
137         /* the application id */
138         if (!j_add_string(result, "id", id))
139                 goto error;
140
141         /* done */
142         return result;
143
144 error:
145         json_object_put(result);
146         errno = ENOMEM;
147         return NULL;
148 }
149
150 /**************** API handling ************************/
151
152 /*
153  * Starts the application described by 'appli' for the 'mode'.
154  * In case of remote start, it returns in uri the uri to
155  * connect to.
156  *
157  * A reference to 'appli' is kept during the live of the
158  * runner. This is made using json_object_get. Thus be aware
159  * that further modifications to 'appli' might create errors.
160  *
161  * Returns the runid in case of success or -1 in case of error
162  */
163 int afm_urun_start(struct json_object *appli)
164 {
165         return afm_urun_once(appli);
166 }
167
168 /*
169  * Returns the runid of a previously started application 'appli'
170  * or if none is running, starts the application described by 'appli'
171  * in local mode.
172  *
173  * A reference to 'appli' is kept during the live of the
174  * runner. This is made using json_object_get. Thus be aware
175  * that further modifications to 'appli' might create errors.
176  *
177  * Returns the runid in case of success or -1 in case of error
178  */
179 int afm_urun_once(struct json_object *appli)
180 {
181         const char *udpath, *state, *uscope, *uname;
182         int rc, isuser;
183
184         /* retrieve basis */
185         rc = get_basis(appli, &isuser, &udpath, 1);
186         if (rc < 0)
187                 goto error;
188
189         /* start the unit */
190         rc = systemd_unit_start_dpath(isuser, udpath);
191         if (rc < 0) {
192                 j_read_string_at(appli, "unit-scope", &uscope);
193                 j_read_string_at(appli, "unit-name", &uname);
194                 ERROR("can't start %s unit %s", uscope, uname);
195                 goto error;
196         }
197
198         state = wait_state_stable(isuser, udpath);
199         if (state == NULL) {
200                 j_read_string_at(appli, "unit-scope", &uscope);
201                 j_read_string_at(appli, "unit-name", &uname);
202                 ERROR("can't wait %s unit %s: %m", uscope, uname);
203                 goto error;
204         }
205         if (state != SysD_State_Active) {
206                 j_read_string_at(appli, "unit-scope", &uscope);
207                 j_read_string_at(appli, "unit-name", &uname);
208                 ERROR("start error %s unit %s: %s", uscope, uname, state);
209                 goto error;
210         }
211
212         rc = systemd_unit_pid_of_dpath(isuser, udpath);
213         if (rc < 0) {
214                 j_read_string_at(appli, "unit-scope", &uscope);
215                 j_read_string_at(appli, "unit-name", &uname);
216                 ERROR("can't getpid of %s unit %s: %m", uscope, uname);
217                 goto error;
218         }
219                 
220         return rc;
221
222 error:
223         return -1;
224 }
225
226 static int not_yet_implemented(const char *what)
227 {
228         ERROR("%s isn't yet implemented", what);
229         errno = ENOTSUP;
230         return -1;
231 }
232
233 /*
234  * Terminates the runner of 'runid'
235  *
236  * Returns 0 in case of success or -1 in case of error
237  */
238 int afm_urun_terminate(int runid)
239 {
240         int rc = systemd_unit_stop_pid(1 /* TODO: isuser? */, (unsigned)runid);
241         return rc < 0 ? rc : 0;
242 }
243
244 /*
245  * Stops (aka pause) the runner of 'runid'
246  *
247  * Returns 0 in case of success or -1 in case of error
248  */
249 int afm_urun_pause(int runid)
250 {
251         return not_yet_implemented("pause");
252 }
253
254 /*
255  * Continue (aka resume) the runner of 'runid'
256  *
257  * Returns 0 in case of success or -1 in case of error
258  */
259 int afm_urun_resume(int runid)
260 {
261         return not_yet_implemented("resume");
262 }
263
264 /*
265  * Get the list of the runners.
266  *
267  * Returns the list or NULL in case of error.
268  */
269 struct json_object *afm_urun_list(struct afm_udb *db)
270 {
271         int i, n, isuser, pid;
272         const char *udpath;
273         const char *id;
274         const char *state;
275         struct json_object *desc;
276         struct json_object *appli;
277         struct json_object *apps;
278         struct json_object *result;
279
280         apps = NULL;
281         result = json_object_new_array();
282         if (result == NULL)
283                 goto error;
284
285         apps = afm_udb_applications_private(db);
286         n = json_object_array_length(apps);
287         for (i = 0 ; i < n ; i++) {
288                 appli = json_object_array_get_idx(apps, i);
289                 if (appli && get_basis(appli, &isuser, &udpath, 0) >= 0) {
290                         pid = systemd_unit_pid_of_dpath(isuser, udpath);
291                         if (pid > 0 && j_read_string_at(appli, "id", &id)) {
292                                 state = systemd_unit_state_of_dpath(isuser, udpath);
293                                 if (state == SysD_State_Active) {
294                                         desc = mkstate(id, pid, pid, state);
295                                         if (desc && json_object_array_add(result, desc) == -1) {
296                                                 ERROR("can't add desc %s to result", json_object_get_string(desc));
297                                                 json_object_put(desc);
298                                         }
299                                 }
300                         }
301                 }
302         }
303
304 error:
305         json_object_put(apps);
306         return result;
307 }
308
309 /*
310  * Get the state of the runner of 'runid'.
311  *
312  * Returns the state or NULL in case of success
313  */
314 struct json_object *afm_urun_state(struct afm_udb *db, int runid)
315 {
316         int i, n, isuser, pid;
317         char *dpath;
318         const char *udpath;
319         const char *id;
320         const char *state;
321         struct json_object *appli;
322         struct json_object *apps;
323         struct json_object *result;
324
325         result = NULL;
326
327         /* get the dpath */
328         dpath = systemd_unit_dpath_by_pid(1 /* TODO: isuser? */, (unsigned)runid);
329         if (!dpath) {
330                 errno = EINVAL;
331                 WARNING("searched runid %d not found", runid);
332         } else {
333                 /* search in the base */
334                 apps = afm_udb_applications_private(db);
335                 n = json_object_array_length(apps);
336                 for (i = 0 ; i < n ; i++) {
337                         appli = json_object_array_get_idx(apps, i);
338                         if (appli
339                          && get_basis(appli, &isuser, &udpath, 0) >= 0
340                          && !strcmp(dpath, udpath)
341                          && j_read_string_at(appli, "id", &id)) {
342                                 pid = systemd_unit_pid_of_dpath(isuser, udpath);
343                                 state = systemd_unit_state_of_dpath(isuser, dpath);
344                                 if (state == SysD_State_Active)
345                                         result = mkstate(id, runid, pid, state);
346                                 goto end;
347                         }
348                 }
349                 errno = ENOENT;
350                 WARNING("searched runid %d of dpath %s isn't an applications", runid, dpath);
351 end:
352                 json_object_put(apps);
353                 free(dpath);    
354         }
355
356         return result;
357 }
358