1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include "sd-bus.h"
4 
5 #include "alloc-util.h"
6 #include "bus-get-properties.h"
7 #include "bus-util.h"
8 #include "dbus-job.h"
9 #include "dbus-unit.h"
10 #include "dbus.h"
11 #include "job.h"
12 #include "log.h"
13 #include "selinux-access.h"
14 #include "string-util.h"
15 #include "strv.h"
16 
17 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, job_type, JobType);
18 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_state, job_state, JobState);
19 
property_get_unit(sd_bus * bus,const char * path,const char * interface,const char * property,sd_bus_message * reply,void * userdata,sd_bus_error * error)20 static int property_get_unit(
21                 sd_bus *bus,
22                 const char *path,
23                 const char *interface,
24                 const char *property,
25                 sd_bus_message *reply,
26                 void *userdata,
27                 sd_bus_error *error) {
28 
29         _cleanup_free_ char *p = NULL;
30         Job *j = userdata;
31 
32         assert(bus);
33         assert(reply);
34         assert(j);
35 
36         p = unit_dbus_path(j->unit);
37         if (!p)
38                 return -ENOMEM;
39 
40         return sd_bus_message_append(reply, "(so)", j->unit->id, p);
41 }
42 
bus_job_method_cancel(sd_bus_message * message,void * userdata,sd_bus_error * error)43 int bus_job_method_cancel(sd_bus_message *message, void *userdata, sd_bus_error *error) {
44         Job *j = userdata;
45         int r;
46 
47         assert(message);
48         assert(j);
49 
50         r = mac_selinux_unit_access_check(j->unit, message, "stop", error);
51         if (r < 0)
52                 return r;
53 
54         /* Access is granted to the job owner */
55         if (!sd_bus_track_contains(j->bus_track, sd_bus_message_get_sender(message))) {
56 
57                 /* And for everybody else consult polkit */
58                 r = bus_verify_manage_units_async(j->unit->manager, message, error);
59                 if (r < 0)
60                         return r;
61                 if (r == 0)
62                         return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
63         }
64 
65         job_finish_and_invalidate(j, JOB_CANCELED, true, false);
66 
67         return sd_bus_reply_method_return(message, NULL);
68 }
69 
bus_job_method_get_waiting_jobs(sd_bus_message * message,void * userdata,sd_bus_error * error)70 int bus_job_method_get_waiting_jobs(sd_bus_message *message, void *userdata, sd_bus_error *error) {
71         _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
72         _cleanup_free_ Job **list = NULL;
73         Job *j = userdata;
74         int r, n;
75 
76         if (strstr(sd_bus_message_get_member(message), "After"))
77                 n = job_get_after(j, &list);
78         else
79                 n = job_get_before(j, &list);
80         if (n < 0)
81                 return n;
82 
83         r = sd_bus_message_new_method_return(message, &reply);
84         if (r < 0)
85                 return r;
86 
87         r = sd_bus_message_open_container(reply, 'a', "(usssoo)");
88         if (r < 0)
89                 return r;
90 
91         for (int i = 0; i < n; i ++) {
92                 _cleanup_free_ char *unit_path = NULL, *job_path = NULL;
93 
94                 job_path = job_dbus_path(list[i]);
95                 if (!job_path)
96                         return -ENOMEM;
97 
98                 unit_path = unit_dbus_path(list[i]->unit);
99                 if (!unit_path)
100                         return -ENOMEM;
101 
102                 r = sd_bus_message_append(reply, "(usssoo)",
103                                           list[i]->id,
104                                           list[i]->unit->id,
105                                           job_type_to_string(list[i]->type),
106                                           job_state_to_string(list[i]->state),
107                                           job_path,
108                                           unit_path);
109                 if (r < 0)
110                         return r;
111         }
112 
113         r = sd_bus_message_close_container(reply);
114         if (r < 0)
115                 return r;
116 
117         return sd_bus_send(NULL, reply, NULL);
118 }
119 
120 const sd_bus_vtable bus_job_vtable[] = {
121         SD_BUS_VTABLE_START(0),
122 
123         SD_BUS_METHOD("Cancel", NULL, NULL, bus_job_method_cancel, SD_BUS_VTABLE_UNPRIVILEGED),
124         SD_BUS_METHOD_WITH_ARGS("GetAfter",
125                                  SD_BUS_NO_ARGS,
126                                  SD_BUS_RESULT("a(usssoo)", jobs),
127                                  bus_job_method_get_waiting_jobs,
128                                  SD_BUS_VTABLE_UNPRIVILEGED),
129         SD_BUS_METHOD_WITH_ARGS("GetBefore",
130                                  SD_BUS_NO_ARGS,
131                                  SD_BUS_RESULT("a(usssoo)", jobs),
132                                  bus_job_method_get_waiting_jobs,
133                                  SD_BUS_VTABLE_UNPRIVILEGED),
134 
135         SD_BUS_PROPERTY("Id", "u", NULL, offsetof(Job, id), SD_BUS_VTABLE_PROPERTY_CONST),
136         SD_BUS_PROPERTY("Unit", "(so)", property_get_unit, 0, SD_BUS_VTABLE_PROPERTY_CONST),
137         SD_BUS_PROPERTY("JobType", "s", property_get_type, offsetof(Job, type), SD_BUS_VTABLE_PROPERTY_CONST),
138         SD_BUS_PROPERTY("State", "s", property_get_state, offsetof(Job, state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
139         SD_BUS_VTABLE_END
140 };
141 
bus_job_find(sd_bus * bus,const char * path,const char * interface,void * userdata,void ** found,sd_bus_error * error)142 static int bus_job_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) {
143         Manager *m = userdata;
144         Job *j;
145         int r;
146 
147         assert(bus);
148         assert(path);
149         assert(interface);
150         assert(found);
151         assert(m);
152 
153         r = manager_get_job_from_dbus_path(m, path, &j);
154         if (r < 0)
155                 return 0;
156 
157         *found = j;
158         return 1;
159 }
160 
bus_job_enumerate(sd_bus * bus,const char * path,void * userdata,char *** nodes,sd_bus_error * error)161 static int bus_job_enumerate(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
162         _cleanup_strv_free_ char **l = NULL;
163         Manager *m = userdata;
164         unsigned k = 0;
165         Job *j;
166 
167         l = new0(char*, hashmap_size(m->jobs)+1);
168         if (!l)
169                 return -ENOMEM;
170 
171         HASHMAP_FOREACH(j, m->jobs) {
172                 l[k] = job_dbus_path(j);
173                 if (!l[k])
174                         return -ENOMEM;
175 
176                 k++;
177         }
178 
179         assert(hashmap_size(m->jobs) == k);
180 
181         *nodes = TAKE_PTR(l);
182 
183         return k;
184 }
185 
186 const BusObjectImplementation job_object = {
187         "/org/freedesktop/systemd1/job",
188         "org.freedesktop.systemd1.Job",
189         .fallback_vtables = BUS_FALLBACK_VTABLES({bus_job_vtable, bus_job_find}),
190         .node_enumerator = bus_job_enumerate,
191 };
192 
send_new_signal(sd_bus * bus,void * userdata)193 static int send_new_signal(sd_bus *bus, void *userdata) {
194         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
195         _cleanup_free_ char *p = NULL;
196         Job *j = userdata;
197         int r;
198 
199         assert(bus);
200         assert(j);
201 
202         p = job_dbus_path(j);
203         if (!p)
204                 return -ENOMEM;
205 
206         r = sd_bus_message_new_signal(
207                         bus,
208                         &m,
209                         "/org/freedesktop/systemd1",
210                         "org.freedesktop.systemd1.Manager",
211                         "JobNew");
212         if (r < 0)
213                 return r;
214 
215         r = sd_bus_message_append(m, "uos", j->id, p, j->unit->id);
216         if (r < 0)
217                 return r;
218 
219         return sd_bus_send(bus, m, NULL);
220 }
221 
send_changed_signal(sd_bus * bus,void * userdata)222 static int send_changed_signal(sd_bus *bus, void *userdata) {
223         _cleanup_free_ char *p = NULL;
224         Job *j = userdata;
225 
226         assert(bus);
227         assert(j);
228 
229         p = job_dbus_path(j);
230         if (!p)
231                 return -ENOMEM;
232 
233         return sd_bus_emit_properties_changed(bus, p, "org.freedesktop.systemd1.Job", "State", NULL);
234 }
235 
bus_job_send_change_signal(Job * j)236 void bus_job_send_change_signal(Job *j) {
237         int r;
238 
239         assert(j);
240 
241         /* Make sure that any change signal on the unit is reflected before we send out the change signal on the job */
242         bus_unit_send_pending_change_signal(j->unit, true);
243 
244         if (j->in_dbus_queue) {
245                 LIST_REMOVE(dbus_queue, j->manager->dbus_job_queue, j);
246                 j->in_dbus_queue = false;
247         }
248 
249         r = bus_foreach_bus(j->manager, j->bus_track, j->sent_dbus_new_signal ? send_changed_signal : send_new_signal, j);
250         if (r < 0)
251                 log_debug_errno(r, "Failed to send job change signal for %u: %m", j->id);
252 
253         j->sent_dbus_new_signal = true;
254 }
255 
bus_job_send_pending_change_signal(Job * j,bool including_new)256 void bus_job_send_pending_change_signal(Job *j, bool including_new) {
257         assert(j);
258 
259         if (!j->in_dbus_queue)
260                 return;
261 
262         if (!j->sent_dbus_new_signal && !including_new)
263                 return;
264 
265         if (MANAGER_IS_RELOADING(j->unit->manager))
266                 return;
267 
268         bus_job_send_change_signal(j);
269 }
270 
send_removed_signal(sd_bus * bus,void * userdata)271 static int send_removed_signal(sd_bus *bus, void *userdata) {
272         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
273         _cleanup_free_ char *p = NULL;
274         Job *j = userdata;
275         int r;
276 
277         assert(bus);
278         assert(j);
279 
280         p = job_dbus_path(j);
281         if (!p)
282                 return -ENOMEM;
283 
284         r = sd_bus_message_new_signal(
285                         bus,
286                         &m,
287                         "/org/freedesktop/systemd1",
288                         "org.freedesktop.systemd1.Manager",
289                         "JobRemoved");
290         if (r < 0)
291                 return r;
292 
293         r = sd_bus_message_append(m, "uoss", j->id, p, j->unit->id, job_result_to_string(j->result));
294         if (r < 0)
295                 return r;
296 
297         return sd_bus_send(bus, m, NULL);
298 }
299 
bus_job_send_removed_signal(Job * j)300 void bus_job_send_removed_signal(Job *j) {
301         int r;
302 
303         assert(j);
304 
305         if (!j->sent_dbus_new_signal)
306                 bus_job_send_change_signal(j);
307 
308         /* Make sure that any change signal on the unit is reflected before we send out the change signal on the job */
309         bus_unit_send_pending_change_signal(j->unit, true);
310 
311         r = bus_foreach_bus(j->manager, j->bus_track, send_removed_signal, j);
312         if (r < 0)
313                 log_debug_errno(r, "Failed to send job remove signal for %u: %m", j->id);
314 }
315 
bus_job_track_handler(sd_bus_track * t,void * userdata)316 static int bus_job_track_handler(sd_bus_track *t, void *userdata) {
317         Job *j = userdata;
318 
319         assert(t);
320         assert(j);
321 
322         j->bus_track = sd_bus_track_unref(j->bus_track); /* make sure we aren't called again */
323 
324         /* Last client dropped off the bus, maybe we should GC this now? */
325         job_add_to_gc_queue(j);
326         return 0;
327 }
328 
bus_job_allocate_bus_track(Job * j)329 static int bus_job_allocate_bus_track(Job *j) {
330 
331         assert(j);
332 
333         if (j->bus_track)
334                 return 0;
335 
336         return sd_bus_track_new(j->unit->manager->api_bus, &j->bus_track, bus_job_track_handler, j);
337 }
338 
bus_job_coldplug_bus_track(Job * j)339 int bus_job_coldplug_bus_track(Job *j) {
340         int r;
341         _cleanup_strv_free_ char **deserialized_clients = NULL;
342 
343         assert(j);
344 
345         deserialized_clients = TAKE_PTR(j->deserialized_clients);
346 
347         if (strv_isempty(deserialized_clients))
348                 return 0;
349 
350         if (!j->manager->api_bus)
351                 return 0;
352 
353         r = bus_job_allocate_bus_track(j);
354         if (r < 0)
355                 return r;
356 
357         return bus_track_add_name_many(j->bus_track, deserialized_clients);
358 }
359 
bus_job_track_sender(Job * j,sd_bus_message * m)360 int bus_job_track_sender(Job *j, sd_bus_message *m) {
361         int r;
362 
363         assert(j);
364         assert(m);
365 
366         if (sd_bus_message_get_bus(m) != j->unit->manager->api_bus) {
367                 j->ref_by_private_bus = true;
368                 return 0;
369         }
370 
371         r = bus_job_allocate_bus_track(j);
372         if (r < 0)
373                 return r;
374 
375         return sd_bus_track_add_sender(j->bus_track, m);
376 }
377