1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
7 #include "cpu-set-util.h"
8 #include "device-util.h"
9 #include "devnum-util.h"
11 #include "format-util.h"
13 #include "hostname-util.h"
15 #include "missing_sched.h"
16 #include "nspawn-oci.h"
17 #include "path-util.h"
18 #include "rlimit-util.h"
19 #include "seccomp-util.h"
20 #include "stdio-util.h"
21 #include "string-util.h"
23 #include "user-util.h"
26 * OCI runtime tool implementation
31 * How is RLIM_INFINITY supposed to be encoded?
32 * configured effective caps is bullshit, as execv() corrupts it anyway
33 * pipes bind mounted is *very* different from pipes newly created, comments regarding bind mount or not are bogus
34 * annotation values structured? or string?
35 * configurable file system namespace path, but then also root path? wtf?
36 * apply sysctl inside of the container? or outside?
37 * how is unlimited pids tasks limit to be encoded?
38 * what are the defaults for caps if not specified?
39 * what are the default uid/gid mappings if one is missing but the other set, or when user ns is on but no namespace configured
40 * the source field of "mounts" is really weird, as it cannot realistically be relative to the bundle, since we never know if that's what the fs wants
41 * spec contradicts itself on the mount "type" field, as the example uses "bind" as type, but it's not listed in /proc/filesystem, and is something made up by /bin/mount
42 * if type of mount is left out, what shall be assumed? "bind"?
43 * readonly mounts is entirely redundant?
44 * should escaping be applied when joining mount options with ","?
45 * devices cgroup support is bogus, "allow" and "deny" on the kernel level is about adding/removing entries, not about access
46 * spec needs to say that "rwm" devices cgroup combination can't be the empty string
47 * cgrouspv1 crap: kernel, kernelTCP, swappiness, disableOOMKiller, swap, devices, leafWeight
48 * general: it shouldn't leak lower level abstractions this obviously
49 * unmanagable cgroups stuff: realtimeRuntime/realtimePeriod
50 * needs to say what happense when some option is not specified, i.e. which defaults apply
51 * no architecture? no personality?
52 * seccomp example and logic is simply broken: there's no constant "SCMP_ACT_ERRNO".
53 * spec should say what to do with unknown props
54 * /bin/mount regarding NFS and FUSE required?
55 * what does terminal=false mean?
56 * sysctl inside or outside? allow-listing?
57 * swapiness typo -> swappiness
62 * selinuxLabel + mountLabel
67 * swappiness, disableOOMKiller, kernel, kernelTCP, leafWeight (because it's dead, cgroupsv2 can't do it and hence systemd neither)
69 * Non-slice cgroup paths
70 * Propagation that is not slave + shared
71 * more than one uid/gid mapping, mappings with a container base != 0, or non-matching uid/gid mappings
72 * device cgroups access = false items that are not catchall
73 * device cgroups matches where minor is specified, but major isn't. similar where major is specified but char/block is not. also, any match that only has a type set that has less than "rwm" set. also, any entry that has none of rwm set.
77 static int oci_unexpected(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
78 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
79 "Unexpected OCI element '%s' of type '%s'.", name
, json_variant_type_to_string(json_variant_type(v
)));
82 static int oci_dispatch(JsonVariant
*v
, const JsonDispatch table
[], JsonDispatchFlags flags
, void *userdata
) {
83 return json_dispatch_full(v
, table
, oci_unexpected
, flags
, userdata
, /* reterr_bad_field= */ NULL
);
86 static int oci_unsupported(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
87 return json_log(v
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
),
88 "Unsupported OCI element '%s' of type '%s'.", name
, json_variant_type_to_string(json_variant_type(v
)));
91 static int oci_terminal(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
92 Settings
*s
= ASSERT_PTR(userdata
);
94 /* If not specified, or set to true, we'll default to either an interactive or a read-only
95 * console. If specified as false, we'll forcibly move to "pipe" mode though. */
96 s
->console_mode
= json_variant_boolean(v
) ? _CONSOLE_MODE_INVALID
: CONSOLE_PIPE
;
100 static int oci_console_dimension(const char *name
, JsonVariant
*variant
, JsonDispatchFlags flags
, void *userdata
) {
101 unsigned *u
= ASSERT_PTR(userdata
);
104 k
= json_variant_unsigned(variant
);
106 return json_log(variant
, flags
, SYNTHETIC_ERRNO(ERANGE
),
107 "Console size field '%s' is too small.", strna(name
));
108 if (k
> USHRT_MAX
) /* TIOCSWINSZ's struct winsize uses "unsigned short" for width and height */
109 return json_log(variant
, flags
, SYNTHETIC_ERRNO(ERANGE
),
110 "Console size field '%s' is too large.", strna(name
));
116 static int oci_console_size(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
117 Settings
*s
= ASSERT_PTR(userdata
);
119 static const JsonDispatch table
[] = {
120 { "height", JSON_VARIANT_UNSIGNED
, oci_console_dimension
, offsetof(Settings
, console_height
), JSON_MANDATORY
},
121 { "width", JSON_VARIANT_UNSIGNED
, oci_console_dimension
, offsetof(Settings
, console_width
), JSON_MANDATORY
},
125 return oci_dispatch(v
, table
, flags
, s
);
128 static int oci_absolute_path(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
129 char **p
= ASSERT_PTR(userdata
);
132 n
= json_variant_string(v
);
134 if (!path_is_absolute(n
))
135 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
136 "Path in JSON field '%s' is not absolute: %s", strna(name
), n
);
138 return free_and_strdup_warn(p
, n
);
141 static int oci_env(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
142 char ***l
= ASSERT_PTR(userdata
);
146 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
149 if (!json_variant_is_string(e
))
150 return json_log(e
, flags
, SYNTHETIC_ERRNO(EINVAL
),
151 "Environment array contains non-string.");
153 assert_se(n
= json_variant_string(e
));
155 if (!env_assignment_is_valid(n
))
156 return json_log(e
, flags
, SYNTHETIC_ERRNO(EINVAL
),
157 "Environment assignment not valid: %s", n
);
159 r
= strv_extend(l
, n
);
167 static int oci_args(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
168 _cleanup_strv_free_
char **l
= NULL
;
169 char ***value
= ASSERT_PTR(userdata
);
172 r
= json_variant_strv(v
, &l
);
174 return json_log(v
, flags
, r
, "Cannot parse arguments as list of strings: %m");
177 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
178 "Argument list empty, refusing.");
181 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
182 "Executable name is empty, refusing.");
184 return strv_free_and_replace(*value
, l
);
187 static int oci_rlimit_type(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
189 int *type
= ASSERT_PTR(userdata
);
192 z
= startswith(json_variant_string(v
), "RLIMIT_");
194 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
195 "rlimit entry's name does not begin with 'RLIMIT_', refusing: %s",
196 json_variant_string(v
));
198 t
= rlimit_from_string(z
);
200 return json_log(v
, flags
, t
,
201 "rlimit name unknown: %s", json_variant_string(v
));
207 static int oci_rlimit_value(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
208 rlim_t
*value
= ASSERT_PTR(userdata
);
211 if (json_variant_is_negative(v
))
214 if (!json_variant_is_unsigned(v
))
215 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
216 "rlimits limit not unsigned, refusing.");
218 z
= (rlim_t
) json_variant_unsigned(v
);
220 if ((uint64_t) z
!= json_variant_unsigned(v
))
221 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
222 "rlimits limit out of range, refusing.");
229 static int oci_rlimits(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
230 Settings
*s
= ASSERT_PTR(userdata
);
234 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
242 .soft
= RLIM_INFINITY
,
243 .hard
= RLIM_INFINITY
,
246 static const JsonDispatch table
[] = {
247 { "soft", JSON_VARIANT_NUMBER
, oci_rlimit_value
, offsetof(struct rlimit_data
, soft
), JSON_MANDATORY
},
248 { "hard", JSON_VARIANT_NUMBER
, oci_rlimit_value
, offsetof(struct rlimit_data
, hard
), JSON_MANDATORY
},
249 { "type", JSON_VARIANT_STRING
, oci_rlimit_type
, offsetof(struct rlimit_data
, type
), JSON_MANDATORY
},
253 r
= oci_dispatch(e
, table
, flags
, &data
);
257 assert(data
.type
>= 0);
258 assert(data
.type
< _RLIMIT_MAX
);
260 if (s
->rlimit
[data
.type
])
261 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
262 "rlimits array contains duplicate entry, refusing.");
264 s
->rlimit
[data
.type
] = new(struct rlimit
, 1);
265 if (!s
->rlimit
[data
.type
])
268 *s
->rlimit
[data
.type
] = (struct rlimit
) {
269 .rlim_cur
= data
.soft
,
270 .rlim_max
= data
.hard
,
277 static int oci_capability_array(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
278 uint64_t *mask
= ASSERT_PTR(userdata
);
282 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
286 if (!json_variant_is_string(e
))
287 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
288 "Entry in capabilities array is not a string.");
290 assert_se(n
= json_variant_string(e
));
292 cap
= capability_from_name(n
);
294 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
295 "Unknown capability: %s", n
);
297 m
|= UINT64_C(1) << cap
;
300 if (*mask
== UINT64_MAX
)
308 static int oci_capabilities(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
310 static const JsonDispatch table
[] = {
311 { "effective", JSON_VARIANT_ARRAY
, oci_capability_array
, offsetof(CapabilityQuintet
, effective
) },
312 { "bounding", JSON_VARIANT_ARRAY
, oci_capability_array
, offsetof(CapabilityQuintet
, bounding
) },
313 { "inheritable", JSON_VARIANT_ARRAY
, oci_capability_array
, offsetof(CapabilityQuintet
, inheritable
) },
314 { "permitted", JSON_VARIANT_ARRAY
, oci_capability_array
, offsetof(CapabilityQuintet
, permitted
) },
315 { "ambient", JSON_VARIANT_ARRAY
, oci_capability_array
, offsetof(CapabilityQuintet
, ambient
) },
319 Settings
*s
= ASSERT_PTR(userdata
);
322 r
= oci_dispatch(v
, table
, flags
, &s
->full_capabilities
);
326 if (s
->full_capabilities
.bounding
!= UINT64_MAX
) {
327 s
->capability
= s
->full_capabilities
.bounding
;
328 s
->drop_capability
= ~s
->full_capabilities
.bounding
;
334 static int oci_oom_score_adj(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
335 Settings
*s
= ASSERT_PTR(userdata
);
338 k
= json_variant_integer(v
);
339 if (k
< OOM_SCORE_ADJ_MIN
|| k
> OOM_SCORE_ADJ_MAX
)
340 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
341 "oomScoreAdj value out of range: %" PRIi64
, k
);
343 s
->oom_score_adjust
= (int) k
;
344 s
->oom_score_adjust_set
= true;
349 static int oci_uid_gid(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
350 uid_t
*uid
= ASSERT_PTR(userdata
);
354 assert_cc(sizeof(uid_t
) == sizeof(gid_t
));
356 k
= json_variant_unsigned(v
);
358 if ((uint64_t) u
!= k
)
359 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
360 "UID/GID out of range: %" PRIu64
, k
);
362 if (!uid_is_valid(u
))
363 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
364 "UID/GID is not valid: " UID_FMT
, u
);
370 static int oci_supplementary_gids(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
371 Settings
*s
= ASSERT_PTR(userdata
);
375 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
378 if (!json_variant_is_unsigned(e
))
379 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
380 "Supplementary GID entry is not a UID.");
382 r
= oci_uid_gid(name
, e
, flags
, &gid
);
386 a
= reallocarray(s
->supplementary_gids
, s
->n_supplementary_gids
+ 1, sizeof(gid_t
));
390 s
->supplementary_gids
= a
;
391 s
->supplementary_gids
[s
->n_supplementary_gids
++] = gid
;
397 static int oci_user(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
399 static const JsonDispatch table
[] = {
400 { "uid", JSON_VARIANT_UNSIGNED
, oci_uid_gid
, offsetof(Settings
, uid
), JSON_MANDATORY
},
401 { "gid", JSON_VARIANT_UNSIGNED
, oci_uid_gid
, offsetof(Settings
, gid
), JSON_MANDATORY
},
402 { "additionalGids", JSON_VARIANT_ARRAY
, oci_supplementary_gids
, 0, 0 },
406 return oci_dispatch(v
, table
, flags
, userdata
);
409 static int oci_process(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
411 static const JsonDispatch table
[] = {
412 { "terminal", JSON_VARIANT_BOOLEAN
, oci_terminal
, 0, 0 },
413 { "consoleSize", JSON_VARIANT_OBJECT
, oci_console_size
, 0, 0 },
414 { "cwd", JSON_VARIANT_STRING
, oci_absolute_path
, offsetof(Settings
, working_directory
), 0 },
415 { "env", JSON_VARIANT_ARRAY
, oci_env
, offsetof(Settings
, environment
), 0 },
416 { "args", JSON_VARIANT_ARRAY
, oci_args
, offsetof(Settings
, parameters
), 0 },
417 { "rlimits", JSON_VARIANT_ARRAY
, oci_rlimits
, 0, 0 },
418 { "apparmorProfile", JSON_VARIANT_STRING
, oci_unsupported
, 0, JSON_PERMISSIVE
},
419 { "capabilities", JSON_VARIANT_OBJECT
, oci_capabilities
, 0, 0 },
420 { "noNewPrivileges", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(Settings
, no_new_privileges
), 0 },
421 { "oomScoreAdj", JSON_VARIANT_INTEGER
, oci_oom_score_adj
, 0, 0 },
422 { "selinuxLabel", JSON_VARIANT_STRING
, oci_unsupported
, 0, JSON_PERMISSIVE
},
423 { "user", JSON_VARIANT_OBJECT
, oci_user
, 0, 0 },
427 return oci_dispatch(v
, table
, flags
, userdata
);
430 static int oci_root(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
431 Settings
*s
= ASSERT_PTR(userdata
);
434 static const JsonDispatch table
[] = {
435 { "path", JSON_VARIANT_STRING
, json_dispatch_string
, offsetof(Settings
, root
) },
436 { "readonly", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(Settings
, read_only
) },
440 r
= oci_dispatch(v
, table
, flags
, s
);
444 if (s
->root
&& !path_is_absolute(s
->root
)) {
447 joined
= path_join(s
->bundle
, s
->root
);
451 free_and_replace(s
->root
, joined
);
457 static int oci_hostname(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
458 Settings
*s
= ASSERT_PTR(userdata
);
461 assert_se(n
= json_variant_string(v
));
463 if (!hostname_is_valid(n
, 0))
464 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
465 "Hostname string is not a valid hostname: %s", n
);
467 return free_and_strdup_warn(&s
->hostname
, n
);
470 static bool oci_exclude_mount(const char *path
) {
472 /* Returns "true" for all mounts we insist to mount on our own, and hence ignore the OCI data. */
474 if (PATH_IN_SET(path
,
492 "/proc/sysrq-trigger",
501 /* Similar, skip the whole /sys/fs/cgroups subtree */
502 if (path_startswith(path
, "/sys/fs/cgroup"))
508 typedef struct oci_mount_data
{
515 static void oci_mount_data_done(oci_mount_data
*data
) {
518 free(data
->destination
);
521 strv_free(data
->options
);
524 static int oci_mounts(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
525 Settings
*s
= ASSERT_PTR(userdata
);
529 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
530 static const JsonDispatch table
[] = {
531 { "destination", JSON_VARIANT_STRING
, oci_absolute_path
, offsetof(oci_mount_data
, destination
), JSON_MANDATORY
},
532 { "source", JSON_VARIANT_STRING
, json_dispatch_string
, offsetof(oci_mount_data
, source
), 0 },
533 { "options", JSON_VARIANT_ARRAY
, json_dispatch_strv
, offsetof(oci_mount_data
, options
), 0, },
534 { "type", JSON_VARIANT_STRING
, json_dispatch_string
, offsetof(oci_mount_data
, type
), 0 },
538 _cleanup_free_
char *joined_options
= NULL
;
539 _cleanup_(oci_mount_data_done
) oci_mount_data data
= {};
542 r
= oci_dispatch(e
, table
, flags
, &data
);
546 if (!path_is_absolute(data
.destination
))
547 return json_log(e
, flags
, SYNTHETIC_ERRNO(EINVAL
),
548 "Mount destination not an absolute path: %s", data
.destination
);
550 if (oci_exclude_mount(data
.destination
))
554 joined_options
= strv_join(data
.options
, ",");
559 if (!data
.type
|| streq(data
.type
, "bind")) {
560 if (data
.source
&& !path_is_absolute(data
.source
)) {
563 joined
= path_join(s
->bundle
, data
.source
);
567 free_and_replace(data
.source
, joined
);
570 data
.type
= mfree(data
.type
);
572 m
= custom_mount_add(&s
->custom_mounts
, &s
->n_custom_mounts
, CUSTOM_MOUNT_BIND
);
574 m
= custom_mount_add(&s
->custom_mounts
, &s
->n_custom_mounts
, CUSTOM_MOUNT_ARBITRARY
);
578 m
->destination
= TAKE_PTR(data
.destination
);
579 m
->source
= TAKE_PTR(data
.source
);
580 m
->options
= TAKE_PTR(joined_options
);
581 m
->type_argument
= TAKE_PTR(data
.type
);
587 static int oci_namespace_type(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
588 unsigned long *nsflags
= ASSERT_PTR(userdata
);
591 assert_se(n
= json_variant_string(v
));
593 /* We don't use namespace_flags_from_string() here, as the OCI spec uses slightly different names than the
596 *nsflags
= CLONE_NEWPID
;
597 else if (streq(n
, "network"))
598 *nsflags
= CLONE_NEWNET
;
599 else if (streq(n
, "mount"))
600 *nsflags
= CLONE_NEWNS
;
601 else if (streq(n
, "ipc"))
602 *nsflags
= CLONE_NEWIPC
;
603 else if (streq(n
, "uts"))
604 *nsflags
= CLONE_NEWUTS
;
605 else if (streq(n
, "user"))
606 *nsflags
= CLONE_NEWUSER
;
607 else if (streq(n
, "cgroup"))
608 *nsflags
= CLONE_NEWCGROUP
;
610 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
611 "Unknown namespace type, refusing: %s", n
);
616 struct namespace_data
{
621 static void namespace_data_done(struct namespace_data
*data
) {
627 static int oci_namespaces(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
628 Settings
*s
= ASSERT_PTR(userdata
);
633 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
634 _cleanup_(namespace_data_done
) struct namespace_data data
= {};
636 static const JsonDispatch table
[] = {
637 { "type", JSON_VARIANT_STRING
, oci_namespace_type
, offsetof(struct namespace_data
, type
), JSON_MANDATORY
},
638 { "path", JSON_VARIANT_STRING
, oci_absolute_path
, offsetof(struct namespace_data
, path
), 0 },
642 r
= oci_dispatch(e
, table
, flags
, &data
);
647 if (data
.type
!= CLONE_NEWNET
)
648 return json_log(e
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
),
649 "Specifying namespace path for non-network namespace is not supported.");
651 if (s
->network_namespace_path
)
652 return json_log(e
, flags
, SYNTHETIC_ERRNO(EINVAL
),
653 "Network namespace path specified more than once, refusing.");
655 free_and_replace(s
->network_namespace_path
, data
.path
);
658 if (FLAGS_SET(n
, data
.type
))
659 return json_log(e
, flags
, SYNTHETIC_ERRNO(EINVAL
),
660 "Duplicate namespace specification, refusing.");
665 if (!FLAGS_SET(n
, CLONE_NEWNS
))
666 return json_log(v
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
),
667 "Containers without a mount namespace aren't supported.");
669 s
->private_network
= FLAGS_SET(n
, CLONE_NEWNET
);
670 s
->userns_mode
= FLAGS_SET(n
, CLONE_NEWUSER
) ? USER_NAMESPACE_FIXED
: USER_NAMESPACE_NO
;
671 s
->use_cgns
= FLAGS_SET(n
, CLONE_NEWCGROUP
);
673 s
->clone_ns_flags
= n
& (CLONE_NEWIPC
|CLONE_NEWPID
|CLONE_NEWUTS
);
678 static int oci_uid_gid_range(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
679 uid_t
*uid
= ASSERT_PTR(userdata
);
683 assert_cc(sizeof(uid_t
) == sizeof(gid_t
));
685 /* This is very much like oci_uid_gid(), except the checks are a bit different, as this is a UID range rather
686 * than a specific UID, and hence UID_INVALID has no special significance. OTOH a range of zero makes no
689 k
= json_variant_unsigned(v
);
691 if ((uint64_t) u
!= k
)
692 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
693 "UID/GID out of range: %" PRIu64
, k
);
695 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
696 "UID/GID range can't be zero.");
702 static int oci_uid_gid_mappings(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
703 struct mapping_data
{
708 .host_id
= UID_INVALID
,
709 .container_id
= UID_INVALID
,
713 static const JsonDispatch table
[] = {
714 { "containerID", JSON_VARIANT_UNSIGNED
, oci_uid_gid
, offsetof(struct mapping_data
, container_id
), JSON_MANDATORY
},
715 { "hostID", JSON_VARIANT_UNSIGNED
, oci_uid_gid
, offsetof(struct mapping_data
, host_id
), JSON_MANDATORY
},
716 { "size", JSON_VARIANT_UNSIGNED
, oci_uid_gid_range
, offsetof(struct mapping_data
, range
), JSON_MANDATORY
},
720 Settings
*s
= ASSERT_PTR(userdata
);
724 if (json_variant_elements(v
) == 0)
727 if (json_variant_elements(v
) > 1)
728 return json_log(v
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
),
729 "UID/GID mappings with more than one entry are not supported.");
731 assert_se(e
= json_variant_by_index(v
, 0));
733 r
= oci_dispatch(e
, table
, flags
, &data
);
737 if (data
.host_id
+ data
.range
< data
.host_id
||
738 data
.container_id
+ data
.range
< data
.container_id
)
739 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
740 "UID/GID range goes beyond UID/GID validity range, refusing.");
742 if (data
.container_id
!= 0)
743 return json_log(v
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
),
744 "UID/GID mappings with a non-zero container base are not supported.");
746 if (data
.range
< 0x10000)
747 json_log(v
, flags
|JSON_WARNING
, 0,
748 "UID/GID mapping with less than 65536 UID/GIDS set up, you are looking for trouble.");
750 if (s
->uid_range
!= UID_INVALID
&&
751 (s
->uid_shift
!= data
.host_id
|| s
->uid_range
!= data
.range
))
752 return json_log(v
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
),
753 "Non-matching UID and GID mappings are not supported.");
755 s
->uid_shift
= data
.host_id
;
756 s
->uid_range
= data
.range
;
761 static int oci_device_type(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
762 mode_t
*mode
= ASSERT_PTR(userdata
);
765 assert_se(t
= json_variant_string(v
));
767 if (STR_IN_SET(t
, "c", "u"))
768 *mode
= (*mode
& ~S_IFMT
) | S_IFCHR
;
769 else if (streq(t
, "b"))
770 *mode
= (*mode
& ~S_IFMT
) | S_IFBLK
;
771 else if (streq(t
, "p"))
772 *mode
= (*mode
& ~S_IFMT
) | S_IFIFO
;
774 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
775 "Unknown device type: %s", t
);
780 static int oci_device_major(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
781 unsigned *u
= ASSERT_PTR(userdata
);
784 k
= json_variant_unsigned(v
);
785 if (!DEVICE_MAJOR_VALID(k
))
786 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
787 "Device major %" PRIu64
" out of range.", k
);
793 static int oci_device_minor(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
794 unsigned *u
= ASSERT_PTR(userdata
);
797 k
= json_variant_unsigned(v
);
798 if (!DEVICE_MINOR_VALID(k
))
799 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
800 "Device minor %" PRIu64
" out of range.", k
);
806 static int oci_device_file_mode(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
807 mode_t
*mode
= ASSERT_PTR(userdata
);
811 k
= json_variant_unsigned(v
);
814 if ((m
& ~07777) != 0 || (uint64_t) m
!= k
)
815 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
816 "fileMode out of range, refusing.");
818 *mode
= (*mode
& ~07777) | m
;
822 static int oci_devices(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
823 Settings
*s
= ASSERT_PTR(userdata
);
827 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
829 static const JsonDispatch table
[] = {
830 { "type", JSON_VARIANT_STRING
, oci_device_type
, offsetof(DeviceNode
, mode
), JSON_MANDATORY
},
831 { "path", JSON_VARIANT_STRING
, oci_absolute_path
, offsetof(DeviceNode
, path
), JSON_MANDATORY
},
832 { "major", JSON_VARIANT_UNSIGNED
, oci_device_major
, offsetof(DeviceNode
, major
), 0 },
833 { "minor", JSON_VARIANT_UNSIGNED
, oci_device_minor
, offsetof(DeviceNode
, minor
), 0 },
834 { "fileMode", JSON_VARIANT_UNSIGNED
, oci_device_file_mode
, offsetof(DeviceNode
, mode
), 0 },
835 { "uid", JSON_VARIANT_UNSIGNED
, oci_uid_gid
, offsetof(DeviceNode
, uid
), 0 },
836 { "gid", JSON_VARIANT_UNSIGNED
, oci_uid_gid
, offsetof(DeviceNode
, gid
), 0 },
840 DeviceNode
*node
, *nodes
;
842 nodes
= reallocarray(s
->extra_nodes
, s
->n_extra_nodes
+ 1, sizeof(DeviceNode
));
846 s
->extra_nodes
= nodes
;
848 node
= nodes
+ s
->n_extra_nodes
;
849 *node
= (DeviceNode
) {
857 r
= oci_dispatch(e
, table
, flags
, node
);
861 if (S_ISCHR(node
->mode
) || S_ISBLK(node
->mode
)) {
862 _cleanup_free_
char *path
= NULL
;
864 if (node
->major
== UINT_MAX
|| node
->minor
== UINT_MAX
) {
865 r
= json_log(e
, flags
, SYNTHETIC_ERRNO(EINVAL
),
866 "Major/minor required when device node is device node");
870 /* Suppress a couple of implicit device nodes */
871 r
= devname_from_devnum(node
->mode
, makedev(node
->major
, node
->minor
), &path
);
873 json_log(e
, flags
|JSON_DEBUG
, r
, "Failed to resolve device node %u:%u, ignoring: %m", node
->major
, node
->minor
);
875 if (PATH_IN_SET(path
,
887 json_log(e
, flags
|JSON_DEBUG
, 0, "Ignoring devices item for device '%s', as it is implicitly created anyway.", path
);
905 static int oci_cgroups_path(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
906 _cleanup_free_
char *slice
= NULL
, *backwards
= NULL
;
907 Settings
*s
= ASSERT_PTR(userdata
);
911 assert_se(p
= json_variant_string(v
));
913 r
= cg_path_get_slice(p
, &slice
);
915 return json_log(v
, flags
, r
, "Couldn't derive slice unit name from path '%s': %m", p
);
917 r
= cg_slice_to_path(slice
, &backwards
);
919 return json_log(v
, flags
, r
, "Couldn't convert slice unit name '%s' back to path: %m", slice
);
921 if (!path_equal(backwards
, p
))
922 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
923 "Control group path '%s' does not refer to slice unit, refusing.", p
);
925 free_and_replace(s
->slice
, slice
);
929 static int oci_cgroup_device_type(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
930 mode_t
*mode
= ASSERT_PTR(userdata
);
933 assert_se(n
= json_variant_string(v
));
937 else if (streq(n
, "b"))
940 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
941 "Control group device type unknown: %s", n
);
956 static int oci_cgroup_device_access(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
957 struct device_data
*d
= ASSERT_PTR(userdata
);
958 bool r
= false, w
= false, m
= false;
962 assert_se(s
= json_variant_string(v
));
964 for (i
= 0; s
[i
]; i
++)
967 else if (s
[i
] == 'w')
969 else if (s
[i
] == 'm')
972 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
973 "Unknown device access character '%c'.", s
[i
]);
982 static int oci_cgroup_devices(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
983 _cleanup_free_
struct device_data
*list
= NULL
;
984 Settings
*s
= ASSERT_PTR(userdata
);
985 size_t n_list
= 0, i
;
990 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
992 struct device_data data
= {
997 static const JsonDispatch table
[] = {
998 { "allow", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(struct device_data
, allow
), JSON_MANDATORY
},
999 { "type", JSON_VARIANT_STRING
, oci_cgroup_device_type
, offsetof(struct device_data
, type
), 0 },
1000 { "major", JSON_VARIANT_UNSIGNED
, oci_device_major
, offsetof(struct device_data
, major
), 0 },
1001 { "minor", JSON_VARIANT_UNSIGNED
, oci_device_minor
, offsetof(struct device_data
, minor
), 0 },
1002 { "access", JSON_VARIANT_STRING
, oci_cgroup_device_access
, 0, 0 },
1006 r
= oci_dispatch(e
, table
, flags
, &data
);
1011 /* The fact that OCI allows 'deny' entries makes really no sense, as 'allow'
1012 * vs. 'deny' for the devices cgroup controller is really not about allow-listing and
1013 * deny-listing but about adding and removing entries from the allow list. Since we
1014 * always start out with an empty allow list we hence ignore the whole thing, as
1015 * removing entries which don't exist make no sense. We'll log about this, since this
1016 * is really borked in the spec, with one exception: the entry that's supposed to
1017 * drop the kernel's default we ignore silently */
1019 if (!data
.r
|| !data
.w
|| !data
.m
|| data
.type
!= 0 || data
.major
!= UINT_MAX
|| data
.minor
!= UINT_MAX
)
1020 json_log(v
, flags
|JSON_WARNING
, 0, "Devices cgroup allow list with arbitrary 'allow' entries not supported, ignoring.");
1022 /* We ignore the 'deny' entry as for us that's implied */
1026 if (!data
.r
&& !data
.w
&& !data
.m
) {
1027 json_log(v
, flags
|LOG_WARNING
, 0, "Device cgroup allow list entry with no effect found, ignoring.");
1031 if (data
.minor
!= UINT_MAX
&& data
.major
== UINT_MAX
)
1032 return json_log(v
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
),
1033 "Device cgroup allow list entries with minors but no majors not supported.");
1035 if (data
.major
!= UINT_MAX
&& data
.type
== 0)
1036 return json_log(v
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
),
1037 "Device cgroup allow list entries with majors but no device node type not supported.");
1039 if (data
.type
== 0) {
1040 if (data
.r
&& data
.w
&& data
.m
) /* a catchall allow list entry means we are looking at a noop */
1043 return json_log(v
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
),
1044 "Device cgroup allow list entries with no type not supported.");
1047 a
= reallocarray(list
, n_list
+ 1, sizeof(struct device_data
));
1052 list
[n_list
++] = data
;
1058 r
= settings_allocate_properties(s
);
1062 r
= sd_bus_message_open_container(s
->properties
, 'r', "sv");
1064 return bus_log_create_error(r
);
1066 r
= sd_bus_message_append(s
->properties
, "s", "DeviceAllow");
1068 return bus_log_create_error(r
);
1070 r
= sd_bus_message_open_container(s
->properties
, 'v', "a(ss)");
1072 return bus_log_create_error(r
);
1074 r
= sd_bus_message_open_container(s
->properties
, 'a', "(ss)");
1076 return bus_log_create_error(r
);
1078 for (i
= 0; i
< n_list
; i
++) {
1079 _cleanup_free_
char *pattern
= NULL
;
1083 if (list
[i
].minor
== UINT_MAX
) {
1086 if (list
[i
].type
== S_IFBLK
)
1089 assert(list
[i
].type
== S_IFCHR
);
1093 if (list
[i
].major
== UINT_MAX
) {
1094 pattern
= strjoin(t
, "-*");
1098 if (asprintf(&pattern
, "%s-%u", t
, list
[i
].major
) < 0)
1103 assert(list
[i
].major
!= UINT_MAX
); /* If a minor is specified, then a major also needs to be specified */
1105 r
= device_path_make_major_minor(list
[i
].type
, makedev(list
[i
].major
, list
[i
].minor
), &pattern
);
1120 r
= sd_bus_message_append(s
->properties
, "(ss)", pattern
, access
);
1122 return bus_log_create_error(r
);
1125 r
= sd_bus_message_close_container(s
->properties
);
1127 return bus_log_create_error(r
);
1129 r
= sd_bus_message_close_container(s
->properties
);
1131 return bus_log_create_error(r
);
1133 r
= sd_bus_message_close_container(s
->properties
);
1135 return bus_log_create_error(r
);
1140 static int oci_cgroup_memory_limit(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1141 uint64_t *m
= ASSERT_PTR(userdata
);
1144 if (json_variant_is_negative(v
)) {
1149 if (!json_variant_is_unsigned(v
))
1150 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
1151 "Memory limit is not an unsigned integer");
1153 k
= json_variant_unsigned(v
);
1154 if (k
>= UINT64_MAX
)
1155 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
1156 "Memory limit too large: %" PRIu64
, k
);
1162 static int oci_cgroup_memory(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1164 struct memory_data
{
1166 uint64_t reservation
;
1169 .limit
= UINT64_MAX
,
1170 .reservation
= UINT64_MAX
,
1174 static const JsonDispatch table
[] = {
1175 { "limit", JSON_VARIANT_NUMBER
, oci_cgroup_memory_limit
, offsetof(struct memory_data
, limit
), 0 },
1176 { "reservation", JSON_VARIANT_NUMBER
, oci_cgroup_memory_limit
, offsetof(struct memory_data
, reservation
), 0 },
1177 { "swap", JSON_VARIANT_NUMBER
, oci_cgroup_memory_limit
, offsetof(struct memory_data
, swap
), 0 },
1178 { "kernel", JSON_VARIANT_NUMBER
, oci_unsupported
, 0, JSON_PERMISSIVE
},
1179 { "kernelTCP", JSON_VARIANT_NUMBER
, oci_unsupported
, 0, JSON_PERMISSIVE
},
1180 { "swapiness", JSON_VARIANT_NUMBER
, oci_unsupported
, 0, JSON_PERMISSIVE
},
1181 { "disableOOMKiller", JSON_VARIANT_BOOLEAN
, oci_unsupported
, 0, JSON_PERMISSIVE
},
1185 Settings
*s
= ASSERT_PTR(userdata
);
1188 r
= oci_dispatch(v
, table
, flags
, &data
);
1192 if (data
.swap
!= UINT64_MAX
) {
1193 if (data
.limit
== UINT64_MAX
)
1194 json_log(v
, flags
|LOG_WARNING
, 0, "swap limit without memory limit is not supported, ignoring.");
1195 else if (data
.swap
< data
.limit
)
1196 json_log(v
, flags
|LOG_WARNING
, 0, "swap limit is below memory limit, ignoring.");
1198 r
= settings_allocate_properties(s
);
1202 r
= sd_bus_message_append(s
->properties
, "(sv)", "MemorySwapMax", "t", data
.swap
- data
.limit
);
1204 return bus_log_create_error(r
);
1208 if (data
.limit
!= UINT64_MAX
) {
1209 r
= settings_allocate_properties(s
);
1213 r
= sd_bus_message_append(s
->properties
, "(sv)", "MemoryMax", "t", data
.limit
);
1215 return bus_log_create_error(r
);
1218 if (data
.reservation
!= UINT64_MAX
) {
1219 r
= settings_allocate_properties(s
);
1223 r
= sd_bus_message_append(s
->properties
, "(sv)", "MemoryLow", "t", data
.reservation
);
1225 return bus_log_create_error(r
);
1238 static int oci_cgroup_cpu_shares(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1239 uint64_t *u
= ASSERT_PTR(userdata
);
1242 k
= json_variant_unsigned(v
);
1243 if (k
< CGROUP_CPU_SHARES_MIN
|| k
> CGROUP_CPU_SHARES_MAX
)
1244 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
1245 "shares value out of range.");
1251 static int oci_cgroup_cpu_quota(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1252 uint64_t *u
= ASSERT_PTR(userdata
);
1255 k
= json_variant_unsigned(v
);
1256 if (k
<= 0 || k
>= UINT64_MAX
)
1257 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
1258 "period/quota value out of range.");
1264 static int oci_cgroup_cpu_cpus(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1265 struct cpu_data
*data
= ASSERT_PTR(userdata
);
1270 assert_se(n
= json_variant_string(v
));
1272 r
= parse_cpu_set(n
, &set
);
1274 return json_log(v
, flags
, r
, "Failed to parse CPU set specification: %s", n
);
1276 cpu_set_reset(&data
->cpu_set
);
1277 data
->cpu_set
= set
;
1282 static int oci_cgroup_cpu(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1284 static const JsonDispatch table
[] = {
1285 { "shares", JSON_VARIANT_UNSIGNED
, oci_cgroup_cpu_shares
, offsetof(struct cpu_data
, shares
), 0 },
1286 { "quota", JSON_VARIANT_UNSIGNED
, oci_cgroup_cpu_quota
, offsetof(struct cpu_data
, quota
), 0 },
1287 { "period", JSON_VARIANT_UNSIGNED
, oci_cgroup_cpu_quota
, offsetof(struct cpu_data
, period
), 0 },
1288 { "realtimeRuntime", JSON_VARIANT_UNSIGNED
, oci_unsupported
, 0, 0 },
1289 { "realtimePeriod", JSON_VARIANT_UNSIGNED
, oci_unsupported
, 0, 0 },
1290 { "cpus", JSON_VARIANT_STRING
, oci_cgroup_cpu_cpus
, 0, 0 },
1291 { "mems", JSON_VARIANT_STRING
, oci_unsupported
, 0, 0 },
1295 struct cpu_data data
= {
1296 .shares
= UINT64_MAX
,
1297 .quota
= UINT64_MAX
,
1298 .period
= UINT64_MAX
,
1301 Settings
*s
= ASSERT_PTR(userdata
);
1304 r
= oci_dispatch(v
, table
, flags
, &data
);
1306 cpu_set_reset(&data
.cpu_set
);
1310 cpu_set_reset(&s
->cpu_set
);
1311 s
->cpu_set
= data
.cpu_set
;
1313 if (data
.shares
!= UINT64_MAX
) {
1314 r
= settings_allocate_properties(s
);
1318 r
= sd_bus_message_append(s
->properties
, "(sv)", "CPUShares", "t", data
.shares
);
1320 return bus_log_create_error(r
);
1323 if (data
.quota
!= UINT64_MAX
&& data
.period
!= UINT64_MAX
) {
1324 r
= settings_allocate_properties(s
);
1328 r
= sd_bus_message_append(s
->properties
, "(sv)", "CPUQuotaPerSecUSec", "t", (uint64_t) (data
.quota
* USEC_PER_SEC
/ data
.period
));
1330 return bus_log_create_error(r
);
1332 } else if ((data
.quota
!= UINT64_MAX
) != (data
.period
!= UINT64_MAX
))
1333 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
1334 "CPU quota and period not used together.");
1339 static int oci_cgroup_block_io_weight(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1340 Settings
*s
= ASSERT_PTR(userdata
);
1344 k
= json_variant_unsigned(v
);
1345 if (k
< CGROUP_BLKIO_WEIGHT_MIN
|| k
> CGROUP_BLKIO_WEIGHT_MAX
)
1346 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
1347 "Block I/O weight out of range.");
1349 r
= settings_allocate_properties(s
);
1353 r
= sd_bus_message_append(s
->properties
, "(sv)", "BlockIOWeight", "t", (uint64_t) k
);
1355 return bus_log_create_error(r
);
1360 static int oci_cgroup_block_io_weight_device(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1361 Settings
*s
= ASSERT_PTR(userdata
);
1365 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
1366 struct device_data
{
1373 .weight
= UINT64_MAX
,
1376 static const JsonDispatch table
[] = {
1377 { "major", JSON_VARIANT_UNSIGNED
, oci_device_major
, offsetof(struct device_data
, major
), JSON_MANDATORY
},
1378 { "minor", JSON_VARIANT_UNSIGNED
, oci_device_minor
, offsetof(struct device_data
, minor
), JSON_MANDATORY
},
1379 { "weight", JSON_VARIANT_UNSIGNED
, json_dispatch_uint64
, offsetof(struct device_data
, weight
), 0 },
1380 { "leafWeight", JSON_VARIANT_INTEGER
, oci_unsupported
, 0, JSON_PERMISSIVE
},
1384 _cleanup_free_
char *path
= NULL
;
1386 r
= oci_dispatch(e
, table
, flags
, &data
);
1390 if (data
.weight
== UINT64_MAX
)
1393 if (data
.weight
< CGROUP_BLKIO_WEIGHT_MIN
|| data
.weight
> CGROUP_BLKIO_WEIGHT_MAX
)
1394 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
1395 "Block I/O device weight out of range.");
1397 r
= device_path_make_major_minor(S_IFBLK
, makedev(data
.major
, data
.minor
), &path
);
1399 return json_log(v
, flags
, r
, "Failed to build device path: %m");
1401 r
= settings_allocate_properties(s
);
1405 r
= sd_bus_message_append(s
->properties
, "(sv)", "BlockIODeviceWeight", "a(st)", 1, path
, (uint64_t) data
.weight
);
1407 return bus_log_create_error(r
);
1413 static int oci_cgroup_block_io_throttle(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1414 Settings
*s
= ASSERT_PTR(userdata
);
1419 pname
= streq(name
, "throttleReadBpsDevice") ? "IOReadBandwidthMax" :
1420 streq(name
, "throttleWriteBpsDevice") ? "IOWriteBandwidthMax" :
1421 streq(name
, "throttleReadIOPSDevice") ? "IOReadIOPSMax" :
1424 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
1425 struct device_data
{
1434 static const JsonDispatch table
[] = {
1435 { "major", JSON_VARIANT_UNSIGNED
, oci_device_major
, offsetof(struct device_data
, major
), JSON_MANDATORY
},
1436 { "minor", JSON_VARIANT_UNSIGNED
, oci_device_minor
, offsetof(struct device_data
, minor
), JSON_MANDATORY
},
1437 { "rate", JSON_VARIANT_UNSIGNED
, json_dispatch_uint64
, offsetof(struct device_data
, rate
), JSON_MANDATORY
},
1441 _cleanup_free_
char *path
= NULL
;
1443 r
= oci_dispatch(e
, table
, flags
, &data
);
1447 if (data
.rate
>= UINT64_MAX
)
1448 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
1449 "Block I/O device rate out of range.");
1451 r
= device_path_make_major_minor(S_IFBLK
, makedev(data
.major
, data
.minor
), &path
);
1453 return json_log(v
, flags
, r
, "Failed to build device path: %m");
1455 r
= settings_allocate_properties(s
);
1459 r
= sd_bus_message_append(s
->properties
, "(sv)", pname
, "a(st)", 1, path
, (uint64_t) data
.rate
);
1461 return bus_log_create_error(r
);
1467 static int oci_cgroup_block_io(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1469 static const JsonDispatch table
[] = {
1470 { "weight", JSON_VARIANT_UNSIGNED
, oci_cgroup_block_io_weight
, 0, 0 },
1471 { "leafWeight", JSON_VARIANT_UNSIGNED
, oci_unsupported
, 0, JSON_PERMISSIVE
},
1472 { "weightDevice", JSON_VARIANT_ARRAY
, oci_cgroup_block_io_weight_device
, 0, 0 },
1473 { "throttleReadBpsDevice", JSON_VARIANT_ARRAY
, oci_cgroup_block_io_throttle
, 0, 0 },
1474 { "throttleWriteBpsDevice", JSON_VARIANT_ARRAY
, oci_cgroup_block_io_throttle
, 0, 0 },
1475 { "throttleReadIOPSDevice", JSON_VARIANT_ARRAY
, oci_cgroup_block_io_throttle
, 0, 0 },
1476 { "throttleWriteIOPSDevice", JSON_VARIANT_ARRAY
, oci_cgroup_block_io_throttle
, 0, 0 },
1480 return oci_dispatch(v
, table
, flags
, userdata
);
1483 static int oci_cgroup_pids(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1485 static const JsonDispatch table
[] = {
1486 { "limit", JSON_VARIANT_NUMBER
, json_dispatch_variant
, 0, JSON_MANDATORY
},
1490 _cleanup_(json_variant_unrefp
) JsonVariant
*k
= NULL
;
1491 Settings
*s
= ASSERT_PTR(userdata
);
1495 r
= oci_dispatch(v
, table
, flags
, &k
);
1499 if (json_variant_is_negative(k
))
1502 if (!json_variant_is_unsigned(k
))
1503 return json_log(k
, flags
, SYNTHETIC_ERRNO(EINVAL
),
1504 "pids limit not unsigned integer, refusing.");
1506 m
= (uint64_t) json_variant_unsigned(k
);
1508 if ((uint64_t) m
!= json_variant_unsigned(k
))
1509 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
1510 "pids limit out of range, refusing.");
1513 r
= settings_allocate_properties(s
);
1517 r
= sd_bus_message_append(s
->properties
, "(sv)", "TasksMax", "t", m
);
1519 return bus_log_create_error(r
);
1524 static int oci_resources(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1526 static const JsonDispatch table
[] = {
1527 { "devices", JSON_VARIANT_ARRAY
, oci_cgroup_devices
, 0, 0 },
1528 { "memory", JSON_VARIANT_OBJECT
, oci_cgroup_memory
, 0, 0 },
1529 { "cpu", JSON_VARIANT_OBJECT
, oci_cgroup_cpu
, 0, 0 },
1530 { "blockIO", JSON_VARIANT_OBJECT
, oci_cgroup_block_io
, 0, 0 },
1531 { "hugepageLimits", JSON_VARIANT_ARRAY
, oci_unsupported
, 0, 0 },
1532 { "network", JSON_VARIANT_OBJECT
, oci_unsupported
, 0, 0 },
1533 { "pids", JSON_VARIANT_OBJECT
, oci_cgroup_pids
, 0, 0 },
1534 { "rdma", JSON_VARIANT_OBJECT
, oci_unsupported
, 0, 0 },
1538 return oci_dispatch(v
, table
, flags
, userdata
);
1541 static bool sysctl_key_valid(const char *s
) {
1544 /* Note that we are a bit stricter here than in systemd-sysctl, as that inherited semantics from the old sysctl
1545 * tool, which were really weird (as it swaps / and . in both ways) */
1552 if (*s
<= ' ' || *s
>= 127)
1558 if (dot
) /* Don't allow two dots next to each other (or at the beginning) */
1566 if (dot
) /* don't allow a dot at the end */
1572 static int oci_sysctl(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1573 Settings
*s
= ASSERT_PTR(userdata
);
1578 JSON_VARIANT_OBJECT_FOREACH(k
, w
, v
) {
1581 if (!json_variant_is_string(w
))
1582 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
1583 "sysctl parameter is not a string, refusing.");
1585 assert_se(m
= json_variant_string(w
));
1587 if (!sysctl_key_valid(k
))
1588 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
1589 "sysctl key invalid, refusing: %s", k
);
1591 r
= strv_extend_strv(&s
->sysctl
, STRV_MAKE(k
, m
), false);
1600 static int oci_seccomp_action_from_string(const char *name
, uint32_t *ret
) {
1602 static const struct {
1606 { "SCMP_ACT_ALLOW", SCMP_ACT_ALLOW
},
1607 { "SCMP_ACT_ERRNO", SCMP_ACT_ERRNO(EPERM
) }, /* the OCI spec doesn't document the error, but it appears EPERM is supposed to be used */
1608 { "SCMP_ACT_KILL", SCMP_ACT_KILL
},
1609 #ifdef SCMP_ACT_KILL_PROCESS
1610 { "SCMP_ACT_KILL_PROCESS", SCMP_ACT_KILL_PROCESS
},
1612 #ifdef SCMP_ACT_KILL_THREAD
1613 { "SCMP_ACT_KILL_THREAD", SCMP_ACT_KILL_THREAD
},
1616 { "SCMP_ACT_LOG", SCMP_ACT_LOG
},
1618 { "SCMP_ACT_TRAP", SCMP_ACT_TRAP
},
1620 /* We don't support SCMP_ACT_TRACE because that requires a tracer, and that doesn't really make sense
1626 for (i
= 0; i
< ELEMENTSOF(table
); i
++)
1627 if (streq_ptr(name
, table
[i
].name
)) {
1628 *ret
= table
[i
].action
;
1635 static int oci_seccomp_arch_from_string(const char *name
, uint32_t *ret
) {
1637 static const struct {
1641 { "SCMP_ARCH_AARCH64", SCMP_ARCH_AARCH64
},
1642 { "SCMP_ARCH_ARM", SCMP_ARCH_ARM
},
1643 #ifdef SCMP_ARCH_LOONGARCH64
1644 { "SCMP_ARCH_LOONGARCH64", SCMP_ARCH_LOONGARCH64
},
1646 { "SCMP_ARCH_MIPS", SCMP_ARCH_MIPS
},
1647 { "SCMP_ARCH_MIPS64", SCMP_ARCH_MIPS64
},
1648 { "SCMP_ARCH_MIPS64N32", SCMP_ARCH_MIPS64N32
},
1649 { "SCMP_ARCH_MIPSEL", SCMP_ARCH_MIPSEL
},
1650 { "SCMP_ARCH_MIPSEL64", SCMP_ARCH_MIPSEL64
},
1651 { "SCMP_ARCH_MIPSEL64N32", SCMP_ARCH_MIPSEL64N32
},
1652 { "SCMP_ARCH_NATIVE", SCMP_ARCH_NATIVE
},
1653 #ifdef SCMP_ARCH_PARISC
1654 { "SCMP_ARCH_PARISC", SCMP_ARCH_PARISC
},
1656 #ifdef SCMP_ARCH_PARISC64
1657 { "SCMP_ARCH_PARISC64", SCMP_ARCH_PARISC64
},
1659 { "SCMP_ARCH_PPC", SCMP_ARCH_PPC
},
1660 { "SCMP_ARCH_PPC64", SCMP_ARCH_PPC64
},
1661 { "SCMP_ARCH_PPC64LE", SCMP_ARCH_PPC64LE
},
1662 #ifdef SCMP_ARCH_RISCV64
1663 { "SCMP_ARCH_RISCV64", SCMP_ARCH_RISCV64
},
1665 { "SCMP_ARCH_S390", SCMP_ARCH_S390
},
1666 { "SCMP_ARCH_S390X", SCMP_ARCH_S390X
},
1667 { "SCMP_ARCH_X32", SCMP_ARCH_X32
},
1668 { "SCMP_ARCH_X86", SCMP_ARCH_X86
},
1669 { "SCMP_ARCH_X86_64", SCMP_ARCH_X86_64
},
1674 for (i
= 0; i
< ELEMENTSOF(table
); i
++)
1675 if (streq_ptr(table
[i
].name
, name
)) {
1676 *ret
= table
[i
].arch
;
1683 static int oci_seccomp_compare_from_string(const char *name
, enum scmp_compare
*ret
) {
1685 static const struct {
1687 enum scmp_compare op
;
1689 { "SCMP_CMP_NE", SCMP_CMP_NE
},
1690 { "SCMP_CMP_LT", SCMP_CMP_LT
},
1691 { "SCMP_CMP_LE", SCMP_CMP_LE
},
1692 { "SCMP_CMP_EQ", SCMP_CMP_EQ
},
1693 { "SCMP_CMP_GE", SCMP_CMP_GE
},
1694 { "SCMP_CMP_GT", SCMP_CMP_GT
},
1695 { "SCMP_CMP_MASKED_EQ", SCMP_CMP_MASKED_EQ
},
1700 for (i
= 0; i
< ELEMENTSOF(table
); i
++)
1701 if (streq_ptr(table
[i
].name
, name
)) {
1709 static int oci_seccomp_archs(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1710 scmp_filter_ctx
*sc
= ASSERT_PTR(userdata
);
1714 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
1717 if (!json_variant_is_string(e
))
1718 return json_log(e
, flags
, SYNTHETIC_ERRNO(EINVAL
),
1719 "Architecture entry is not a string");
1721 r
= oci_seccomp_arch_from_string(json_variant_string(e
), &a
);
1723 return json_log(e
, flags
, r
, "Unknown architecture: %s", json_variant_string(e
));
1725 r
= seccomp_arch_add(sc
, a
);
1729 return json_log(e
, flags
, r
, "Failed to add architecture to seccomp filter: %m");
1735 struct syscall_rule
{
1738 struct scmp_arg_cmp
*arguments
;
1742 static void syscall_rule_done(struct syscall_rule
*rule
) {
1745 strv_free(rule
->names
);
1746 free(rule
->arguments
);
1749 static int oci_seccomp_action(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1750 uint32_t *action
= ASSERT_PTR(userdata
);
1753 r
= oci_seccomp_action_from_string(json_variant_string(v
), action
);
1755 return json_log(v
, flags
, r
, "Unknown system call action '%s': %m", json_variant_string(v
));
1760 static int oci_seccomp_op(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1761 enum scmp_compare
*op
= ASSERT_PTR(userdata
);
1764 r
= oci_seccomp_compare_from_string(json_variant_string(v
), op
);
1766 return json_log(v
, flags
, r
, "Unknown seccomp operator '%s': %m", json_variant_string(v
));
1771 static int oci_seccomp_args(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1772 struct syscall_rule
*rule
= ASSERT_PTR(userdata
);
1776 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
1777 static const struct JsonDispatch table
[] = {
1778 { "index", JSON_VARIANT_UNSIGNED
, json_dispatch_uint32
, offsetof(struct scmp_arg_cmp
, arg
), JSON_MANDATORY
},
1779 { "value", JSON_VARIANT_UNSIGNED
, json_dispatch_uint64
, offsetof(struct scmp_arg_cmp
, datum_a
), JSON_MANDATORY
},
1780 { "valueTwo", JSON_VARIANT_UNSIGNED
, json_dispatch_uint64
, offsetof(struct scmp_arg_cmp
, datum_b
), 0 },
1781 { "op", JSON_VARIANT_STRING
, oci_seccomp_op
, offsetof(struct scmp_arg_cmp
, op
), JSON_MANDATORY
},
1785 struct scmp_arg_cmp
*a
, *p
;
1788 a
= reallocarray(rule
->arguments
, rule
->n_arguments
+ 1, sizeof(struct syscall_rule
));
1792 rule
->arguments
= a
;
1793 p
= rule
->arguments
+ rule
->n_arguments
;
1795 *p
= (struct scmp_arg_cmp
) {
1802 r
= oci_dispatch(e
, table
, flags
, p
);
1806 expected
= p
->op
== SCMP_CMP_MASKED_EQ
? 4 : 3;
1808 json_log(e
, flags
|JSON_WARNING
, 0, "Wrong number of system call arguments for JSON data, ignoring.");
1810 /* Note that we are a bit sloppy here and do not insist that SCMP_CMP_MASKED_EQ gets two datum values,
1811 * and the other only one. That's because buildah for example by default calls things with
1812 * SCMP_CMP_MASKED_EQ but only one argument. We use 0 when the value is not specified. */
1814 rule
->n_arguments
++;
1820 static int oci_seccomp_syscalls(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1821 scmp_filter_ctx
*sc
= ASSERT_PTR(userdata
);
1825 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
1826 static const JsonDispatch table
[] = {
1827 { "names", JSON_VARIANT_ARRAY
, json_dispatch_strv
, offsetof(struct syscall_rule
, names
), JSON_MANDATORY
},
1828 { "action", JSON_VARIANT_STRING
, oci_seccomp_action
, offsetof(struct syscall_rule
, action
), JSON_MANDATORY
},
1829 { "args", JSON_VARIANT_ARRAY
, oci_seccomp_args
, 0, 0 },
1832 _cleanup_(syscall_rule_done
) struct syscall_rule rule
= {
1833 .action
= UINT32_MAX
,
1836 r
= oci_dispatch(e
, table
, flags
, &rule
);
1840 if (strv_isempty(rule
.names
)) {
1841 json_log(e
, flags
, 0, "System call name list is empty.");
1845 STRV_FOREACH(i
, rule
.names
) {
1848 nr
= seccomp_syscall_resolve_name(*i
);
1849 if (nr
== __NR_SCMP_ERROR
) {
1850 log_debug("Unknown syscall %s, skipping.", *i
);
1854 r
= seccomp_rule_add_array(sc
, rule
.action
, nr
, rule
.n_arguments
, rule
.arguments
);
1864 static int oci_seccomp(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1867 static const JsonDispatch table
[] = {
1868 { "defaultAction", JSON_VARIANT_STRING
, NULL
, 0, JSON_MANDATORY
},
1869 { "architectures", JSON_VARIANT_ARRAY
, oci_seccomp_archs
, 0, 0 },
1870 { "syscalls", JSON_VARIANT_ARRAY
, oci_seccomp_syscalls
, 0, 0 },
1874 _cleanup_(seccomp_releasep
) scmp_filter_ctx sc
= NULL
;
1875 Settings
*s
= ASSERT_PTR(userdata
);
1880 def
= json_variant_by_key(v
, "defaultAction");
1882 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
), "defaultAction element missing.");
1884 if (!json_variant_is_string(def
))
1885 return json_log(def
, flags
, SYNTHETIC_ERRNO(EINVAL
), "defaultAction is not a string.");
1887 r
= oci_seccomp_action_from_string(json_variant_string(def
), &d
);
1889 return json_log(def
, flags
, r
, "Unknown default action: %s", json_variant_string(def
));
1891 sc
= seccomp_init(d
);
1893 return json_log(v
, flags
, SYNTHETIC_ERRNO(ENOMEM
), "Couldn't allocate seccomp object.");
1895 r
= oci_dispatch(v
, table
, flags
, sc
);
1899 seccomp_release(s
->seccomp
);
1900 s
->seccomp
= TAKE_PTR(sc
);
1903 return json_log(v
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
), "libseccomp support not enabled, can't parse seccomp object.");
1907 static int oci_rootfs_propagation(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1910 s
= json_variant_string(v
);
1912 if (streq(s
, "shared"))
1915 json_log(v
, flags
|JSON_DEBUG
, 0, "Ignoring rootfsPropagation setting '%s'.", s
);
1919 static int oci_masked_paths(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1920 Settings
*s
= ASSERT_PTR(userdata
);
1923 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
1924 _cleanup_free_
char *destination
= NULL
;
1928 if (!json_variant_is_string(e
))
1929 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
1930 "Path is not a string, refusing.");
1932 assert_se(p
= json_variant_string(e
));
1934 if (!path_is_absolute(p
))
1935 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
1936 "Path is not absolute, refusing: %s", p
);
1938 if (oci_exclude_mount(p
))
1941 destination
= strdup(p
);
1945 m
= custom_mount_add(&s
->custom_mounts
, &s
->n_custom_mounts
, CUSTOM_MOUNT_INACCESSIBLE
);
1949 m
->destination
= TAKE_PTR(destination
);
1951 /* The spec doesn't say this, but apparently pre-existing implementations are lenient towards
1952 * non-existing paths to mask. Let's hence be too. */
1959 static int oci_readonly_paths(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1960 Settings
*s
= ASSERT_PTR(userdata
);
1963 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
1964 _cleanup_free_
char *source
= NULL
, *destination
= NULL
;
1968 if (!json_variant_is_string(e
))
1969 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
1970 "Path is not a string, refusing.");
1972 assert_se(p
= json_variant_string(e
));
1974 if (!path_is_absolute(p
))
1975 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
1976 "Path is not absolute, refusing: %s", p
);
1978 if (oci_exclude_mount(p
))
1981 source
= strjoin("+", p
);
1985 destination
= strdup(p
);
1989 m
= custom_mount_add(&s
->custom_mounts
, &s
->n_custom_mounts
, CUSTOM_MOUNT_BIND
);
1993 m
->source
= TAKE_PTR(source
);
1994 m
->destination
= TAKE_PTR(destination
);
1995 m
->read_only
= true;
2001 static int oci_linux(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
2003 static const JsonDispatch table
[] = {
2004 { "namespaces", JSON_VARIANT_ARRAY
, oci_namespaces
, 0, 0 },
2005 { "uidMappings", JSON_VARIANT_ARRAY
, oci_uid_gid_mappings
, 0, 0 },
2006 { "gidMappings", JSON_VARIANT_ARRAY
, oci_uid_gid_mappings
, 0, 0 },
2007 { "devices", JSON_VARIANT_ARRAY
, oci_devices
, 0, 0 },
2008 { "cgroupsPath", JSON_VARIANT_STRING
, oci_cgroups_path
, 0, 0 },
2009 { "resources", JSON_VARIANT_OBJECT
, oci_resources
, 0, 0 },
2010 { "intelRdt", JSON_VARIANT_OBJECT
, oci_unsupported
, 0, JSON_PERMISSIVE
},
2011 { "sysctl", JSON_VARIANT_OBJECT
, oci_sysctl
, 0, 0 },
2012 { "seccomp", JSON_VARIANT_OBJECT
, oci_seccomp
, 0, 0 },
2013 { "rootfsPropagation", JSON_VARIANT_STRING
, oci_rootfs_propagation
, 0, 0 },
2014 { "maskedPaths", JSON_VARIANT_ARRAY
, oci_masked_paths
, 0, 0 },
2015 { "readonlyPaths", JSON_VARIANT_ARRAY
, oci_readonly_paths
, 0, 0 },
2016 { "mountLabel", JSON_VARIANT_STRING
, oci_unsupported
, 0, JSON_PERMISSIVE
},
2020 return oci_dispatch(v
, table
, flags
, userdata
);
2023 static int oci_hook_timeout(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
2024 usec_t
*u
= ASSERT_PTR(userdata
);
2027 k
= json_variant_unsigned(v
);
2028 if (k
== 0 || k
> (UINT64_MAX
-1)/USEC_PER_SEC
)
2029 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
2030 "Hook timeout value out of range.");
2032 *u
= k
* USEC_PER_SEC
;
2036 static int oci_hooks_array(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
2037 Settings
*s
= ASSERT_PTR(userdata
);
2041 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
2043 static const JsonDispatch table
[] = {
2044 { "path", JSON_VARIANT_STRING
, oci_absolute_path
, offsetof(OciHook
, path
), JSON_MANDATORY
},
2045 { "args", JSON_VARIANT_ARRAY
, oci_args
, offsetof(OciHook
, args
), 0 },
2046 { "env", JSON_VARIANT_ARRAY
, oci_env
, offsetof(OciHook
, env
), 0 },
2047 { "timeout", JSON_VARIANT_UNSIGNED
, oci_hook_timeout
, offsetof(OciHook
, timeout
), 0 },
2051 OciHook
*a
, **array
, *new_item
;
2054 if (streq(name
, "prestart")) {
2055 array
= &s
->oci_hooks_prestart
;
2056 n_array
= &s
->n_oci_hooks_prestart
;
2057 } else if (streq(name
, "poststart")) {
2058 array
= &s
->oci_hooks_poststart
;
2059 n_array
= &s
->n_oci_hooks_poststart
;
2061 assert(streq(name
, "poststop"));
2062 array
= &s
->oci_hooks_poststop
;
2063 n_array
= &s
->n_oci_hooks_poststop
;
2066 a
= reallocarray(*array
, *n_array
+ 1, sizeof(OciHook
));
2071 new_item
= a
+ *n_array
;
2073 *new_item
= (OciHook
) {
2074 .timeout
= USEC_INFINITY
,
2077 r
= oci_dispatch(e
, table
, flags
, new_item
);
2079 free(new_item
->path
);
2080 strv_free(new_item
->args
);
2081 strv_free(new_item
->env
);
2091 static int oci_hooks(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
2093 static const JsonDispatch table
[] = {
2094 { "prestart", JSON_VARIANT_ARRAY
, oci_hooks_array
, 0, 0 },
2095 { "poststart", JSON_VARIANT_ARRAY
, oci_hooks_array
, 0, 0 },
2096 { "poststop", JSON_VARIANT_ARRAY
, oci_hooks_array
, 0, 0 },
2100 return oci_dispatch(v
, table
, flags
, userdata
);
2103 static int oci_annotations(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
2107 JSON_VARIANT_OBJECT_FOREACH(k
, w
, v
) {
2110 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
2111 "Annotation with empty key, refusing.");
2113 if (!json_variant_is_string(w
))
2114 return json_log(w
, flags
, SYNTHETIC_ERRNO(EINVAL
),
2115 "Annotation has non-string value, refusing.");
2117 json_log(w
, flags
|JSON_DEBUG
, 0, "Ignoring annotation '%s' with value '%s'.", k
, json_variant_string(w
));
2123 int oci_load(FILE *f
, const char *bundle
, Settings
**ret
) {
2125 static const JsonDispatch table
[] = {
2126 { "ociVersion", JSON_VARIANT_STRING
, NULL
, 0, JSON_MANDATORY
},
2127 { "process", JSON_VARIANT_OBJECT
, oci_process
, 0, 0 },
2128 { "root", JSON_VARIANT_OBJECT
, oci_root
, 0, 0 },
2129 { "hostname", JSON_VARIANT_STRING
, oci_hostname
, 0, 0 },
2130 { "mounts", JSON_VARIANT_ARRAY
, oci_mounts
, 0, 0 },
2131 { "linux", JSON_VARIANT_OBJECT
, oci_linux
, 0, 0 },
2132 { "hooks", JSON_VARIANT_OBJECT
, oci_hooks
, 0, 0 },
2133 { "annotations", JSON_VARIANT_OBJECT
, oci_annotations
, 0, 0 },
2137 _cleanup_(json_variant_unrefp
) JsonVariant
*oci
= NULL
;
2138 _cleanup_(settings_freep
) Settings
*s
= NULL
;
2139 unsigned line
= 0, column
= 0;
2146 path
= strjoina(bundle
, "/config.json");
2148 r
= json_parse_file(f
, path
, 0, &oci
, &line
, &column
);
2150 if (line
!= 0 && column
!= 0)
2151 return log_error_errno(r
, "Failed to parse '%s' at %u:%u: %m", path
, line
, column
);
2153 return log_error_errno(r
, "Failed to parse '%s': %m", path
);
2156 v
= json_variant_by_key(oci
, "ociVersion");
2158 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
2159 "JSON file '%s' is not an OCI bundle configuration file. Refusing.",
2161 if (!streq_ptr(json_variant_string(v
), "1.0.0"))
2162 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
2163 "OCI bundle version not supported: %s",
2164 strna(json_variant_string(v
)));
2167 // _cleanup_free_ char *formatted = NULL;
2168 // assert_se(json_variant_format(oci, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR, &formatted) >= 0);
2169 // fputs(formatted, stdout);
2176 s
->start_mode
= START_PID1
;
2177 s
->resolv_conf
= RESOLV_CONF_OFF
;
2178 s
->link_journal
= LINK_NO
;
2179 s
->timezone
= TIMEZONE_OFF
;
2181 s
->bundle
= strdup(bundle
);
2185 r
= oci_dispatch(oci
, table
, 0, s
);
2189 if (s
->properties
) {
2190 r
= sd_bus_message_seal(s
->properties
, 0, 0);
2192 return log_error_errno(r
, "Cannot seal properties bus message: %m");