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

systemd / systemd / 14554080340

19 Apr 2025 11:46AM UTC coverage: 72.101% (-0.03%) from 72.13%
14554080340

push

github

web-flow
Add two new paragraphs to coding style about header files (#37188)

296880 of 411754 relevant lines covered (72.1%)

687547.52 hits per line

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

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

3
#include <errno.h>
4
#include <stddef.h>
5
#include <stdlib.h>
6
#include <sys/file.h>
7
#include <linux/falloc.h>
8
#include <linux/magic.h>
9
#include <unistd.h>
10

11
#include "alloc-util.h"
12
#include "btrfs.h"
13
#include "chattr-util.h"
14
#include "dirent-util.h"
15
#include "fd-util.h"
16
#include "fileio.h"
17
#include "fs-util.h"
18
#include "hostname-util.h"
19
#include "label.h"
20
#include "lock-util.h"
21
#include "log.h"
22
#include "macro.h"
23
#include "missing_fcntl.h"
24
#include "missing_fs.h"
25
#include "missing_syscall.h"
26
#include "mkdir.h"
27
#include "parse-util.h"
28
#include "path-util.h"
29
#include "process-util.h"
30
#include "random-util.h"
31
#include "ratelimit.h"
32
#include "stat-util.h"
33
#include "stdio-util.h"
34
#include "string-util.h"
35
#include "strv.h"
36
#include "time-util.h"
37
#include "tmpfile-util.h"
38
#include "umask-util.h"
39
#include "user-util.h"
40

41
int rmdir_parents(const char *path, const char *stop) {
70,686✔
42
        char *p;
70,686✔
43
        int r;
70,686✔
44

45
        assert(path);
70,686✔
46
        assert(stop);
70,686✔
47

48
        if (!path_is_safe(path))
70,686✔
49
                return -EINVAL;
50

51
        if (!path_is_safe(stop))
70,685✔
52
                return -EINVAL;
53

54
        p = strdupa_safe(path);
70,684✔
55

56
        for (;;) {
3,369✔
57
                char *slash = NULL;
74,053✔
58

59
                /* skip the last component. */
60
                r = path_find_last_component(p, /* accept_dot_dot= */ false, (const char **) &slash, NULL);
74,053✔
61
                if (r <= 0)
74,053✔
62
                        return r;
70,684✔
63
                if (slash == p)
74,053✔
64
                        return 0;
65

66
                assert(*slash == '/');
74,053✔
67
                *slash = '\0';
74,053✔
68

69
                if (path_startswith_full(stop, p, /* accept_dot_dot= */ false))
74,053✔
70
                        return 0;
71

72
                if (rmdir(p) < 0 && errno != ENOENT)
73,917✔
73
                        return -errno;
70,548✔
74
        }
75
}
76

77
int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
96✔
78
        int r;
96✔
79

80
        assert(olddirfd >= 0 || olddirfd == AT_FDCWD);
96✔
81
        assert(oldpath);
96✔
82
        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
96✔
83
        assert(newpath);
96✔
84

85
        /* Try the ideal approach first */
86
        if (renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE) >= 0)
96✔
87
                return 0;
88

89
        /* renameat2() exists since Linux 3.15, btrfs and FAT added support for it later. If it is not implemented,
90
         * fall back to a different method. */
91
        if (!ERRNO_IS_NOT_SUPPORTED(errno) && errno != EINVAL)
36✔
92
                return -errno;
36✔
93

94
        /* Let's try to use linkat()+unlinkat() as fallback. This doesn't work on directories and on some file systems
95
         * that do not support hard links (such as FAT, most prominently), but for files it's pretty close to what we
96
         * want — though not atomic (i.e. for a short period both the new and the old filename will exist). */
97
        if (linkat(olddirfd, oldpath, newdirfd, newpath, 0) >= 0) {
×
98

99
                r = RET_NERRNO(unlinkat(olddirfd, oldpath, 0));
×
100
                if (r < 0) {
×
101
                        (void) unlinkat(newdirfd, newpath, 0);
×
102
                        return r;
×
103
                }
104

105
                return 0;
×
106
        }
107

108
        if (!ERRNO_IS_NOT_SUPPORTED(errno) && !IN_SET(errno, EINVAL, EPERM)) /* FAT returns EPERM on link()… */
×
109
                return -errno;
×
110

111
        /* OK, neither RENAME_NOREPLACE nor linkat()+unlinkat() worked. Let's then fall back to the racy TOCTOU
112
         * vulnerable accessat(F_OK) check followed by classic, replacing renameat(), we have nothing better. */
113

114
        if (faccessat(newdirfd, newpath, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
×
115
                return -EEXIST;
116
        if (errno != ENOENT)
×
117
                return -errno;
×
118

119
        return RET_NERRNO(renameat(olddirfd, oldpath, newdirfd, newpath));
×
120
}
121

122
int readlinkat_malloc(int fd, const char *p, char **ret) {
3,739,447✔
123
        size_t l = PATH_MAX;
3,739,447✔
124

125
        assert(fd >= 0 || fd == AT_FDCWD);
3,739,447✔
126

127
        if (fd < 0 && isempty(p))
3,739,447✔
128
                return -EISDIR; /* In this case, the fd points to the current working directory, and is
129
                                 * definitely not a symlink. Let's return earlier. */
130

131
        for (;;) {
3,739,447✔
132
                _cleanup_free_ char *c = NULL;
3,739,447✔
133
                ssize_t n;
3,739,447✔
134

135
                c = new(char, l+1);
3,739,447✔
136
                if (!c)
3,739,447✔
137
                        return -ENOMEM;
138

139
                n = readlinkat(fd, strempty(p), c, l);
3,739,449✔
140
                if (n < 0)
3,739,447✔
141
                        return -errno;
387,541✔
142

143
                if ((size_t) n < l) {
3,351,906✔
144
                        c[n] = 0;
3,351,906✔
145

146
                        if (ret)
3,351,906✔
147
                                *ret = TAKE_PTR(c);
3,351,906✔
148

149
                        return 0;
3,351,906✔
150
                }
151

152
                if (l > (SSIZE_MAX-1)/2) /* readlinkat() returns an ssize_t, and we want an extra byte for a
×
153
                                          * trailing NUL, hence do an overflow check relative to SSIZE_MAX-1
154
                                          * here */
155
                        return -EFBIG;
156

157
                l *= 2;
×
158
        }
159
}
160

161
int readlink_value(const char *p, char **ret) {
606,271✔
162
        _cleanup_free_ char *link = NULL, *name = NULL;
606,271✔
163
        int r;
606,271✔
164

165
        assert(p);
606,271✔
166
        assert(ret);
606,271✔
167

168
        r = readlink_malloc(p, &link);
606,271✔
169
        if (r < 0)
606,271✔
170
                return r;
171

172
        r = path_extract_filename(link, &name);
361,970✔
173
        if (r < 0)
361,970✔
174
                return r;
175
        if (r == O_DIRECTORY)
361,970✔
176
                return -EINVAL;
177

178
        *ret = TAKE_PTR(name);
361,970✔
179
        return 0;
361,970✔
180
}
181

182
int readlink_and_make_absolute(const char *p, char **ret) {
3,828✔
183
        _cleanup_free_ char *target = NULL;
3,828✔
184
        int r;
3,828✔
185

186
        assert(p);
3,828✔
187
        assert(ret);
3,828✔
188

189
        r = readlink_malloc(p, &target);
3,828✔
190
        if (r < 0)
3,828✔
191
                return r;
192

193
        return file_in_same_dir(p, target, ret);
28✔
194
}
195

196
int chmod_and_chown_at(int dir_fd, const char *path, mode_t mode, uid_t uid, gid_t gid) {
23,257✔
197
        _cleanup_close_ int fd = -EBADF;
23,257✔
198

199
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
23,257✔
200

201
        if (path) {
23,257✔
202
                /* Let's acquire an O_PATH fd, as precaution to change mode/owner on the same file */
203
                fd = openat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOFOLLOW);
23,257✔
204
                if (fd < 0)
23,257✔
205
                        return -errno;
×
206
                dir_fd = fd;
207

208
        } else if (dir_fd == AT_FDCWD) {
×
209
                /* Let's acquire an O_PATH fd of the current directory */
210
                fd = openat(dir_fd, ".", O_PATH|O_CLOEXEC|O_NOFOLLOW|O_DIRECTORY);
×
211
                if (fd < 0)
×
212
                        return -errno;
×
213
                dir_fd = fd;
214
        }
215

216
        return fchmod_and_chown(dir_fd, mode, uid, gid);
23,257✔
217
}
218

219
int fchmod_and_chown_with_fallback(int fd, const char *path, mode_t mode, uid_t uid, gid_t gid) {
108,258✔
220
        bool do_chown, do_chmod;
108,258✔
221
        struct stat st;
108,258✔
222
        int r;
108,258✔
223

224
        /* Change ownership and access mode of the specified fd. Tries to do so safely, ensuring that at no
225
         * point in time the access mode is above the old access mode under the old ownership or the new
226
         * access mode under the new ownership. Note: this call tries hard to leave the access mode
227
         * unaffected if the uid/gid is changed, i.e. it undoes implicit suid/sgid dropping the kernel does
228
         * on chown().
229
         *
230
         * This call is happy with O_PATH fds.
231
         *
232
         * If path is given, allow a fallback path which does not use /proc/self/fd/. On any normal system
233
         * /proc will be mounted, but in certain improperly assembled environments it might not be. This is
234
         * less secure (potential TOCTOU), so should only be used after consideration. */
235

236
        if (fstat(fd, &st) < 0)
108,258✔
237
                return -errno;
×
238

239
        do_chown =
216,516✔
240
                (uid != UID_INVALID && st.st_uid != uid) ||
108,258✔
241
                (gid != GID_INVALID && st.st_gid != gid);
14,308✔
242

243
        do_chmod =
216,516✔
244
                !S_ISLNK(st.st_mode) && /* chmod is not defined on symlinks */
108,258✔
245
                ((mode != MODE_INVALID && ((st.st_mode ^ mode) & 07777) != 0) ||
108,250✔
246
                 do_chown); /* If we change ownership, make sure we reset the mode afterwards, since chown()
247
                             * modifies the access mode too */
248

249
        if (mode == MODE_INVALID)
108,258✔
250
                mode = st.st_mode; /* If we only shall do a chown(), save original mode, since chown() might break it. */
251
        else if ((mode & S_IFMT) != 0 && ((mode ^ st.st_mode) & S_IFMT) != 0)
69,101✔
252
                return -EINVAL; /* insist on the right file type if it was specified */
253

254
        if (do_chown && do_chmod) {
108,254✔
255
                mode_t minimal = st.st_mode & mode; /* the subset of the old and the new mask */
17,795✔
256

257
                if (((minimal ^ st.st_mode) & 07777) != 0) {
17,795✔
258
                        r = fchmod_opath(fd, minimal & 07777);
7✔
259
                        if (r < 0) {
7✔
260
                                if (!path || r != -ENOSYS)
×
261
                                        return r;
262

263
                                /* Fallback path which doesn't use /proc/self/fd/. */
264
                                if (chmod(path, minimal & 07777) < 0)
×
265
                                        return -errno;
×
266
                        }
267
                }
268
        }
269

270
        if (do_chown)
108,254✔
271
                if (fchownat(fd, "", uid, gid, AT_EMPTY_PATH) < 0)
17,798✔
272
                        return -errno;
1✔
273

274
        if (do_chmod) {
108,253✔
275
                r = fchmod_opath(fd, mode & 07777);
17,975✔
276
                if (r < 0) {
17,975✔
277
                        if (!path || r != -ENOSYS)
×
278
                                return r;
279

280
                        /* Fallback path which doesn't use /proc/self/fd/. */
281
                        if (chmod(path, mode & 07777) < 0)
×
282
                                return -errno;
×
283
                }
284
        }
285

286
        return do_chown || do_chmod;
108,253✔
287
}
288

289
int fchmod_umask(int fd, mode_t m) {
2,721✔
290
        _cleanup_umask_ mode_t u = umask(0777);
2,721✔
291

292
        return RET_NERRNO(fchmod(fd, m & (~u)));
2,721✔
293
}
294

295
int fchmod_opath(int fd, mode_t m) {
20,788✔
296
        /* This function operates also on fd that might have been opened with
297
         * O_PATH. The tool set we have is non-intuitive:
298
         * - fchmod(2) only operates on open files (i. e., fds with an open file description);
299
         * - fchmodat(2) does not have a flag arg like fchownat(2) does, so no way to pass AT_EMPTY_PATH;
300
         *   + it should not be confused with the libc fchmodat(3) interface, which adds 4th flag argument,
301
         *     but does not support AT_EMPTY_PATH (only supports AT_SYMLINK_NOFOLLOW);
302
         * - fchmodat2(2) supports all the AT_* flags, but is still very recent.
303
         *
304
         * We try to use fchmodat2(), and, if it is not supported, resort
305
         * to the /proc/self/fd dance. */
306

307
        assert(fd >= 0);
20,788✔
308

309
        if (fchmodat2(fd, "", m, AT_EMPTY_PATH) >= 0)
20,788✔
310
                return 0;
311
        if (!IN_SET(errno, ENOSYS, EPERM)) /* Some container managers block unknown syscalls with EPERM */
2✔
312
                return -errno;
2✔
313

314
        if (chmod(FORMAT_PROC_FD_PATH(fd), m) < 0) {
×
315
                if (errno != ENOENT)
×
316
                        return -errno;
×
317

318
                return proc_fd_enoent_errno();
×
319
        }
320

321
        return 0;
×
322
}
323

324
int futimens_opath(int fd, const struct timespec ts[2]) {
103,645✔
325
        /* Similar to fchmod_opath() but for futimens() */
326

327
        assert(fd >= 0);
103,645✔
328

329
        if (utimensat(fd, "", ts, AT_EMPTY_PATH) >= 0)
103,645✔
330
                return 0;
331
        if (errno != EINVAL)
×
332
                return -errno;
×
333

334
        /* Support for AT_EMPTY_PATH is added rather late (kernel 5.8), so fall back to going through /proc/
335
         * if unavailable. */
336

337
        if (utimensat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), ts, /* flags = */ 0) < 0) {
×
338
                if (errno != ENOENT)
×
339
                        return -errno;
×
340

341
                return proc_fd_enoent_errno();
×
342
        }
343

344
        return 0;
×
345
}
346

347
int stat_warn_permissions(const char *path, const struct stat *st) {
97,154✔
348
        assert(path);
97,154✔
349
        assert(st);
97,154✔
350

351
        /* Don't complain if we are reading something that is not a file, for example /dev/null */
352
        if (!S_ISREG(st->st_mode))
97,154✔
353
                return 0;
354

355
        if (st->st_mode & 0111)
97,153✔
356
                log_warning("Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway.", path);
×
357

358
        if (st->st_mode & 0002)
97,153✔
359
                log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path);
×
360

361
        if (getpid_cached() == 1 && (st->st_mode & 0044) != 0044)
97,153✔
362
                log_warning("Configuration file %s is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway.", path);
12✔
363

364
        return 0;
365
}
366

367
int fd_warn_permissions(const char *path, int fd) {
8,598✔
368
        struct stat st;
8,598✔
369

370
        assert(path);
8,598✔
371
        assert(fd >= 0);
8,598✔
372

373
        if (fstat(fd, &st) < 0)
8,598✔
374
                return -errno;
×
375

376
        return stat_warn_permissions(path, &st);
8,598✔
377
}
378

379
int touch_fd(int fd, usec_t stamp) {
70,156✔
380
        assert(fd >= 0);
70,156✔
381

382
        if (stamp == USEC_INFINITY)
70,156✔
383
                return futimens_opath(fd, /* ts= */ NULL);
69,560✔
384

385
        struct timespec ts[2];
596✔
386
        timespec_store(ts + 0, stamp);
596✔
387
        ts[1] = ts[0];
596✔
388
        return futimens_opath(fd, ts);
596✔
389
}
390

391
int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) {
70,160✔
392
        _cleanup_close_ int fd = -EBADF;
70,160✔
393
        int ret;
70,160✔
394

395
        assert(path);
70,160✔
396

397
        /* Note that touch_file() does not follow symlinks: if invoked on an existing symlink, then it is the symlink
398
         * itself which is updated, not its target
399
         *
400
         * Returns the first error we encounter, but tries to apply as much as possible. */
401

402
        if (parents)
70,160✔
403
                (void) mkdir_parents(path, 0755);
30,446✔
404

405
        /* Initially, we try to open the node with O_PATH, so that we get a reference to the node. This is useful in
406
         * case the path refers to an existing device or socket node, as we can open it successfully in all cases, and
407
         * won't trigger any driver magic or so. */
408
        fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW);
70,160✔
409
        if (fd < 0) {
70,160✔
410
                if (errno != ENOENT)
52,769✔
411
                        return -errno;
×
412

413
                /* if the node doesn't exist yet, we create it, but with O_EXCL, so that we only create a regular file
414
                 * here, and nothing else */
415
                fd = open(path, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, IN_SET(mode, 0, MODE_INVALID) ? 0644 : mode);
52,769✔
416
                if (fd < 0)
52,769✔
417
                        return -errno;
4✔
418
        }
419

420
        /* Let's make a path from the fd, and operate on that. With this logic, we can adjust the access mode,
421
         * ownership and time of the file node in all cases, even if the fd refers to an O_PATH object — which is
422
         * something fchown(), fchmod(), futimensat() don't allow. */
423
        ret = fchmod_and_chown(fd, mode, uid, gid);
70,156✔
424

425
        return RET_GATHER(ret, touch_fd(fd, stamp));
70,156✔
426
}
427

428
int symlinkat_idempotent(const char *from, int atfd, const char *to, bool make_relative) {
314✔
429
        _cleanup_free_ char *relpath = NULL;
314✔
430
        int r;
314✔
431

432
        assert(from);
314✔
433
        assert(to);
314✔
434

435
        if (make_relative) {
314✔
436
                r = path_make_relative_parent(to, from, &relpath);
185✔
437
                if (r < 0)
185✔
438
                        return r;
439

440
                from = relpath;
185✔
441
        }
442

443
        if (symlinkat(from, atfd, to) < 0) {
314✔
444
                _cleanup_free_ char *p = NULL;
57✔
445

446
                if (errno != EEXIST)
57✔
447
                        return -errno;
×
448

449
                r = readlinkat_malloc(atfd, to, &p);
57✔
450
                if (r == -EINVAL) /* Not a symlink? In that case return the original error we encountered: -EEXIST */
57✔
451
                        return -EEXIST;
452
                if (r < 0) /* Any other error? In that case propagate it as is */
57✔
453
                        return r;
454

455
                if (!streq(p, from)) /* Not the symlink we want it to be? In that case, propagate the original -EEXIST */
57✔
456
                        return -EEXIST;
457
        }
458

459
        return 0;
460
}
461

462
int symlinkat_atomic_full(const char *from, int atfd, const char *to, bool make_relative) {
137,673✔
463
        _cleanup_free_ char *relpath = NULL, *t = NULL;
137,673✔
464
        int r;
137,673✔
465

466
        assert(from);
137,673✔
467
        assert(to);
137,673✔
468

469
        if (make_relative) {
137,673✔
470
                r = path_make_relative_parent(to, from, &relpath);
135,189✔
471
                if (r < 0)
135,189✔
472
                        return r;
473

474
                from = relpath;
135,189✔
475
        }
476

477
        r = tempfn_random(to, NULL, &t);
137,673✔
478
        if (r < 0)
137,673✔
479
                return r;
480

481
        if (symlinkat(from, atfd, t) < 0)
137,673✔
482
                return -errno;
×
483

484
        r = RET_NERRNO(renameat(atfd, t, atfd, to));
137,674✔
485
        if (r < 0) {
1✔
486
                (void) unlinkat(atfd, t, 0);
1✔
487
                return r;
1✔
488
        }
489

490
        return 0;
491
}
492

493
int mknodat_atomic(int atfd, const char *path, mode_t mode, dev_t dev) {
×
494
        _cleanup_free_ char *t = NULL;
×
495
        int r;
×
496

497
        assert(path);
×
498

499
        r = tempfn_random(path, NULL, &t);
×
500
        if (r < 0)
×
501
                return r;
502

503
        if (mknodat(atfd, t, mode, dev) < 0)
×
504
                return -errno;
×
505

506
        r = RET_NERRNO(renameat(atfd, t, atfd, path));
×
507
        if (r < 0) {
×
508
                (void) unlinkat(atfd, t, 0);
×
509
                return r;
×
510
        }
511

512
        return 0;
513
}
514

515
int mkfifoat_atomic(int atfd, const char *path, mode_t mode) {
1✔
516
        _cleanup_free_ char *t = NULL;
1✔
517
        int r;
1✔
518

519
        assert(path);
1✔
520

521
        /* We're only interested in the (random) filename.  */
522
        r = tempfn_random(path, NULL, &t);
1✔
523
        if (r < 0)
1✔
524
                return r;
525

526
        if (mkfifoat(atfd, t, mode) < 0)
1✔
527
                return -errno;
×
528

529
        r = RET_NERRNO(renameat(atfd, t, atfd, path));
1✔
530
        if (r < 0) {
×
531
                (void) unlinkat(atfd, t, 0);
×
532
                return r;
×
533
        }
534

535
        return 0;
536
}
537

538
int get_files_in_directory(const char *path, char ***list) {
299✔
539
        _cleanup_strv_free_ char **l = NULL;
×
540
        _cleanup_closedir_ DIR *d = NULL;
299✔
541
        size_t n = 0;
299✔
542

543
        assert(path);
299✔
544

545
        /* Returns all files in a directory in *list, and the number
546
         * of files as return value. If list is NULL returns only the
547
         * number. */
548

549
        d = opendir(path);
299✔
550
        if (!d)
299✔
551
                return -errno;
2✔
552

553
        FOREACH_DIRENT_ALL(de, d, return -errno) {
1,257✔
554
                if (!dirent_is_file(de))
960✔
555
                        continue;
645✔
556

557
                if (list) {
315✔
558
                        /* one extra slot is needed for the terminating NULL */
559
                        if (!GREEDY_REALLOC(l, n + 2))
302✔
560
                                return -ENOMEM;
561

562
                        l[n] = strdup(de->d_name);
302✔
563
                        if (!l[n])
302✔
564
                                return -ENOMEM;
565

566
                        l[++n] = NULL;
302✔
567
                } else
568
                        n++;
13✔
569
        }
570

571
        if (list)
297✔
572
                *list = TAKE_PTR(l);
294✔
573

574
        return n;
297✔
575
}
576

577
static int getenv_tmp_dir(const char **ret_path) {
2,320✔
578
        int r, ret = 0;
2,320✔
579

580
        assert(ret_path);
2,320✔
581

582
        /* We use the same order of environment variables python uses in tempfile.gettempdir():
583
         * https://docs.python.org/3/library/tempfile.html#tempfile.gettempdir */
584
        FOREACH_STRING(n, "TMPDIR", "TEMP", "TMP") {
9,277✔
585
                const char *e;
6,958✔
586

587
                e = secure_getenv(n);
6,958✔
588
                if (!e)
6,958✔
589
                        continue;
6,956✔
590
                if (!path_is_absolute(e)) {
2✔
591
                        r = -ENOTDIR;
×
592
                        goto next;
×
593
                }
594
                if (!path_is_normalized(e)) {
2✔
595
                        r = -EPERM;
×
596
                        goto next;
×
597
                }
598

599
                r = is_dir(e, true);
2✔
600
                if (r < 0)
2✔
601
                        goto next;
1✔
602
                if (r == 0) {
1✔
603
                        r = -ENOTDIR;
×
604
                        goto next;
×
605
                }
606

607
                *ret_path = e;
1✔
608
                return 1;
1✔
609

610
        next:
1✔
611
                /* Remember first error, to make this more debuggable */
612
                if (ret >= 0)
1✔
613
                        ret = r;
1✔
614
        }
615

616
        if (ret < 0)
2,319✔
617
                return ret;
618

619
        *ret_path = NULL;
2,318✔
620
        return ret;
2,318✔
621
}
622

623
static int tmp_dir_internal(const char *def, const char **ret) {
2,320✔
624
        const char *e;
2,320✔
625
        int r, k;
2,320✔
626

627
        assert(def);
2,320✔
628
        assert(ret);
2,320✔
629

630
        r = getenv_tmp_dir(&e);
2,320✔
631
        if (r > 0) {
2,320✔
632
                *ret = e;
1✔
633
                return 0;
1✔
634
        }
635

636
        k = is_dir(def, /* follow = */ true);
2,319✔
637
        if (k == 0)
2,319✔
638
                k = -ENOTDIR;
639
        if (k < 0)
2,319✔
640
                return RET_GATHER(r, k);
×
641

642
        *ret = def;
2,319✔
643
        return 0;
2,319✔
644
}
645

646
int var_tmp_dir(const char **ret) {
2,211✔
647
        assert(ret);
2,211✔
648

649
        /* Returns the location for "larger" temporary files, that is backed by physical storage if available, and thus
650
         * even might survive a boot: /var/tmp. If $TMPDIR (or related environment variables) are set, its value is
651
         * returned preferably however. Note that both this function and tmp_dir() below are affected by $TMPDIR,
652
         * making it a variable that overrides all temporary file storage locations. */
653

654
        return tmp_dir_internal("/var/tmp", ret);
2,211✔
655
}
656

657
int tmp_dir(const char **ret) {
109✔
658
        assert(ret);
109✔
659

660
        /* Similar to var_tmp_dir() above, but returns the location for "smaller" temporary files, which is usually
661
         * backed by an in-memory file system: /tmp. */
662

663
        return tmp_dir_internal("/tmp", ret);
109✔
664
}
665

666
int unlink_or_warn(const char *filename) {
175✔
667
        assert(filename);
175✔
668

669
        if (unlink(filename) < 0 && errno != ENOENT)
175✔
670
                /* If the file doesn't exist and the fs simply was read-only (in which
671
                 * case unlink() returns EROFS even if the file doesn't exist), don't
672
                 * complain */
673
                if (errno != EROFS || access(filename, F_OK) >= 0)
×
674
                        return log_error_errno(errno, "Failed to remove \"%s\": %m", filename);
×
675

676
        return 0;
677
}
678

679
int access_fd(int fd, int mode) {
26,725✔
680
        assert(fd >= 0);
26,725✔
681

682
        /* Like access() but operates on an already open fd */
683

684
        if (faccessat(fd, "", mode, AT_EMPTY_PATH) >= 0)
26,725✔
685
                return 0;
686
        if (errno != EINVAL)
459✔
687
                return -errno;
459✔
688

689
        /* Support for AT_EMPTY_PATH is added rather late (kernel 5.8), so fall back to going through /proc/
690
         * if unavailable. */
691

692
        if (access(FORMAT_PROC_FD_PATH(fd), mode) < 0) {
×
693
                if (errno != ENOENT)
×
694
                        return -errno;
×
695

696
                return proc_fd_enoent_errno();
×
697
        }
698

699
        return 0;
×
700
}
701

702
int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
75✔
703
        _cleanup_close_ int truncate_fd = -EBADF;
75✔
704
        struct stat st;
75✔
705
        off_t l, bs;
75✔
706

707
        assert(fd >= 0 || fd == AT_FDCWD);
75✔
708
        assert(name);
75✔
709
        assert((flags & ~(UNLINK_REMOVEDIR|UNLINK_ERASE)) == 0);
75✔
710

711
        /* Operates like unlinkat() but also deallocates the file contents if it is a regular file and there's no other
712
         * link to it. This is useful to ensure that other processes that might have the file open for reading won't be
713
         * able to keep the data pinned on disk forever. This call is particular useful whenever we execute clean-up
714
         * jobs ("vacuuming"), where we want to make sure the data is really gone and the disk space released and
715
         * returned to the free pool.
716
         *
717
         * Deallocation is preferably done by FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE (👊) if supported, which means
718
         * the file won't change size. That's a good thing since we shouldn't needlessly trigger SIGBUS in other
719
         * programs that have mmap()ed the file. (The assumption here is that changing file contents to all zeroes
720
         * underneath those programs is the better choice than simply triggering SIGBUS in them which truncation does.)
721
         * However if hole punching is not implemented in the kernel or file system we'll fall back to normal file
722
         * truncation (🔪), as our goal of deallocating the data space trumps our goal of being nice to readers (💐).
723
         *
724
         * Note that we attempt deallocation, but failure to succeed with that is not considered fatal, as long as the
725
         * primary job – to delete the file – is accomplished. */
726

727
        if (!FLAGS_SET(flags, UNLINK_REMOVEDIR)) {
75✔
728
                truncate_fd = openat(fd, name, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK);
75✔
729
                if (truncate_fd < 0) {
75✔
730

731
                        /* If this failed because the file doesn't exist propagate the error right-away. Also,
732
                         * AT_REMOVEDIR wasn't set, and we tried to open the file for writing, which means EISDIR is
733
                         * returned when this is a directory but we are not supposed to delete those, hence propagate
734
                         * the error right-away too. */
735
                        if (IN_SET(errno, ENOENT, EISDIR))
×
736
                                return -errno;
×
737

738
                        if (errno != ELOOP) /* don't complain if this is a symlink */
×
739
                                log_debug_errno(errno, "Failed to open file '%s' for deallocation, ignoring: %m", name);
×
740
                }
741
        }
742

743
        if (unlinkat(fd, name, FLAGS_SET(flags, UNLINK_REMOVEDIR) ? AT_REMOVEDIR : 0) < 0)
75✔
744
                return -errno;
×
745

746
        if (truncate_fd < 0) /* Don't have a file handle, can't do more ☹️ */
75✔
747
                return 0;
748

749
        if (fstat(truncate_fd, &st) < 0) {
75✔
750
                log_debug_errno(errno, "Failed to stat file '%s' for deallocation, ignoring: %m", name);
×
751
                return 0;
×
752
        }
753

754
        if (!S_ISREG(st.st_mode))
75✔
755
                return 0;
756

757
        if (FLAGS_SET(flags, UNLINK_ERASE) && st.st_size > 0 && st.st_nlink == 0) {
75✔
758
                uint64_t left = st.st_size;
3✔
759
                char buffer[64 * 1024];
3✔
760

761
                /* If erasing is requested, let's overwrite the file with random data once before deleting
762
                 * it. This isn't going to give you shred(1) semantics, but hopefully should be good enough
763
                 * for stuff backed by tmpfs at least.
764
                 *
765
                 * Note that we only erase like this if the link count of the file is zero. If it is higher it
766
                 * is still linked by someone else and we'll leave it to them to remove it securely
767
                 * eventually! */
768

769
                random_bytes(buffer, sizeof(buffer));
3✔
770

771
                while (left > 0) {
6✔
772
                        ssize_t n;
3✔
773

774
                        n = write(truncate_fd, buffer, MIN(sizeof(buffer), left));
3✔
775
                        if (n < 0) {
3✔
776
                                log_debug_errno(errno, "Failed to erase data in file '%s', ignoring.", name);
×
777
                                break;
778
                        }
779

780
                        assert(left >= (size_t) n);
3✔
781
                        left -= n;
3✔
782
                }
783

784
                /* Let's refresh metadata */
785
                if (fstat(truncate_fd, &st) < 0) {
3✔
786
                        log_debug_errno(errno, "Failed to stat file '%s' for deallocation, ignoring: %m", name);
×
787
                        return 0;
×
788
                }
789
        }
790

791
        /* Don't deallocate if there's nothing to deallocate or if the file is linked elsewhere */
792
        if (st.st_blocks == 0 || st.st_nlink > 0)
75✔
793
                return 0;
794

795
        /* If this is a regular file, it actually took up space on disk and there are no other links it's time to
796
         * punch-hole/truncate this to release the disk space. */
797

798
        bs = MAX(st.st_blksize, 512);
75✔
799
        l = ROUND_UP(st.st_size, bs); /* Round up to next block size */
75✔
800

801
        if (fallocate(truncate_fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, 0, l) >= 0)
75✔
802
                return 0; /* Successfully punched a hole! 😊 */
803

804
        /* Fall back to truncation */
805
        if (ftruncate(truncate_fd, 0) < 0) {
×
806
                log_debug_errno(errno, "Failed to truncate file to 0, ignoring: %m");
×
807
                return 0;
×
808
        }
809

810
        return 0;
811
}
812

813
int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode) {
1,765,969✔
814
        _cleanup_free_ char *parent = NULL;
1,765,969✔
815
        int r;
1,765,969✔
816

817
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
1,765,969✔
818
        assert(path);
1,765,969✔
819

820
        r = path_extract_directory(path, &parent);
1,765,969✔
821
        if (r == -EDESTADDRREQ) {
1,765,969✔
822
                parent = strdup(".");
35✔
823
                if (!parent)
35✔
824
                        return -ENOMEM;
825
        } else if (r == -EADDRNOTAVAIL) {
1,765,934✔
826
                parent = strdup(path);
×
827
                if (!parent)
×
828
                        return -ENOMEM;
829
        } else if (r < 0)
1,765,934✔
830
                return r;
831

832
        /* Let's insist on O_DIRECTORY since the parent of a file or directory is a directory. Except if we open an
833
         * O_TMPFILE file, because in that case we are actually create a regular file below the parent directory. */
834

835
        if (FLAGS_SET(flags, O_PATH))
1,765,969✔
836
                flags |= O_DIRECTORY;
×
837
        else if (!FLAGS_SET(flags, O_TMPFILE))
1,765,969✔
838
                flags |= O_DIRECTORY|O_RDONLY;
1,763,219✔
839

840
        return RET_NERRNO(openat(dir_fd, parent, flags, mode));
1,766,002✔
841
}
842

843
int conservative_renameat(
37,245✔
844
                int olddirfd, const char *oldpath,
845
                int newdirfd, const char *newpath) {
846

847
        _cleanup_close_ int old_fd = -EBADF, new_fd = -EBADF;
37,245✔
848
        struct stat old_stat, new_stat;
37,245✔
849

850
        /* Renames the old path to the new path, much like renameat() — except if both are regular files and
851
         * have the exact same contents and basic file attributes already. In that case remove the new file
852
         * instead. This call is useful for reducing inotify wakeups on files that are updated but don't
853
         * actually change. This function is written in a style that we rather rename too often than suppress
854
         * too much. I.e. whenever we are in doubt, we rather rename than fail. After all reducing inotify
855
         * events is an optimization only, not more. */
856

857
        old_fd = openat(olddirfd, oldpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
37,245✔
858
        if (old_fd < 0)
37,245✔
859
                goto do_rename;
×
860

861
        new_fd = openat(newdirfd, newpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
37,245✔
862
        if (new_fd < 0)
37,245✔
863
                goto do_rename;
3,268✔
864

865
        if (fstat(old_fd, &old_stat) < 0)
33,977✔
866
                goto do_rename;
×
867

868
        if (!S_ISREG(old_stat.st_mode))
33,977✔
869
                goto do_rename;
×
870

871
        if (fstat(new_fd, &new_stat) < 0)
33,977✔
872
                goto do_rename;
×
873

874
        if (stat_inode_same(&new_stat, &old_stat))
33,977✔
875
                goto is_same;
1✔
876

877
        if (old_stat.st_mode != new_stat.st_mode ||
33,976✔
878
            old_stat.st_size != new_stat.st_size ||
33,976✔
879
            old_stat.st_uid != new_stat.st_uid ||
23,758✔
880
            old_stat.st_gid != new_stat.st_gid)
23,758✔
881
                goto do_rename;
10,218✔
882

883
        for (;;) {
6✔
884
                uint8_t buf1[16*1024];
23,764✔
885
                uint8_t buf2[sizeof(buf1)];
23,764✔
886
                ssize_t l1, l2;
23,764✔
887

888
                l1 = read(old_fd, buf1, sizeof(buf1));
23,764✔
889
                if (l1 < 0)
23,764✔
890
                        goto do_rename;
289✔
891

892
                if (l1 == sizeof(buf1))
23,764✔
893
                        /* Read the full block, hence read a full block in the other file too */
894

895
                        l2 = read(new_fd, buf2, l1);
7✔
896
                else {
897
                        assert((size_t) l1 < sizeof(buf1));
23,757✔
898

899
                        /* Short read. This hence was the last block in the first file, and then came
900
                         * EOF. Read one byte more in the second file, so that we can verify we hit EOF there
901
                         * too. */
902

903
                        assert((size_t) (l1 + 1) <= sizeof(buf2));
23,757✔
904
                        l2 = read(new_fd, buf2, l1 + 1);
23,757✔
905
                }
906
                if (l2 != l1)
23,764✔
907
                        goto do_rename;
×
908

909
                if (memcmp(buf1, buf2, l1) != 0)
23,764✔
910
                        goto do_rename;
289✔
911

912
                if ((size_t) l1 < sizeof(buf1)) /* We hit EOF on the first file, and the second file too, hence exit
23,475✔
913
                                                 * now. */
914
                        break;
915
        }
916

917
is_same:
23,470✔
918
        /* Everything matches? Then don't rename, instead remove the source file, and leave the existing
919
         * destination in place */
920

921
        if (unlinkat(olddirfd, oldpath, 0) < 0)
23,470✔
922
                goto do_rename;
×
923

924
        return 0;
925

926
do_rename:
13,775✔
927
        if (renameat(olddirfd, oldpath, newdirfd, newpath) < 0)
13,775✔
928
                return -errno;
×
929

930
        return 1;
931
}
932

933
int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size) {
1,681✔
934
        RateLimit rl;
1,681✔
935
        int r;
1,681✔
936

937
        r = posix_fallocate(fd, offset, size); /* returns positive errnos on error */
1,681✔
938
        if (r != EINTR)
1,681✔
939
                return -r; /* Let's return negative errnos, like common in our codebase */
1,681✔
940

941
        /* On EINTR try a couple of times more, but protect against busy looping
942
         * (not more than 16 times per 10s) */
943
        rl = (const RateLimit) { 10 * USEC_PER_SEC, 16 };
×
944
        while (ratelimit_below(&rl)) {
×
945
                r = posix_fallocate(fd, offset, size);
×
946
                if (r != EINTR)
×
947
                        return -r;
×
948
        }
949

950
        return -EINTR;
951
}
952

953
int parse_cifs_service(
15✔
954
                const char *s,
955
                char **ret_host,
956
                char **ret_service,
957
                char **ret_path) {
958

959
        _cleanup_free_ char *h = NULL, *ss = NULL, *x = NULL;
15✔
960
        const char *p, *e, *d;
15✔
961
        char delimiter;
15✔
962

963
        /* Parses a CIFS service in form of //host/service/path… and splitting it in three parts. The last
964
         * part is optional, in which case NULL is returned there. To maximize compatibility syntax with
965
         * backslashes instead of slashes is accepted too. */
966

967
        if (!s)
15✔
968
                return -EINVAL;
969

970
        p = startswith(s, "//");
14✔
971
        if (!p) {
14✔
972
                p = startswith(s, "\\\\");
6✔
973
                if (!p)
6✔
974
                        return -EINVAL;
975
        }
976

977
        delimiter = s[0];
11✔
978
        e = strchr(p, delimiter);
11✔
979
        if (!e)
11✔
980
                return -EINVAL;
981

982
        h = strndup(p, e - p);
11✔
983
        if (!h)
11✔
984
                return -ENOMEM;
985

986
        if (!hostname_is_valid(h, 0))
11✔
987
                return -EINVAL;
988

989
        e++;
10✔
990

991
        d = strchrnul(e, delimiter);
10✔
992

993
        ss = strndup(e, d - e);
10✔
994
        if (!ss)
10✔
995
                return -ENOMEM;
996

997
        if (!filename_is_valid(ss))
10✔
998
                return -EINVAL;
999

1000
        if (!isempty(d)) {
8✔
1001
                x = strdup(skip_leading_chars(d, CHAR_TO_STR(delimiter)));
6✔
1002
                if (!x)
6✔
1003
                        return -EINVAL;
2✔
1004

1005
                /* Make sure to convert Windows-style "\" → Unix-style / */
1006
                for (char *i = x; *i; i++)
33✔
1007
                        if (*i == delimiter)
27✔
1008
                                *i = '/';
3✔
1009

1010
                if (!path_is_valid(x))
6✔
1011
                        return -EINVAL;
1012

1013
                path_simplify(x);
6✔
1014
                if (!path_is_normalized(x))
6✔
1015
                        return -EINVAL;
1016
        }
1017

1018
        if (ret_host)
6✔
1019
                *ret_host = TAKE_PTR(h);
6✔
1020
        if (ret_service)
6✔
1021
                *ret_service = TAKE_PTR(ss);
6✔
1022
        if (ret_path)
6✔
1023
                *ret_path = TAKE_PTR(x);
6✔
1024

1025
        return 0;
1026
}
1027

1028
int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_flags, mode_t mode) {
219,811✔
1029
        _cleanup_close_ int fd = -EBADF, parent_fd = -EBADF;
219,811✔
1030
        _cleanup_free_ char *fname = NULL, *parent = NULL;
219,811✔
1031
        int r;
219,811✔
1032

1033
        /* Creates a directory with mkdirat() and then opens it, in the "most atomic" fashion we can
1034
         * do. Guarantees that the returned fd refers to a directory. If O_EXCL is specified will fail if the
1035
         * dir already exists. Otherwise will open an existing dir, but only if it is one.  */
1036

1037
        if (flags & ~(O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_EXCL|O_NOATIME|O_NOFOLLOW|O_PATH))
219,811✔
1038
                return -EINVAL;
1039
        if ((flags & O_ACCMODE_STRICT) != O_RDONLY)
219,811✔
1040
                return -EINVAL;
1041

1042
        /* Note that O_DIRECTORY|O_NOFOLLOW is implied, but we allow specifying it anyway. The following
1043
         * flags actually make sense to specify: O_CLOEXEC, O_EXCL, O_NOATIME, O_PATH */
1044

1045
        /* If this is not a valid filename, it's a path. Let's open the parent directory then, so
1046
         * that we can pin it, and operate below it. */
1047
        r = path_extract_directory(path, &parent);
219,811✔
1048
        if (r < 0) {
219,811✔
1049
                if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL))
5,153✔
1050
                        return r;
1051
        } else {
1052
                r = path_extract_filename(path, &fname);
214,658✔
1053
                if (r < 0)
214,658✔
1054
                        return r;
1055

1056
                parent_fd = openat(dirfd, parent, O_PATH|O_DIRECTORY|O_CLOEXEC);
214,658✔
1057
                if (parent_fd < 0)
214,658✔
1058
                        return -errno;
×
1059

1060
                dirfd = parent_fd;
214,658✔
1061
                path = fname;
214,658✔
1062
        }
1063

1064
        fd = xopenat_full(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, xopen_flags, mode);
219,811✔
1065
        if (IN_SET(fd, -ELOOP, -ENOTDIR))
439,621✔
1066
                return -EEXIST;
1067
        if (fd < 0)
219,810✔
1068
                return fd;
3,951✔
1069

1070
        return TAKE_FD(fd);
1071
}
1072

1073
int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created) {
2,475,105✔
1074
        int fd;
2,475,105✔
1075

1076
        /* Just like openat(), but adds one thing: optionally returns whether we created the file anew or if
1077
         * it already existed before. This is only relevant if O_CREAT is set without O_EXCL, and thus will
1078
         * shortcut to openat() otherwise.
1079
         *
1080
         * Note that this routine is a bit more strict with symlinks than regular openat() is. If O_NOFOLLOW
1081
         * is not specified, then we'll follow the symlink when opening an existing file but we will *not*
1082
         * follow it when creating a new one (because that's a terrible UNIX misfeature and generally a
1083
         * security hole). */
1084

1085
        if (!FLAGS_SET(flags, O_CREAT) || FLAGS_SET(flags, O_EXCL)) {
2,475,105✔
1086
                fd = openat(dirfd, pathname, flags, mode);
482,590✔
1087
                if (fd < 0)
482,590✔
1088
                        return -errno;
39,643✔
1089

1090
                if (ret_newly_created)
442,947✔
1091
                        *ret_newly_created = FLAGS_SET(flags, O_CREAT);
442,947✔
1092
                return fd;
442,947✔
1093
        }
1094

1095
        for (unsigned attempts = 7;;) {
1096
                /* First, attempt to open without O_CREAT/O_EXCL, i.e. open existing file */
1097
                fd = openat(dirfd, pathname, flags & ~(O_CREAT | O_EXCL), mode);
1,992,769✔
1098
                if (fd >= 0) {
1,992,769✔
1099
                        if (ret_newly_created)
1,774,477✔
1100
                                *ret_newly_created = false;
1,774,477✔
1101
                        return fd;
1,774,477✔
1102
                }
1103
                if (errno != ENOENT)
218,292✔
1104
                        return -errno;
×
1105

1106
                /* So the file didn't exist yet, hence create it with O_CREAT/O_EXCL/O_NOFOLLOW. */
1107
                fd = openat(dirfd, pathname, flags | O_CREAT | O_EXCL | O_NOFOLLOW, mode);
218,292✔
1108
                if (fd >= 0) {
218,292✔
1109
                        if (ret_newly_created)
218,036✔
1110
                                *ret_newly_created = true;
218,035✔
1111
                        return fd;
218,036✔
1112
                }
1113
                if (errno != EEXIST)
256✔
1114
                        return -errno;
×
1115

1116
                /* Hmm, so now we got EEXIST? Then someone might have created the file between the first and
1117
                 * second call to openat(). Let's try again but with a limit so we don't spin forever. */
1118

1119
                if (--attempts == 0) /* Give up eventually, somebody is playing with us */
256✔
1120
                        return -EEXIST;
1121
        }
1122
}
1123

1124
int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode) {
1,051,682✔
1125
        _cleanup_close_ int fd = -EBADF;
1,051,682✔
1126
        bool made_dir = false, made_file = false;
1,051,682✔
1127
        int r;
1,051,682✔
1128

1129
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
1,051,682✔
1130

1131
        /* An inode cannot be both a directory and a regular file at the same time. */
1132
        assert(!(FLAGS_SET(open_flags, O_DIRECTORY) && FLAGS_SET(xopen_flags, XO_REGULAR)));
1,051,682✔
1133

1134
        /* This is like openat(), but has a few tricks up its sleeves, extending behaviour:
1135
         *
1136
         *   • O_DIRECTORY|O_CREAT is supported, which causes a directory to be created, and immediately
1137
         *     opened. When used with the XO_SUBVOLUME flag this will even create a btrfs subvolume.
1138
         *
1139
         *   • If O_CREAT is used with XO_LABEL, any created file will be immediately relabelled.
1140
         *
1141
         *   • If the path is specified NULL or empty, behaves like fd_reopen().
1142
         *
1143
         *   • If XO_NOCOW is specified will turn on the NOCOW btrfs flag on the file, if available.
1144
         *
1145
         *   • if XO_REGULAR is specified will return an error if inode is not a regular file.
1146
         *
1147
         *   • If mode is specified as MODE_INVALID, we'll use 0755 for dirs, and 0644 for regular files.
1148
         */
1149

1150
        if (mode == MODE_INVALID)
1,051,682✔
1151
                mode = (open_flags & O_DIRECTORY) ? 0755 : 0644;
49,708✔
1152

1153
        if (isempty(path)) {
1,051,682✔
1154
                assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL));
2,092✔
1155

1156
                if (FLAGS_SET(xopen_flags, XO_REGULAR)) {
2,092✔
1157
                        r = fd_verify_regular(dir_fd);
43✔
1158
                        if (r < 0)
43✔
1159
                                return r;
1160
                }
1161

1162
                return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW);
2,092✔
1163
        }
1164

1165
        bool call_label_ops_post = false;
1,049,590✔
1166

1167
        if (FLAGS_SET(open_flags, O_CREAT) && FLAGS_SET(xopen_flags, XO_LABEL)) {
1,049,590✔
1168
                r = label_ops_pre(dir_fd, path, FLAGS_SET(open_flags, O_DIRECTORY) ? S_IFDIR : S_IFREG);
529✔
1169
                if (r < 0)
529✔
1170
                        return r;
1171

1172
                call_label_ops_post = true;
1173
        }
1174

1175
        if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) {
1,049,590✔
1176
                if (FLAGS_SET(xopen_flags, XO_SUBVOLUME))
254,415✔
1177
                        r = btrfs_subvol_make_fallback(dir_fd, path, mode);
×
1178
                else
1179
                        r = RET_NERRNO(mkdirat(dir_fd, path, mode));
254,415✔
1180
                if (r == -EEXIST) {
146,133✔
1181
                        if (FLAGS_SET(open_flags, O_EXCL))
146,129✔
1182
                                return -EEXIST;
1183
                } else if (r < 0)
108,286✔
1184
                        return r;
1185
                else
1186
                        made_dir = true;
1187

1188
                open_flags &= ~(O_EXCL|O_CREAT);
250,463✔
1189
        }
1190

1191
        if (FLAGS_SET(xopen_flags, XO_REGULAR)) {
1,045,638✔
1192
                /* Guarantee we return a regular fd only, and don't open the file unless we verified it
1193
                 * first */
1194

1195
                if (FLAGS_SET(open_flags, O_PATH)) {
401,910✔
1196
                        fd = openat(dir_fd, path, open_flags, mode);
1✔
1197
                        if (fd < 0) {
1✔
1198
                                r = -errno;
×
1199
                                goto error;
×
1200
                        }
1201

1202
                        r = fd_verify_regular(fd);
1✔
1203
                        if (r < 0)
1✔
1204
                                goto error;
×
1205

1206
                } else if (FLAGS_SET(open_flags, O_CREAT|O_EXCL)) {
401,909✔
1207
                        /* In O_EXCL mode we can just create the thing, everything is dealt with for us */
1208
                        fd = openat(dir_fd, path, open_flags, mode);
2✔
1209
                        if (fd < 0) {
2✔
1210
                                r = -errno;
1✔
1211
                                goto error;
1✔
1212
                        }
1213

1214
                        made_file = true;
1✔
1215
                } else {
1216
                        /* Otherwise pin the inode first via O_PATH */
1217
                        _cleanup_close_ int inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC|(open_flags & O_NOFOLLOW));
401,907✔
1218
                        if (inode_fd < 0) {
401,907✔
1219
                                if (errno != ENOENT || !FLAGS_SET(open_flags, O_CREAT)) {
21✔
1220
                                        r = -errno;
1✔
1221
                                        goto error;
1✔
1222
                                }
1223

1224
                                /* Doesn't exist yet, then try to create it */
1225
                                fd = openat(dir_fd, path, open_flags|O_CREAT|O_EXCL, mode);
20✔
1226
                                if (fd < 0) {
20✔
1227
                                        r = -errno;
×
1228
                                        goto error;
×
1229
                                }
1230

1231
                                made_file = true;
20✔
1232
                        } else {
1233
                                /* OK, we pinned it. Now verify it's actually a regular file, and then reopen it */
1234
                                r = fd_verify_regular(inode_fd);
401,886✔
1235
                                if (r < 0)
401,886✔
1236
                                        goto error;
5✔
1237

1238
                                fd = fd_reopen(inode_fd, open_flags & ~(O_NOFOLLOW|O_CREAT));
401,881✔
1239
                                if (fd < 0) {
401,881✔
1240
                                        r = fd;
×
1241
                                        goto error;
×
1242
                                }
1243
                        }
1244
                }
1245
        } else {
1246
                fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file);
643,728✔
1247
                if (fd < 0) {
643,728✔
1248
                        r = fd;
38,033✔
1249
                        goto error;
38,033✔
1250
                }
1251
        }
1252

1253
        if (call_label_ops_post) {
1,007,598✔
1254
                call_label_ops_post = false;
529✔
1255

1256
                r = label_ops_post(fd, /* path= */ NULL, made_file || made_dir);
529✔
1257
                if (r < 0)
529✔
1258
                        goto error;
×
1259
        }
1260

1261
        if (FLAGS_SET(xopen_flags, XO_NOCOW)) {
1,007,598✔
1262
                r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL);
69✔
1263
                if (r < 0 && !ERRNO_IS_NOT_SUPPORTED(r))
69✔
1264
                        goto error;
×
1265
        }
1266

1267
        return TAKE_FD(fd);
1268

1269
error:
38,040✔
1270
        if (call_label_ops_post)
38,040✔
1271
                (void) label_ops_post(fd >= 0 ? fd : dir_fd, fd >= 0 ? NULL : path, made_dir || made_file);
×
1272

1273
        if (made_dir || made_file)
38,040✔
1274
                (void) unlinkat(dir_fd, path, made_dir ? AT_REMOVEDIR : 0);
×
1275

1276
        return r;
1277
}
1278

1279
int xopenat_lock_full(
249,833✔
1280
                int dir_fd,
1281
                const char *path,
1282
                int open_flags,
1283
                XOpenFlags xopen_flags,
1284
                mode_t mode,
1285
                LockType locktype,
1286
                int operation) {
1287

1288
        _cleanup_close_ int fd = -EBADF;
249,833✔
1289
        int r;
249,833✔
1290

1291
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
249,833✔
1292
        assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH));
249,833✔
1293

1294
        /* POSIX/UNPOSIX locks don't work on directories (errno is set to -EBADF so let's return early with
1295
         * the same error here). */
1296
        if (FLAGS_SET(open_flags, O_DIRECTORY) && !IN_SET(locktype, LOCK_BSD, LOCK_NONE))
249,833✔
1297
                return -EBADF;
1298

1299
        for (;;) {
286,298✔
1300
                struct stat st;
268,065✔
1301

1302
                fd = xopenat_full(dir_fd, path, open_flags, xopen_flags, mode);
268,065✔
1303
                if (fd < 0)
268,065✔
1304
                        return fd;
8✔
1305

1306
                r = lock_generic(fd, locktype, operation);
268,065✔
1307
                if (r < 0)
268,065✔
1308
                        return r;
1309

1310
                /* If we acquired the lock, let's check if the file/directory still exists in the file
1311
                 * system. If not, then the previous exclusive owner removed it and then closed it. In such a
1312
                 * case our acquired lock is worthless, hence try again. */
1313

1314
                if (fstat(fd, &st) < 0)
268,057✔
1315
                        return -errno;
×
1316
                if (st.st_nlink > 0)
268,057✔
1317
                        break;
1318

1319
                fd = safe_close(fd);
18,233✔
1320
        }
1321

1322
        return TAKE_FD(fd);
249,824✔
1323
}
1324

1325
int link_fd(int fd, int newdirfd, const char *newpath) {
3,555✔
1326
        int r, k;
3,555✔
1327

1328
        assert(fd >= 0);
3,555✔
1329
        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
3,555✔
1330
        assert(newpath);
3,555✔
1331

1332
        /* Try linking via /proc/self/fd/ first. */
1333
        r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW));
3,555✔
1334
        if (r != -ENOENT)
901✔
1335
                return r;
3,555✔
1336

1337
        /* Fall back to symlinking via AT_EMPTY_PATH as fallback (this requires CAP_DAC_READ_SEARCH and a
1338
         * more recent kernel, but does not require /proc/ mounted) */
1339
        k = proc_mounted();
×
1340
        if (k < 0)
×
1341
                return r;
1342
        if (k > 0)
×
1343
                return -EBADF;
1344

1345
        return RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH));
×
1346
}
1347

1348
int linkat_replace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
1,801✔
1349
        _cleanup_close_ int old_fd = -EBADF;
1,801✔
1350
        int r;
1,801✔
1351

1352
        assert(olddirfd >= 0 || olddirfd == AT_FDCWD);
1,801✔
1353
        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
1,801✔
1354
        assert(!isempty(newpath)); /* source path is optional, but the target path is not */
1,801✔
1355

1356
        /* Like linkat() but replaces the target if needed. Is a NOP if source and target already share the
1357
         * same inode. */
1358

1359
        if (olddirfd == AT_FDCWD && isempty(oldpath)) /* Refuse operating on the cwd (which is a dir, and dirs can't be hardlinked) */
3,602✔
1360
                return -EISDIR;
1361

1362
        if (path_implies_directory(oldpath)) /* Refuse these definite directories early */
1,801✔
1363
                return -EISDIR;
1364

1365
        if (path_implies_directory(newpath))
1,801✔
1366
                return -EISDIR;
1367

1368
        /* First, try to link this directly */
1369
        if (oldpath)
1,801✔
1370
                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, newpath, 0));
19✔
1371
        else
1372
                r = link_fd(olddirfd, newdirfd, newpath);
1,782✔
1373
        if (r >= 0)
1,784✔
1374
                return 0;
901✔
1375
        if (r != -EEXIST)
900✔
1376
                return r;
1377

1378
        old_fd = xopenat(olddirfd, oldpath, O_PATH|O_CLOEXEC);
900✔
1379
        if (old_fd < 0)
900✔
1380
                return old_fd;
1381

1382
        struct stat old_st;
900✔
1383
        if (fstat(old_fd, &old_st) < 0)
900✔
1384
                return -errno;
×
1385

1386
        if (S_ISDIR(old_st.st_mode)) /* Don't bother if we are operating on a directory */
900✔
1387
                return -EISDIR;
1388

1389
        struct stat new_st;
900✔
1390
        if (fstatat(newdirfd, newpath, &new_st, AT_SYMLINK_NOFOLLOW) < 0)
900✔
1391
                return -errno;
×
1392

1393
        if (S_ISDIR(new_st.st_mode)) /* Refuse replacing directories */
900✔
1394
                return -EEXIST;
1395

1396
        if (stat_inode_same(&old_st, &new_st)) /* Already the same inode? Then shortcut this */
900✔
1397
                return 0;
1398

1399
        _cleanup_free_ char *tmp_path = NULL;
899✔
1400
        r = tempfn_random(newpath, /* extra= */ NULL, &tmp_path);
899✔
1401
        if (r < 0)
899✔
1402
                return r;
1403

1404
        r = link_fd(old_fd, newdirfd, tmp_path);
899✔
1405
        if (r < 0) {
899✔
1406
                if (!ERRNO_IS_PRIVILEGE(r))
×
1407
                        return r;
1408

1409
                /* If that didn't work due to permissions then go via the path of the dentry */
1410
                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, tmp_path, 0));
×
1411
                if (r < 0)
×
1412
                        return r;
1413
        }
1414

1415
        r = RET_NERRNO(renameat(newdirfd, tmp_path, newdirfd, newpath));
899✔
1416
        if (r < 0) {
×
1417
                (void) unlinkat(newdirfd, tmp_path, /* flags= */ 0);
×
1418
                return r;
×
1419
        }
1420

1421
        return 0;
1422
}
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