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

systemd / systemd / 21192089104

20 Jan 2026 11:35PM UTC coverage: 72.524% (-0.3%) from 72.818%
21192089104

push

github

yuwata
mkdir: reset mtime *after* fchown()

Follow-up for 34c3d5747

Also, drop pointless shortcut.

1 of 2 new or added lines in 1 file covered. (50.0%)

2960 existing lines in 48 files now uncovered.

309808 of 427181 relevant lines covered (72.52%)

1236537.64 hits per line

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

66.01
/src/shared/install-file.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

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

7
#include "btrfs-util.h"
8
#include "chattr-util.h"
9
#include "env-util.h"
10
#include "errno-util.h"
11
#include "fd-util.h"
12
#include "fs-util.h"
13
#include "install-file.h"
14
#include "log.h"
15
#include "rm-rf.h"
16
#include "sync-util.h"
17
#include "time-util.h"
18

19
static int fs_make_very_read_only(int fd) {
2✔
20
        struct stat st;
2✔
21
        int r;
2✔
22

23
        assert(fd >= 0);
2✔
24

25
        /* Tries to make the specified fd "comprehensively" read-only. Primary use case for this is OS images,
26
         * i.e. either loopback files or larger directory hierarchies. Depending on the inode type and
27
         * backing file system this means something different:
28
         *
29
         * 1. If the fd refers to a btrfs subvolume we'll mark it read-only as a whole
30
         * 2. If the fd refers to any other directory we'll set the FS_IMMUTABLE_FL flag on it
31
         * 3. If the fd refers to a regular file we'll drop the w bits.
32
         * 4. If the fd refers to a block device, use BLKROSET to set read-only state
33
         *
34
         * You might wonder why not drop the x bits for directories. That's because we want to guarantee that
35
         * everything "inside" the image remains largely the way it is, in case you mount it. And since the
36
         * mode of the root dir of the image is pretty visible we don't want to modify it. btrfs subvol flags
37
         * and the FS_IMMUTABLE_FL otoh are much less visible. Changing the mode of regular files should be
38
         * OK though, since after all this is supposed to be used for disk images, i.e. the fs in the disk
39
         * image doesn't make the mode of the loopback file it is stored in visible. */
40

41
        if (fstat(fd, &st) < 0)
2✔
UNCOV
42
                return -errno;
×
43

44
        switch (st.st_mode & S_IFMT) {
2✔
45

46
        case S_IFDIR:
1✔
47
                if (btrfs_might_be_subvol(&st)) {
1✔
UNCOV
48
                        r = btrfs_subvol_set_read_only_fd(fd, true);
×
49
                        if (r >= 0)
×
50
                                return 0;
UNCOV
51
                        if (!ERRNO_IS_NEG_IOCTL_NOT_SUPPORTED(r))
×
52
                                return r;
53
                }
54

55
                r = chattr_fd(fd, FS_IMMUTABLE_FL, FS_IMMUTABLE_FL);
1✔
56
                if (r < 0)
1✔
57
                        return r;
1✔
58

59
                break;
60

61
        case S_IFREG:
1✔
62
                if ((st.st_mode & 0222) != 0)
1✔
63
                        if (fchmod(fd, st.st_mode & 07555) < 0)
1✔
UNCOV
64
                                return -errno;
×
65

66
                break;
67

UNCOV
68
        case S_IFBLK: {
×
69
                int ro = 1;
×
70

UNCOV
71
                if (ioctl(fd, BLKROSET, &ro) < 0)
×
72
                        return -errno;
×
73

UNCOV
74
                break;
×
75
        }
76

77
        default:
78
                return -EBADFD;
79
        }
80

81
        return 0;
82
}
83

84
static int unlinkat_maybe_dir(int dirfd, const char *pathname) {
11✔
85

86
        /* Invokes unlinkat() for regular files first, and if this fails with EISDIR tries again with
87
         * AT_REMOVEDIR */
88

89
        if (unlinkat(dirfd, pathname, 0) < 0) {
11✔
90
                if (errno != EISDIR)
10✔
UNCOV
91
                        return -errno;
×
92

93
                if (unlinkat(dirfd, pathname, AT_REMOVEDIR) < 0)
10✔
UNCOV
94
                        return -errno;
×
95
        }
96

97
        return 0;
98
}
99

100
static bool need_opath(InstallFileFlags flags) {
166✔
101
        /* Returns true when we need to pin the source file via an O_PATH fd */
102
        return (flags & (INSTALL_FSYNC|INSTALL_FSYNC_FULL|INSTALL_SYNCFS|INSTALL_READ_ONLY)) != 0;
166✔
103
}
104

105
int install_file(int source_atfd, const char *source_name,
166✔
106
                 int target_atfd, const char *target_name,
107
                 InstallFileFlags flags) {
108

109
        _cleanup_close_ int rofd = -EBADF;
166✔
110
        int r;
166✔
111

112
        /* Moves a file or directory tree into place, with some bells and whistles:
113
         *
114
         * 1. Optionally syncs before/after to ensure file installation can be used as barrier
115
         * 2. Optionally marks the file/directory read-only using fs_make_very_read_only()
116
         * 3. Optionally operates in replacing or in non-replacing mode.
117
         * 4. If it replaces will remove the old tree if needed.
118
         */
119

120
        assert(source_atfd >= 0 || source_atfd == AT_FDCWD);
166✔
121
        assert(source_name);
166✔
122
        assert(target_atfd >= 0 || target_atfd == AT_FDCWD);
166✔
123

124
        /* If target_name is specified as NULL no renaming takes place. Instead it is assumed the file is
125
         * already in place, and only the syncing/read-only marking shall be applied. Note that with
126
         * target_name=NULL and flags=0 this call is a NOP */
127

128
        if (need_opath(flags)) {
166✔
129
                _cleanup_close_ int pfd = -EBADF;
134✔
130
                struct stat st;
134✔
131

132
                /* Open an O_PATH fd for the source if we need to sync things or mark things read only. */
133

134
                pfd = openat(source_atfd, source_name, O_PATH|O_CLOEXEC|O_NOFOLLOW);
134✔
135
                if (pfd < 0)
134✔
136
                        return -errno;
3✔
137

138
                if (fstat(pfd, &st) < 0)
131✔
UNCOV
139
                        return -errno;
×
140

141
                switch (st.st_mode & S_IFMT) {
131✔
142

143
                case S_IFREG: {
77✔
UNCOV
144
                        _cleanup_close_ int regfd = -EBADF;
×
145

146
                        regfd = fd_reopen(pfd, O_RDONLY|O_CLOEXEC);
77✔
147
                        if (regfd < 0) {
77✔
UNCOV
148
                                if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
×
149
                                        return log_debug_errno(regfd, "Failed to open referenced inode: %m");
×
150

UNCOV
151
                                log_debug_errno(regfd, "Failed to open referenced inode, ignoring: %m");
×
152
                        } else {
153
                                if ((flags & (INSTALL_FSYNC_FULL|INSTALL_SYNCFS)) != 0)
77✔
154
                                        /* If this is just a regular file (as opposed to a fully populated
155
                                         * directory) let's downgrade INSTALL_SYNCFS to INSTALL_FSYNC_FULL,
156
                                         * after all this is going to be a single inode we install */
157
                                        r = fsync_full(regfd);
76✔
158
                                else if (flags & INSTALL_FSYNC)
1✔
159
                                        r = RET_NERRNO(fsync(regfd));
1✔
160
                                else
161
                                        r = 0;
162
                                if (r < 0) {
76✔
UNCOV
163
                                        if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
×
164
                                                return log_debug_errno(r, "Failed to sync source inode: %m");
×
165

UNCOV
166
                                        log_debug_errno(r, "Failed to sync source inode, ignoring: %m");
×
167
                                }
168

169
                                if (flags & INSTALL_READ_ONLY)
77✔
170
                                        rofd = TAKE_FD(regfd);
1✔
171
                        }
172

173
                        break;
77✔
174
                }
175

176
                case S_IFDIR: {
50✔
177
                        _cleanup_close_ int dfd = -EBADF;
3✔
178

179
                        dfd = fd_reopen(pfd, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
50✔
180
                        if (dfd < 0) {
50✔
UNCOV
181
                                if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
×
182
                                        return log_debug_errno(dfd, "Failed to open referenced inode: %m");
×
183

UNCOV
184
                                log_debug_errno(dfd, "Failed to open referenced inode, ignoring: %m");
×
185
                        } else {
186
                                if (flags & INSTALL_SYNCFS)
50✔
187
                                        r = RET_NERRNO(syncfs(dfd));
48✔
188
                                else if (flags & INSTALL_FSYNC_FULL)
2✔
189
                                        r = fsync_full(dfd);
2✔
UNCOV
190
                                else if (flags & INSTALL_FSYNC)
×
UNCOV
191
                                        r = RET_NERRNO(fsync(dfd));
×
192
                                else
193
                                        r = 0;
194
                                if (r < 0) {
2✔
UNCOV
195
                                        if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
×
196
                                                return log_debug_errno(r, "Failed to sync source inode: %m");
×
197

UNCOV
198
                                        log_debug_errno(r, "Failed to sync source inode, ignoring: %m");
×
199
                                }
200

201
                                if (flags & INSTALL_READ_ONLY)
50✔
202
                                        rofd = TAKE_FD(dfd);
1✔
203
                        }
204

205
                        break;
50✔
206
                }
207

208
                default:
4✔
209
                        /* Other inodes: char/block device inodes, fifos, symlinks, sockets don't need
210
                         * syncing themselves, as they only exist in the directory, and have no contents on
211
                         * disk */
212

213
                        if (target_name && (flags & (INSTALL_FSYNC_FULL|INSTALL_SYNCFS)) != 0) {
4✔
214
                                r = fsync_directory_of_file(pfd);
×
215
                                if (r < 0) {
×
UNCOV
216
                                        if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
×
UNCOV
217
                                                return log_debug_errno(r, "Failed to sync source inode: %m");
×
218

219
                                        log_debug_errno(r, "Failed to sync source inode, ignoring: %m");
131✔
220
                                }
221
                        }
222

223
                        break;
224
                }
225
        }
226

227
        if (target_name) {
163✔
228
                /* Rename the file */
229

230
                if (flags & INSTALL_REPLACE) {
113✔
231
                        /* First, try a simple renamat(), maybe that's enough */
232
                        if (renameat(source_atfd, source_name, target_atfd, target_name) < 0) {
81✔
UNCOV
233
                                _cleanup_close_ int dfd = -EBADF;
×
234

235
                                if (!IN_SET(errno, EEXIST, ENOTDIR, ENOTEMPTY, EISDIR, EBUSY))
11✔
UNCOV
236
                                        return -errno;
×
237

238
                                /* Hmm, the target apparently existed already. Let's try to use
239
                                 * RENAME_EXCHANGE. But let's first open the inode if it's a directory, so
240
                                 * that we can later remove its contents if it's a directory. Why do this
241
                                 * before the rename()? Mostly because if we have trouble opening the thing
242
                                 * we want to know before we start actually modifying the file system. */
243

244
                                dfd = openat(target_atfd, target_name, O_RDONLY|O_DIRECTORY|O_CLOEXEC, 0);
11✔
245
                                if (dfd < 0 && errno != ENOTDIR)
11✔
UNCOV
246
                                        return -errno;
×
247

248
                                if (renameat2(source_atfd, source_name, target_atfd, target_name, RENAME_EXCHANGE) < 0) {
11✔
249

UNCOV
250
                                        if (!ERRNO_IS_NOT_SUPPORTED(errno) && errno != EINVAL)
×
UNCOV
251
                                                return -errno;
×
252

253
                                        /* The exchange didn't work, let's remove the target first, and try again */
254

UNCOV
255
                                        if (dfd >= 0)
×
256
                                                (void) rm_rf_children(TAKE_FD(dfd), REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_CHMOD, NULL);
×
257

258
                                        r = unlinkat_maybe_dir(target_atfd, target_name);
×
UNCOV
259
                                        if (r < 0)
×
260
                                                return log_debug_errno(r, "Failed to remove target directory: %m");
×
261

UNCOV
262
                                        if (renameat(source_atfd, source_name, target_atfd, target_name) < 0)
×
UNCOV
263
                                                return -errno;
×
264
                                } else {
265
                                        /* The exchange worked, hence let's remove the source (i.e. the old target) */
266
                                        if (dfd >= 0)
11✔
267
                                                (void) rm_rf_children(TAKE_FD(dfd), REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_CHMOD, NULL);
10✔
268

269
                                        r = unlinkat_maybe_dir(source_atfd, source_name);
11✔
270
                                        if (r < 0)
11✔
UNCOV
271
                                                return log_debug_errno(r, "Failed to remove replaced target directory: %m");
×
272
                                }
273
                        }
274
                } else {
275
                        r = rename_noreplace(source_atfd, source_name, target_atfd, target_name);
32✔
276
                        if (r < 0)
32✔
277
                                return r;
278
                }
279
        }
280

281
        if (rofd >= 0) {
160✔
282
                r = fs_make_very_read_only(rofd);
2✔
283
                if (r < 0) {
2✔
284
                        if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
1✔
UNCOV
285
                                return log_debug_errno(r, "Failed to make inode read-only: %m");
×
286

287
                        log_debug_errno(r, "Failed to make inode read-only, ignoring: %m");
1✔
288
                }
289
        }
290

291
        if ((flags & (INSTALL_FSYNC_FULL|INSTALL_SYNCFS)) != 0) {
160✔
292
                if (target_name)
127✔
293
                        r = fsync_parent_at(target_atfd, target_name);
77✔
294
                else
295
                        r = fsync_parent_at(source_atfd, source_name);
50✔
296
                if (r < 0) {
127✔
UNCOV
297
                        if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
×
UNCOV
298
                                return log_debug_errno(r, "Failed to sync inode: %m");
×
299

300
                        log_debug_errno(r, "Failed to sync inode, ignoring: %m");
166✔
301
                }
302
        }
303

304
        return 0;
305
}
306

307
usec_t parse_source_date_epoch(void) {
157✔
308
        static usec_t cache;
157✔
309
        static bool cached = false;
157✔
310
        int r;
157✔
311

312
        if (cached)
157✔
313
                return cache;
21✔
314

315
        uint64_t t;
136✔
316
        r = secure_getenv_uint64("SOURCE_DATE_EPOCH", &t);
136✔
317
        if (r >= 0) {
136✔
UNCOV
318
                if (MUL_SAFE(&cache, t, USEC_PER_SEC)) {
×
UNCOV
319
                        cached = true;
×
UNCOV
320
                        return cache;
×
321
                }
322

323
                r = -ERANGE;
324
        }
325
        if (r != -ENXIO)
136✔
UNCOV
326
                log_debug_errno(r, "Failed to parse $SOURCE_DATE_EPOCH, ignoring: %m");
×
327

328
        cached = true;
136✔
329
        return (cache = USEC_INFINITY);
136✔
330
}
331

332
usec_t source_date_epoch_or_now(void) {
88✔
333
        usec_t epoch;
88✔
334

335
        epoch = parse_source_date_epoch();
88✔
336
        if (epoch != USEC_INFINITY)
88✔
337
                return epoch;
338

339
        return now(CLOCK_REALTIME);
88✔
340
}
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