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
*
* • 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.
*/
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);
}
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) {
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);
assert_se((fd2 = xopenat_full(fd, "", O_RDWR|O_CLOEXEC, 0, 0644)) >= 0);
}
+TEST(xopenat_regular) {
+
+ _cleanup_close_ int fd = -EBADF;
+
+ assert_se(xopenat_full(AT_FDCWD, "/dev/null", O_RDWR|O_CLOEXEC, XO_REGULAR, 0) == -EBADFD);
+ assert_se(xopenat_full(AT_FDCWD, "/proc", O_RDONLY|O_CLOEXEC, XO_REGULAR, 0) == -EISDIR);
+ assert_se(xopenat_full(AT_FDCWD, "/proc/self", O_RDONLY|O_CLOEXEC, XO_REGULAR, 0) == -EISDIR);
+ assert_se(xopenat_full(AT_FDCWD, "/proc/self", O_RDONLY|O_CLOEXEC|O_NOFOLLOW, XO_REGULAR, 0) == -ELOOP);
+
+ fd = xopenat_full(AT_FDCWD, "/proc/mounts", O_RDONLY|O_CLOEXEC, XO_REGULAR, 0);
+ assert_se(fd >= 0);
+ fd = safe_close(fd);
+
+ assert_se(xopenat_full(AT_FDCWD, "/proc/mounts", O_RDONLY|O_CLOEXEC|O_NOFOLLOW, XO_REGULAR, 0) == -ELOOP);
+
+ fd = xopenat_full(AT_FDCWD, "/proc/mounts", O_RDONLY|O_CLOEXEC|O_PATH, XO_REGULAR, 0);
+ assert_se(fd >= 0);
+ fd = safe_close(fd);
+
+ fd = xopenat_full(AT_FDCWD, "/tmp/xopenat-regular-test", O_RDWR|O_CLOEXEC|O_CREAT, XO_REGULAR, 0600);
+ assert_se(fd >= 0);
+ fd = safe_close(fd);
+
+ assert_se(xopenat_full(AT_FDCWD, "/tmp/xopenat-regular-test", O_RDWR|O_CLOEXEC|O_CREAT|O_EXCL, XO_REGULAR, 0600) == -EEXIST);
+
+ assert_se(unlink("/tmp/xopenat-regular-test") >= 0);
+}
+
TEST(xopenat_lock_full) {
_cleanup_(rm_rf_physical_and_freep) char *t = NULL;
_cleanup_close_ int tfd = -EBADF, fd = -EBADF;