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