usb_moded  0.86.0+mer57
usb_moded-systemd.c
Go to the documentation of this file.
1 
26 #include "usb_moded-systemd.h"
27 
28 #include <time.h>
29 
30 #include <glib.h>
31 
32 #include "usb_moded-dbus-private.h"
33 #include "usb_moded-log.h"
34 
35 /* ========================================================================= *
36  * Constants
37  * ========================================================================= */
38 
39 #define SYSTEMD_DBUS_SERVICE "org.freedesktop.systemd1"
40 #define SYSTEMD_DBUS_PATH "/org/freedesktop/systemd1"
41 #define SYSTEMD_DBUS_INTERFACE "org.freedesktop.systemd1.Manager"
42 #define SYSTEMD_DBUS_SIGNAL_JOB_REMOVED "JobRemoved"
43 #define SYSTEMD_DBUS_SIGNAL_JOB_REMOVED_MATCH \
44  "type='signal'," \
45  "path='" SYSTEMD_DBUS_PATH "'," \
46  "interface='" SYSTEMD_DBUS_INTERFACE "'," \
47  "member='" SYSTEMD_DBUS_SIGNAL_JOB_REMOVED "'"
48 
49 /* ========================================================================= *
50  * Prototypes
51  * ========================================================================= */
52 
53 /* ------------------------------------------------------------------------- *
54  * SYSTEMD
55  * ------------------------------------------------------------------------- */
56 
57 gboolean systemd_control_service(const char *name, const char *method, gboolean add_to_waitlist);
58 gboolean systemd_control_wait_for_waitlist(void);
59 gboolean systemd_control_start (void);
60 void systemd_control_stop (void);
61 
62 /* ========================================================================= *
63  * Data
64  * ========================================================================= */
65 
66 /* SystemBus connection ref used for systemd control ipc */
67 static DBusConnection *systemd_con = NULL;
68 
69 /* Systemd job wait list, containing jobs to wait for. Using the fact that
70  * earlier-started unit usually finishes first, stores them in a queue.
71  */
72 static GQueue systemd_waitlist = G_QUEUE_INIT;
73 static gboolean systemd_waitlist_has_error = FALSE;
74 
75 /* ========================================================================= *
76  * Functions
77  * ========================================================================= */
78 
79 // QDBusObjectPath org.freedesktop.systemd1.Manager.StartUnit(QString name, QString mode)
80 // QDBusObjectPath org.freedesktop.systemd1.Manager.StopUnit(QString name, QString mode)
81 
82 // mode = replace
83 // method = StartUnit or StopUnit
84 gboolean systemd_control_service(const char *name, const char *method, gboolean add_to_waitlist)
85 {
86  LOG_REGISTER_CONTEXT;
87 
88  DBusMessage *req = NULL;
89  DBusMessage *rsp = NULL;
90  DBusError err = DBUS_ERROR_INIT;
91  const char *arg = "replace";
92  const char *res = 0;
93 
94  log_debug("%s(%s) ...", method, name);
95 
96  if( !systemd_con ) {
97  log_err("not connected to system bus; skip systemd unit control");
98  goto EXIT;
99  }
100 
101  if (add_to_waitlist && g_queue_get_length(&systemd_waitlist) == 0) {
102  // Add match now to avoid racing with JobRemoved being dispatched.
103  dbus_bus_add_match(
104  systemd_con, SYSTEMD_DBUS_SIGNAL_JOB_REMOVED_MATCH, /* error */ NULL);
105  dbus_connection_flush(systemd_con);
106  }
107 
108  req = dbus_message_new_method_call(SYSTEMD_DBUS_SERVICE,
109  SYSTEMD_DBUS_PATH,
110  SYSTEMD_DBUS_INTERFACE,
111  method);
112  if( !req ) {
113  log_err("failed to construct %s.%s request",
114  SYSTEMD_DBUS_INTERFACE,
115  method);
116  goto EXIT;
117  }
118 
119  if( !dbus_message_append_args(req,
120  DBUS_TYPE_STRING, &name,
121  DBUS_TYPE_STRING, &arg,
122  DBUS_TYPE_INVALID))
123  {
124  log_debug("error appending arguments");
125  goto EXIT;
126  }
127 
128  rsp = dbus_connection_send_with_reply_and_block(systemd_con, req, -1, &err);
129  if( !rsp ) {
130  log_err("no reply to %s.%s request: %s: %s",
131  SYSTEMD_DBUS_INTERFACE,
132  method,
133  err.name, err.message);
134  goto EXIT;
135  }
136 
137  if( dbus_set_error_from_message(&err, rsp) ) {
138  log_err("got error reply to %s.%s request: %s: %s",
139  SYSTEMD_DBUS_INTERFACE,
140  method,
141  err.name, err.message);
142  goto EXIT;
143  }
144 
145  if( !dbus_message_get_args(rsp, &err,
146  DBUS_TYPE_OBJECT_PATH, &res,
147  DBUS_TYPE_INVALID) ) {
148  log_err("failed to parse reply to %s.%s request: %s: %s",
149  SYSTEMD_DBUS_INTERFACE,
150  method,
151  err.name, err.message);
152  goto EXIT;
153  }
154 
155 EXIT:
156 
157  dbus_error_free(&err);
158 
159  log_debug("%s(%s) -> %s", method, name, res ?: "N/A");
160 
161  if (add_to_waitlist) {
162  if (res) {
163  g_queue_push_tail(&systemd_waitlist, g_strdup(res));
164  } else if (g_queue_get_length(&systemd_waitlist) == 0) {
165  dbus_bus_remove_match(
166  systemd_con, SYSTEMD_DBUS_SIGNAL_JOB_REMOVED_MATCH, /* error */ NULL);
167  dbus_connection_flush(systemd_con);
168  }
169  }
170 
171  if( rsp ) dbus_message_unref(rsp);
172  if( req ) dbus_message_unref(req);
173 
174  return res != 0;
175 }
176 
177 gboolean systemd_control_wait_for_waitlist(void)
178 {
179  struct timespec wait_until;
180  gboolean has_error = FALSE;
181 
182  // Wait for at most 5 seconds, otherwise we assume that the job hangs.
183  clock_gettime(CLOCK_MONOTONIC, &wait_until);
184  wait_until.tv_sec += 5;
185 
186  log_debug("Wait 5 seconds for systemd jobs in waitlist to starts");
187 
188  while (g_queue_get_length(&systemd_waitlist) != 0) {
189  dbus_bool_t still_connected = dbus_connection_read_write_dispatch(systemd_con, 1000 /* ms */);
190 
191  struct timespec now;
192  clock_gettime(CLOCK_MONOTONIC, &now);
193 
194  if (!still_connected || now.tv_sec > wait_until.tv_sec)
195  {
196  log_err("Waiting for systemd jobs fails");
197  log_debug("The following jobs are in the wait list at the time:");
198 
199  GList *list;
200  for (list = systemd_waitlist.head; list; list = list->next) {
201  // Print the list and free the name itself in one go.
202 
203  char *job = list->data;
204  log_debug("- %s", job);
205 
206  g_free(job);
207  }
208 
209  g_queue_clear(&systemd_waitlist);
210 
211  has_error = TRUE;
212 
213  break;
214  }
215  }
216 
217  has_error |= systemd_waitlist_has_error;
218  systemd_waitlist_has_error = FALSE;
219 
220  if (has_error)
221  log_err("Some job fails. See the log above to find out more.");
222 
223  dbus_bus_remove_match(
224  systemd_con, SYSTEMD_DBUS_SIGNAL_JOB_REMOVED_MATCH, /* error */ NULL);
225  dbus_connection_flush(systemd_con);
226 
227  return !has_error;
228 }
229 
230 /* ========================================================================= *
231  * start/stop systemd control availability
232  * ========================================================================= */
233 
234 static DBusHandlerResult systemd_dbus_msg_handler(DBusConnection *const connection, DBusMessage *const msg, gpointer const user_data)
235 {
236  (void) connection;
237  (void) user_data;
238 
239  DBusError err = DBUS_ERROR_INIT;
240  DBusHandlerResult status = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
241 
242  dbus_uint32_t id;
243  const char *job = NULL;
244  const char *unit = NULL;
245  const char *result = NULL;
246 
247  GList *entry = NULL;
248  char *match_rules = NULL;
249  gboolean success;
250 
251  if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL)
252  goto EXIT;
253 
254  if (g_strcmp0(dbus_message_get_path(msg), SYSTEMD_DBUS_PATH) != 0)
255  goto EXIT;
256 
257  if (g_strcmp0(dbus_message_get_interface(msg), SYSTEMD_DBUS_INTERFACE) != 0)
258  goto EXIT;
259 
260  if (g_strcmp0(dbus_message_get_member(msg), SYSTEMD_DBUS_SIGNAL_JOB_REMOVED) != 0)
261  goto EXIT;
262 
263  dbus_message_get_args(msg, &err,
264  /* id */ DBUS_TYPE_UINT32, &id,
265  /* job */ DBUS_TYPE_OBJECT_PATH, &job,
266  /* unit */ DBUS_TYPE_STRING, &unit,
267  /* result */ DBUS_TYPE_STRING, &result,
268  DBUS_TYPE_INVALID);
269 
270  if (dbus_error_is_set(&err)) {
271  log_err("Fails to parse systemd JobRemoved signal: %s", err.message);
272  dbus_error_free(&err);
273  goto EXIT;
274  }
275 
276  log_debug("Received JobRemoved for job '%s', unit '%s'. the result is '%s'.",
277  job, unit, result);
278  success = g_strcmp0(result, "done") == 0 || g_strcmp0(result, "skipped") == 0;
279 
280  entry = g_queue_find_custom(&systemd_waitlist, job, (GCompareFunc) g_strcmp0);
281  if (!entry) {
282  log_debug("Job '%s' is not of our interest.", job);
283  status = DBUS_HANDLER_RESULT_HANDLED;
284  goto EXIT;
285  }
286 
287  g_free(entry->data);
288  g_queue_delete_link(&systemd_waitlist, entry);
289 
290  if (!success) {
291  log_err("Job '%s' for unit '%s' fails with result '%s'", job, unit, result);
292  systemd_waitlist_has_error = TRUE;
293  }
294 
295  status = DBUS_HANDLER_RESULT_HANDLED;
296 
297 EXIT:
298  return status;
299 }
300 
301 gboolean
302 systemd_control_start(void)
303 {
304  LOG_REGISTER_CONTEXT;
305 
306  gboolean ack = FALSE;
307  DBusError error = DBUS_ERROR_INIT;
308 
309  log_debug("starting systemd control");
310 
311  /* Get connection ref. Get a private one not shared with umdbus so that
312  * signals won't be dispatched on the main thread.
313  */
314  if( (systemd_con = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error)) == 0 )
315  {
316  log_err("Could not connect to dbus for systemd control: %s\n", error.message);
317  dbus_error_free(&error);
318  goto cleanup;
319  }
320 
321  if (!dbus_connection_add_filter(systemd_con, systemd_dbus_msg_handler, NULL, NULL))
322  goto cleanup;
323 
324  ack = TRUE;
325 
326 cleanup:
327 
328  return ack;
329 }
330 
331 void
332 systemd_control_stop(void)
333 {
334  LOG_REGISTER_CONTEXT;
335 
336  log_debug("stopping systemd control");
337 
338  if(systemd_con)
339  {
340  /* Let go of connection ref */
341  dbus_connection_close(systemd_con);
342  dbus_connection_unref(systemd_con),
343  systemd_con = 0;
344  }
345 }
usb_moded-dbus-private.h
usb_moded-log.h
usb_moded-systemd.h