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

systemd / systemd / 12919407857

23 Jan 2025 12:04AM UTC coverage: 71.444% (+71.3%) from 0.117%
12919407857

push

github

web-flow
core/device: do not drop backslashes in SYSTEMD_WANTS=/SYSTEMD_USER_WANTS= (#35869)

Let consider the following udev rules:
```
PROGRAM="/usr/bin/systemd-escape foo-bar-baz", ENV{SYSTEMD_WANTS}+="test1@$result.service"
PROGRAM="/usr/bin/systemd-escape aaa-bbb-ccc", ENV{SYSTEMD_WANTS}+="test2@$result.service"
```
Then, a device expectedly gains a property:
```
SYSTEMD_WANTS=test1@foo\x2dbar\x2dbaz.service test2@aaa\x2dbbb\x2dccc.service
```
After the event being processed by udevd, PID1 processes the device, the
property previously was parsed with
`extract_first_word(EXTRACT_UNQUOTE)`, then the device unit gained the
following dependencies:
```
Wants=test1@foox2dbarx2dbaz.service test2@aaax2dbbbx2dccc.service
```
So both `%i` and `%I` for the template services did not match with the
original data, and it was hard to use `systemd-escape` in `PROGRAM=`
udev rule token.

This makes the property parsed with
`extract_first_word(EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE)`, hence the
device unit now gains the following dependencies:
```
Wants=test1@foo\x2dbar\x2dbaz.service test2@aaa\x2dbbb\x2dccc.service
```
and `%I` for the template services match with the original data.

Fixes a bug caused by ceed8f0c8 (v233).

Fixes #16735.
Replaces #16737 and #35768.

40 of 40 new or added lines in 2 files covered. (100.0%)

407 existing lines in 13 files now uncovered.

291321 of 407761 relevant lines covered (71.44%)

696525.16 hits per line

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

79.43
/src/basic/pidfd-util.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <sys/ioctl.h>
4
#include <unistd.h>
5

6
#include "errno-util.h"
7
#include "fd-util.h"
8
#include "fileio.h"
9
#include "macro.h"
10
#include "memory-util.h"
11
#include "missing_fs.h"
12
#include "missing_magic.h"
13
#include "missing_threads.h"
14
#include "mountpoint-util.h"
15
#include "parse-util.h"
16
#include "path-util.h"
17
#include "pidfd-util.h"
18
#include "process-util.h"
19
#include "stat-util.h"
20
#include "string-util.h"
21

22
static int have_pidfs = -1;
23

24
static int pidfd_check_pidfs(int pid_fd) {
23,758✔
25

26
        /* NB: the passed fd *must* be acquired via pidfd_open(), i.e. must be a true pidfd! */
27

28
        if (have_pidfs >= 0)
23,758✔
29
                return have_pidfs;
30

31
        return (have_pidfs = fd_is_fs_type(pid_fd, PID_FS_MAGIC));
14,792✔
32
}
33

34
int pidfd_get_namespace(int fd, unsigned long ns_type_cmd) {
8,154✔
35
        static bool cached_supported = true;
8,154✔
36

37
        /* Obtain the namespace fd from pidfd directly through ioctl(PIDFD_GET_*_NAMESPACE).
38
         *
39
         * Returns -EOPNOTSUPP if ioctl on pidfds are not supported, -ENOPKG if the requested namespace
40
         * is disabled in kernel. (The errno used are different from what kernel returns via ioctl(),
41
         * see below) */
42

43
        assert(fd >= 0);
8,154✔
44

45
        /* If we know ahead of time that pidfs is unavailable, shortcut things. But otherwise we don't
46
         * call pidfd_check_pidfs() here, which is kinda extraneous and our own cache is required
47
         * anyways (pidfs is introduced in kernel 6.9 while ioctl support there is added in 6.11). */
48
        if (have_pidfs == 0 || !cached_supported)
8,154✔
49
                return -EOPNOTSUPP;
50

51
        int nsfd = ioctl(fd, ns_type_cmd);
7,038✔
52
        if (nsfd < 0) {
7,038✔
53
                /* Kernel returns EOPNOTSUPP if the ns type in question is disabled. Hence we need to look
54
                 * at precise errno instead of generic ERRNO_IS_(IOCTL_)NOT_SUPPORTED. */
55
                if (IN_SET(errno, ENOTTY, EINVAL)) {
753✔
56
                        cached_supported = false;
753✔
57
                        return -EOPNOTSUPP;
753✔
58
                }
UNCOV
59
                if (errno == EOPNOTSUPP) /* Translate to something more recognizable */
×
60
                        return -ENOPKG;
61

UNCOV
62
                return -errno;
×
63
        }
64

65
        return nsfd;
66
}
67

68
static int pidfd_get_info(int fd, struct pidfd_info *info) {
59,105✔
69
        static bool cached_supported = true;
59,105✔
70

71
        assert(fd >= 0);
59,105✔
72
        assert(info);
59,105✔
73

74
        if (have_pidfs == 0 || !cached_supported)
59,105✔
75
                return -EOPNOTSUPP;
76

77
        if (ioctl(fd, PIDFD_GET_INFO, info) < 0) {
11,818✔
78
                if (ERRNO_IS_IOCTL_NOT_SUPPORTED(errno)) {
11,818✔
79
                        cached_supported = false;
11,818✔
80
                        return -EOPNOTSUPP;
11,818✔
81
                }
82

UNCOV
83
                return -errno;
×
84
        }
85

86
        return 0;
87
}
88

89
static int pidfd_get_pid_fdinfo(int fd, pid_t *ret) {
55,234✔
90
        char path[STRLEN("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)];
55,234✔
91
        _cleanup_free_ char *fdinfo = NULL;
55,234✔
92
        int r;
55,234✔
93

94
        assert(fd >= 0);
55,234✔
95

96
        xsprintf(path, "/proc/self/fdinfo/%i", fd);
55,234✔
97

98
        r = read_full_virtual_file(path, &fdinfo, NULL);
55,234✔
99
        if (r == -ENOENT)
55,234✔
UNCOV
100
                return proc_fd_enoent_errno();
×
101
        if (r < 0)
55,234✔
102
                return r;
103

104
        char *p = find_line_startswith(fdinfo, "Pid:");
55,234✔
105
        if (!p)
55,234✔
106
                return -ENOTTY; /* not a pidfd? */
107

108
        p = skip_leading_chars(p, /* bad = */ NULL);
55,234✔
109
        p[strcspn(p, WHITESPACE)] = 0;
55,234✔
110

111
        if (streq(p, "0"))
55,234✔
112
                return -EREMOTE; /* PID is in foreign PID namespace? */
113
        if (streq(p, "-1"))
55,234✔
114
                return -ESRCH;   /* refers to reaped process? */
115

116
        return parse_pid(p, ret);
54,332✔
117
}
118

119
static int pidfd_get_pid_ioctl(int fd, pid_t *ret) {
55,234✔
120
        struct pidfd_info info = { .mask = PIDFD_INFO_PID };
55,234✔
121
        int r;
55,234✔
122

123
        assert(fd >= 0);
55,234✔
124

125
        r = pidfd_get_info(fd, &info);
55,234✔
126
        if (r < 0)
55,234✔
127
                return r;
55,234✔
128

129
        assert(FLAGS_SET(info.mask, PIDFD_INFO_PID));
×
130

UNCOV
131
        if (ret)
×
UNCOV
132
                *ret = info.pid;
×
133
        return 0;
134
}
135

136
int pidfd_get_pid(int fd, pid_t *ret) {
55,234✔
137
        int r;
55,234✔
138

139
        /* Converts a pidfd into a pid. We try ioctl(PIDFD_GET_INFO) (kernel 6.13+) first,
140
         * /proc/self/fdinfo/ as fallback. Well known errors:
141
         *
142
         *    -EBADF   → fd invalid
143
         *    -ESRCH   → fd valid, but process is already reaped
144
         *
145
         * pidfd_get_pid_fdinfo() might additionally fail for other reasons:
146
         *
147
         *    -ENOSYS  → /proc/ not mounted
148
         *    -ENOTTY  → fd valid, but not a pidfd
149
         *    -EREMOTE → fd valid, but pid is in another namespace we cannot translate to the local one
150
         *               (when using PIDFD_GET_INFO this is indistinguishable from -ESRCH)
151
         */
152

153
        assert(fd >= 0);
55,234✔
154

155
        r = pidfd_get_pid_ioctl(fd, ret);
55,234✔
156
        if (r != -EOPNOTSUPP)
55,234✔
157
                return r;
158

159
        return pidfd_get_pid_fdinfo(fd, ret);
55,234✔
160
}
161

162
int pidfd_verify_pid(int pidfd, pid_t pid) {
42,408✔
163
        pid_t current_pid;
42,408✔
164
        int r;
42,408✔
165

166
        assert(pidfd >= 0);
42,408✔
167
        assert(pid > 0);
42,408✔
168

169
        r = pidfd_get_pid(pidfd, &current_pid);
42,408✔
170
        if (r < 0)
42,408✔
171
                return r;
42,408✔
172

173
        return current_pid != pid ? -ESRCH : 0;
41,508✔
174
}
175

176
int pidfd_get_ppid(int fd, pid_t *ret) {
3,679✔
177
        struct pidfd_info info = { .mask = PIDFD_INFO_PID };
3,679✔
178
        int r;
3,679✔
179

180
        assert(fd >= 0);
3,679✔
181

182
        r = pidfd_get_info(fd, &info);
3,679✔
183
        if (r < 0)
3,679✔
184
                return r;
3,679✔
185

186
        assert(FLAGS_SET(info.mask, PIDFD_INFO_PID));
×
187

UNCOV
188
        if (info.ppid == 0) /* See comments in pid_get_ppid() */
×
189
                return -EADDRNOTAVAIL;
190

UNCOV
191
        if (ret)
×
UNCOV
192
                *ret = info.ppid;
×
193
        return 0;
194
}
195

196
int pidfd_get_uid(int fd, uid_t *ret) {
192✔
197
        struct pidfd_info info = { .mask = PIDFD_INFO_CREDS };
192✔
198
        int r;
192✔
199

200
        assert(fd >= 0);
192✔
201

202
        r = pidfd_get_info(fd, &info);
192✔
203
        if (r < 0)
192✔
204
                return r;
192✔
205

206
        assert(FLAGS_SET(info.mask, PIDFD_INFO_CREDS));
×
207

UNCOV
208
        if (ret)
×
UNCOV
209
                *ret = info.ruid;
×
210
        return 0;
211
}
212

213
int pidfd_get_cgroupid(int fd, uint64_t *ret) {
×
UNCOV
214
        struct pidfd_info info = { .mask = PIDFD_INFO_CGROUPID };
×
215
        int r;
×
216

217
        assert(fd >= 0);
×
218

219
        r = pidfd_get_info(fd, &info);
×
UNCOV
220
        if (r < 0)
×
221
                return r;
×
222

223
        assert(FLAGS_SET(info.mask, PIDFD_INFO_CGROUPID));
×
224

UNCOV
225
        if (ret)
×
UNCOV
226
                *ret = info.cgroupid;
×
227
        return 0;
228
}
229

230
int pidfd_get_inode_id(int fd, uint64_t *ret) {
23,758✔
231
        static bool file_handle_supported = true;
23,758✔
232
        int r;
23,758✔
233

234
        assert(fd >= 0);
23,758✔
235

236
        r = pidfd_check_pidfs(fd);
23,758✔
237
        if (r < 0)
23,758✔
238
                return r;
23,758✔
239
        if (r == 0)
23,758✔
240
                return -EOPNOTSUPP;
241

242
        if (file_handle_supported) {
18,947✔
243
                union {
11,861✔
244
                        struct file_handle file_handle;
245
                        uint8_t space[offsetof(struct file_handle, f_handle) + sizeof(uint64_t)];
246
                } fh = {
11,861✔
247
                        .file_handle.handle_bytes = sizeof(uint64_t),
248
                        .file_handle.handle_type = FILEID_KERNFS,
249
                };
250
                int mnt_id;
11,861✔
251

252
                r = RET_NERRNO(name_to_handle_at(fd, "", &fh.file_handle, &mnt_id, AT_EMPTY_PATH));
11,861✔
253
                if (r >= 0) {
11,861✔
UNCOV
254
                        if (ret)
×
255
                                *ret = *(uint64_t*) fh.file_handle.f_handle;
×
256
                        return 0;
×
257
                }
258
                assert(r != -EOVERFLOW);
11,861✔
259
                if (is_name_to_handle_at_fatal_error(r))
11,861✔
260
                        return r;
261

262
                file_handle_supported = false;
11,861✔
263
        }
264

265
#if SIZEOF_INO_T == 8
266
        struct stat st;
18,947✔
267
        if (fstat(fd, &st) < 0)
18,947✔
268
                return -errno;
×
269

270
        if (ret)
18,947✔
271
                *ret = (uint64_t) st.st_ino;
18,947✔
272
        return 0;
273

274
#elif SIZEOF_INO_T == 4
275
        /* On 32-bit systems (where sizeof(ino_t) == 4), the inode id returned by fstat() cannot be used to
276
         * reliably identify the process, nor can we communicate the origin of the id with the clients.
277
         * Hence let's just refuse to acquire pidfdid through fstat() here. All clients shall also insist on
278
         * the 64-bit id from name_to_handle_at(). */
279
        return -EOPNOTSUPP;
280
#else
281
#  error Unsupported ino_t size
282
#endif
283
}
284

285
int pidfd_get_inode_id_self_cached(uint64_t *ret) {
18,895✔
286
        static thread_local uint64_t cached = 0;
18,895✔
287
        static thread_local pid_t initialized = 0; /* < 0: cached error; == 0: invalid; > 0: valid and pid that was current */
18,895✔
288
        int r;
18,895✔
289

290
        assert(ret);
18,895✔
291

292
        if (initialized == getpid_cached()) {
18,895✔
293
                *ret = cached;
3,752✔
294
                return 0;
3,752✔
295
        }
296
        if (initialized < 0)
15,143✔
297
                return initialized;
298

299
        _cleanup_close_ int fd = pidfd_open(getpid_cached(), 0);
33,662✔
300
        if (fd < 0)
14,767✔
UNCOV
301
                return -errno;
×
302

303
        r = pidfd_get_inode_id(fd, &cached);
14,767✔
304
        if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
14,767✔
305
                return (initialized = -EOPNOTSUPP);
2,927✔
306
        if (r < 0)
11,840✔
307
                return r;
308

309
        *ret = cached;
11,840✔
310
        initialized = getpid_cached();
11,840✔
311
        return 0;
11,840✔
312
}
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