chiark / gitweb /
Do no isolate in case of emergency or severe problems
[elogind.git] / src / core / dbus-unit.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23
24 #include "dbus.h"
25 #include "log.h"
26 #include "dbus-unit.h"
27 #include "bus-errors.h"
28 #include "dbus-common.h"
29 #include "selinux-access.h"
30 #include "cgroup-util.h"
31 #include "strv.h"
32 #include "path-util.h"
33 #include "fileio.h"
34
35 const char bus_unit_interface[] _introspect_("Unit") = BUS_UNIT_INTERFACE;
36
37 #define INVALIDATING_PROPERTIES                 \
38         "LoadState\0"                           \
39         "ActiveState\0"                         \
40         "SubState\0"                            \
41         "InactiveExitTimestamp\0"               \
42         "ActiveEnterTimestamp\0"                \
43         "ActiveExitTimestamp\0"                 \
44         "InactiveEnterTimestamp\0"              \
45         "Job\0"                                 \
46         "NeedDaemonReload\0"
47
48 static int bus_unit_append_names(DBusMessageIter *i, const char *property, void *data) {
49         char *t;
50         Iterator j;
51         DBusMessageIter sub;
52         Unit *u = data;
53
54         if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub))
55                 return -ENOMEM;
56
57         SET_FOREACH(t, u->names, j)
58                 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &t))
59                         return -ENOMEM;
60
61         if (!dbus_message_iter_close_container(i, &sub))
62                 return -ENOMEM;
63
64         return 0;
65 }
66
67 static int bus_unit_append_following(DBusMessageIter *i, const char *property, void *data) {
68         Unit *u = data, *f;
69         const char *d;
70
71         assert(i);
72         assert(property);
73         assert(u);
74
75         f = unit_following(u);
76         d = f ? f->id : "";
77
78         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d))
79                 return -ENOMEM;
80
81         return 0;
82 }
83
84 static int bus_unit_append_dependencies(DBusMessageIter *i, const char *property, void *data) {
85         Unit *u;
86         Iterator j;
87         DBusMessageIter sub;
88         Set *s = data;
89
90         if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub))
91                 return -ENOMEM;
92
93         SET_FOREACH(u, s, j)
94                 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &u->id))
95                         return -ENOMEM;
96
97         if (!dbus_message_iter_close_container(i, &sub))
98                 return -ENOMEM;
99
100         return 0;
101 }
102
103 static int bus_unit_append_description(DBusMessageIter *i, const char *property, void *data) {
104         Unit *u = data;
105         const char *d;
106
107         assert(i);
108         assert(property);
109         assert(u);
110
111         d = unit_description(u);
112
113         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d))
114                 return -ENOMEM;
115
116         return 0;
117 }
118
119 static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_unit_append_load_state, unit_load_state, UnitLoadState);
120
121 static int bus_unit_append_active_state(DBusMessageIter *i, const char *property, void *data) {
122         Unit *u = data;
123         const char *state;
124
125         assert(i);
126         assert(property);
127         assert(u);
128
129         state = unit_active_state_to_string(unit_active_state(u));
130
131         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state))
132                 return -ENOMEM;
133
134         return 0;
135 }
136
137 static int bus_unit_append_sub_state(DBusMessageIter *i, const char *property, void *data) {
138         Unit *u = data;
139         const char *state;
140
141         assert(i);
142         assert(property);
143         assert(u);
144
145         state = unit_sub_state_to_string(u);
146
147         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state))
148                 return -ENOMEM;
149
150         return 0;
151 }
152
153 static int bus_unit_append_file_state(DBusMessageIter *i, const char *property, void *data) {
154         Unit *u = data;
155         const char *state;
156
157         assert(i);
158         assert(property);
159         assert(u);
160
161         state = strempty(unit_file_state_to_string(unit_get_unit_file_state(u)));
162
163         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state))
164                 return -ENOMEM;
165
166         return 0;
167 }
168
169 static int bus_unit_append_can_start(DBusMessageIter *i, const char *property, void *data) {
170         Unit *u = data;
171         dbus_bool_t b;
172
173         assert(i);
174         assert(property);
175         assert(u);
176
177         b = unit_can_start(u) &&
178                 !u->refuse_manual_start;
179
180         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
181                 return -ENOMEM;
182
183         return 0;
184 }
185
186 static int bus_unit_append_can_stop(DBusMessageIter *i, const char *property, void *data) {
187         Unit *u = data;
188         dbus_bool_t b;
189
190         assert(i);
191         assert(property);
192         assert(u);
193
194         /* On the lower levels we assume that every unit we can start
195          * we can also stop */
196
197         b = unit_can_start(u) &&
198                 !u->refuse_manual_stop;
199
200         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
201                 return -ENOMEM;
202
203         return 0;
204 }
205
206 static int bus_unit_append_can_reload(DBusMessageIter *i, const char *property, void *data) {
207         Unit *u = data;
208         dbus_bool_t b;
209
210         assert(i);
211         assert(property);
212         assert(u);
213
214         b = unit_can_reload(u);
215
216         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
217                 return -ENOMEM;
218
219         return 0;
220 }
221
222 static int bus_unit_append_can_isolate(DBusMessageIter *i, const char *property, void *data) {
223         Unit *u = data;
224         dbus_bool_t b;
225
226         assert(i);
227         assert(property);
228         assert(u);
229
230         b = unit_can_isolate(u) &&
231                 !u->refuse_manual_start;
232
233         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
234                 return -ENOMEM;
235
236         return 0;
237 }
238
239 static int bus_unit_append_job(DBusMessageIter *i, const char *property, void *data) {
240         Unit *u = data;
241         DBusMessageIter sub;
242         _cleanup_free_ char *p = NULL;
243
244         assert(i);
245         assert(property);
246         assert(u);
247
248         if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub))
249                 return -ENOMEM;
250
251         if (u->job) {
252
253                 p = job_dbus_path(u->job);
254                 if (!p)
255                         return -ENOMEM;
256
257                 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &u->job->id) ||
258                     !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p))
259                         return -ENOMEM;
260         } else {
261                 uint32_t id = 0;
262
263                 /* No job, so let's fill in some placeholder
264                  * data. Since we need to fill in a valid path we
265                  * simple point to ourselves. */
266
267                 p = unit_dbus_path(u);
268                 if (!p)
269                         return -ENOMEM;
270
271                 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &id) ||
272                     !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p))
273                         return -ENOMEM;
274         }
275
276         if (!dbus_message_iter_close_container(i, &sub))
277                 return -ENOMEM;
278
279         return 0;
280 }
281
282 static int bus_unit_append_default_cgroup(DBusMessageIter *i, const char *property, void *data) {
283         Unit *u = data;
284         char *t;
285         CGroupBonding *cgb;
286         bool success;
287
288         assert(i);
289         assert(property);
290         assert(u);
291
292         cgb = unit_get_default_cgroup(u);
293         if (cgb) {
294                 t = cgroup_bonding_to_string(cgb);
295                 if (!t)
296                         return -ENOMEM;
297         } else
298                 t = (char*) "";
299
300         success = dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t);
301
302         if (cgb)
303                 free(t);
304
305         return success ? 0 : -ENOMEM;
306 }
307
308 static int bus_unit_append_cgroups(DBusMessageIter *i, const char *property, void *data) {
309         Unit *u = data;
310         CGroupBonding *cgb;
311         DBusMessageIter sub;
312
313         if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub))
314                 return -ENOMEM;
315
316         LIST_FOREACH(by_unit, cgb, u->cgroup_bondings) {
317                 char _cleanup_free_ *t = NULL;
318                 bool success;
319
320                 t = cgroup_bonding_to_string(cgb);
321                 if (!t)
322                         return -ENOMEM;
323
324                 success = dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &t);
325                 if (!success)
326                         return -ENOMEM;
327         }
328
329         if (!dbus_message_iter_close_container(i, &sub))
330                 return -ENOMEM;
331
332         return 0;
333 }
334
335 static int bus_unit_append_cgroup_attrs(DBusMessageIter *i, const char *property, void *data) {
336         Unit *u = data;
337         CGroupAttribute *a;
338         DBusMessageIter sub, sub2;
339
340         if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(sss)", &sub))
341                 return -ENOMEM;
342
343         LIST_FOREACH(by_unit, a, u->cgroup_attributes) {
344                 char _cleanup_free_ *v = NULL;
345                 bool success;
346
347                 if (a->semantics && a->semantics->map_write)
348                         a->semantics->map_write(a->semantics, a->value, &v);
349
350                 success =
351                         dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) &&
352                         dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &a->controller) &&
353                         dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &a->name) &&
354                         dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, v ? &v : &a->value) &&
355                         dbus_message_iter_close_container(&sub, &sub2);
356                 if (!success)
357                         return -ENOMEM;
358         }
359
360         if (!dbus_message_iter_close_container(i, &sub))
361                 return -ENOMEM;
362
363         return 0;
364 }
365
366 static int bus_unit_append_need_daemon_reload(DBusMessageIter *i, const char *property, void *data) {
367         Unit *u = data;
368         dbus_bool_t b;
369
370         assert(i);
371         assert(property);
372         assert(u);
373
374         b = unit_need_daemon_reload(u);
375
376         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
377                 return -ENOMEM;
378
379         return 0;
380 }
381
382 static int bus_unit_append_load_error(DBusMessageIter *i, const char *property, void *data) {
383         Unit *u = data;
384         const char *name, *message;
385         DBusMessageIter sub;
386
387         assert(i);
388         assert(property);
389         assert(u);
390
391         if (u->load_error != 0) {
392                 name = bus_errno_to_dbus(u->load_error);
393                 message = strempty(strerror(-u->load_error));
394         } else
395                 name = message = "";
396
397         if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub) ||
398             !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name) ||
399             !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &message) ||
400             !dbus_message_iter_close_container(i, &sub))
401                 return -ENOMEM;
402
403         return 0;
404 }
405
406 static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *connection, DBusMessage *message) {
407         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
408         DBusError error;
409         JobType job_type = _JOB_TYPE_INVALID;
410         bool reload_if_possible = false;
411         int r;
412
413         dbus_error_init(&error);
414
415         if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Start"))
416                 job_type = JOB_START;
417         else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Stop"))
418                 job_type = JOB_STOP;
419         else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Reload"))
420                 job_type = JOB_RELOAD;
421         else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Restart"))
422                 job_type = JOB_RESTART;
423         else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "TryRestart"))
424                 job_type = JOB_TRY_RESTART;
425         else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ReloadOrRestart")) {
426                 reload_if_possible = true;
427                 job_type = JOB_RESTART;
428         } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ReloadOrTryRestart")) {
429                 reload_if_possible = true;
430                 job_type = JOB_TRY_RESTART;
431         } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Kill")) {
432                 const char *swho;
433                 int32_t signo;
434                 KillWho who;
435
436                 if (!dbus_message_get_args(
437                                     message,
438                                     &error,
439                                     DBUS_TYPE_STRING, &swho,
440                                     DBUS_TYPE_INT32, &signo,
441                                     DBUS_TYPE_INVALID))
442                         return bus_send_error_reply(connection, message, &error, -EINVAL);
443
444                 if (isempty(swho))
445                         who = KILL_ALL;
446                 else {
447                         who = kill_who_from_string(swho);
448                         if (who < 0)
449                                 return bus_send_error_reply(connection, message, &error, -EINVAL);
450                 }
451
452                 if (signo <= 0 || signo >= _NSIG)
453                         return bus_send_error_reply(connection, message, &error, -EINVAL);
454
455                 SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "stop");
456
457                 r = unit_kill(u, who, signo, &error);
458                 if (r < 0)
459                         return bus_send_error_reply(connection, message, &error, r);
460
461                 reply = dbus_message_new_method_return(message);
462                 if (!reply)
463                         goto oom;
464
465         } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ResetFailed")) {
466
467                 SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "reload");
468
469                 unit_reset_failed(u);
470
471                 reply = dbus_message_new_method_return(message);
472                 if (!reply)
473                         goto oom;
474
475         } else if (streq_ptr(dbus_message_get_member(message), "SetControlGroup")) {
476                 DBusMessageIter iter;
477
478                 SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start");
479
480                 if (!dbus_message_iter_init(message, &iter))
481                         goto oom;
482
483                 r = bus_unit_cgroup_set(u, &iter);
484                 if (r < 0)
485                         return bus_send_error_reply(connection, message, NULL, r);
486
487                 reply = dbus_message_new_method_return(message);
488                 if (!reply)
489                         goto oom;
490
491         } else if (streq_ptr(dbus_message_get_member(message), "UnsetControlGroup")) {
492                 DBusMessageIter iter;
493
494                 SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "stop");
495
496                 if (!dbus_message_iter_init(message, &iter))
497                         goto oom;
498
499                 r = bus_unit_cgroup_unset(u, &iter);
500                 if (r < 0)
501                         return bus_send_error_reply(connection, message, NULL, r);
502
503                 reply = dbus_message_new_method_return(message);
504                 if (!reply)
505                         goto oom;
506         } else if (streq_ptr(dbus_message_get_member(message), "GetControlGroupAttribute")) {
507                 DBusMessageIter iter;
508                 _cleanup_strv_free_ char **list = NULL;
509
510                 SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "status");
511
512                 if (!dbus_message_iter_init(message, &iter))
513                         goto oom;
514
515                 r = bus_unit_cgroup_attribute_get(u, &iter, &list);
516                 if (r < 0)
517                         return bus_send_error_reply(connection, message, NULL, r);
518
519                 reply = dbus_message_new_method_return(message);
520                 if (!reply)
521                         goto oom;
522
523                 dbus_message_iter_init_append(reply, &iter);
524                 if (bus_append_strv_iter(&iter, list) < 0)
525                         goto oom;
526
527         } else if (streq_ptr(dbus_message_get_member(message), "SetControlGroupAttribute")) {
528                 DBusMessageIter iter;
529
530                 SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start");
531
532                 if (!dbus_message_iter_init(message, &iter))
533                         goto oom;
534
535                 r = bus_unit_cgroup_attribute_set(u, &iter);
536                 if (r < 0)
537                         return bus_send_error_reply(connection, message, NULL, r);
538
539                 reply = dbus_message_new_method_return(message);
540                 if (!reply)
541                         goto oom;
542
543         } else if (streq_ptr(dbus_message_get_member(message), "UnsetControlGroupAttribute")) {
544                 DBusMessageIter iter;
545
546                 SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "stop");
547
548                 if (!dbus_message_iter_init(message, &iter))
549                         goto oom;
550
551                 r = bus_unit_cgroup_attribute_unset(u, &iter);
552                 if (r < 0)
553                         return bus_send_error_reply(connection, message, NULL, r);
554
555                 reply = dbus_message_new_method_return(message);
556                 if (!reply)
557                         goto oom;
558
559         } else if (UNIT_VTABLE(u)->bus_message_handler)
560                 return UNIT_VTABLE(u)->bus_message_handler(u, connection, message);
561         else
562                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
563
564         if (job_type != _JOB_TYPE_INVALID) {
565                 const char *smode;
566                 JobMode mode;
567
568                 if (!dbus_message_get_args(
569                                     message,
570                                     &error,
571                                     DBUS_TYPE_STRING, &smode,
572                                     DBUS_TYPE_INVALID))
573                         return bus_send_error_reply(connection, message, &error, -EINVAL);
574
575                 mode = job_mode_from_string(smode);
576                 if (mode < 0) {
577                         dbus_set_error(&error, BUS_ERROR_INVALID_JOB_MODE, "Job mode %s is invalid.", smode);
578                         return bus_send_error_reply(connection, message, &error, -EINVAL);
579                 }
580
581                 return bus_unit_queue_job(connection, message, u, job_type, mode, reload_if_possible);
582         }
583
584         if (reply)
585                 if (!bus_maybe_send_reply(connection, message, reply))
586                         goto oom;
587
588         return DBUS_HANDLER_RESULT_HANDLED;
589
590 oom:
591         dbus_error_free(&error);
592         return DBUS_HANDLER_RESULT_NEED_MEMORY;
593 }
594
595 static DBusHandlerResult bus_unit_message_handler(DBusConnection *connection, DBusMessage  *message, void *data) {
596         Manager *m = data;
597         Unit *u;
598         int r;
599         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
600         DBusError error;
601
602         assert(connection);
603         assert(message);
604         assert(m);
605
606         dbus_error_init(&error);
607
608         if (streq(dbus_message_get_path(message), "/org/freedesktop/systemd1/unit")) {
609                 /* Be nice to gdbus and return introspection data for our mid-level paths */
610
611                 SELINUX_ACCESS_CHECK(connection, message, "status");
612
613                 if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) {
614                         char *introspection = NULL;
615                         FILE *f;
616                         Iterator i;
617                         const char *k;
618                         size_t size;
619
620                         reply = dbus_message_new_method_return(message);
621                         if (!reply)
622                                 goto oom;
623
624                         /* We roll our own introspection code here, instead of
625                          * relying on bus_default_message_handler() because we
626                          * need to generate our introspection string
627                          * dynamically. */
628
629                         f = open_memstream(&introspection, &size);
630                         if (!f)
631                                 goto oom;
632
633                         fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
634                               "<node>\n", f);
635
636                         fputs(BUS_INTROSPECTABLE_INTERFACE, f);
637                         fputs(BUS_PEER_INTERFACE, f);
638
639                         HASHMAP_FOREACH_KEY(u, k, m->units, i) {
640                                 char *p;
641
642                                 if (k != u->id)
643                                         continue;
644
645                                 p = bus_path_escape(k);
646                                 if (!p) {
647                                         fclose(f);
648                                         free(introspection);
649                                         goto oom;
650                                 }
651
652                                 fprintf(f, "<node name=\"%s\"/>", p);
653                                 free(p);
654                         }
655
656                         fputs("</node>\n", f);
657
658                         if (ferror(f)) {
659                                 fclose(f);
660                                 free(introspection);
661                                 goto oom;
662                         }
663
664                         fclose(f);
665
666                         if (!introspection)
667                                 goto oom;
668
669                         if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) {
670                                 free(introspection);
671                                 goto oom;
672                         }
673
674                         free(introspection);
675
676                         if (!bus_maybe_send_reply(connection, message, reply))
677                                 goto oom;
678
679                         return DBUS_HANDLER_RESULT_HANDLED;
680                 }
681
682                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
683         }
684
685         r = manager_load_unit_from_dbus_path(m, dbus_message_get_path(message), &error, &u);
686         if (r == -ENOMEM)
687                 goto oom;
688         if (r < 0)
689                 return bus_send_error_reply(connection, message, &error, r);
690
691         return bus_unit_message_dispatch(u, connection, message);
692
693 oom:
694         dbus_error_free(&error);
695
696         return DBUS_HANDLER_RESULT_NEED_MEMORY;
697 }
698
699 const DBusObjectPathVTable bus_unit_vtable = {
700         .message_function = bus_unit_message_handler
701 };
702
703 void bus_unit_send_change_signal(Unit *u) {
704         _cleanup_free_ char *p = NULL;
705         _cleanup_dbus_message_unref_ DBusMessage *m = NULL;
706
707         assert(u);
708
709         if (u->in_dbus_queue) {
710                 LIST_REMOVE(Unit, dbus_queue, u->manager->dbus_unit_queue, u);
711                 u->in_dbus_queue = false;
712         }
713
714         if (!u->id)
715                 return;
716
717         if (!bus_has_subscriber(u->manager)) {
718                 u->sent_dbus_new_signal = true;
719                 return;
720         }
721
722         p = unit_dbus_path(u);
723         if (!p)
724                 goto oom;
725
726         if (u->sent_dbus_new_signal) {
727                 /* Send a properties changed signal. First for the
728                  * specific type, then for the generic unit. The
729                  * clients may rely on this order to get atomic
730                  * behavior if needed. */
731
732                 if (UNIT_VTABLE(u)->bus_invalidating_properties) {
733
734                         m = bus_properties_changed_new(p,
735                                                        UNIT_VTABLE(u)->bus_interface,
736                                                        UNIT_VTABLE(u)->bus_invalidating_properties);
737                         if (!m)
738                                 goto oom;
739
740                         if (bus_broadcast(u->manager, m) < 0)
741                                 goto oom;
742
743                         dbus_message_unref(m);
744                 }
745
746                 m = bus_properties_changed_new(p, "org.freedesktop.systemd1.Unit",
747                                                INVALIDATING_PROPERTIES);
748                 if (!m)
749                         goto oom;
750
751         } else {
752                 /* Send a new signal */
753
754                 m = dbus_message_new_signal("/org/freedesktop/systemd1",
755                                             "org.freedesktop.systemd1.Manager",
756                                             "UnitNew");
757                 if (!m)
758                         goto oom;
759
760                 if (!dbus_message_append_args(m,
761                                               DBUS_TYPE_STRING, &u->id,
762                                               DBUS_TYPE_OBJECT_PATH, &p,
763                                               DBUS_TYPE_INVALID))
764                         goto oom;
765         }
766
767         if (bus_broadcast(u->manager, m) < 0)
768                 goto oom;
769
770         u->sent_dbus_new_signal = true;
771
772         return;
773
774 oom:
775         log_oom();
776 }
777
778 void bus_unit_send_removed_signal(Unit *u) {
779         _cleanup_free_ char *p = NULL;
780         _cleanup_dbus_message_unref_ DBusMessage *m = NULL;
781
782         assert(u);
783
784         if (!bus_has_subscriber(u->manager))
785                 return;
786
787         if (!u->sent_dbus_new_signal)
788                 bus_unit_send_change_signal(u);
789
790         if (!u->id)
791                 return;
792
793         p = unit_dbus_path(u);
794         if (!p)
795                 goto oom;
796
797         m = dbus_message_new_signal("/org/freedesktop/systemd1",
798                                     "org.freedesktop.systemd1.Manager",
799                                     "UnitRemoved");
800         if (!m)
801                 goto oom;
802
803         if (!dbus_message_append_args(m,
804                                       DBUS_TYPE_STRING, &u->id,
805                                       DBUS_TYPE_OBJECT_PATH, &p,
806                                       DBUS_TYPE_INVALID))
807                 goto oom;
808
809         if (bus_broadcast(u->manager, m) < 0)
810                 goto oom;
811
812         return;
813
814 oom:
815         log_oom();
816 }
817
818 DBusHandlerResult bus_unit_queue_job(
819                 DBusConnection *connection,
820                 DBusMessage *message,
821                 Unit *u,
822                 JobType type,
823                 JobMode mode,
824                 bool reload_if_possible) {
825
826         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
827         _cleanup_free_ char *path = NULL;
828         Job *j;
829         JobBusClient *cl;
830         DBusError error;
831         int r;
832
833         assert(connection);
834         assert(message);
835         assert(u);
836         assert(type >= 0 && type < _JOB_TYPE_MAX);
837         assert(mode >= 0 && mode < _JOB_MODE_MAX);
838
839         dbus_error_init(&error);
840
841         if (reload_if_possible && unit_can_reload(u)) {
842                 if (type == JOB_RESTART)
843                         type = JOB_RELOAD_OR_START;
844                 else if (type == JOB_TRY_RESTART)
845                         type = JOB_RELOAD;
846         }
847
848         SELINUX_UNIT_ACCESS_CHECK(u, connection, message,
849                                   (type == JOB_START || type == JOB_RESTART || type == JOB_TRY_RESTART) ? "start" :
850                                   type == JOB_STOP ? "stop" : "reload");
851
852         if (type == JOB_STOP && u->load_state == UNIT_ERROR && unit_active_state(u) == UNIT_INACTIVE) {
853                 dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s not loaded.", u->id);
854                 return bus_send_error_reply(connection, message, &error, -EPERM);
855         }
856
857         if ((type == JOB_START && u->refuse_manual_start) ||
858             (type == JOB_STOP && u->refuse_manual_stop) ||
859             ((type == JOB_RESTART || type == JOB_TRY_RESTART) && (u->refuse_manual_start || u->refuse_manual_stop))) {
860                 dbus_set_error(&error, BUS_ERROR_ONLY_BY_DEPENDENCY,
861                                "Operation refused, unit %s may be requested by dependency only.", u->id);
862                 return bus_send_error_reply(connection, message, &error, -EPERM);
863         }
864
865         r = manager_add_job(u->manager, type, u, mode, true, &error, &j);
866         if (r < 0)
867                 return bus_send_error_reply(connection, message, &error, r);
868
869         cl = job_bus_client_new(connection, bus_message_get_sender_with_fallback(message));
870         if (!cl)
871                 goto oom;
872
873         LIST_PREPEND(JobBusClient, client, j->bus_client_list, cl);
874
875         reply = dbus_message_new_method_return(message);
876         if (!reply)
877                 goto oom;
878
879         path = job_dbus_path(j);
880         if (!path)
881                 goto oom;
882
883         if (!dbus_message_append_args(
884                             reply,
885                             DBUS_TYPE_OBJECT_PATH, &path,
886                             DBUS_TYPE_INVALID))
887                 goto oom;
888
889         if (!bus_maybe_send_reply(connection, message, reply))
890                 goto oom;
891
892         return DBUS_HANDLER_RESULT_HANDLED;
893
894 oom:
895         dbus_error_free(&error);
896
897         return DBUS_HANDLER_RESULT_NEED_MEMORY;
898 }
899
900 static int parse_mode(DBusMessageIter *iter, bool *runtime, bool next) {
901         const char *mode;
902         int r;
903
904         assert(iter);
905         assert(runtime);
906
907         r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &mode, next);
908         if (r < 0)
909                 return r;
910
911         if (streq(mode, "runtime"))
912                 *runtime = true;
913         else if (streq(mode, "persistent"))
914                 *runtime = false;
915         else
916                 return -EINVAL;
917
918         return 0;
919 }
920
921 int bus_unit_cgroup_set(Unit *u, DBusMessageIter *iter) {
922         _cleanup_free_ char *controller = NULL, *old_path = NULL, *new_path = NULL, *contents = NULL;
923         const char *name;
924         CGroupBonding *b;
925         bool runtime;
926         int r;
927
928         assert(u);
929         assert(iter);
930
931         if (!unit_get_exec_context(u))
932                 return -EINVAL;
933
934         r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, true);
935         if (r < 0)
936                 return r;
937
938         r = parse_mode(iter, &runtime, false);
939         if (r < 0)
940                 return r;
941
942         r = cg_split_spec(name, &controller, &new_path);
943         if (r < 0)
944                 return r;
945
946         if (!new_path) {
947                 new_path = unit_default_cgroup_path(u);
948                 if (!new_path)
949                         return -ENOMEM;
950         }
951
952         if (!controller || streq(controller, SYSTEMD_CGROUP_CONTROLLER))
953                 return -EINVAL;
954
955         b = cgroup_bonding_find_list(u->cgroup_bondings, controller);
956         if (b) {
957                 if (streq(b->path, new_path))
958                         return 0;
959
960                 if (b->essential)
961                         return -EINVAL;
962
963                 old_path = strdup(b->path);
964                 if (!old_path)
965                         return -ENOMEM;
966         }
967
968         r = unit_add_cgroup_from_text(u, name, true, &b);
969         if (r < 0)
970                 return r;
971         if (r > 0) {
972                 CGroupAttribute *a;
973
974                 /* Try to move things to the new place, and clean up the old place */
975                 cgroup_bonding_realize(b);
976                 cgroup_bonding_migrate(b, u->cgroup_bondings);
977
978                 if (old_path)
979                         cg_trim(controller, old_path, true);
980
981                 /* Apply the attributes to the new group */
982                 LIST_FOREACH(by_unit, a, u->cgroup_attributes)
983                         if (streq(a->controller, controller))
984                                 cgroup_attribute_apply(a, b);
985         }
986
987         contents = strjoin("[", UNIT_VTABLE(u)->exec_section, "]\n"
988                            "ControlGroup=", name, "\n", NULL);
989         if (!contents)
990                 return -ENOMEM;
991
992         return unit_write_drop_in(u, runtime, controller, contents);
993 }
994
995 int bus_unit_cgroup_unset(Unit *u, DBusMessageIter *iter) {
996         _cleanup_free_ char *controller = NULL, *path = NULL, *target = NULL;
997         const char *name;
998         CGroupAttribute *a, *n;
999         CGroupBonding *b;
1000         bool runtime;
1001         int r;
1002
1003         assert(u);
1004         assert(iter);
1005
1006         if (!unit_get_exec_context(u))
1007                 return -EINVAL;
1008
1009         r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, true);
1010         if (r < 0)
1011                 return r;
1012
1013         r = parse_mode(iter, &runtime, false);
1014         if (r < 0)
1015                 return r;
1016
1017         r = cg_split_spec(name, &controller, &path);
1018         if (r < 0)
1019                 return r;
1020
1021         if (!controller || streq(controller, SYSTEMD_CGROUP_CONTROLLER))
1022                 return -EINVAL;
1023
1024         b = cgroup_bonding_find_list(u->cgroup_bondings, controller);
1025         if (!b)
1026                 return -ENOENT;
1027
1028         if (path && !path_equal(path, b->path))
1029                 return -ENOENT;
1030
1031         if (b->essential)
1032                 return -EINVAL;
1033
1034         unit_remove_drop_in(u, runtime, controller);
1035
1036         /* Try to migrate the old group away */
1037         if (cg_get_by_pid(controller, 0, &target) >= 0)
1038                 cgroup_bonding_migrate_to(u->cgroup_bondings, target, false);
1039
1040         cgroup_bonding_free(b, true);
1041
1042         /* Drop all attributes of this controller */
1043         LIST_FOREACH_SAFE(by_unit, a, n, u->cgroup_attributes) {
1044                 if (!streq(a->controller, controller))
1045                         continue;
1046
1047                 unit_remove_drop_in(u, runtime, a->name);
1048                 cgroup_attribute_free(a);
1049         }
1050
1051         return 0;
1052 }
1053
1054 int bus_unit_cgroup_attribute_get(Unit *u, DBusMessageIter *iter, char ***_result) {
1055         _cleanup_free_ char *controller = NULL;
1056         CGroupAttribute *a;
1057         CGroupBonding *b;
1058         const char *name;
1059         char **l = NULL;
1060         int r;
1061
1062         assert(u);
1063         assert(iter);
1064         assert(_result);
1065
1066         if (!unit_get_exec_context(u))
1067                 return -EINVAL;
1068
1069         r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, false);
1070         if (r < 0)
1071                 return r;
1072
1073         r = cg_controller_from_attr(name, &controller);
1074         if (r < 0)
1075                 return r;
1076
1077         /* First attempt, read the value from the kernel */
1078         b = cgroup_bonding_find_list(u->cgroup_bondings, controller);
1079         if (b) {
1080                 _cleanup_free_ char *p = NULL, *v = NULL;
1081
1082                 r = cg_get_path(b->controller, b->path, name, &p);
1083                 if (r < 0)
1084                         return r;
1085
1086                 r = read_full_file(p, &v, NULL);
1087                 if (r >= 0) {
1088                         /* Split on new lines */
1089                         l = strv_split_newlines(v);
1090                         if (!l)
1091                                 return -ENOMEM;
1092
1093                         *_result = l;
1094                         return 0;
1095
1096                 }
1097         }
1098
1099         /* If that didn't work, read our cached value */
1100         LIST_FOREACH(by_unit, a, u->cgroup_attributes) {
1101
1102                 if (!cgroup_attribute_matches(a, controller, name))
1103                         continue;
1104
1105                 r = strv_extend(&l, a->value);
1106                 if (r < 0) {
1107                         strv_free(l);
1108                         return r;
1109                 }
1110         }
1111
1112         if (!l)
1113                 return -ENOENT;
1114
1115         *_result = l;
1116         return 0;
1117 }
1118
1119 static int update_attribute_drop_in(Unit *u, bool runtime, const char *name) {
1120         _cleanup_free_ char *buf = NULL;
1121         CGroupAttribute *a;
1122
1123         assert(u);
1124         assert(name);
1125
1126         LIST_FOREACH(by_unit, a, u->cgroup_attributes) {
1127                 if (!cgroup_attribute_matches(a, NULL, name))
1128                         continue;
1129
1130                 if (!buf) {
1131                         buf = strjoin("[", UNIT_VTABLE(u)->exec_section, "]\n"
1132                                       "ControlGroupAttribute=", a->name, " ", a->value, "\n", NULL);
1133
1134                         if (!buf)
1135                                 return -ENOMEM;
1136                 } else {
1137                         char *b;
1138
1139                         b = strjoin(buf,
1140                                     "ControlGroupAttribute=", a->name, " ", a->value, "\n", NULL);
1141
1142                         if (!b)
1143                                 return -ENOMEM;
1144
1145                         free(buf);
1146                         buf = b;
1147                 }
1148         }
1149
1150         if (buf)
1151                 return unit_write_drop_in(u, runtime, name, buf);
1152         else
1153                 return unit_remove_drop_in(u, runtime, name);
1154 }
1155
1156 int bus_unit_cgroup_attribute_set(Unit *u, DBusMessageIter *iter) {
1157         _cleanup_strv_free_ char **l = NULL;
1158         int r;
1159         bool runtime = false;
1160         char **value;
1161         const char *name;
1162
1163         assert(u);
1164         assert(iter);
1165
1166         if (!unit_get_exec_context(u))
1167                 return -EINVAL;
1168
1169         r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, true);
1170         if (r < 0)
1171                 return r;
1172
1173         r = bus_parse_strv_iter(iter, &l);
1174         if (r < 0)
1175                 return r;
1176
1177         if (!dbus_message_iter_next(iter))
1178                 return -EINVAL;
1179
1180         r = parse_mode(iter, &runtime, false);
1181         if (r < 0)
1182                 return r;
1183
1184         STRV_FOREACH(value, l) {
1185                 _cleanup_free_ char *v = NULL;
1186                 CGroupAttribute *a;
1187                 const CGroupSemantics *s;
1188
1189                 r = cgroup_semantics_find(NULL, name, *value, &v, &s);
1190                 if (r < 0)
1191                         return r;
1192
1193                 if (s && !s->multiple && l[1])
1194                         return -EINVAL;
1195
1196                 r = unit_add_cgroup_attribute(u, s, NULL, name, v ? v : *value, &a);
1197                 if (r < 0)
1198                         return r;
1199
1200                 if (r > 0) {
1201                         CGroupBonding *b;
1202
1203                         b = cgroup_bonding_find_list(u->cgroup_bondings, a->controller);
1204                         if (!b) {
1205                                 /* Doesn't exist yet? Then let's add it */
1206                                 r = unit_add_cgroup_from_text(u, a->controller, false, &b);
1207                                 if (r < 0)
1208                                         return r;
1209
1210                                 if (r > 0) {
1211                                         cgroup_bonding_realize(b);
1212                                         cgroup_bonding_migrate(b, u->cgroup_bondings);
1213                                 }
1214                         }
1215
1216                         /* Make it count */
1217                         cgroup_attribute_apply(a, u->cgroup_bondings);
1218                 }
1219
1220         }
1221
1222         r = update_attribute_drop_in(u, runtime, name);
1223         if (r < 0)
1224                 return r;
1225
1226         return 0;
1227 }
1228
1229 int bus_unit_cgroup_attribute_unset(Unit *u, DBusMessageIter *iter) {
1230         const char *name;
1231         bool runtime;
1232         int r;
1233
1234         assert(u);
1235         assert(iter);
1236
1237         if (!unit_get_exec_context(u))
1238                 return -EINVAL;
1239
1240         r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, true);
1241         if (r < 0)
1242                 return r;
1243
1244         r = parse_mode(iter, &runtime, false);
1245         if (r < 0)
1246                 return r;
1247
1248         cgroup_attribute_free_some(u->cgroup_attributes, NULL, name);
1249         update_attribute_drop_in(u, runtime, name);
1250
1251         return 0;
1252 }
1253
1254 const BusProperty bus_unit_properties[] = {
1255         { "Id",                   bus_property_append_string,         "s", offsetof(Unit, id),                                         true },
1256         { "Names",                bus_unit_append_names,             "as", 0 },
1257         { "Following",            bus_unit_append_following,          "s", 0 },
1258         { "Requires",             bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_REQUIRES]),                true },
1259         { "RequiresOverridable",  bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_REQUIRES_OVERRIDABLE]),    true },
1260         { "Requisite",            bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_REQUISITE]),               true },
1261         { "RequisiteOverridable", bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_REQUISITE_OVERRIDABLE]),   true },
1262         { "Wants",                bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_WANTS]),                   true },
1263         { "BindsTo",              bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_BINDS_TO]),                true },
1264         { "PartOf",               bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_PART_OF]),                 true },
1265         { "RequiredBy",           bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_REQUIRED_BY]),             true },
1266         { "RequiredByOverridable",bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_REQUIRED_BY_OVERRIDABLE]), true },
1267         { "WantedBy",             bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_WANTED_BY]),               true },
1268         { "BoundBy",              bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_BOUND_BY]),                true },
1269         { "ConsistsOf",           bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_CONSISTS_OF]),             true },
1270         { "Conflicts",            bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_CONFLICTS]),               true },
1271         { "ConflictedBy",         bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_CONFLICTED_BY]),           true },
1272         { "Before",               bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_BEFORE]),                  true },
1273         { "After",                bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_AFTER]),                   true },
1274         { "OnFailure",            bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_ON_FAILURE]),              true },
1275         { "Triggers",             bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_TRIGGERS]),                true },
1276         { "TriggeredBy",          bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_TRIGGERED_BY]),            true },
1277         { "PropagatesReloadTo",   bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_PROPAGATES_RELOAD_TO]),    true },
1278         { "ReloadPropagatedFrom", bus_unit_append_dependencies,      "as", offsetof(Unit, dependencies[UNIT_RELOAD_PROPAGATED_FROM]),  true },
1279         { "RequiresMountsFor",    bus_property_append_strv,          "as", offsetof(Unit, requires_mounts_for),                        true },
1280         { "Documentation",        bus_property_append_strv,          "as", offsetof(Unit, documentation),                              true },
1281         { "Description",          bus_unit_append_description,        "s", 0 },
1282         { "LoadState",            bus_unit_append_load_state,         "s", offsetof(Unit, load_state)                         },
1283         { "ActiveState",          bus_unit_append_active_state,       "s", 0 },
1284         { "SubState",             bus_unit_append_sub_state,          "s", 0 },
1285         { "FragmentPath",         bus_property_append_string,         "s", offsetof(Unit, fragment_path),                              true },
1286         { "SourcePath",           bus_property_append_string,         "s", offsetof(Unit, source_path),                                true },
1287         { "UnitFileState",        bus_unit_append_file_state,         "s", 0 },
1288         { "InactiveExitTimestamp",bus_property_append_usec,           "t", offsetof(Unit, inactive_exit_timestamp.realtime)   },
1289         { "InactiveExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, inactive_exit_timestamp.monotonic)  },
1290         { "ActiveEnterTimestamp", bus_property_append_usec,           "t", offsetof(Unit, active_enter_timestamp.realtime)    },
1291         { "ActiveEnterTimestampMonotonic", bus_property_append_usec,  "t", offsetof(Unit, active_enter_timestamp.monotonic)   },
1292         { "ActiveExitTimestamp",  bus_property_append_usec,           "t", offsetof(Unit, active_exit_timestamp.realtime)     },
1293         { "ActiveExitTimestampMonotonic",  bus_property_append_usec,  "t", offsetof(Unit, active_exit_timestamp.monotonic)    },
1294         { "InactiveEnterTimestamp", bus_property_append_usec,         "t", offsetof(Unit, inactive_enter_timestamp.realtime)  },
1295         { "InactiveEnterTimestampMonotonic",bus_property_append_usec, "t", offsetof(Unit, inactive_enter_timestamp.monotonic) },
1296         { "CanStart",             bus_unit_append_can_start,          "b", 0 },
1297         { "CanStop",              bus_unit_append_can_stop,           "b", 0 },
1298         { "CanReload",            bus_unit_append_can_reload,         "b", 0 },
1299         { "CanIsolate",           bus_unit_append_can_isolate,        "b", 0 },
1300         { "Job",                  bus_unit_append_job,             "(uo)", 0 },
1301         { "StopWhenUnneeded",     bus_property_append_bool,           "b", offsetof(Unit, stop_when_unneeded)                 },
1302         { "RefuseManualStart",    bus_property_append_bool,           "b", offsetof(Unit, refuse_manual_start)                },
1303         { "RefuseManualStop",     bus_property_append_bool,           "b", offsetof(Unit, refuse_manual_stop)                 },
1304         { "AllowIsolate",         bus_property_append_bool,           "b", offsetof(Unit, allow_isolate)                      },
1305         { "DefaultDependencies",  bus_property_append_bool,           "b", offsetof(Unit, default_dependencies)               },
1306         { "OnFailureIsolate",     bus_property_append_bool,           "b", offsetof(Unit, on_failure_isolate)                 },
1307         { "IgnoreOnIsolate",      bus_property_append_bool,           "b", offsetof(Unit, ignore_on_isolate)                  },
1308         { "IgnoreOnSnapshot",     bus_property_append_bool,           "b", offsetof(Unit, ignore_on_snapshot)                 },
1309         { "NeedDaemonReload",     bus_unit_append_need_daemon_reload, "b", 0 },
1310         { "JobTimeoutUSec",       bus_property_append_usec,           "t", offsetof(Unit, job_timeout)                        },
1311         { "ConditionTimestamp",   bus_property_append_usec,           "t", offsetof(Unit, condition_timestamp.realtime)       },
1312         { "ConditionTimestampMonotonic", bus_property_append_usec,    "t", offsetof(Unit, condition_timestamp.monotonic)      },
1313         { "ConditionResult",      bus_property_append_bool,           "b", offsetof(Unit, condition_result)                   },
1314         { "LoadError",            bus_unit_append_load_error,      "(ss)", 0 },
1315         { NULL, }
1316 };
1317
1318 const BusProperty bus_unit_cgroup_properties[] = {
1319         { "DefaultControlGroup",    bus_unit_append_default_cgroup,     "s", 0 },
1320         { "ControlGroups",          bus_unit_append_cgroups,           "as", 0 },
1321         { "ControlGroupAttributes", bus_unit_append_cgroup_attrs,  "a(sss)", 0 },
1322         { NULL, }
1323 };