2 Copyright (C) 2015-2020 IoT.bzh
4 author: José Bollo <jose.bollo@iot.bzh>
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
10 http://www.apache.org/licenses/LICENSE-2.0
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.
23 #include <sys/types.h>
31 #include <json-c/json.h>
34 #include "utils-dir.h"
35 #include "utils-json.h"
36 #include "utils-systemd.h"
40 static const char key_unit_d_path[] = "-unit-dpath-";
42 /**************** get appli basis *********************/
44 static int get_basis(struct json_object *appli, int *isuser, const char **dpath, int uid)
47 char *dp, *arodot, *nun;
48 const char *uname, *uscope;
49 struct json_object *odp;
53 if (!j_read_string_at(appli, "unit-scope", &uscope)) {
54 ERROR("'unit-scope' missing in appli description %s", json_object_get_string(appli));
57 *isuser = strcmp(uscope, "system") != 0;
59 /* get dpaths of known users */
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);
67 assert(json_object_get_type(odp) == json_type_object);
70 ERROR("unexpected uid %d", uid);
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))
81 if (!j_read_string_at(appli, "unit-name", &uname)) {
82 ERROR("'unit-name' missing in appli description %s", json_object_get_string(appli));
86 /* is user parametric? */
87 arodot = strchr(uname, '@');
88 if (arodot && *++arodot == '.') {
92 ERROR("unexpected uid %d", uid);
95 rc = snprintf(userid, sizeof userid, "%d", uid);
96 assert(rc < (int)(sizeof userid));
98 /* create the dpaths of known users */
99 odp = json_object_new_object();
102 json_object_object_add(appli, key_unit_d_path, odp);
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);
110 ERROR("Can't load unit of name %s for %s: %m", nun, uscope);
113 /* record the dpath */
114 if (!j_add_string(odp, userid, dp)) {
119 j_read_string_at(odp, userid, dpath);
122 dp = systemd_unit_dpath_by_name(*isuser, uname, 1);
124 ERROR("Can't load unit of name %s for %s: %m", uname, uscope);
127 /* record the dpath */
128 if (!j_add_string(appli, key_unit_d_path, dp)) {
133 j_read_string_at(appli, key_unit_d_path, dpath);
138 ERROR("out of memory");
148 static enum SysD_State wait_state_stable(int isuser, const char *dpath)
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;
158 for (trial = 1 ; trial <= trial_count ; trial++) {
159 state = systemd_unit_state_of_dpath(isuser, dpath);
161 case SysD_State_Active:
162 case SysD_State_Failed:
163 case SysD_State_Inactive:
167 tispec.tv_nsec = period_ns;
168 nanosleep(&tispec, NULL);
176 * Creates a json object that describes the state for:
177 * - 'id', the id of the application
178 * - 'runid', its runid
180 * - 'state', its systemd state
182 * Returns the created object or NULL in case of error.
184 static json_object *mkstate(const char *id, int runid, int pid, enum SysD_State state)
186 struct json_object *result, *pids;
189 result = json_object_new_object();
194 if (!j_add_integer(result, "runid", runid))
199 pids = j_add_new_array(result, "pids");
202 if (!j_add_integer(pids, NULL, pid))
207 if (!j_add_string(result, "state", state == SysD_State_Active ? "running" : "terminated"))
210 /* the application id */
211 if (!j_add_string(result, "id", id))
218 json_object_put(result);
223 /**************** API handling ************************/
226 * Starts the application described by 'appli' for the 'mode'.
227 * In case of remote start, it returns in uri the uri to
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.
234 * Returns the runid in case of success or -1 in case of error
236 int afm_urun_start(struct json_object *appli, int uid)
238 return afm_urun_once(appli, uid);
242 * Returns the runid of a previously started application 'appli'
243 * or if none is running, starts the application described by 'appli'
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.
250 * Returns the runid in case of success or -1 in case of error
252 int afm_urun_once(struct json_object *appli, int uid)
254 const char *udpath, *uscope, *uname;
255 enum SysD_State state;
259 rc = get_basis(appli, &isuser, &udpath, uid);
264 rc = systemd_unit_start_dpath(isuser, udpath);
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);
272 state = wait_state_stable(isuser, udpath);
274 case SysD_State_Active:
275 case SysD_State_Inactive:
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));
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);
290 rc = systemd_unit_pid_of_dpath(isuser, udpath);
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);
304 static int not_yet_implemented(const char *what)
306 ERROR("%s isn't yet implemented", what);
312 * Terminates the runner of 'runid'
314 * Returns 0 in case of success or -1 in case of error
316 int afm_urun_terminate(int runid, int uid)
318 int rc = systemd_unit_stop_pid(1 /* TODO: isuser? */, (unsigned)runid);
320 rc = systemd_unit_stop_pid(0 /* TODO: isuser? */, (unsigned)runid);
321 return rc < 0 ? rc : 0;
325 * Stops (aka pause) the runner of 'runid'
327 * Returns 0 in case of success or -1 in case of error
329 int afm_urun_pause(int runid, int uid)
331 return not_yet_implemented("pause");
335 * Continue (aka resume) the runner of 'runid'
337 * Returns 0 in case of success or -1 in case of error
339 int afm_urun_resume(int runid, int uid)
341 return not_yet_implemented("resume");
345 * Get the list of the runners.
347 * Returns the list or NULL in case of error.
349 struct json_object *afm_urun_list(struct afm_udb *db, int all, int uid)
351 int i, n, isuser, pid;
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;
361 result = json_object_new_array();
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);
385 json_object_put(apps);
390 * Get the state of the runner of 'runid'.
392 * Returns the state or NULL in case of success
394 struct json_object *afm_urun_state(struct afm_udb *db, int runid, int uid)
396 int i, n, isuser, pid, wasuser;
400 enum SysD_State state;
401 struct json_object *appli;
402 struct json_object *apps;
403 struct json_object *result;
408 dpath = systemd_unit_dpath_by_pid(wasuser = 1, (unsigned)runid);
410 dpath = systemd_unit_dpath_by_pid(wasuser = 0, (unsigned)runid);
413 WARNING("searched runid %d not found", runid);
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);
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);
432 WARNING("searched runid %d of dpath %s isn't an applications", runid, dpath);
434 json_object_put(apps);
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.
445 int afm_urun_search_runid(struct afm_udb *db, const char *id, int uid)
449 struct json_object *appli;
451 appli = afm_udb_get_application_private(db, id, uid);
453 NOTICE("Unknown appid %s", id);
456 } else if (get_basis(appli, &isuser, &udpath, uid) < 0) {
459 pid = systemd_unit_pid_of_dpath(isuser, udpath);