• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 18988181302

31 Oct 2025 09:30PM UTC coverage: 72.241% (+0.2%) from 72.046%
18988181302

push

github

web-flow
core: Add RootDirectoryFileDescriptor= (#39480)

RootDirectory= but via a open_tree() file descriptor. This allows
setting up the execution environment for a service by the client in a
mount namespace and then starting a transient unit in that execution
environment using the new property.

We also add --root-directory= and --same-root-dir= to systemd-run to
have it run services within the given root directory. As systemd-run
might be invoked from a different mount namespace than what systemd is
running in, systemd-run opens the given path with open_tree() and then
sends it to systemd using the new RootDirectoryFileDescriptor= property.

45 of 76 new or added lines in 8 files covered. (59.21%)

2101 existing lines in 44 files now uncovered.

305020 of 422226 relevant lines covered (72.24%)

1081585.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.38
/src/nspawn/nspawn-bind-user.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <unistd.h>
4

5
#include "sd-json.h"
6

7
#include "alloc-util.h"
8
#include "chase.h"
9
#include "fd-util.h"
10
#include "fileio.h"
11
#include "format-util.h"
12
#include "io-util.h"
13
#include "log.h"
14
#include "nspawn.h"
15
#include "machine-bind-user.h"
16
#include "nspawn-bind-user.h"
17
#include "strv.h"
18
#include "user-record.h"
19
#include "group-record.h"
20
#include "path-util.h"
21
#include "string-util.h"
22
#include "user-util.h"
23

24
static int write_and_symlink(
90✔
25
                const char *root,
26
                sd_json_variant *v,
27
                const char *name,
28
                uid_t uid,
29
                const char *suffix,
30
                WriteStringFileFlags extra_flags) {
31

32
        _cleanup_free_ char *j = NULL, *f = NULL, *p = NULL, *q = NULL;
90✔
33
        int r;
90✔
34

35
        assert(root);
90✔
36
        assert(v);
90✔
37
        assert(name);
90✔
38
        assert(uid_is_valid(uid));
90✔
39
        assert(suffix);
90✔
40

41
        r = sd_json_variant_format(v, SD_JSON_FORMAT_NEWLINE, &j);
90✔
42
        if (r < 0)
90✔
43
                return log_error_errno(r, "Failed to format user record JSON: %m");
×
44

45
        f = strjoin(name, suffix);
90✔
46
        if (!f)
90✔
47
                return log_oom();
×
48

49
        p = path_join(root, "/run/host/userdb/", f);
90✔
50
        if (!p)
90✔
UNCOV
51
                return log_oom();
×
52

53
        if (asprintf(&q, "%s/run/host/userdb/" UID_FMT "%s", root, uid, suffix) < 0)
90✔
UNCOV
54
                return log_oom();
×
55

56
        if (symlink(f, q) < 0)
90✔
57
                return log_error_errno(errno, "Failed to create symlink '%s': %m", q);
×
58

59
        r = userns_lchown(q, 0, 0);
90✔
60
        if (r < 0)
90✔
61
                return log_error_errno(r, "Failed to adjust access mode of '%s': %m", q);
×
62

63
        r = write_string_file(p, j, WRITE_STRING_FILE_CREATE|extra_flags);
90✔
64
        if (r < 0)
90✔
65
                return log_error_errno(r, "Failed to write %s: %m", p);
×
66

67
        r = userns_lchown(p, 0, 0);
90✔
68
        if (r < 0)
90✔
UNCOV
69
                return log_error_errno(r, "Failed to adjust access mode of '%s': %m", p);
×
70

71
        return 0;
72
}
73

UNCOV
74
static int write_membership(const char *root, const char *user, const char *group) {
×
UNCOV
75
        int r;
×
76

UNCOV
77
        assert(user);
×
UNCOV
78
        assert(group);
×
79

UNCOV
80
        _cleanup_free_ char *membership = strjoin(user, ":", group, ".membership");
×
UNCOV
81
        if (!membership)
×
UNCOV
82
                return log_oom();
×
83

UNCOV
84
        _cleanup_free_ char *p = path_join("/run/host/userdb/", membership);
×
UNCOV
85
        if (!p)
×
UNCOV
86
                return log_oom();
×
87

UNCOV
88
        _cleanup_close_ int fd = chase_and_open(
×
89
                        p,
90
                        root,
91
                        CHASE_PREFIX_ROOT|CHASE_NO_AUTOFS,
92
                        O_WRONLY|O_CREAT|O_CLOEXEC,
93
                        /* ret_path= */ NULL);
UNCOV
94
        if (fd < 0)
×
95
                return log_error_errno(errno, "Failed to create %s: %m", p);
×
96

UNCOV
97
        r = userns_chown_at(fd, /* fname= */ NULL, /* uid= */ 0, /* gid= */ 0, /* flags= */ 0);
×
UNCOV
98
        if (r < 0)
×
UNCOV
99
                return log_error_errno(r, "Failed to adjust access mode of '%s': %m", p);
×
100

UNCOV
101
        r = loop_write(fd, "{}\n", SIZE_MAX);
×
UNCOV
102
        if (r < 0)
×
UNCOV
103
                return log_error_errno(r, "Failed to write empty JSON object into %s: %m", p);
×
104

105
        return 0;
106
}
107

108
int bind_user_setup(const MachineBindUserContext *c, const char *root) {
249✔
109
        static const UserRecordLoadFlags strip_flags = /* Removes privileged info */
249✔
110
                USER_RECORD_LOAD_MASK_PRIVILEGED|
111
                USER_RECORD_PERMISSIVE;
112
        static const UserRecordLoadFlags shadow_flags = /* Extracts privileged info */
249✔
113
                USER_RECORD_EXTRACT_PRIVILEGED|
114
                USER_RECORD_EMPTY_OK|
115
                USER_RECORD_PERMISSIVE;
116
        int r;
249✔
117

118
        assert(root);
249✔
119

120
        if (!c || c->n_data == 0)
249✔
121
                return 0;
122

123
        r = make_run_host(root);
22✔
124
        if (r < 0)
22✔
125
                return r;
126

127
        r = userns_mkdir(root, "/run/host/home", 0755, 0, 0);
22✔
128
        if (r < 0)
22✔
UNCOV
129
                return log_error_errno(r, "Failed to create /run/host/home: %m");
×
130

131
        r = userns_mkdir(root, "/run/host/userdb", 0755, 0, 0);
22✔
132
        if (r < 0)
22✔
UNCOV
133
                return log_error_errno(r, "Failed to create /run/host/userdb: %m");
×
134

135
        FOREACH_ARRAY(d, c->data, c->n_data) {
52✔
136
                _cleanup_(group_record_unrefp) GroupRecord *stripped_group = NULL, *shadow_group = NULL;
30✔
137
                _cleanup_(user_record_unrefp) UserRecord *stripped_user = NULL, *shadow_user = NULL;
30✔
138

139
                /* First, write shadow (i.e. privileged) data for group record */
140
                r = group_record_clone(d->payload_group, shadow_flags, &shadow_group);
30✔
141
                if (r < 0)
30✔
UNCOV
142
                        return log_error_errno(r, "Failed to extract privileged information from group record: %m");
×
143

144
                if (!sd_json_variant_is_blank_object(shadow_group->json)) {
30✔
UNCOV
145
                        r = write_and_symlink(
×
146
                                        root,
UNCOV
147
                                        shadow_group->json,
×
UNCOV
148
                                        d->payload_group->group_name,
×
UNCOV
149
                                        d->payload_group->gid,
×
150
                                        ".group-privileged",
151
                                        WRITE_STRING_FILE_MODE_0600);
UNCOV
152
                        if (r < 0)
×
153
                                return r;
154
                }
155

156
                /* Second, write main part of group record. */
157
                r = group_record_clone(d->payload_group, strip_flags, &stripped_group);
30✔
158
                if (r < 0)
30✔
UNCOV
159
                        return log_error_errno(r, "Failed to strip privileged information from group record: %m");
×
160

161
                r = write_and_symlink(
60✔
162
                                root,
163
                                stripped_group->json,
30✔
164
                                d->payload_group->group_name,
30✔
165
                                d->payload_group->gid,
30✔
166
                                ".group",
167
                                0);
168
                if (r < 0)
30✔
169
                        return r;
170

171
                STRV_FOREACH(u, stripped_group->members) {
30✔
UNCOV
172
                        r = write_membership(root, *u, stripped_group->group_name);
×
UNCOV
173
                        if (r < 0)
×
174
                                return r;
175
                }
176

177
                /* Third, write out user shadow data. i.e. extract privileged info from user record */
178
                r = user_record_clone(d->payload_user, shadow_flags, &shadow_user);
30✔
179
                if (r < 0)
30✔
UNCOV
180
                        return log_error_errno(r, "Failed to extract privileged information from user record: %m");
×
181

182
                if (!sd_json_variant_is_blank_object(shadow_user->json)) {
30✔
183
                        r = write_and_symlink(
60✔
184
                                        root,
185
                                        shadow_user->json,
30✔
186
                                        d->payload_user->user_name,
30✔
187
                                        d->payload_user->uid,
30✔
188
                                        ".user-privileged",
189
                                        WRITE_STRING_FILE_MODE_0600);
190
                        if (r < 0)
30✔
191
                                return r;
192
                }
193

194
                /* Finally write out the main part of the user record */
195
                r = user_record_clone(d->payload_user, strip_flags, &stripped_user);
30✔
196
                if (r < 0)
30✔
UNCOV
197
                        return log_error_errno(r, "Failed to strip privileged information from user record: %m");
×
198

199
                r = write_and_symlink(
60✔
200
                                root,
201
                                stripped_user->json,
30✔
202
                                d->payload_user->user_name,
30✔
203
                                d->payload_user->uid,
30✔
204
                                ".user",
205
                                0);
206
                if (r < 0)
30✔
207
                        return r;
208

209
                STRV_FOREACH(g, stripped_user->member_of) {
30✔
UNCOV
210
                        r = write_membership(root, stripped_user->user_name, *g);
×
UNCOV
211
                        if (r < 0)
×
212
                                return r;
213
                }
214
        }
215

216
        return 1;
217
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc