fs-util: add XO_REGULAR flag for xopenat()

If this flag is set we guarantee that the fd returned refers to a
regular file. If the file exists and is not one, fails.
This commit is contained in:
Lennart Poettering
2024-12-21 12:54:53 +01:00
parent 2144b5dccf
commit dffafa47ae
3 changed files with 104 additions and 7 deletions

View File

@@ -1123,6 +1123,9 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_
assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
/* An inode cannot be both a directory and a regular file at the same time. */
assert(!(FLAGS_SET(open_flags, O_DIRECTORY) && FLAGS_SET(xopen_flags, XO_REGULAR)));
/* This is like openat(), but has a few tricks up its sleeves, extending behaviour:
*
* • O_DIRECTORY|O_CREAT is supported, which causes a directory to be created, and immediately
@@ -1134,6 +1137,8 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_
*
* • If XO_NOCOW is specified will turn on the NOCOW btrfs flag on the file, if available.
*
* • if XO_REGULAR is specified will return an error if inode is not a regular file.
*
* • If mode is specified as MODE_INVALID, we'll use 0755 for dirs, and 0644 for regular files.
*/
@@ -1142,6 +1147,13 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_
if (isempty(path)) {
assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL));
if (FLAGS_SET(xopen_flags, XO_REGULAR)) {
r = fd_verify_regular(dir_fd);
if (r < 0)
return r;
}
return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW);
}
@@ -1171,10 +1183,66 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_
open_flags &= ~(O_EXCL|O_CREAT);
}
fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file);
if (fd < 0) {
r = fd;
goto error;
if (FLAGS_SET(xopen_flags, XO_REGULAR)) {
/* Guarantee we return a regular fd only, and don't open the file unless we verified it
* first */
if (FLAGS_SET(open_flags, O_PATH)) {
fd = openat(dir_fd, path, open_flags, mode);
if (fd < 0) {
r = -errno;
goto error;
}
r = fd_verify_regular(fd);
if (r < 0)
goto error;
} else if (FLAGS_SET(open_flags, O_CREAT|O_EXCL)) {
/* In O_EXCL mode we can just create the thing, everything is dealt with for us */
fd = openat(dir_fd, path, open_flags, mode);
if (fd < 0) {
r = -errno;
goto error;
}
made_file = true;
} else {
/* Otherwise pin the inode first via O_PATH */
_cleanup_close_ int inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC|(open_flags & O_NOFOLLOW));
if (inode_fd < 0) {
if (errno != ENOENT || !FLAGS_SET(open_flags, O_CREAT)) {
r = -errno;
goto error;
}
/* Doesn't exist yet, then try to create it */
fd = openat(dir_fd, path, open_flags|O_CREAT|O_EXCL, mode);
if (fd < 0) {
r = -errno;
goto error;
}
made_file = true;
} else {
/* OK, we pinned it. Now verify it's actually a regular file, and then reopen it */
r = fd_verify_regular(inode_fd);
if (r < 0)
goto error;
fd = fd_reopen(inode_fd, open_flags & ~(O_NOFOLLOW|O_CREAT));
if (fd < 0) {
r = fd;
goto error;
}
}
}
} else {
fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file);
if (fd < 0) {
r = fd;
goto error;
}
}
if (call_label_ops_post) {

View File

@@ -134,9 +134,10 @@ 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);
typedef enum XOpenFlags {
XO_LABEL = 1 << 0,
XO_SUBVOLUME = 1 << 1,
XO_NOCOW = 1 << 2,
XO_LABEL = 1 << 0, /* When creating: relabel */
XO_SUBVOLUME = 1 << 1, /* When creating as directory: make it a subvolume */
XO_NOCOW = 1 << 2, /* Enable NOCOW mode after opening */
XO_REGULAR = 1 << 3, /* Fail if the inode is not a regular file */
} XOpenFlags;
int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_flags, mode_t mode);