libgdbus: modify code style
[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", name, suffix, new_name);
217                 return -EINVAL;
218         }
219
220         ext = strrchr(name, '.');
221         if (ext == name) {
222                 _E("Wrong file name %s", name);
223                 return -EINVAL;
224         }
225
226         /* if ext is same as suffix */
227         if (ext && strcmp(ext, suffix) == 0) {
228                 *new_name = strdup(name);
229                 return 0;
230         }
231
232         /* otherwise, make new unit name */
233         if (ext)
234                 len = ext - name;
235         else
236                 len = strlen(name);
237
238         /* check max len */
239         if ((len + strlen(suffix)) >= UNIT_NAME_MAX) {
240                 _E("Name is too long:%d", (len + strlen(suffix)));
241                 return -ENAMETOOLONG;
242         }
243
244         buf = (char *)malloc(sizeof(char) * (len + strlen(suffix) + 1));
245         if (!buf) {
246                 _E("Failed to alloc mem");
247                 return -ENOMEM;
248         }
249
250         ret = snprintf(buf, len + 1, "%s", name);
251         if (ret < 0) {
252                 ret = -errno;
253                 _E("Failed to snprintf %d", ret);
254                 goto err;
255         }
256         ret = snprintf(buf + len, strlen(suffix) + 1, "%s", suffix);
257         if (ret < 0) {
258                 ret = -errno;
259                 _E("Failed to snprintf %d", ret);
260                 goto err;
261         }
262
263         *new_name = buf;
264
265         return 0;
266
267 err:
268         free(buf);
269         return ret;
270 }
271
272 /*
273 _systemd_control_unit
274
275 Start or Stop systemd unit.
276         1) Wait for started/stopped
277                 - Send StartUnit/StopUnit Method call(sync)
278                         reply:(o):/org/freedesktop/systemd1/job/[jobid]
279                 - Wait JobRemoved signal from systemd
280                         (uoss):(uint32 [jobid], objectpath '/org/freedesktop/systemd1/job/[jobid]', '[unit name]', '[result]')
281         2) Asynchronous
282                 - Send StartUnit/StopUnit Method call(sync)
283
284 @param name: unit name
285 @param suffix: (nullable): change extension of unit name, or %NULL
286 @param timeout_msec: the timeout in milliseconds, -1 to use the default
287 @param method: method name, "StartUnit" or "StopUnit"
288 @param wait: %TRUE
289 Returns: the exit status
290 */
291 static int _systemd_control_unit(const char *name,
292                                 const char *suffix,
293                                 int timeout_msec,
294                                 const char *method,
295                                 int wait)
296 {
297         char *new_name = NULL;
298         int ret = 0;
299
300         if (!name || !method) {
301                 _E("Wrong param name %s, method %s", name, method);
302                 return -EINVAL;
303         }
304         if (timeout_msec < -1) {
305                 _E("wrong timeout. timeout(>=0 or -1)");
306                 return -EINVAL;
307         }
308
309         if (suffix) {
310                 ret = _change_suffix(name, suffix, &new_name);
311                 if (ret < 0)
312                         return ret;
313                 name = new_name;
314         } else {
315                 if (strlen(name) > UNIT_NAME_MAX) {
316                         _E("Invalid name length %d(>%d)", strlen(name), UNIT_NAME_MAX);
317                         return -EINVAL;
318                 }
319         }
320
321         if (wait)
322                 ret = _systemd_control_unit_wait(method, name, timeout_msec);
323         else
324                 ret = _systemd_control_unit_async(method, name);
325
326         if (new_name)
327                 free(new_name);
328
329         return ret;
330 }
331
332 int systemd_start_unit_wait_started(const char *name, const char *suffix, int timeout_msec)
333 {
334         return _systemd_control_unit(name, suffix, timeout_msec, "StartUnit", TRUE);
335 }
336
337 int systemd_stop_unit_wait_stopped(const char *name, const char *suffix, int timeout_msec)
338 {
339         return _systemd_control_unit(name, suffix, timeout_msec, "StopUnit", TRUE);
340 }
341
342 int systemd_start_unit_async(const char *name, const char *suffix)
343 {
344         return _systemd_control_unit(name, suffix, -1, "StartUnit", FALSE);
345 }
346
347 int systemd_stop_unit_async(const char *name, const char *suffix)
348 {
349         return _systemd_control_unit(name, suffix, -1, "StopUnit", FALSE);
350 }
351
352 #define SYSTEMD_UNIT_ESCAPE_CHAR ".-"
353
354 static char *systemd_get_unit_dbus_path(const char *unit)
355 {
356         char *path = NULL;
357         int i;
358         size_t p, k, prefix_len, unit_len;
359         size_t path_len, len, escape;
360
361         if (!unit)
362                 return NULL;
363         unit_len = strlen(unit);
364
365         for (escape = 0, p = 0; p < unit_len; escape++) {
366                 k = strcspn(unit + p, SYSTEMD_UNIT_ESCAPE_CHAR);
367                 if (p + k >= unit_len)
368                         break;
369                 p += k+1;
370         }
371
372         prefix_len = strlen(SYSTEMD_DBUS_UNIT_PATH);
373         /* assume we try to get object path of foo-bar.service then
374         * the object path will be
375         * "/org/freedesktop/systemd1/unit/foo_2dbar_2eservice\n". In
376         * this case we can find two escape characters, one of escape
377         * char('-') is changed to three of char("_2d"). So the total
378         * length will be: */
379         /* (PREFIX) + (unit - escape + 3*escape) + NULL */
380         path_len = prefix_len + (unit_len - escape)
381                 + (escape * 3 * sizeof(char)) + 1;
382         path = (char *)calloc(path_len, sizeof(char));
383         if (!path)
384                 return NULL;
385
386         strncpy(path, SYSTEMD_DBUS_UNIT_PATH, prefix_len);
387         for (i = 0, p = 0; i <= escape; i++) {
388                 k = strcspn(unit + p, SYSTEMD_UNIT_ESCAPE_CHAR);
389                 strncpy(path + prefix_len, unit + p, k);
390                 if (k < strlen(unit + p)) {
391                         len = path_len - (prefix_len + k);
392                         snprintf(path + prefix_len + k, len,
393                                         "_%x", *(unit + p + k) & 0xff);
394                         prefix_len += k + 3;
395                         p += k+1;
396                 }
397         }
398
399         return path;
400 }
401
402 GVariant *systemd_get_manager_property(const char *property)
403 {
404         GVariant *reply = NULL;
405         GVariant *val = NULL;
406
407         if (!property)
408                 return NULL;
409
410         reply = dbus_handle_method_sync_with_reply_var(SYSTEMD_DBUS_DEST,
411                                                         SYSTEMD_DBUS_PATH,
412                                                         DBUS_IFACE_DBUS_PROPERTIES,
413                                                         "Get",
414                                                         g_variant_new("(ss)", SYSTEMD_DBUS_MANAGER_IFACE, property));
415         if (!reply || !dh_get_param_from_var(reply, "(v)", &val))
416                 _E("Failed to get variant");
417         if (reply)
418                 g_variant_unref(reply);
419
420         return val;
421 }
422
423 GVariant *systemd_get_unit_property(const char *unit, const char *property)
424 {
425         char *escaped;
426         GVariant *reply = NULL;
427         GVariant *val = NULL;
428
429         if (!unit || !property)
430                 return NULL;
431
432         escaped = systemd_get_unit_dbus_path(unit);
433
434         reply = dbus_handle_method_sync_with_reply_var(SYSTEMD_DBUS_DEST,
435                                                         escaped,
436                                                         DBUS_IFACE_DBUS_PROPERTIES,
437                                                         "Get",
438                                                         g_variant_new("(ss)", SYSTEMD_DBUS_UNIT_IFACE, property));
439
440         if (!reply || !dh_get_param_from_var(reply, "(v)", &val))
441                 _E("Failed to get variant");
442         if (reply)
443                 g_variant_unref(reply);
444         free(escaped);
445
446         return val;
447 }
448
449 GVariant *systemd_get_service_property(const char *unit, const char *property)
450 {
451         char *escaped;
452         GVariant *reply = NULL;
453         GVariant *val = NULL;
454
455         if (!unit || !property)
456                 return NULL;
457
458         escaped = systemd_get_unit_dbus_path(unit);
459
460         reply = dbus_handle_method_sync_with_reply_var(SYSTEMD_DBUS_DEST,
461                                                         escaped,
462                                                         DBUS_IFACE_DBUS_PROPERTIES,
463                                                         "Get",
464                                                         g_variant_new("(ss)", SYSTEMD_DBUS_SERVICE_IFACE, property));
465         if (!reply || !dh_get_param_from_var(reply, "(v)", &val))
466                 _E("Failed to get variant");
467         if (reply)
468                 g_variant_unref(reply);
469         free(escaped);
470         return val;
471 }