Add dlog for debugging and fix build warnings
[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         const char *job_id;
55         const 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         unitinfo uinfo;
112         int quit_reason;
113
114         ctx = dbus_handle_new_signal_ctx();
115         if (!ctx)
116                 return -ENOMEM;
117
118         _I("Starting: %s %s", method, name);
119
120         /* synchronous signal subscription */
121         ret = subscribe_dbus_signal_ctx(NULL, ctx, SYSTEMD_DBUS_SERVICE, SYSTEMD_DBUS_PATH, SYSTEMD_DBUS_IFACE_MANAGER, "JobRemoved", _cb_JobRemoved);
122         if (ret == 0) {
123                 ret = -1;
124                 goto finish;
125         }
126
127         reply = dbus_handle_method_sync_with_reply_var(SYSTEMD_DBUS_DEST,
128                 SYSTEMD_DBUS_PATH,
129                 SYSTEMD_DBUS_MANAGER_IFACE,
130                 method,
131                 g_variant_new("(ss)", name, "replace"));
132         if (!reply || !dh_get_param_from_var(reply, "(o)", &objpath)) {
133                 _E("fail (%s): no message", method);
134                 ret = -EBADMSG;
135                 goto finish;
136         }
137
138         uinfo.job_id = objpath;
139         uinfo.unit_name = name;
140         ctx->user_data = &uinfo;
141
142         /* set timeout */
143         ret = dbus_handle_signal_ctx_add_timeout(ctx, timeout_msec);
144         if (ret < 0) {
145                 _E("Failed to set timeout, %d", ret);
146                 goto finish;
147         }
148
149         /* run loop and wait signal callback */
150         quit_reason = dbus_handle_signal_ctx_wait(ctx);
151         if (quit_reason != CTX_QUIT_NORMAL) {
152                 ret = -1;
153                 _E("Failed to receive JobRemoved signal %d", quit_reason);
154                 goto finish;
155         }
156
157         _I("Finished: %s %s", method, name);
158
159 finish:
160         if (reply)
161                 g_variant_unref(reply);
162         g_free(objpath);
163
164         dbus_handle_free_signal_ctx(ctx);
165
166         return ret;
167 }
168
169 static int _systemd_control_unit_async(const char *method, const char *name)
170 {
171         GVariant *reply = NULL;
172         gchar *objpath = NULL;
173         int ret = 0;
174
175         _I("Starting: %s %s", method, name);
176
177         reply = dbus_handle_method_sync_with_reply_var(SYSTEMD_DBUS_DEST,
178                 SYSTEMD_DBUS_PATH,
179                 SYSTEMD_DBUS_MANAGER_IFACE,
180                 method,
181                 g_variant_new("(ss)", name, "replace"));
182
183         if (!reply || !dh_get_param_from_var(reply, "(o)", &objpath)) {
184                 _E("fail (%s): no message", method);
185                 ret = -EBADMSG;
186                 goto finish;
187         }
188
189         _I("Finished: %s %s", method, name);
190 finish:
191         if (reply)
192                 g_variant_unref(reply);
193         g_free(objpath);
194
195         return ret;
196 }
197
198 static int _change_suffix(const char *name, const char *suffix, char **new_name)
199 {
200         char *buf = NULL;
201         char *ext = NULL;
202         unsigned int len = 0;
203         int ret = 0;
204
205         if (!name || !suffix || !new_name) {
206                 _E("Wrong param name:%s, suffix:%s, new_name:%s", name, suffix, new_name);
207                 return -EINVAL;
208         }
209
210         ext = strrchr(name, '.');
211         if (ext == name) {
212                 _E("Wrong file name %s", name);
213                 return -EINVAL;
214         }
215
216         /* if ext is same as suffix */
217         if (ext && strcmp(ext, suffix) == 0) {
218                 *new_name = strdup(name);
219                 return 0;
220         }
221
222         /* otherwise, make new unit name */
223         if (ext)
224                 len = ext - name;
225         else
226                 len = strlen(name);
227
228         /* check max len */
229         if ((len + strlen(suffix)) >= UNIT_NAME_MAX) {
230                 _E("Name is too long:%d", (len + strlen(suffix)));
231                 return -ENAMETOOLONG;
232         }
233
234         buf = (char *)malloc(sizeof(char) * (len + strlen(suffix) + 1));
235         if (!buf) {
236                 _E("Failed to alloc mem");
237                 return -ENOMEM;
238         }
239
240         ret = snprintf(buf, len + 1, "%s", name);
241         if (ret < 0) {
242                 ret = -errno;
243                 _E("Failed to snprintf %d", ret);
244                 goto err;
245         }
246         ret = snprintf(buf + len, strlen(suffix) + 1, "%s", suffix);
247         if (ret < 0) {
248                 ret = -errno;
249                 _E("Failed to snprintf %d", ret);
250                 goto err;
251         }
252
253         *new_name = buf;
254
255         return 0;
256
257 err:
258         free(buf);
259         return ret;
260 }
261
262 /*
263 _systemd_start_unit_internal
264
265 Start or Stop systemd unit.
266         1) synchronous
267                 - Send StartUnit/StopUnit Method call(sync)
268                         reply:(o):/org/freedesktop/systemd1/job/[jobid]
269                 - Wait JobRemoved signal from systemd
270                         (uoss):(uint32 [jobid], objectpath '/org/freedesktop/systemd1/job/[jobid]', '[unit name]', '[result]')
271         2) asynchronous
272                 - Send StartUnit/StopUnit Method call(sync)
273
274 @param name: unit name
275 @param suffix: (nullable): change extension of unit name, or %NULL
276 @param timeout_msec: the timeout in milliseconds, -1 to use the default
277 @param method: method name, "StartUnit" or "StopUnit"
278 @param sync: %TRUE
279 Returns: the exit status
280 */
281 static int _systemd_start_unit_internal(const char *name, const char *suffix,
282                 int timeout_msec, const char *method, int sync)
283 {
284         char *new_name = NULL;
285         int ret = 0;
286
287         if (!name || !method) {
288                 _E("Wrong param name %s, method %s", name, method);
289                 return -EINVAL;
290         }
291         if (timeout_msec < -1) {
292                 _E("wrong timeout. timeout(>=0 or -1)");
293                 return -EINVAL;
294         }
295
296         if (suffix) {
297                 ret = _change_suffix(name, suffix, &new_name);
298                 if (ret < 0)
299                         return ret;
300                 name = new_name;
301         } else {
302                 if (strlen(name) > UNIT_NAME_MAX) {
303                         _E("Invalid name length %d(>%d)", strlen(name), UNIT_NAME_MAX);
304                         return -EINVAL;
305                 }
306         }
307
308         if (sync)
309                 ret = _systemd_control_unit_sync(method, name, timeout_msec);
310         else
311                 ret = _systemd_control_unit_async(method, name);
312
313         if (new_name)
314                 free(new_name);
315
316         return ret;
317 }
318
319 int systemd_start_unit_sync(const char *name, const char *suffix, int timeout_msec)
320 {
321         return _systemd_start_unit_internal(name, suffix, timeout_msec, "StartUnit", TRUE);
322 }
323
324 int systemd_stop_unit_sync(const char *name, const char *suffix, int timeout_msec)
325 {
326         return _systemd_start_unit_internal(name, suffix, timeout_msec, "StopUnit", TRUE);
327 }
328
329 int systemd_start_unit_async(const char *name, const char *suffix)
330 {
331         return _systemd_start_unit_internal(name, suffix, -1, "StartUnit", FALSE);
332 }
333
334 int systemd_stop_unit_async(const char *name, const char *suffix)
335 {
336         return _systemd_start_unit_internal(name, suffix, -1, "StopUnit", FALSE);
337 }
338
339 #define SYSTEMD_UNIT_ESCAPE_CHAR ".-"
340
341 static char *systemd_get_unit_dbus_path(const char *unit)
342 {
343         char *path = NULL;
344         int i;
345         size_t p, k, prefix_len, unit_len;
346         size_t path_len, len, escape;
347
348         if (!unit)
349                 return NULL;
350         unit_len = strlen(unit);
351
352         for (escape = 0, p = 0; p < unit_len; escape++) {
353                 k = strcspn(unit + p, SYSTEMD_UNIT_ESCAPE_CHAR);
354                 if (p + k >= unit_len)
355                         break;
356                 p += k+1;
357         }
358
359         prefix_len = strlen(SYSTEMD_DBUS_UNIT_PATH);
360         /* assume we try to get object path of foo-bar.service then
361         * the object path will be
362         * "/org/freedesktop/systemd1/unit/foo_2dbar_2eservice\n". In
363         * this case we can find two escape characters, one of escape
364         * char('-') is changed to three of char("_2d"). So the total
365         * length will be: */
366         /* (PREFIX) + (unit - escape + 3*escape) + NULL */
367         path_len = prefix_len + (unit_len - escape)
368                 + (escape * 3 * sizeof(char)) + 1;
369         path = (char *)calloc(path_len, sizeof(char));
370         if (!path)
371                 return NULL;
372
373         strncpy(path, SYSTEMD_DBUS_UNIT_PATH, prefix_len);
374         for (i = 0, p = 0; i <= escape; i++) {
375                 k = strcspn(unit + p, SYSTEMD_UNIT_ESCAPE_CHAR);
376                 strncpy(path + prefix_len, unit + p, k);
377                 if (k < strlen(unit + p)) {
378                         len = path_len - (prefix_len + k);
379                         snprintf(path + prefix_len + k, len,
380                                         "_%x", *(unit + p + k) & 0xff);
381                         prefix_len += k + 3;
382                         p += k+1;
383                 }
384         }
385
386         return path;
387 }
388
389 GVariant *systemd_get_manager_property(const char *property)
390 {
391         GVariant *reply = NULL;
392         GVariant *val = NULL;
393
394         if (!property)
395                 return NULL;
396
397         reply = dbus_handle_method_sync_with_reply_var(SYSTEMD_DBUS_DEST,
398                                         SYSTEMD_DBUS_PATH,
399                                         DBUS_IFACE_DBUS_PROPERTIES,
400                                         "Get",
401                                         g_variant_new("(ss)", SYSTEMD_DBUS_MANAGER_IFACE, property));
402         if (!reply || !dh_get_param_from_var(reply, "(v)", &val))
403                 _E("Failed to get variant");
404         if (reply)
405                 g_variant_unref(reply);
406
407         return val;
408 }
409
410 GVariant *systemd_get_unit_property(const char *unit,
411                                       const char *property)
412 {
413         char *escaped;
414         GVariant *reply = NULL;
415         GVariant *val = NULL;
416
417         if (!unit || !property)
418                 return NULL;
419
420         escaped = systemd_get_unit_dbus_path(unit);
421
422         reply = dbus_handle_method_sync_with_reply_var(SYSTEMD_DBUS_DEST,
423                                         escaped,
424                                         DBUS_IFACE_DBUS_PROPERTIES,
425                                         "Get",
426                                         g_variant_new("(ss)", SYSTEMD_DBUS_UNIT_IFACE, property));
427
428         if (!reply || !dh_get_param_from_var(reply, "(v)", &val))
429                 _E("Failed to get variant");
430         if (reply)
431                 g_variant_unref(reply);
432         free(escaped);
433
434         return val;
435 }
436
437 GVariant *systemd_get_service_property(const char *unit,
438                                          const char *property)
439 {
440         char *escaped;
441         GVariant *reply = NULL;
442         GVariant *val = NULL;
443
444         if (!unit || !property)
445                 return NULL;
446
447         escaped = systemd_get_unit_dbus_path(unit);
448
449         reply = dbus_handle_method_sync_with_reply_var(SYSTEMD_DBUS_DEST,
450                                         escaped,
451                                         DBUS_IFACE_DBUS_PROPERTIES,
452                                         "Get",
453                                         g_variant_new("(ss)", SYSTEMD_DBUS_SERVICE_IFACE, property));
454         if (!reply || !dh_get_param_from_var(reply, "(v)", &val))
455                 _E("Failed to get variant");
456         if (reply)
457                 g_variant_unref(reply);
458         free(escaped);
459         return val;
460 }