1 /* SPDX-License-Identifier: LGPL-2.1+ */
10 #include "cpu-set-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"
20 #include "seccomp-util.h"
22 #include "stat-util.h"
23 #include "stdio-util.h"
24 #include "string-util.h"
26 #include "user-util.h"
29 * OCI runtime tool implementation
34 * How is RLIM_INFINITY supposed to be encoded?
35 * configured effective caps is bullshit, as execv() corrupts it anyway
36 * pipes bind mounted is *very* different from pipes newly created, comments regarding bind mount or not are bogus
37 * annotation values structured? or string?
38 * configurable file system namespace path, but then also root path? wtf?
39 * apply sysctl inside of the container? or outside?
40 * how is unlimited pids tasks limit to be encoded?
41 * what are the defaults for caps if not specified?
42 * 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
43 * 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
44 * 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
45 * if type of mount is left out, what shall be assumed? "bind"?
46 * readonly mounts is entirely redundant?
47 * should escaping be applied when joining mount options with ","?
48 * devices cgroup support is bogus, "allow" and "deny" on the kernel level is about adding/removing entries, not about access
49 * spec needs to say that "rwm" devices cgroup combination can't be the empty string
50 * cgrouspv1 crap: kernel, kernelTCP, swapiness, disableOOMKiller, swap, devices, leafWeight
51 * general: it shouldn't leak lower level abstractions this obviously
52 * unmanagable cgroups stuff: realtimeRuntime/realtimePeriod
53 * needs to say what happense when some option is not specified, i.e. which defautls apply
54 * no architecture? no personality?
55 * seccomp example and logic is simply broken: there's no constant "SCMP_ACT_ERRNO".
56 * spec should say what to do with unknown props
57 * /bin/mount regarding NFS and FUSE required?
58 * what does terminal=false mean?
59 * sysctl inside or outside? whitelisting?
64 * selinuxLabel + mountLabel
69 * swappiness, disableOOMKiller, kernel, kernelTCP, leafWeight (because it's dead, cgroupsv2 can't do it and hence systemd neither)
71 * Non-slice cgroup paths
72 * Propagation that is not slave + shared
73 * more than one uid/gid mapping, mappings with a container base != 0, or non-matching uid/gid mappings
74 * device cgroups access = false items that are not catchall
75 * 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.
79 static int oci_unexpected(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
80 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
81 "Unexpected OCI element '%s' of type '%s'.", name
, json_variant_type_to_string(json_variant_type(v
)));
84 static int oci_unsupported(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
85 return json_log(v
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
),
86 "Unsupported OCI element '%s' of type '%s'.", name
, json_variant_type_to_string(json_variant_type(v
)));
89 static int oci_terminal(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
90 Settings
*s
= userdata
;
92 /* If not specified, or set to true, we'll default to either an interactive or a read-only
93 * console. If specified as false, we'll forcibly move to "pipe" mode though. */
94 s
->console_mode
= json_variant_boolean(v
) ? _CONSOLE_MODE_INVALID
: CONSOLE_PIPE
;
98 static int oci_console_dimension(const char *name
, JsonVariant
*variant
, JsonDispatchFlags flags
, void *userdata
) {
99 unsigned *u
= 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
) {
118 static const JsonDispatch table
[] = {
119 { "height", JSON_VARIANT_UNSIGNED
, oci_console_dimension
, offsetof(Settings
, console_height
), JSON_MANDATORY
},
120 { "width", JSON_VARIANT_UNSIGNED
, oci_console_dimension
, offsetof(Settings
, console_width
), JSON_MANDATORY
},
124 return json_dispatch(v
, table
, oci_unexpected
, flags
, userdata
);
127 static int oci_absolute_path(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
133 n
= json_variant_string(v
);
135 if (!path_is_absolute(n
))
136 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
137 "Path in JSON field '%s' is not absolute: %s", strna(name
), n
);
139 return free_and_strdup_warn(p
, n
);
142 static int oci_env(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
143 char ***l
= userdata
;
149 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
152 if (!json_variant_is_string(e
))
153 return json_log(e
, flags
, SYNTHETIC_ERRNO(EINVAL
),
154 "Environment array contains non-string.");
156 assert_se(n
= json_variant_string(e
));
158 if (!env_assignment_is_valid(n
))
159 return json_log(e
, flags
, SYNTHETIC_ERRNO(EINVAL
),
160 "Environment assignment not valid: %s", n
);
162 r
= strv_extend(l
, n
);
170 static int oci_args(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
171 _cleanup_strv_free_
char **l
= NULL
;
172 char ***value
= userdata
;
178 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
181 if (!json_variant_is_string(e
))
182 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
183 "Argument is not a string.");
185 assert_se(n
= json_variant_string(e
));
187 r
= strv_extend(&l
, n
);
193 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
194 "Argument list empty, refusing.");
197 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
198 "Executable name is empty, refusing.");
200 return strv_free_and_replace(*value
, l
);
203 static int oci_rlimit_type(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
205 int t
, *type
= userdata
;
209 z
= startswith(json_variant_string(v
), "RLIMIT_");
211 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
212 "rlimit entry's name does not begin with 'RLIMIT_', refusing: %s",
213 json_variant_string(v
));
215 t
= rlimit_from_string(z
);
217 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
218 "rlimit name unknown: %s", json_variant_string(v
));
224 static int oci_rlimit_value(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
225 rlim_t z
, *value
= userdata
;
229 if (json_variant_is_negative(v
))
232 if (!json_variant_is_unsigned(v
))
233 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
234 "rlimits limit not unsigned, refusing.");
236 z
= (rlim_t
) json_variant_unsigned(v
);
238 if ((uintmax_t) z
!= json_variant_unsigned(v
))
239 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
240 "rlimits limit out of range, refusing.");
247 static int oci_rlimits(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
249 Settings
*s
= userdata
;
255 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
263 .soft
= RLIM_INFINITY
,
264 .hard
= RLIM_INFINITY
,
267 static const JsonDispatch table
[] = {
268 { "soft", JSON_VARIANT_NUMBER
, oci_rlimit_value
, offsetof(struct rlimit_data
, soft
), JSON_MANDATORY
},
269 { "hard", JSON_VARIANT_NUMBER
, oci_rlimit_value
, offsetof(struct rlimit_data
, hard
), JSON_MANDATORY
},
270 { "type", JSON_VARIANT_STRING
, oci_rlimit_type
, offsetof(struct rlimit_data
, type
), JSON_MANDATORY
},
275 r
= json_dispatch(e
, table
, oci_unexpected
, flags
, &data
);
279 assert(data
.type
>= 0);
280 assert(data
.type
< _RLIMIT_MAX
);
282 if (s
->rlimit
[data
.type
])
283 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
284 "rlimits array contains duplicate entry, refusing.");
286 s
->rlimit
[data
.type
] = new(struct rlimit
, 1);
287 if (!s
->rlimit
[data
.type
])
290 *s
->rlimit
[data
.type
] = (struct rlimit
) {
291 .rlim_cur
= data
.soft
,
292 .rlim_max
= data
.hard
,
299 static int oci_capability_array(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
300 uint64_t *mask
= userdata
, m
= 0;
303 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
307 if (!json_variant_is_string(e
))
308 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
309 "Entry in capabilities array is not a string.");
311 assert_se(n
= json_variant_string(e
));
313 cap
= capability_from_name(n
);
315 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
316 "Unknown capability: %s", n
);
318 m
|= UINT64_C(1) << cap
;
321 if (*mask
== (uint64_t) -1)
329 static int oci_capabilities(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
331 static const JsonDispatch table
[] = {
332 { "effective", JSON_VARIANT_ARRAY
, oci_capability_array
, offsetof(CapabilityQuintet
, effective
) },
333 { "bounding", JSON_VARIANT_ARRAY
, oci_capability_array
, offsetof(CapabilityQuintet
, bounding
) },
334 { "inheritable", JSON_VARIANT_ARRAY
, oci_capability_array
, offsetof(CapabilityQuintet
, inheritable
) },
335 { "permitted", JSON_VARIANT_ARRAY
, oci_capability_array
, offsetof(CapabilityQuintet
, permitted
) },
336 { "ambient", JSON_VARIANT_ARRAY
, oci_capability_array
, offsetof(CapabilityQuintet
, ambient
) },
340 Settings
*s
= userdata
;
345 r
= json_dispatch(v
, table
, oci_unexpected
, flags
, &s
->full_capabilities
);
349 if (s
->full_capabilities
.bounding
!= (uint64_t) -1) {
350 s
->capability
= s
->full_capabilities
.bounding
;
351 s
->drop_capability
= ~s
->full_capabilities
.bounding
;
357 static int oci_oom_score_adj(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
358 Settings
*s
= userdata
;
363 k
= json_variant_integer(v
);
364 if (k
< OOM_SCORE_ADJ_MIN
|| k
> OOM_SCORE_ADJ_MAX
)
365 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
366 "oomScoreAdj value out of range: %ji", k
);
368 s
->oom_score_adjust
= (int) k
;
369 s
->oom_score_adjust_set
= true;
374 static int oci_uid_gid(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
375 uid_t
*uid
= userdata
, u
;
379 assert_cc(sizeof(uid_t
) == sizeof(gid_t
));
381 k
= json_variant_unsigned(v
);
383 if ((uintmax_t) u
!= k
)
384 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
385 "UID/GID out of range: %ji", k
);
387 if (!uid_is_valid(u
))
388 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
389 "UID/GID is not valid: " UID_FMT
, u
);
395 static int oci_supplementary_gids(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
396 Settings
*s
= userdata
;
402 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
405 if (!json_variant_is_unsigned(e
))
406 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
407 "Supplementary GID entry is not a UID.");
409 r
= oci_uid_gid(name
, e
, flags
, &gid
);
413 a
= reallocarray(s
->supplementary_gids
, s
->n_supplementary_gids
+ 1, sizeof(gid_t
));
417 s
->supplementary_gids
= a
;
418 s
->supplementary_gids
[s
->n_supplementary_gids
++] = gid
;
424 static int oci_user(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
425 static const JsonDispatch table
[] = {
426 { "uid", JSON_VARIANT_UNSIGNED
, oci_uid_gid
, offsetof(Settings
, uid
), JSON_MANDATORY
},
427 { "gid", JSON_VARIANT_UNSIGNED
, oci_uid_gid
, offsetof(Settings
, gid
), JSON_MANDATORY
},
428 { "additionalGids", JSON_VARIANT_ARRAY
, oci_supplementary_gids
, 0, 0 },
432 return json_dispatch(v
, table
, oci_unexpected
, flags
, userdata
);
435 static int oci_process(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
437 static const JsonDispatch table
[] = {
438 { "terminal", JSON_VARIANT_BOOLEAN
, oci_terminal
, 0, 0 },
439 { "consoleSize", JSON_VARIANT_OBJECT
, oci_console_size
, 0, 0 },
440 { "cwd", JSON_VARIANT_STRING
, oci_absolute_path
, offsetof(Settings
, working_directory
), 0 },
441 { "env", JSON_VARIANT_ARRAY
, oci_env
, offsetof(Settings
, environment
), 0 },
442 { "args", JSON_VARIANT_ARRAY
, oci_args
, offsetof(Settings
, parameters
), 0 },
443 { "rlimits", JSON_VARIANT_ARRAY
, oci_rlimits
, 0, 0 },
444 { "apparmorProfile", JSON_VARIANT_STRING
, oci_unsupported
, 0, JSON_PERMISSIVE
},
445 { "capabilities", JSON_VARIANT_OBJECT
, oci_capabilities
, 0, 0 },
446 { "noNewPrivileges", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(Settings
, no_new_privileges
), 0 },
447 { "oomScoreAdj", JSON_VARIANT_INTEGER
, oci_oom_score_adj
, 0, 0 },
448 { "selinuxLabel", JSON_VARIANT_STRING
, oci_unsupported
, 0, JSON_PERMISSIVE
},
449 { "user", JSON_VARIANT_OBJECT
, oci_user
, 0, 0 },
453 return json_dispatch(v
, table
, oci_unexpected
, flags
, userdata
);
456 static int oci_root(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
458 static const JsonDispatch table
[] = {
459 { "path", JSON_VARIANT_STRING
, json_dispatch_string
, offsetof(Settings
, root
) },
460 { "readonly", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(Settings
, read_only
) },
464 return json_dispatch(v
, table
, oci_unexpected
, flags
, userdata
);
467 static int oci_hostname(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
468 Settings
*s
= userdata
;
473 assert_se(n
= json_variant_string(v
));
475 if (!hostname_is_valid(n
, false))
476 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
477 "Hostname string is not a valid hostname: %s", n
);
479 return free_and_strdup_warn(&s
->hostname
, n
);
482 static bool oci_exclude_mount(const char *path
) {
484 /* Returns "true" for all mounts we insist to mount on our own, and hence ignore the OCI data. */
486 if (PATH_IN_SET(path
,
504 "/proc/sysrq-trigger",
513 /* Similar, skip the whole /sys/fs/cgroups subtree */
514 if (path_startswith(path
, "/sys/fs/cgroup"))
520 static int oci_mounts(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
521 Settings
*s
= userdata
;
527 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
536 static const JsonDispatch table
[] = {
537 { "destination", JSON_VARIANT_STRING
, oci_absolute_path
, offsetof(struct mount_data
, destination
), JSON_MANDATORY
},
538 { "source", JSON_VARIANT_STRING
, json_dispatch_string
, offsetof(struct mount_data
, source
), 0 },
539 { "options", JSON_VARIANT_ARRAY
, json_dispatch_strv
, offsetof(struct mount_data
, options
), 0, },
540 { "type", JSON_VARIANT_STRING
, json_dispatch_string
, offsetof(struct mount_data
, type
), 0 },
544 _cleanup_free_
char *joined_options
= NULL
;
547 r
= json_dispatch(e
, table
, oci_unexpected
, flags
, &data
);
551 if (!path_is_absolute(data
.destination
)) {
552 r
= json_log(e
, flags
, SYNTHETIC_ERRNO(EINVAL
),
553 "Mount destination not an absolute path: %s", data
.destination
);
557 if (oci_exclude_mount(data
.destination
))
561 joined_options
= strv_join(data
.options
, ",");
562 if (!joined_options
) {
568 if (!data
.type
|| streq(data
.type
, "bind")) {
570 if (!path_is_absolute(data
.source
)) {
573 joined
= path_join(s
->bundle
, data
.source
);
579 free_and_replace(data
.source
, joined
);
582 data
.type
= mfree(data
.type
);
584 m
= custom_mount_add(&s
->custom_mounts
, &s
->n_custom_mounts
, CUSTOM_MOUNT_BIND
);
586 m
= custom_mount_add(&s
->custom_mounts
, &s
->n_custom_mounts
, CUSTOM_MOUNT_ARBITRARY
);
592 m
->destination
= TAKE_PTR(data
.destination
);
593 m
->source
= TAKE_PTR(data
.source
);
594 m
->options
= TAKE_PTR(joined_options
);
595 m
->type_argument
= TAKE_PTR(data
.type
);
597 strv_free(data
.options
);
601 free(data
.destination
);
603 strv_free(data
.options
);
609 free(data
.destination
);
611 strv_free(data
.options
);
618 static int oci_namespace_type(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
619 unsigned long *nsflags
= userdata
;
623 assert_se(n
= json_variant_string(v
));
625 /* We don't use namespace_flags_from_string() here, as the OCI spec uses slightly different names than the
628 *nsflags
= CLONE_NEWPID
;
629 else if (streq(n
, "network"))
630 *nsflags
= CLONE_NEWNET
;
631 else if (streq(n
, "mount"))
632 *nsflags
= CLONE_NEWNS
;
633 else if (streq(n
, "ipc"))
634 *nsflags
= CLONE_NEWIPC
;
635 else if (streq(n
, "uts"))
636 *nsflags
= CLONE_NEWUTS
;
637 else if (streq(n
, "user"))
638 *nsflags
= CLONE_NEWUSER
;
639 else if (streq(n
, "cgroup"))
640 *nsflags
= CLONE_NEWCGROUP
;
642 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
643 "Unknown cgroup type, refusing: %s", n
);
648 static int oci_namespaces(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
649 Settings
*s
= userdata
;
656 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
658 struct namespace_data
{
663 static const JsonDispatch table
[] = {
664 { "type", JSON_VARIANT_STRING
, oci_namespace_type
, offsetof(struct namespace_data
, type
), JSON_MANDATORY
},
665 { "path", JSON_VARIANT_STRING
, oci_absolute_path
, offsetof(struct namespace_data
, path
), 0 },
669 r
= json_dispatch(e
, table
, oci_unexpected
, flags
, &data
);
676 if (data
.type
!= CLONE_NEWNET
) {
678 return json_log(e
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
),
679 "Specifying namespace path for non-network namespace is not supported.");
682 if (s
->network_namespace_path
) {
684 return json_log(e
, flags
, SYNTHETIC_ERRNO(EINVAL
),
685 "Network namespace path specified more than once, refusing.");
688 free(s
->network_namespace_path
);
689 s
->network_namespace_path
= data
.path
;
692 if (FLAGS_SET(n
, data
.type
)) {
693 return json_log(e
, flags
, SYNTHETIC_ERRNO(EINVAL
),
694 "Duplicate namespace specification, refusing.");
700 if (!FLAGS_SET(n
, CLONE_NEWNS
))
701 return json_log(v
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
),
702 "Containers without file system namespace aren't supported.");
704 s
->private_network
= FLAGS_SET(n
, CLONE_NEWNET
);
705 s
->userns_mode
= FLAGS_SET(n
, CLONE_NEWUSER
) ? USER_NAMESPACE_FIXED
: USER_NAMESPACE_NO
;
706 s
->use_cgns
= FLAGS_SET(n
, CLONE_NEWCGROUP
);
708 s
->clone_ns_flags
= n
& (CLONE_NEWIPC
|CLONE_NEWPID
|CLONE_NEWUTS
);
713 static int oci_uid_gid_range(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
714 uid_t
*uid
= userdata
, u
;
718 assert_cc(sizeof(uid_t
) == sizeof(gid_t
));
720 /* This is very much like oci_uid_gid(), except the checks are a bit different, as this is a UID range rather
721 * than a specific UID, and hence (uid_t) -1 has no special significance. OTOH a range of zero makes no
724 k
= json_variant_unsigned(v
);
726 if ((uintmax_t) u
!= k
)
727 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
728 "UID/GID out of range: %ji", k
);
730 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
731 "UID/GID range can't be zero.");
737 static int oci_uid_gid_mappings(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
738 struct mapping_data
{
743 .host_id
= UID_INVALID
,
744 .container_id
= UID_INVALID
,
748 static const JsonDispatch table
[] = {
749 { "containerID", JSON_VARIANT_UNSIGNED
, oci_uid_gid
, offsetof(struct mapping_data
, container_id
), JSON_MANDATORY
},
750 { "hostID", JSON_VARIANT_UNSIGNED
, oci_uid_gid
, offsetof(struct mapping_data
, host_id
), JSON_MANDATORY
},
751 { "size", JSON_VARIANT_UNSIGNED
, oci_uid_gid_range
, offsetof(struct mapping_data
, range
), JSON_MANDATORY
},
755 Settings
*s
= userdata
;
761 if (json_variant_elements(v
) == 0)
764 if (json_variant_elements(v
) > 1)
765 return json_log(v
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
),
766 "UID/GID mappings with more than one entry are not supported.");
768 assert_se(e
= json_variant_by_index(v
, 0));
770 r
= json_dispatch(e
, table
, oci_unexpected
, flags
, &data
);
774 if (data
.host_id
+ data
.range
< data
.host_id
||
775 data
.container_id
+ data
.range
< data
.container_id
)
776 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
777 "UID/GID range goes beyond UID/GID validity range, refusing.");
779 if (data
.container_id
!= 0)
780 return json_log(v
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
),
781 "UID/GID mappings with a non-zero container base are not supported.");
783 if (data
.range
< 0x10000)
784 json_log(v
, flags
|JSON_WARNING
, 0,
785 "UID/GID mapping with less than 65536 UID/GIDS set up, you are looking for trouble.");
787 if (s
->uid_range
!= UID_INVALID
&&
788 (s
->uid_shift
!= data
.host_id
|| s
->uid_range
!= data
.range
))
789 return json_log(v
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
),
790 "Non-matching UID and GID mappings are not supported.");
792 s
->uid_shift
= data
.host_id
;
793 s
->uid_range
= data
.range
;
798 static int oci_device_type(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
799 mode_t
*mode
= userdata
;
803 assert_se(t
= json_variant_string(v
));
805 if (STR_IN_SET(t
, "c", "u"))
806 *mode
= (*mode
& ~S_IFMT
) | S_IFCHR
;
807 else if (streq(t
, "b"))
808 *mode
= (*mode
& ~S_IFMT
) | S_IFBLK
;
809 else if (streq(t
, "p"))
810 *mode
= (*mode
& ~S_IFMT
) | S_IFIFO
;
812 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
813 "Unknown device type: %s", t
);
818 static int oci_device_major(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
819 unsigned *u
= userdata
;
824 k
= json_variant_unsigned(v
);
825 if (!DEVICE_MAJOR_VALID(k
))
826 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
827 "Device major %ji out of range.", k
);
833 static int oci_device_minor(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
834 unsigned *u
= userdata
;
839 k
= json_variant_unsigned(v
);
840 if (!DEVICE_MINOR_VALID(k
))
841 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
842 "Device minor %ji out of range.", k
);
848 static int oci_device_file_mode(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
849 mode_t
*mode
= userdata
, m
;
854 k
= json_variant_unsigned(v
);
857 if ((m
& ~07777) != 0 || (uintmax_t) m
!= k
)
858 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
859 "fileMode out of range, refusing.");
865 static int oci_devices(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
866 Settings
*s
= userdata
;
872 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
874 static const JsonDispatch table
[] = {
875 { "type", JSON_VARIANT_STRING
, oci_device_type
, offsetof(DeviceNode
, mode
), JSON_MANDATORY
},
876 { "path", JSON_VARIANT_STRING
, oci_absolute_path
, offsetof(DeviceNode
, path
), JSON_MANDATORY
},
877 { "major", JSON_VARIANT_UNSIGNED
, oci_device_major
, offsetof(DeviceNode
, major
), 0 },
878 { "minor", JSON_VARIANT_UNSIGNED
, oci_device_minor
, offsetof(DeviceNode
, minor
), 0 },
879 { "fileMode", JSON_VARIANT_UNSIGNED
, oci_device_file_mode
, offsetof(DeviceNode
, mode
), 0 },
880 { "uid", JSON_VARIANT_UNSIGNED
, oci_uid_gid
, offsetof(DeviceNode
, uid
), 0 },
881 { "gid", JSON_VARIANT_UNSIGNED
, oci_uid_gid
, offsetof(DeviceNode
, gid
), 0 },
885 DeviceNode
*node
, *nodes
;
887 nodes
= reallocarray(s
->extra_nodes
, s
->n_extra_nodes
+ 1, sizeof(DeviceNode
));
891 s
->extra_nodes
= nodes
;
893 node
= nodes
+ s
->n_extra_nodes
;
894 *node
= (DeviceNode
) {
897 .major
= (unsigned) -1,
898 .minor
= (unsigned) -1,
902 r
= json_dispatch(e
, table
, oci_unexpected
, flags
, node
);
906 if (S_ISCHR(node
->mode
) || S_ISBLK(node
->mode
)) {
907 _cleanup_free_
char *path
= NULL
;
909 if (node
->major
== (unsigned) -1 || node
->minor
== (unsigned) -1) {
910 r
= json_log(e
, flags
, SYNTHETIC_ERRNO(EINVAL
),
911 "Major/minor required when device node is device node");
915 /* Suppress a couple of implicit device nodes */
916 r
= device_path_make_canonical(node
->mode
, makedev(node
->major
, node
->minor
), &path
);
918 json_log(e
, flags
|JSON_DEBUG
, 0, "Failed to resolve device node %u:%u, ignoring: %m", node
->major
, node
->minor
);
920 if (PATH_IN_SET(path
,
932 json_log(e
, flags
|JSON_DEBUG
, 0, "Ignoring devices item for device '%s', as it is implicitly created anyway.", path
);
950 static int oci_cgroups_path(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
951 _cleanup_free_
char *slice
= NULL
, *backwards
= NULL
;
952 Settings
*s
= userdata
;
958 assert_se(p
= json_variant_string(v
));
960 r
= cg_path_get_slice(p
, &slice
);
962 return json_log(v
, flags
, r
, "Couldn't derive slice unit name from path '%s': %m", p
);
964 r
= cg_slice_to_path(slice
, &backwards
);
966 return json_log(v
, flags
, r
, "Couldn't convert slice unit name '%s' back to path: %m", slice
);
968 if (!path_equal(backwards
, p
))
969 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
970 "Control group path '%s' does not refer to slice unit, refusing.", p
);
972 free_and_replace(s
->slice
, slice
);
976 static int oci_cgroup_device_type(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
977 mode_t
*mode
= userdata
;
980 assert_se(n
= json_variant_string(v
));
984 else if (streq(n
, "b"))
987 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
988 "Control group device type unknown: %s", n
);
1003 static int oci_cgroup_device_access(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1004 struct device_data
*d
= userdata
;
1005 bool r
= false, w
= false, m
= false;
1009 assert_se(s
= json_variant_string(v
));
1011 for (i
= 0; s
[i
]; i
++)
1014 else if (s
[i
] == 'w')
1016 else if (s
[i
] == 'm')
1019 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
1020 "Unknown device access character '%c'.", s
[i
]);
1029 static int oci_cgroup_devices(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1031 _cleanup_free_
struct device_data
*list
= NULL
;
1032 Settings
*s
= userdata
;
1033 size_t n_list
= 0, i
;
1040 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
1042 struct device_data data
= {
1043 .major
= (unsigned) -1,
1044 .minor
= (unsigned) -1,
1047 static const JsonDispatch table
[] = {
1048 { "allow", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(struct device_data
, allow
), JSON_MANDATORY
},
1049 { "type", JSON_VARIANT_STRING
, oci_cgroup_device_type
, offsetof(struct device_data
, type
), 0 },
1050 { "major", JSON_VARIANT_UNSIGNED
, oci_device_major
, offsetof(struct device_data
, major
), 0 },
1051 { "minor", JSON_VARIANT_UNSIGNED
, oci_device_minor
, offsetof(struct device_data
, minor
), 0 },
1052 { "access", JSON_VARIANT_STRING
, oci_cgroup_device_access
, 0, 0 },
1056 r
= json_dispatch(e
, table
, oci_unexpected
, flags
, &data
);
1061 /* The fact that OCI allows 'deny' entries makes really no sense, as 'allow' vs. 'deny' for the
1062 * devices cgroup controller is really not about whitelisting and blacklisting but about adding
1063 * and removing entries from the whitelist. Since we always start out with an empty whitelist
1064 * we hence ignore the whole thing, as removing entries which don't exist make no sense. We'll
1065 * log about this, since this is really borked in the spec, with one exception: the entry
1066 * that's supposed to drop the kernel's default we ignore silently */
1068 if (!data
.r
|| !data
.w
|| !data
.m
|| data
.type
!= 0 || data
.major
!= (unsigned) -1 || data
.minor
!= (unsigned) -1)
1069 json_log(v
, flags
|JSON_WARNING
, 0, "Devices cgroup whitelist with arbitrary 'allow' entries not supported, ignoring.");
1071 /* We ignore the 'deny' entry as for us that's implied */
1075 if (!data
.r
&& !data
.w
&& !data
.m
) {
1076 json_log(v
, flags
|LOG_WARNING
, 0, "Device cgroup whitelist entry with no effect found, ignoring.");
1080 if (data
.minor
!= (unsigned) -1 && data
.major
== (unsigned) -1)
1081 return json_log(v
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
),
1082 "Device cgroup whitelist entries with minors but no majors not supported.");
1084 if (data
.major
!= (unsigned) -1 && data
.type
== 0)
1085 return json_log(v
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
),
1086 "Device cgroup whitelist entries with majors but no device node type not supported.");
1088 if (data
.type
== 0) {
1089 if (data
.r
&& data
.w
&& data
.m
) /* a catchall whitelist entry means we are looking at a noop */
1092 return json_log(v
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
),
1093 "Device cgroup whitelist entries with no type not supported.");
1096 a
= reallocarray(list
, n_list
+ 1, sizeof(struct device_data
));
1101 list
[n_list
++] = data
;
1107 r
= settings_allocate_properties(s
);
1111 r
= sd_bus_message_open_container(s
->properties
, 'r', "sv");
1113 return bus_log_create_error(r
);
1115 r
= sd_bus_message_append(s
->properties
, "s", "DeviceAllow");
1117 return bus_log_create_error(r
);
1119 r
= sd_bus_message_open_container(s
->properties
, 'v', "a(ss)");
1121 return bus_log_create_error(r
);
1123 r
= sd_bus_message_open_container(s
->properties
, 'a', "(ss)");
1125 return bus_log_create_error(r
);
1127 for (i
= 0; i
< n_list
; i
++) {
1128 _cleanup_free_
char *pattern
= NULL
;
1132 if (list
[i
].minor
== (unsigned) -1) {
1135 if (list
[i
].type
== S_IFBLK
)
1138 assert(list
[i
].type
== S_IFCHR
);
1142 if (list
[i
].major
== (unsigned) -1) {
1143 pattern
= strjoin(t
, "-*");
1147 if (asprintf(&pattern
, "%s-%u", t
, list
[i
].major
) < 0)
1152 assert(list
[i
].major
!= (unsigned) -1); /* If a minor is specified, then a major also needs to be specified */
1154 r
= device_path_make_major_minor(list
[i
].type
, makedev(list
[i
].major
, list
[i
].minor
), &pattern
);
1169 r
= sd_bus_message_append(s
->properties
, "(ss)", pattern
, access
);
1171 return bus_log_create_error(r
);
1174 r
= sd_bus_message_close_container(s
->properties
);
1176 return bus_log_create_error(r
);
1178 r
= sd_bus_message_close_container(s
->properties
);
1180 return bus_log_create_error(r
);
1182 r
= sd_bus_message_close_container(s
->properties
);
1184 return bus_log_create_error(r
);
1189 static int oci_cgroup_memory_limit(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1190 uint64_t *m
= userdata
;
1195 if (json_variant_is_negative(v
)) {
1200 if (!json_variant_is_unsigned(v
))
1201 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
1202 "Memory limit is not an unsigned integer");
1204 k
= json_variant_unsigned(v
);
1205 if (k
>= UINT64_MAX
)
1206 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
1207 "Memory limit too large: %ji", k
);
1213 static int oci_cgroup_memory(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1215 struct memory_data
{
1217 uint64_t reservation
;
1220 .limit
= UINT64_MAX
,
1221 .reservation
= UINT64_MAX
,
1225 static const JsonDispatch table
[] = {
1226 { "limit", JSON_VARIANT_NUMBER
, oci_cgroup_memory_limit
, offsetof(struct memory_data
, limit
), 0 },
1227 { "reservation", JSON_VARIANT_NUMBER
, oci_cgroup_memory_limit
, offsetof(struct memory_data
, reservation
), 0 },
1228 { "swap", JSON_VARIANT_NUMBER
, oci_cgroup_memory_limit
, offsetof(struct memory_data
, swap
), 0 },
1229 { "kernel", JSON_VARIANT_NUMBER
, oci_unsupported
, 0, JSON_PERMISSIVE
},
1230 { "kernelTCP", JSON_VARIANT_NUMBER
, oci_unsupported
, 0, JSON_PERMISSIVE
},
1231 { "swapiness", JSON_VARIANT_NUMBER
, oci_unsupported
, 0, JSON_PERMISSIVE
},
1232 { "disableOOMKiller", JSON_VARIANT_NUMBER
, oci_unsupported
, 0, JSON_PERMISSIVE
},
1236 Settings
*s
= userdata
;
1239 r
= json_dispatch(v
, table
, oci_unexpected
, flags
, &data
);
1243 if (data
.swap
!= UINT64_MAX
) {
1244 if (data
.limit
== UINT64_MAX
)
1245 json_log(v
, flags
|LOG_WARNING
, 0, "swap limit without memory limit is not supported, ignoring.");
1246 else if (data
.swap
< data
.limit
)
1247 json_log(v
, flags
|LOG_WARNING
, 0, "swap limit is below memory limit, ignoring.");
1249 r
= settings_allocate_properties(s
);
1253 r
= sd_bus_message_append(s
->properties
, "(sv)", "MemorySwapMax", "t", data
.swap
- data
.limit
);
1255 return bus_log_create_error(r
);
1259 if (data
.limit
!= UINT64_MAX
) {
1260 r
= settings_allocate_properties(s
);
1264 r
= sd_bus_message_append(s
->properties
, "(sv)", "MemoryMax", "t", data
.limit
);
1266 return bus_log_create_error(r
);
1269 if (data
.reservation
!= UINT64_MAX
) {
1270 r
= settings_allocate_properties(s
);
1274 r
= sd_bus_message_append(s
->properties
, "(sv)", "MemoryLow", "t", data
.reservation
);
1276 return bus_log_create_error(r
);
1290 static int oci_cgroup_cpu_shares(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1291 uint64_t *u
= userdata
;
1296 k
= json_variant_unsigned(v
);
1297 if (k
< CGROUP_CPU_SHARES_MIN
|| k
> CGROUP_CPU_SHARES_MAX
)
1298 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
1299 "shares value out of range.");
1305 static int oci_cgroup_cpu_quota(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1306 uint64_t *u
= userdata
;
1311 k
= json_variant_unsigned(v
);
1312 if (k
<= 0 || k
>= UINT64_MAX
)
1313 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
1314 "period/quota value out of range.");
1320 static int oci_cgroup_cpu_cpus(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1321 struct cpu_data
*data
= userdata
;
1328 assert_se(n
= json_variant_string(v
));
1330 ncpus
= parse_cpu_set(n
, &set
);
1332 return json_log(v
, flags
, ncpus
, "Failed to parse CPU set specification: %s", n
);
1334 CPU_FREE(data
->cpuset
);
1336 data
->ncpus
= ncpus
;
1341 static int oci_cgroup_cpu(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1343 static const JsonDispatch table
[] = {
1344 { "shares", JSON_VARIANT_UNSIGNED
, oci_cgroup_cpu_shares
, offsetof(struct cpu_data
, shares
), 0 },
1345 { "quota", JSON_VARIANT_UNSIGNED
, oci_cgroup_cpu_quota
, offsetof(struct cpu_data
, quota
), 0 },
1346 { "period", JSON_VARIANT_UNSIGNED
, oci_cgroup_cpu_quota
, offsetof(struct cpu_data
, period
), 0 },
1347 { "realtimeRuntime", JSON_VARIANT_UNSIGNED
, oci_unsupported
, 0, 0 },
1348 { "realtimePeriod", JSON_VARIANT_UNSIGNED
, oci_unsupported
, 0, 0 },
1349 { "cpus", JSON_VARIANT_STRING
, oci_cgroup_cpu_cpus
, 0, 0 },
1350 { "mems", JSON_VARIANT_STRING
, oci_unsupported
, 0, 0 },
1354 struct cpu_data data
= {
1355 .shares
= UINT64_MAX
,
1356 .quota
= UINT64_MAX
,
1357 .period
= UINT64_MAX
,
1360 Settings
*s
= userdata
;
1363 r
= json_dispatch(v
, table
, oci_unexpected
, flags
, &data
);
1365 CPU_FREE(data
.cpuset
);
1369 CPU_FREE(s
->cpuset
);
1370 s
->cpuset
= data
.cpuset
;
1371 s
->cpuset_ncpus
= data
.ncpus
;
1373 if (data
.shares
!= UINT64_MAX
) {
1374 r
= settings_allocate_properties(s
);
1378 r
= sd_bus_message_append(s
->properties
, "(sv)", "CPUShares", "t", data
.shares
);
1380 return bus_log_create_error(r
);
1383 if (data
.quota
!= UINT64_MAX
&& data
.period
!= UINT64_MAX
) {
1384 r
= settings_allocate_properties(s
);
1388 r
= sd_bus_message_append(s
->properties
, "(sv)", "CPUQuotaPerSecUSec", "t", (uint64_t) (data
.quota
* USEC_PER_SEC
/ data
.period
));
1390 return bus_log_create_error(r
);
1392 } else if ((data
.quota
!= UINT64_MAX
) != (data
.period
!= UINT64_MAX
))
1393 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
1394 "CPU quota and period not used together.");
1399 static int oci_cgroup_block_io_weight(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1400 Settings
*s
= userdata
;
1406 k
= json_variant_unsigned(v
);
1407 if (k
< CGROUP_BLKIO_WEIGHT_MIN
|| k
> CGROUP_BLKIO_WEIGHT_MAX
)
1408 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
1409 "Block I/O weight out of range.");
1411 r
= settings_allocate_properties(s
);
1415 r
= sd_bus_message_append(s
->properties
, "(sv)", "BlockIOWeight", "t", (uint64_t) k
);
1417 return bus_log_create_error(r
);
1422 static int oci_cgroup_block_io_weight_device(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1423 Settings
*s
= userdata
;
1429 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
1430 struct device_data
{
1435 .major
= (unsigned) -1,
1436 .minor
= (unsigned) -1,
1437 .weight
= UINTMAX_MAX
,
1440 static const JsonDispatch table
[] = {
1441 { "major", JSON_VARIANT_UNSIGNED
, oci_device_major
, offsetof(struct device_data
, major
), JSON_MANDATORY
},
1442 { "minor", JSON_VARIANT_UNSIGNED
, oci_device_minor
, offsetof(struct device_data
, minor
), JSON_MANDATORY
},
1443 { "weight", JSON_VARIANT_UNSIGNED
, json_dispatch_unsigned
, offsetof(struct device_data
, weight
), 0 },
1444 { "leafWeight", JSON_VARIANT_INTEGER
, oci_unsupported
, 0, JSON_PERMISSIVE
},
1448 _cleanup_free_
char *path
= NULL
;
1450 r
= json_dispatch(e
, table
, oci_unexpected
, flags
, &data
);
1454 if (data
.weight
== UINTMAX_MAX
)
1457 if (data
.weight
< CGROUP_BLKIO_WEIGHT_MIN
|| data
.weight
> CGROUP_BLKIO_WEIGHT_MAX
)
1458 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
1459 "Block I/O device weight out of range.");
1461 r
= device_path_make_major_minor(S_IFBLK
, makedev(data
.major
, data
.minor
), &path
);
1463 return json_log(v
, flags
, r
, "Failed to build device path: %m");
1465 r
= settings_allocate_properties(s
);
1469 r
= sd_bus_message_append(s
->properties
, "(sv)", "BlockIODeviceWeight", "a(st)", 1, path
, (uint64_t) data
.weight
);
1471 return bus_log_create_error(r
);
1477 static int oci_cgroup_block_io_throttle(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1478 Settings
*s
= userdata
;
1485 pname
= streq(name
, "throttleReadBpsDevice") ? "IOReadBandwidthMax" :
1486 streq(name
, "throttleWriteBpsDevice") ? "IOWriteBandwidthMax" :
1487 streq(name
, "throttleReadIOPSDevice") ? "IOReadIOPSMax" :
1490 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
1491 struct device_data
{
1496 .major
= (unsigned) -1,
1497 .minor
= (unsigned) -1,
1500 static const JsonDispatch table
[] = {
1501 { "major", JSON_VARIANT_UNSIGNED
, oci_device_major
, offsetof(struct device_data
, major
), JSON_MANDATORY
},
1502 { "minor", JSON_VARIANT_UNSIGNED
, oci_device_minor
, offsetof(struct device_data
, minor
), JSON_MANDATORY
},
1503 { "rate", JSON_VARIANT_UNSIGNED
, json_dispatch_unsigned
, offsetof(struct device_data
, rate
), JSON_MANDATORY
},
1507 _cleanup_free_
char *path
= NULL
;
1509 r
= json_dispatch(e
, table
, oci_unexpected
, flags
, &data
);
1513 if (data
.rate
>= UINT64_MAX
)
1514 return json_log(v
, flags
, SYNTHETIC_ERRNO(ERANGE
),
1515 "Block I/O device rate out of range.");
1517 r
= device_path_make_major_minor(S_IFBLK
, makedev(data
.major
, data
.minor
), &path
);
1519 return json_log(v
, flags
, r
, "Failed to build device path: %m");
1521 r
= settings_allocate_properties(s
);
1525 r
= sd_bus_message_append(s
->properties
, "(sv)", pname
, "a(st)", 1, path
, (uint64_t) data
.rate
);
1527 return bus_log_create_error(r
);
1533 static int oci_cgroup_block_io(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1535 static const JsonDispatch table
[] = {
1536 { "weight", JSON_VARIANT_UNSIGNED
, oci_cgroup_block_io_weight
, 0, 0 },
1537 { "leafWeight", JSON_VARIANT_UNSIGNED
, oci_unsupported
, 0, JSON_PERMISSIVE
},
1538 { "weightDevice", JSON_VARIANT_ARRAY
, oci_cgroup_block_io_weight_device
, 0, 0 },
1539 { "throttleReadBpsDevice", JSON_VARIANT_ARRAY
, oci_cgroup_block_io_throttle
, 0, 0 },
1540 { "throttleWriteBpsDevice", JSON_VARIANT_ARRAY
, oci_cgroup_block_io_throttle
, 0, 0 },
1541 { "throttleReadIOPSDevice", JSON_VARIANT_ARRAY
, oci_cgroup_block_io_throttle
, 0, 0 },
1542 { "throttleWriteIOPSDevice", JSON_VARIANT_ARRAY
, oci_cgroup_block_io_throttle
, 0, 0 },
1546 return json_dispatch(v
, table
, oci_unexpected
, flags
, userdata
);
1549 static int oci_cgroup_pids(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1551 static const JsonDispatch table
[] = {
1552 { "limit", JSON_VARIANT_NUMBER
, json_dispatch_variant
, 0, JSON_MANDATORY
},
1556 _cleanup_(json_variant_unrefp
) JsonVariant
*k
= NULL
;
1557 Settings
*s
= userdata
;
1563 r
= json_dispatch(v
, table
, oci_unexpected
, flags
, &k
);
1567 if (json_variant_is_negative(k
))
1570 if (!json_variant_is_unsigned(k
))
1571 return json_log(k
, flags
, SYNTHETIC_ERRNO(EINVAL
),
1572 "pids limit not unsigned integer, refusing.");
1574 m
= (uint64_t) json_variant_unsigned(k
);
1576 if ((uintmax_t) m
!= json_variant_unsigned(k
))
1577 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
1578 "pids limit out of range, refusing.");
1581 r
= settings_allocate_properties(s
);
1585 r
= sd_bus_message_append(s
->properties
, "(sv)", "TasksMax", "t", m
);
1587 return bus_log_create_error(r
);
1592 static int oci_resources(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1594 static const JsonDispatch table
[] = {
1595 { "devices", JSON_VARIANT_ARRAY
, oci_cgroup_devices
, 0, 0 },
1596 { "memory", JSON_VARIANT_OBJECT
, oci_cgroup_memory
, 0, 0 },
1597 { "cpu", JSON_VARIANT_OBJECT
, oci_cgroup_cpu
, 0, 0 },
1598 { "blockIO", JSON_VARIANT_OBJECT
, oci_cgroup_block_io
, 0, 0 },
1599 { "hugepageLimits", JSON_VARIANT_ARRAY
, oci_unsupported
, 0, 0 },
1600 { "network", JSON_VARIANT_OBJECT
, oci_unsupported
, 0, 0 },
1601 { "pids", JSON_VARIANT_OBJECT
, oci_cgroup_pids
, 0, 0 },
1602 { "rdma", JSON_VARIANT_OBJECT
, oci_unsupported
, 0, 0 },
1606 return json_dispatch(v
, table
, oci_unexpected
, flags
, userdata
);
1609 static bool sysctl_key_valid(const char *s
) {
1612 /* Note that we are a bit stricter here than in systemd-sysctl, as that inherited semantics from the old sysctl
1613 * tool, which were really weird (as it swaps / and . in both ways) */
1620 if (*s
<= ' ' || *s
>= 127)
1626 if (dot
) /* Don't allow two dots next to each other (or at the beginning) */
1634 if (dot
) /* don't allow a dot at the end */
1640 static int oci_sysctl(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1641 Settings
*s
= userdata
;
1647 JSON_VARIANT_OBJECT_FOREACH(k
, w
, v
) {
1650 if (!json_variant_is_string(w
))
1651 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
1652 "sysctl parameter is not a string, refusing.");
1654 assert_se(n
= json_variant_string(k
));
1655 assert_se(m
= json_variant_string(w
));
1657 if (sysctl_key_valid(n
))
1658 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
1659 "sysctl key invalid, refusing: %s", n
);
1661 r
= strv_extend_strv(&s
->sysctl
, STRV_MAKE(n
, m
), false);
1670 static int oci_seccomp_action_from_string(const char *name
, uint32_t *ret
) {
1672 static const struct {
1676 { "SCMP_ACT_ALLOW", SCMP_ACT_ALLOW
},
1677 { "SCMP_ACT_ERRNO", SCMP_ACT_ERRNO(EPERM
) }, /* the OCI spec doesn't document the error, but it appears EPERM is supposed to be used */
1678 { "SCMP_ACT_KILL", SCMP_ACT_KILL
},
1680 { "SCMP_ACT_LOG", SCMP_ACT_LOG
},
1682 { "SCMP_ACT_TRAP", SCMP_ACT_TRAP
},
1684 /* We don't support SCMP_ACT_TRACE because that requires a tracer, and that doesn't really make sense
1690 for (i
= 0; i
< ELEMENTSOF(table
); i
++)
1691 if (streq_ptr(name
, table
[i
].name
)) {
1692 *ret
= table
[i
].action
;
1699 static int oci_seccomp_arch_from_string(const char *name
, uint32_t *ret
) {
1701 static const struct {
1705 { "SCMP_ARCH_AARCH64", SCMP_ARCH_AARCH64
},
1706 { "SCMP_ARCH_ARM", SCMP_ARCH_ARM
},
1707 { "SCMP_ARCH_MIPS", SCMP_ARCH_MIPS
},
1708 { "SCMP_ARCH_MIPS64", SCMP_ARCH_MIPS64
},
1709 { "SCMP_ARCH_MIPS64N32", SCMP_ARCH_MIPS64N32
},
1710 { "SCMP_ARCH_MIPSEL", SCMP_ARCH_MIPSEL
},
1711 { "SCMP_ARCH_MIPSEL64", SCMP_ARCH_MIPSEL64
},
1712 { "SCMP_ARCH_MIPSEL64N32", SCMP_ARCH_MIPSEL64N32
},
1713 { "SCMP_ARCH_NATIVE", SCMP_ARCH_NATIVE
},
1714 #ifdef SCMP_ARCH_PARISC
1715 { "SCMP_ARCH_PARISC", SCMP_ARCH_PARISC
},
1717 #ifdef SCMP_ARCH_PARISC64
1718 { "SCMP_ARCH_PARISC64", SCMP_ARCH_PARISC64
},
1720 { "SCMP_ARCH_PPC", SCMP_ARCH_PPC
},
1721 { "SCMP_ARCH_PPC64", SCMP_ARCH_PPC64
},
1722 { "SCMP_ARCH_PPC64LE", SCMP_ARCH_PPC64LE
},
1723 { "SCMP_ARCH_S390", SCMP_ARCH_S390
},
1724 { "SCMP_ARCH_S390X", SCMP_ARCH_S390X
},
1725 { "SCMP_ARCH_X32", SCMP_ARCH_X32
},
1726 { "SCMP_ARCH_X86", SCMP_ARCH_X86
},
1727 { "SCMP_ARCH_X86_64", SCMP_ARCH_X86_64
},
1732 for (i
= 0; i
< ELEMENTSOF(table
); i
++)
1733 if (streq_ptr(table
[i
].name
, name
)) {
1734 *ret
= table
[i
].arch
;
1741 static int oci_seccomp_compare_from_string(const char *name
, enum scmp_compare
*ret
) {
1743 static const struct {
1745 enum scmp_compare op
;
1747 { "SCMP_CMP_NE", SCMP_CMP_NE
},
1748 { "SCMP_CMP_LT", SCMP_CMP_LT
},
1749 { "SCMP_CMP_LE", SCMP_CMP_LE
},
1750 { "SCMP_CMP_EQ", SCMP_CMP_EQ
},
1751 { "SCMP_CMP_GE", SCMP_CMP_GE
},
1752 { "SCMP_CMP_GT", SCMP_CMP_GT
},
1753 { "SCMP_CMP_MASKED_EQ", SCMP_CMP_MASKED_EQ
},
1758 for (i
= 0; i
< ELEMENTSOF(table
); i
++)
1759 if (streq_ptr(table
[i
].name
, name
)) {
1767 static int oci_seccomp_archs(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1768 scmp_filter_ctx
*sc
= userdata
;
1774 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
1777 if (!json_variant_is_string(e
))
1778 return json_log(e
, flags
, SYNTHETIC_ERRNO(EINVAL
),
1779 "Architecture entry is not a string");
1781 r
= oci_seccomp_arch_from_string(json_variant_string(e
), &a
);
1783 return json_log(e
, flags
, r
, "Unknown architecture: %s", json_variant_string(e
));
1785 r
= seccomp_arch_add(sc
, a
);
1789 return json_log(e
, flags
, r
, "Failed to add architecture to seccomp filter: %m");
1795 struct syscall_rule
{
1798 struct scmp_arg_cmp
*arguments
;
1802 static void syscall_rule_free(struct syscall_rule
*rule
) {
1805 strv_free(rule
->names
);
1806 free(rule
->arguments
);
1809 static int oci_seccomp_action(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1810 uint32_t *action
= userdata
;
1815 r
= oci_seccomp_action_from_string(json_variant_string(v
), action
);
1817 return json_log(v
, flags
, r
, "Unknown system call action '%s': %m", json_variant_string(v
));
1822 static int oci_seccomp_op(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1823 enum scmp_compare
*op
= userdata
;
1828 r
= oci_seccomp_compare_from_string(json_variant_string(v
), op
);
1830 return json_log(v
, flags
, r
, "Unknown seccomp operator '%s': %m", json_variant_string(v
));
1835 static int oci_seccomp_args(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1836 struct syscall_rule
*rule
= userdata
;
1842 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
1843 static const struct JsonDispatch table
[] = {
1844 { "index", JSON_VARIANT_UNSIGNED
, json_dispatch_uint32
, offsetof(struct scmp_arg_cmp
, arg
), JSON_MANDATORY
},
1845 { "value", JSON_VARIANT_UNSIGNED
, json_dispatch_uint64
, offsetof(struct scmp_arg_cmp
, datum_a
), JSON_MANDATORY
},
1846 { "valueTwo", JSON_VARIANT_UNSIGNED
, json_dispatch_uint64
, offsetof(struct scmp_arg_cmp
, datum_b
), 0 },
1847 { "op", JSON_VARIANT_STRING
, oci_seccomp_op
, offsetof(struct scmp_arg_cmp
, op
), JSON_MANDATORY
},
1851 struct scmp_arg_cmp
*a
, *p
;
1854 a
= reallocarray(rule
->arguments
, rule
->n_arguments
+ 1, sizeof(struct syscall_rule
));
1858 rule
->arguments
= a
;
1859 p
= rule
->arguments
+ rule
->n_arguments
;
1861 *p
= (struct scmp_arg_cmp
) {
1868 r
= json_dispatch(e
, table
, oci_unexpected
, flags
, p
);
1872 expected
= p
->op
== SCMP_CMP_MASKED_EQ
? 4 : 3;
1874 json_log(e
, flags
|JSON_WARNING
, 0, "Wrong number of system call arguments for JSON data data, ignoring.");
1876 /* Note that we are a bit sloppy here and do not insist that SCMP_CMP_MASKED_EQ gets two datum values,
1877 * and the other only one. That's because buildah for example by default calls things with
1878 * SCMP_CMP_MASKED_EQ but only one argument. We use 0 when the value is not specified. */
1880 rule
->n_arguments
++;
1886 static int oci_seccomp_syscalls(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1887 scmp_filter_ctx
*sc
= userdata
;
1893 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
1894 static const JsonDispatch table
[] = {
1895 { "names", JSON_VARIANT_ARRAY
, json_dispatch_strv
, offsetof(struct syscall_rule
, names
), JSON_MANDATORY
},
1896 { "action", JSON_VARIANT_STRING
, oci_seccomp_action
, offsetof(struct syscall_rule
, action
), JSON_MANDATORY
},
1897 { "args", JSON_VARIANT_ARRAY
, oci_seccomp_args
, 0, 0 },
1899 struct syscall_rule rule
= {
1900 .action
= (uint32_t) -1,
1904 r
= json_dispatch(e
, table
, oci_unexpected
, flags
, &rule
);
1908 if (strv_isempty(rule
.names
)) {
1909 json_log(e
, flags
, 0, "System call name list is empty.");
1914 STRV_FOREACH(i
, rule
.names
) {
1917 nr
= seccomp_syscall_resolve_name(*i
);
1918 if (nr
== __NR_SCMP_ERROR
) {
1919 log_debug("Unknown syscall %s, skipping.", *i
);
1923 r
= seccomp_rule_add_array(sc
, rule
.action
, nr
, rule
.n_arguments
, rule
.arguments
);
1928 syscall_rule_free(&rule
);
1932 syscall_rule_free(&rule
);
1940 static int oci_seccomp(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1943 static const JsonDispatch table
[] = {
1944 { "defaultAction", JSON_VARIANT_STRING
, NULL
, 0, JSON_MANDATORY
},
1945 { "architectures", JSON_VARIANT_ARRAY
, oci_seccomp_archs
, 0, 0 },
1946 { "syscalls", JSON_VARIANT_ARRAY
, oci_seccomp_syscalls
, 0, 0 },
1950 _cleanup_(seccomp_releasep
) scmp_filter_ctx sc
= NULL
;
1951 Settings
*s
= userdata
;
1958 def
= json_variant_by_key(v
, "defaultAction");
1960 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
), "defaultAction element missing.");
1962 if (!json_variant_is_string(def
))
1963 return json_log(def
, flags
, SYNTHETIC_ERRNO(EINVAL
), "defaultAction is not a string.");
1965 r
= oci_seccomp_action_from_string(json_variant_string(def
), &d
);
1967 return json_log(def
, flags
, r
, "Unknown default action: %s", json_variant_string(def
));
1969 sc
= seccomp_init(d
);
1971 return json_log(v
, flags
, SYNTHETIC_ERRNO(ENOMEM
), "Couldn't allocate seccomp object.");
1973 r
= json_dispatch(v
, table
, oci_unexpected
, flags
, sc
);
1977 seccomp_release(s
->seccomp
);
1978 s
->seccomp
= TAKE_PTR(sc
);
1981 return json_log(v
, flags
, SYNTHETIC_ERRNO(EOPNOTSUPP
), "libseccomp support not enabled, can't parse seccomp object.");
1985 static int oci_rootfs_propagation(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1988 s
= json_variant_string(v
);
1990 if (streq(s
, "shared"))
1993 json_log(v
, flags
|JSON_DEBUG
, 0, "Ignoring rootfsPropagation setting '%s'.", s
);
1997 static int oci_masked_paths(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
1998 Settings
*s
= userdata
;
2003 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
2004 _cleanup_free_
char *destination
= NULL
;
2008 if (!json_variant_is_string(e
))
2009 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
2010 "Path is not a string, refusing.");
2012 assert_se(p
= json_variant_string(e
));
2014 if (!path_is_absolute(p
))
2015 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
2016 "Path is not not absolute, refusing: %s", p
);
2018 if (oci_exclude_mount(p
))
2021 destination
= strdup(p
);
2025 m
= custom_mount_add(&s
->custom_mounts
, &s
->n_custom_mounts
, CUSTOM_MOUNT_INACCESSIBLE
);
2029 m
->destination
= TAKE_PTR(destination
);
2031 /* The spec doesn't say this, but apparently pre-existing implementations are lenient towards
2032 * non-existing paths to mask. Let's hence be too. */
2039 static int oci_readonly_paths(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
2040 Settings
*s
= userdata
;
2045 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
2046 _cleanup_free_
char *source
= NULL
, *destination
= NULL
;
2050 if (!json_variant_is_string(e
))
2051 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
2052 "Path is not a string, refusing.");
2054 assert_se(p
= json_variant_string(e
));
2056 if (!path_is_absolute(p
))
2057 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
2058 "Path is not not absolute, refusing: %s", p
);
2060 if (oci_exclude_mount(p
))
2063 source
= strjoin("+", p
);
2067 destination
= strdup(p
);
2071 m
= custom_mount_add(&s
->custom_mounts
, &s
->n_custom_mounts
, CUSTOM_MOUNT_BIND
);
2075 m
->source
= TAKE_PTR(source
);
2076 m
->destination
= TAKE_PTR(destination
);
2077 m
->read_only
= true;
2083 static int oci_linux(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
2085 static const JsonDispatch table
[] = {
2086 { "namespaces", JSON_VARIANT_ARRAY
, oci_namespaces
, 0, 0 },
2087 { "uidMappings", JSON_VARIANT_ARRAY
, oci_uid_gid_mappings
, 0, 0 },
2088 { "gidMappings", JSON_VARIANT_ARRAY
, oci_uid_gid_mappings
, 0, 0 },
2089 { "devices", JSON_VARIANT_ARRAY
, oci_devices
, 0, 0 },
2090 { "cgroupsPath", JSON_VARIANT_STRING
, oci_cgroups_path
, 0, 0 },
2091 { "resources", JSON_VARIANT_OBJECT
, oci_resources
, 0, 0 },
2092 { "intelRdt", JSON_VARIANT_OBJECT
, oci_unsupported
, 0, JSON_PERMISSIVE
},
2093 { "sysctl", JSON_VARIANT_OBJECT
, oci_sysctl
, 0, 0 },
2094 { "seccomp", JSON_VARIANT_OBJECT
, oci_seccomp
, 0, 0 },
2095 { "rootfsPropagation", JSON_VARIANT_STRING
, oci_rootfs_propagation
, 0, 0 },
2096 { "maskedPaths", JSON_VARIANT_ARRAY
, oci_masked_paths
, 0, 0 },
2097 { "readonlyPaths", JSON_VARIANT_ARRAY
, oci_readonly_paths
, 0, 0 },
2098 { "mountLabel", JSON_VARIANT_STRING
, oci_unsupported
, 0, JSON_PERMISSIVE
},
2102 return json_dispatch(v
, table
, oci_unexpected
, flags
, userdata
);
2105 static int oci_hook_timeout(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
2106 usec_t
*u
= userdata
;
2109 k
= json_variant_unsigned(v
);
2111 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
2112 "Hook timeout cannot be zero.");
2114 if (k
> (UINT64_MAX
-1/USEC_PER_SEC
))
2115 return json_log(v
, flags
, SYNTHETIC_ERRNO(EINVAL
),
2116 "Hook timeout too large.");
2118 *u
= k
* USEC_PER_SEC
;
2122 static int oci_hooks_array(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
2123 Settings
*s
= userdata
;
2129 JSON_VARIANT_ARRAY_FOREACH(e
, v
) {
2131 static const JsonDispatch table
[] = {
2132 { "path", JSON_VARIANT_STRING
, oci_absolute_path
, offsetof(OciHook
, path
), JSON_MANDATORY
},
2133 { "args", JSON_VARIANT_ARRAY
, oci_args
, offsetof(OciHook
, args
), 0 },
2134 { "env", JSON_VARIANT_ARRAY
, oci_env
, offsetof(OciHook
, env
), 0 },
2135 { "timeout", JSON_VARIANT_UNSIGNED
, oci_hook_timeout
, offsetof(OciHook
, timeout
), 0 },
2139 OciHook
*a
, **array
, *new_item
;
2142 if (streq(name
, "prestart")) {
2143 array
= &s
->oci_hooks_prestart
;
2144 n_array
= &s
->n_oci_hooks_prestart
;
2145 } else if (streq(name
, "poststart")) {
2146 array
= &s
->oci_hooks_poststart
;
2147 n_array
= &s
->n_oci_hooks_poststart
;
2149 assert(streq(name
, "poststop"));
2150 array
= &s
->oci_hooks_poststop
;
2151 n_array
= &s
->n_oci_hooks_poststop
;
2154 a
= reallocarray(*array
, *n_array
+ 1, sizeof(OciHook
));
2159 new_item
= a
+ *n_array
;
2161 *new_item
= (OciHook
) {
2162 .timeout
= USEC_INFINITY
,
2165 r
= json_dispatch(e
, table
, oci_unexpected
, flags
, userdata
);
2167 free(new_item
->path
);
2168 strv_free(new_item
->args
);
2169 strv_free(new_item
->env
);
2179 static int oci_hooks(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
2181 static const JsonDispatch table
[] = {
2182 { "prestart", JSON_VARIANT_OBJECT
, oci_hooks_array
, 0, 0 },
2183 { "poststart", JSON_VARIANT_OBJECT
, oci_hooks_array
, 0, 0 },
2184 { "poststop", JSON_VARIANT_OBJECT
, oci_hooks_array
, 0, 0 },
2188 return json_dispatch(v
, table
, oci_unexpected
, flags
, userdata
);
2191 static int oci_annotations(const char *name
, JsonVariant
*v
, JsonDispatchFlags flags
, void *userdata
) {
2194 JSON_VARIANT_OBJECT_FOREACH(k
, w
, v
) {
2197 assert_se(n
= json_variant_string(k
));
2200 return json_log(k
, flags
, SYNTHETIC_ERRNO(EINVAL
),
2201 "Annotation with empty key, refusing.");
2203 if (!json_variant_is_string(w
))
2204 return json_log(w
, flags
, SYNTHETIC_ERRNO(EINVAL
),
2205 "Annotation has non-string value, refusing.");
2207 json_log(k
, flags
|JSON_DEBUG
, 0, "Ignoring annotation '%s' with value '%s'.", n
, json_variant_string(w
));
2213 int oci_load(FILE *f
, const char *bundle
, Settings
**ret
) {
2215 static const JsonDispatch table
[] = {
2216 { "ociVersion", JSON_VARIANT_STRING
, NULL
, 0, JSON_MANDATORY
},
2217 { "process", JSON_VARIANT_OBJECT
, oci_process
, 0, 0 },
2218 { "root", JSON_VARIANT_OBJECT
, oci_root
, 0, 0 },
2219 { "hostname", JSON_VARIANT_STRING
, oci_hostname
, 0, 0 },
2220 { "mounts", JSON_VARIANT_ARRAY
, oci_mounts
, 0, 0 },
2221 { "linux", JSON_VARIANT_OBJECT
, oci_linux
, 0, 0 },
2222 { "hooks", JSON_VARIANT_OBJECT
, oci_hooks
, 0, 0 },
2223 { "annotations", JSON_VARIANT_OBJECT
, oci_annotations
, 0, 0 },
2227 _cleanup_(json_variant_unrefp
) JsonVariant
*oci
= NULL
;
2228 _cleanup_(settings_freep
) Settings
*s
= NULL
;
2229 unsigned line
= 0, column
= 0;
2236 path
= strjoina(bundle
, "/config.json");
2238 r
= json_parse_file(f
, path
, &oci
, &line
, &column
);
2240 if (line
!= 0 && column
!= 0)
2241 return log_error_errno(r
, "Failed to parse '%s' at %u:%u: %m", path
, line
, column
);
2243 return log_error_errno(r
, "Failed to parse '%s': %m", path
);
2246 v
= json_variant_by_key(oci
, "ociVersion");
2248 log_error("JSON file '%s' is not an OCI bundle configuration file. Refusing.", path
);
2251 if (!streq_ptr(json_variant_string(v
), "1.0.0")) {
2252 log_error("OCI bundle version not supported: %s", strna(json_variant_string(v
)));
2257 // _cleanup_free_ char *formatted = NULL;
2258 // assert_se(json_variant_format(oci, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR, &formatted) >= 0);
2259 // fputs(formatted, stdout);
2266 s
->start_mode
= START_PID1
;
2267 s
->resolv_conf
= RESOLV_CONF_OFF
;
2268 s
->link_journal
= LINK_NO
;
2269 s
->timezone
= TIMEZONE_OFF
;
2271 s
->bundle
= strdup(bundle
);
2275 r
= json_dispatch(oci
, table
, oci_unexpected
, 0, s
);
2279 if (s
->properties
) {
2280 r
= sd_bus_message_seal(s
->properties
, 0, 0);
2282 return log_error_errno(r
, "Cannot seal properties bus message: %m");