libgdbus: add api to start or stop systemd unit
[platform/core/system/libsyscommon.git] / src / libgdbus / dbus-systemd.c
1 /*
2  * libsyscommon
3  *
4  * Copyright (c) 2019 Samsung Electronics Co., Ltd.
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 #include <errno.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <stdint.h>
23 #include <stdbool.h>
24 #include <libgdbus/dbus-system.h>
25
26 #include "shared/log.h"
27
28 #define SYSTEMD_DBUS_SERVICE    "org.freedesktop.systemd1"
29 #define SYSTEMD_DBUS_PATH               "/org/freedesktop/systemd1"
30 #define SYSTEMD_DBUS_UNIT_PATH  "/org/freedesktop/systemd1/unit/"
31
32 #define SYSTEMD_DBUS_MANAGER_IFACE      "org.freedesktop.systemd1.Manager"
33 #define SYSTEMD_DBUS_UNIT_IFACE         "org.freedesktop.systemd1.Unit"
34 #define SYSTEMD_DBUS_SERVICE_IFACE      "org.freedesktop.systemd1.Service"
35 #define SYSTEMD_DBUS_TARGET_IFACE       "org.freedesktop.systemd1.Target"
36
37 #define DBUS_IFACE_DBUS_PROPERTIES      "org.freedesktop.DBus.Properties"
38
39 #define SUFFIX_SERVICE ".service"
40 #define SUFFIX_SOCKET ".socket"
41 #define SUFFIX_BUSNAME ".busname"
42 #define SUFFIX_TARGET ".target"
43 #define SUFFIX_DEVICE ".device"
44 #define SUFFIX_MOUNT ".mount"
45 #define SUFFIX_SWAP ".swap"
46 #define SUFFIX_TIMER ".timer"
47 #define SUFFIX_PATH ".path"
48 #define SUFFIX_SLICE ".slice"
49 #define SUFFIX_SCOPE ".scope"
50
51 #define UNIT_NAME_MAX 256
52
53 typedef struct {
54         char *job_id;
55         char *unit_name;
56 } unitinfo;
57
58 static void _cb_JobRemoved(GDBusConnection *conn,
59         const char *sender,
60         const char *path,
61         const char *iface,
62         const char *name,
63         GVariant *param,
64         gpointer data)
65 {
66         sig_ctx *ctx = data;
67         gchar *job_id = NULL;
68         gchar *unit_name = NULL;
69         unitinfo *uinfo = NULL;
70
71         if (!ctx) {
72                 _E("User data ctx is null");
73                 return ;
74         }
75
76         uinfo = ctx->user_data;
77         if (!uinfo) {
78                 _E("User_data uinfo is null");
79                 return ;
80         }
81         if (!dh_get_param_from_var(param, "(uoss)", NULL, &job_id, &unit_name, NULL)) {
82                 _E("Failed to get param");
83                 return ;
84         }
85         if (strcmp(uinfo->job_id, job_id) || strcmp(uinfo->unit_name, unit_name)) {
86                 _E("Not matched: job_id:%s, unit_name:%s", job_id, unit_name);
87                 goto err;
88         }
89
90         /* otherwise, if matched signal, quit loop */
91
92         ctx->quit_reason = CTX_QUIT_NORMAL;
93         if (ctx->timeout_src) {
94                 g_source_destroy(ctx->timeout_src);
95                 ctx->timeout_src = NULL;
96         }
97
98         g_main_loop_quit(ctx->loop);
99
100 err:
101         g_free(job_id);
102         g_free(unit_name);
103 }
104
105 static int _systemd_control_unit_sync(const char *method, const char *name, int timeout_msec)
106 {
107         GVariant *reply = NULL;
108         gchar *objpath = NULL;
109         int ret = 0;
110         sig_ctx *ctx = NULL;
111         gchar *unit_name = NULL;
112         unitinfo uinfo;
113         int quit_reason;
114
115         ctx = dbus_handle_new_signal_ctx();
116         if (!ctx)
117                 return -ENOMEM;
118
119         _I("Starting: %s %s", method, name);
120
121         /* synchronous siganl subscriptsion */
122         ret = subscribe_dbus_signal_ctx(NULL, ctx, SYSTEMD_DBUS_SERVICE, SYSTEMD_DBUS_PATH, SYSTEMD_DBUS_IFACE_MANAGER, "JobRemoved", _cb_JobRemoved);
123         if (ret == 0) {
124                 ret = -1;
125                 goto finish;
126         }
127
128         reply = dbus_handle_method_sync_with_reply_var(SYSTEMD_DBUS_DEST,
129                 SYSTEMD_DBUS_PATH,
130                 SYSTEMD_DBUS_MANAGER_IFACE,
131                 method,
132                 g_variant_new("(ss)", name, "replace"));
133         if (!reply || !dh_get_param_from_var(reply, "(o)", &objpath)) {
134                 _E("fail (%s): no message", method);
135                 ret = -EBADMSG;
136                 goto finish;
137         }
138
139         uinfo.job_id = objpath;
140         uinfo.unit_name = name;
141         ctx->user_data = &uinfo;
142
143         /* set timeout */
144         ret = dbus_handle_signal_ctx_add_timeout(ctx, timeout_msec);
145         if (ret < 0) {
146                 _E("Failed to set timeout, %d", ret);
147                 goto finish;
148         }
149
150         /* run loop and wait signal callback */
151         quit_reason = dbus_handle_signal_ctx_wait(ctx);
152         if (quit_reason != CTX_QUIT_NORMAL) {
153                 ret = -1;
154                 _E("Failed to receive JobRemoved signal %d", quit_reason);
155                 goto finish;
156         }
157
158         _I("Finished: %s %s", method, name);
159
160 finish:
161         if (unit_name)
162                 g_free(unit_name);
163
164         if (reply)
165                 g_variant_unref(reply);
166         g_free(objpath);
167
168         dbus_handle_free_signal_ctx(ctx);
169
170         return ret;
171 }
172
173 static int _systemd_control_unit_async(const char *method, const char *name)
174 {
175         GVariant *reply = NULL;
176         gchar *objpath = NULL;
177         int ret = 0;
178
179         _I("Starting: %s %s", method, name);
180
181         reply = dbus_handle_method_sync_with_reply_var(SYSTEMD_DBUS_DEST,
182                 SYSTEMD_DBUS_PATH,
183                 SYSTEMD_DBUS_MANAGER_IFACE,
184                 method,
185                 g_variant_new("(ss)", name, "replace"));
186
187         if (!reply || !dh_get_param_from_var(reply, "(o)", &objpath)) {
188                 _E("fail (%s): no message", method);
189                 ret = -EBADMSG;
190                 goto finish;
191         }
192
193         _I("Finished: %s %s", method, name);
194 finish:
195         if (reply)
196                 g_variant_unref(reply);
197         g_free(objpath);
198
199         return ret;
200 }
201
202 static int _has_suffix(const char *service_name, const char *suffix)
203 {
204         int index = 0;
205
206         if (!service_name || !suffix)
207                 return FALSE;
208
209         index = strlen(service_name) - strlen(suffix);
210         if (index <= 0)
211                 return FALSE;
212
213         if (strcmp(service_name + index, suffix) == 0)
214                 return TRUE;
215         return FALSE;
216 }
217
218 static int _change_suffix(const char *name, const char *suffix, char **new_name)
219 {
220         char *buf = NULL;
221         char *ext = NULL;
222         unsigned int len = 0;
223         int ret = 0;
224
225         if (!name || !suffix || !new_name) {
226                 _E("Wrong param name:%s, suffix:%s, new_name:%s", name, suffix, new_name);
227                 return -EINVAL;
228         }
229
230         ext = strrchr(name, '.');
231         if (ext == name) {
232                 _E("Wrong file name %s", name);
233                 return -EINVAL;
234         }
235
236         /* if ext is same as suffix */
237         if (ext && strcmp(ext, suffix) == 0) {
238                 *new_name = strdup(name);
239                 return 0;
240         }
241
242         /* otherwise, make new unit name */
243         if (ext)
244                 len = ext - name;
245         else
246                 len = strlen(name);
247
248         /* check max len */
249         if ((len + strlen(suffix)) >= UNIT_NAME_MAX) {
250                 _E("Name is too long:%d", (len + strlen(suffix)));
251                 return -ENAMETOOLONG;
252         }
253
254         buf = (char *)malloc(sizeof(char) * (len + strlen(suffix) + 1));
255         if (!buf) {
256                 _E("Failed to alloc mem");
257                 return -ENOMEM;
258         }
259
260         ret = snprintf(buf, len + 1, "%s", name);
261         if (ret < 0) {
262                 ret = -errno;
263                 _E("Failed to snprintf %d", ret);
264                 goto err;
265         }
266         ret = snprintf(buf + len, strlen(suffix) + 1, "%s", suffix);
267         if (ret < 0) {
268                 ret = -errno;
269                 _E("Failed to snprintf %d", ret);
270                 goto err;
271         }
272
273         *new_name = buf;
274
275         return 0;
276
277 err:
278         free(buf);
279         return ret;
280 }
281
282 /*
283 _systemd_start_unit_internal
284
285 Start or Stop systemd unit.
286         1) synchronous
287                 - Send StartUnit/StopUnit Method call(sync)
288                         reply:(o):/org/freedesktop/systemd1/job/[jobid]
289                 - Wait JobRemoved signal from systemd
290                         (uoss):(uint32 [jobid], objectpath '/org/freedesktop/systemd1/job/[jobid]', '[unit name]', '[result]')
291         2) asynchronous
292                 - Send StartUnit/StopUnit Method call(sync)
293
294 @param name: unit name
295 @param suffix: (nullable): change extension of unit name, or %NULL
296 @param timeout_msec: the timeout in milliseconds, -1 to use the default
297 @param method: method name, "StartUnit" or "StopUnit"
298 @param sync: %TRUE
299 Returns: the exit status
300 */
301 static int _systemd_start_unit_internal(const char *name, const char *suffix,
302                 int timeout_msec, const char *method, int sync)
303 {
304         unsigned int len = 0;
305         char *new_name = NULL;
306         int ret = 0;
307
308         if (!name || !method) {
309                 _E("Wrong param name %s, method %s", name, method);
310                 return -EINVAL;
311         }
312         if (timeout_msec < -1) {
313                 _E("wrong timeout. timeout(>=0 or -1)");
314                 return -EINVAL;
315         }
316
317         if (suffix) {
318                 ret = _change_suffix(name, suffix, &new_name);
319                 if (ret < 0)
320                         return ret;
321                 name = new_name;
322         } else {
323                 if (strlen(name) > UNIT_NAME_MAX) {
324                         _E("Invalid name length %d(>%d)", strlen(name), UNIT_NAME_MAX);
325                         return -EINVAL;
326                 }
327         }
328
329         if (sync)
330                 ret = _systemd_control_unit_sync(method, name, timeout_msec);
331         else
332                 ret = _systemd_control_unit_async(method, name);
333
334         if (new_name)
335                 free(new_name);
336
337         return ret;
338 }
339
340 int systemd_start_unit_sync(const char *name, const char *suffix, int timeout_msec)
341 {
342         return _systemd_start_unit_internal(name, suffix, timeout_msec, "StartUnit", TRUE);
343 }
344
345 int systemd_stop_unit_sync(const char *name, const char *suffix, int timeout_msec)
346 {
347         return _systemd_start_unit_internal(name, suffix, timeout_msec, "StopUnit", TRUE);
348 }
349
350 int systemd_start_unit_async(const char *name, const char *suffix)
351 {
352         return _systemd_start_unit_internal(name, suffix, -1, "StartUnit", FALSE);
353 }
354
355 int systemd_stop_unit_async(const char *name, const char *suffix)
356 {
357         return _systemd_start_unit_internal(name, suffix, -1, "StopUnit", FALSE);
358 }
359
360 #define SYSTEMD_UNIT_ESCAPE_CHAR ".-"
361
362 static char *systemd_get_unit_dbus_path(const char *unit)
363 {
364         char *path = NULL;
365         int i;
366         size_t p, k, prefix_len, unit_len;
367         size_t path_len, len, escape;
368
369         if (!unit)
370                 return NULL;
371         unit_len = strlen(unit);
372
373         for (escape = 0, p = 0; p < unit_len; escape++) {
374                 k = strcspn(unit + p, SYSTEMD_UNIT_ESCAPE_CHAR);
375                 if (p + k >= unit_len)
376                         break;
377                 p += k+1;
378         }
379
380         prefix_len = strlen(SYSTEMD_DBUS_UNIT_PATH);
381         /* assume we try to get object path of foo-bar.service then
382         * the object path will be
383         * "/org/freedesktop/systemd1/unit/foo_2dbar_2eservice\n". In
384         * this case we can find two escape characters, one of escape
385         * char('-') is changed to three of char("_2d"). So the total
386         * length will be: */
387         /* (PREFIX) + (unit - escape + 3*escape) + NULL */
388         path_len = prefix_len + (unit_len - escape)
389                 + (escape * 3 * sizeof(char)) + 1;
390         path = (char *)calloc(path_len, sizeof(char));
391         if (!path)
392                 return NULL;
393
394         strncpy(path, SYSTEMD_DBUS_UNIT_PATH, prefix_len);
395         for (i = 0, p = 0; i <= escape; i++) {
396                 k = strcspn(unit + p, SYSTEMD_UNIT_ESCAPE_CHAR);
397                 strncpy(path + prefix_len, unit + p, k);
398                 if (k < strlen(unit + p)) {
399                         len = path_len - (prefix_len + k);
400                         snprintf(path + prefix_len + k, len,
401                                         "_%x", *(unit + p + k) & 0xff);
402                         prefix_len += k + 3;
403                         p += k+1;
404                 }
405         }
406
407         return path;
408 }
409
410 GVariant *systemd_get_manager_property(const char *property)
411 {
412         GVariant *reply = NULL;
413         GVariant *val = NULL;
414
415         if (!property)
416                 return NULL;
417
418         reply = dbus_handle_method_sync_with_reply_var(SYSTEMD_DBUS_DEST,
419                                         SYSTEMD_DBUS_PATH,
420                                         DBUS_IFACE_DBUS_PROPERTIES,
421                                         "Get",
422                                         g_variant_new("(ss)", SYSTEMD_DBUS_MANAGER_IFACE, property));
423         if (!reply || !dh_get_param_from_var(reply, "(v)", &val))
424                 _E("Failed to get variant");
425         if (reply)
426                 g_variant_unref(reply);
427
428         return val;
429 }
430
431 GVariant *systemd_get_unit_property(const char *unit,
432                                       const char *property)
433 {
434         char *escaped;
435         GVariant *reply = NULL;
436         GVariant *val = NULL;
437
438         if (!unit || !property)
439                 return NULL;
440
441         escaped = systemd_get_unit_dbus_path(unit);
442
443         reply = dbus_handle_method_sync_with_reply_var(SYSTEMD_DBUS_DEST,
444                                         escaped,
445                                         DBUS_IFACE_DBUS_PROPERTIES,
446                                         "Get",
447                                         g_variant_new("(ss)", SYSTEMD_DBUS_UNIT_IFACE, property));
448
449         if (!reply || !dh_get_param_from_var(reply, "(v)", &val))
450                 _E("Failed to get variant");
451         if (reply)
452                 g_variant_unref(reply);
453         free(escaped);
454
455         return val;
456 }
457
458 GVariant *systemd_get_service_property(const char *unit,
459                                          const char *property)
460 {
461         char *escaped;
462         GVariant *reply = NULL;
463         GVariant *val = NULL;
464
465         if (!unit || !property)
466                 return NULL;
467
468         escaped = systemd_get_unit_dbus_path(unit);
469
470         reply = dbus_handle_method_sync_with_reply_var(SYSTEMD_DBUS_DEST,
471                                         escaped,
472                                         DBUS_IFACE_DBUS_PROPERTIES,
473                                         "Get",
474                                         g_variant_new("(ss)", SYSTEMD_DBUS_SERVICE_IFACE, property));
475         if (!reply || !dh_get_param_from_var(reply, "(v)", &val))
476                 _E("Failed to get variant");
477         if (reply)
478                 g_variant_unref(reply);
479         free(escaped);
480         return val;
481 }