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