diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 11039fd75d5..14aea54e260 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -1083,3 +1083,42 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) { return TAKE_FD(fd); } + +int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created) { + unsigned attempts = 7; + + /* Just like openat(), but adds one thing: optionally returns whether we created the file anew or if + * it already existed before. This is only relevant of O_CREAT is set without O_EXCL, and thus will + * shortcut to openat() otherwise */ + + if (!FLAGS_SET(flags, O_CREAT) || FLAGS_SET(flags, O_EXCL) || !ret_newly_created) + return RET_NERRNO(openat(dirfd, pathname, flags, mode)); + + for (;;) { + int fd; + + /* First, attempt to open without O_CREAT/O_EXCL, i.e. open existing file */ + fd = openat(dirfd, pathname, flags & ~(O_CREAT | O_EXCL), mode); + if (fd >= 0) { + *ret_newly_created = false; + return fd; + } + if (errno != ENOENT) + return -errno; + + /* So the file didn't exist yet, hence create it with O_CREAT/O_EXCL. */ + fd = openat(dirfd, pathname, flags | O_CREAT | O_EXCL, mode); + if (fd >= 0) { + *ret_newly_created = true; + return fd; + } + if (errno != EEXIST) + return -errno; + + /* Hmm, so now we got EEXIST? So it apparently exists now? If so, let's try to open again + * without the two flags. But let's not spin forever, hnce put a limit on things */ + + if (--attempts == 0) /* Give up eventually, somebody is playing with us */ + return -EEXIST; + } +} diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index 0bbb3f62981..e48cf6800fa 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -110,3 +110,5 @@ int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size); int parse_cifs_service(const char *s, char **ret_host, char **ret_service, char **ret_path); int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode); + +int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created); diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index ac8ad145eda..27875fb9e71 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -3336,6 +3336,9 @@ int journal_file_open( if (!IN_SET((flags & O_ACCMODE), O_RDONLY, O_RDWR)) return -EINVAL; + if ((flags & O_ACCMODE) == O_RDONLY && FLAGS_SET(flags, O_CREAT)) + return -EINVAL; + if (fname && (flags & O_CREAT) && !endswith(fname, ".journal")) return -EINVAL; @@ -3421,9 +3424,9 @@ int journal_file_open( * or so, we likely fail quickly than block for long. For regular files O_NONBLOCK has no effect, hence * it doesn't hurt in that case. */ - f->fd = open(f->path, f->flags|O_CLOEXEC|O_NONBLOCK, f->mode); + f->fd = openat_report_new(AT_FDCWD, f->path, f->flags|O_CLOEXEC|O_NONBLOCK, f->mode, &newly_created); if (f->fd < 0) { - r = -errno; + r = f->fd; goto fail; } @@ -3433,6 +3436,19 @@ int journal_file_open( r = fd_nonblock(f->fd, false); if (r < 0) goto fail; + + if (!newly_created) { + r = journal_file_fstat(f); + if (r < 0) + goto fail; + } + } else { + r = journal_file_fstat(f); + if (r < 0) + goto fail; + + /* If we just got the fd passed in, we don't really know if we created the file anew */ + newly_created = f->last_stat.st_size == 0 && f->writable; } f->cache_fd = mmap_cache_add_fd(mmap_cache, f->fd, prot_from_flags(flags)); @@ -3441,12 +3457,7 @@ int journal_file_open( goto fail; } - r = journal_file_fstat(f); - if (r < 0) - goto fail; - - if (f->last_stat.st_size == 0 && f->writable) { - + if (newly_created) { (void) journal_file_warn_btrfs(f); /* Let's attach the creation time to the journal file, so that the vacuuming code knows the age of this @@ -3473,8 +3484,6 @@ int journal_file_open( r = journal_file_fstat(f); if (r < 0) goto fail; - - newly_created = true; } if (f->last_stat.st_size < (off_t) HEADER_SIZE_MIN) { diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index 67b69969070..c93723ad293 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -970,6 +970,56 @@ TEST(open_mkdir_at) { assert_se(subsubdir_fd >= 0); } +TEST(openat_report_new) { + _cleanup_free_ char *j = NULL; + _cleanup_(rm_rf_physical_and_freep) char *d = NULL; + _cleanup_close_ int fd = -1; + bool b; + + assert_se(mkdtemp_malloc(NULL, &d) >= 0); + + j = path_join(d, "test"); + assert_se(j); + + fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, &b); + assert_se(fd >= 0); + fd = safe_close(fd); + assert_se(b); + + fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, &b); + assert_se(fd >= 0); + fd = safe_close(fd); + assert_se(!b); + + fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, &b); + assert_se(fd >= 0); + fd = safe_close(fd); + assert_se(!b); + + assert_se(unlink(j) >= 0); + + fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, &b); + assert_se(fd >= 0); + fd = safe_close(fd); + assert_se(b); + + fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, &b); + assert_se(fd >= 0); + fd = safe_close(fd); + assert_se(!b); + + assert_se(unlink(j) >= 0); + + fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, NULL); + assert_se(fd >= 0); + fd = safe_close(fd); + + fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, &b); + assert_se(fd >= 0); + fd = safe_close(fd); + assert_se(!b); +} + static int intro(void) { arg_test_dir = saved_argv[1]; return EXIT_SUCCESS;