]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
file: add FD_{ADD,PREPARE}()
authorChristian Brauner <brauner@kernel.org>
Sun, 23 Nov 2025 16:33:19 +0000 (17:33 +0100)
committerChristian Brauner <brauner@kernel.org>
Fri, 28 Nov 2025 11:42:23 +0000 (12:42 +0100)
I've been playing with this to allow for moderately flexible usage of
the get_unused_fd_flags() + create file + fd_install() pattern that's
used quite extensively.

How callers allocate files is really heterogenous so it's not really
convenient to fold them into a single class. It's possibe to split them
into subclasses like for anon inodes. I think that's not necessarily
nice as well.

My take is to add two primites:
(1) FD_ADD() the simple cases a file is installed:

    fd = FD_ADD(O_CLOEXEC, open_file(some, args)));
    if (fd >= 0)
            kvm_get_kvm(vcpu->kvm);
    return fd;

(2) FD_PREPARE() that captures all the cases where access to fd or file
    or additional work before publishing the fd is needed:

    FD_PREPARE(fdf, open_flag, file_open_handle(&path, open_flag));
    if (fdf.err)
            return fdf.err;

    if (copy_to_user(/* something something */))
            return -EFAULT;

    return fd_publish(fdf);

I've converted all of the easy cases over to it and it gets rid of an
aweful lot of convoluted cleanup logic.

It's centered around struct fd_prepare. FD_PREPARE() encapsulates all of
allocation and cleanup logic and must be followed by a call to
fd_publish() which associates the fd with the file and installs it into
the callers fdtable. If fd_publish() isn't called both are deallocated.

It mandates a specific order namely that first we allocate the fd and
then instantiate the file. But that shouldn't be a problem nearly
everyone I've converted uses this exact pattern anyway.

There's a bunch of additional cases where it would be easy to convert
them to this pattern. For example, the whole sync file stuff in dma
currently retains the containing structure of the file instead of the
file itself even though it's only used to allocate files. Changing that
would make it fall into the FD_PREPARE() pattern easily. I've not done
that work yet.

There's room for extending this in a way that wed'd have subclasses for
some particularly often use patterns but as I said I'm not even sure
that's worth it.

Link: https://patch.msgid.link/20251123-work-fd-prepare-v4-1-b6efa1706cfd@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
include/linux/cleanup.h
include/linux/file.h

index 2573585b7f068abe992af1ac05f478fef7b34306..361104bcfe9250d85d0e0325f6d7592af89fea11 100644 (file)
@@ -261,6 +261,10 @@ const volatile void * __must_check_fn(const volatile void *val)
  * CLASS(name, var)(args...):
  *     declare the variable @var as an instance of the named class
  *
+ * CLASS_INIT(name, var, init_expr):
+ *     declare the variable @var as an instance of the named class with
+ *     custom initialization expression.
+ *
  * Ex.
  *
  * DEFINE_CLASS(fdget, struct fd, fdput(_T), fdget(fd), int fd)
@@ -290,6 +294,9 @@ static inline class_##_name##_t class_##_name##ext##_constructor(_init_args) \
        class_##_name##_t var __cleanup(class_##_name##_destructor) =   \
                class_##_name##_constructor
 
+#define CLASS_INIT(_name, _var, _init_expr)                             \
+        class_##_name##_t _var __cleanup(class_##_name##_destructor) = (_init_expr)
+
 #define scoped_class(_name, var, args)                          \
        for (CLASS(_name, var)(args);                           \
             __guard_ptr(_name)(&var) || !__is_cond_ptr(_name); \
index af1768d934a010999fdce8bf71cb374bd7531dd5..cf389fde9bc28c4b4ee3c3f65abced2388484860 100644 (file)
@@ -127,4 +127,130 @@ extern void __fput_sync(struct file *);
 
 extern unsigned int sysctl_nr_open_min, sysctl_nr_open_max;
 
+/*
+ * fd_prepare: Combined fd + file allocation cleanup class.
+ * @err: Error code to indicate if allocation succeeded.
+ * @__fd: Allocated fd (may not be accessed directly)
+ * @__file: Allocated struct file pointer (may not be accessed directly)
+ *
+ * Allocates an fd and a file together. On error paths, automatically cleans
+ * up whichever resource was successfully allocated. Allows flexible file
+ * allocation with different functions per usage.
+ *
+ * Do not use directly.
+ */
+struct fd_prepare {
+       s32 err;
+       s32 __fd; /* do not access directly */
+       struct file *__file; /* do not access directly */
+};
+
+/* Typedef for fd_prepare cleanup guards. */
+typedef struct fd_prepare class_fd_prepare_t;
+
+/*
+ * Accessors for fd_prepare class members.
+ * _Generic() is used for zero-cost type safety.
+ */
+#define fd_prepare_fd(_fdf) \
+       (_Generic((_fdf), struct fd_prepare: (_fdf).__fd))
+
+#define fd_prepare_file(_fdf) \
+       (_Generic((_fdf), struct fd_prepare: (_fdf).__file))
+
+/* Do not use directly. */
+static inline void class_fd_prepare_destructor(const struct fd_prepare *fdf)
+{
+       if (unlikely(fdf->err)) {
+               if (likely(fdf->__fd >= 0))
+                       put_unused_fd(fdf->__fd);
+               if (unlikely(!IS_ERR_OR_NULL(fdf->__file)))
+                       fput(fdf->__file);
+       }
+}
+
+/* Do not use directly. */
+static inline int class_fd_prepare_lock_err(const struct fd_prepare *fdf)
+{
+       if (unlikely(fdf->err))
+               return fdf->err;
+       if (unlikely(fdf->__fd < 0))
+               return fdf->__fd;
+       if (unlikely(IS_ERR(fdf->__file)))
+               return PTR_ERR(fdf->__file);
+       if (unlikely(!fdf->__file))
+               return -ENOMEM;
+       return 0;
+}
+
+/*
+ * __FD_PREPARE_INIT - Helper to initialize fd_prepare class.
+ * @_fd_flags: flags for get_unused_fd_flags()
+ * @_file_owned: expression that returns struct file *
+ *
+ * Returns a struct fd_prepare with fd, file, and err set.
+ * If fd allocation fails, fd will be negative and err will be set. If
+ * fd succeeds but file_init_expr fails, file will be ERR_PTR and err
+ * will be set. The err field is the single source of truth for error
+ * checking.
+ */
+#define __FD_PREPARE_INIT(_fd_flags, _file_owned)                 \
+       ({                                                        \
+               struct fd_prepare fdf = {                         \
+                       .__fd = get_unused_fd_flags((_fd_flags)), \
+               };                                                \
+               if (likely(fdf.__fd >= 0))                        \
+                       fdf.__file = (_file_owned);               \
+               fdf.err = ACQUIRE_ERR(fd_prepare, &fdf);          \
+               fdf;                                              \
+       })
+
+/*
+ * FD_PREPARE - Macro to declare and initialize an fd_prepare variable.
+ *
+ * Declares and initializes an fd_prepare variable with automatic
+ * cleanup. No separate scope required - cleanup happens when variable
+ * goes out of scope.
+ *
+ * @_fdf: name of struct fd_prepare variable to define
+ * @_fd_flags: flags for get_unused_fd_flags()
+ * @_file_owned: struct file to take ownership of (can be expression)
+ */
+#define FD_PREPARE(_fdf, _fd_flags, _file_owned) \
+       CLASS_INIT(fd_prepare, _fdf, __FD_PREPARE_INIT(_fd_flags, _file_owned))
+
+/*
+ * fd_publish - Publish prepared fd and file to the fd table.
+ * @_fdf: struct fd_prepare variable
+ */
+#define fd_publish(_fdf)                                       \
+       ({                                                     \
+               struct fd_prepare *fdp = &(_fdf);              \
+               VFS_WARN_ON_ONCE(fdp->err);                    \
+               VFS_WARN_ON_ONCE(fdp->__fd < 0);               \
+               VFS_WARN_ON_ONCE(IS_ERR_OR_NULL(fdp->__file)); \
+               fd_install(fdp->__fd, fdp->__file);            \
+               fdp->__fd;                                     \
+       })
+
+/* Do not use directly. */
+#define __FD_ADD(_fdf, _fd_flags, _file_owned)            \
+       ({                                                \
+               FD_PREPARE(_fdf, _fd_flags, _file_owned); \
+               s32 ret = _fdf.err;                       \
+               if (likely(!ret))                         \
+                       ret = fd_publish(_fdf);           \
+               ret;                                      \
+       })
+
+/*
+ * FD_ADD - Allocate and install an fd and file in one step.
+ * @_fd_flags: flags for get_unused_fd_flags()
+ * @_file_owned: struct file to take ownership of
+ *
+ * Returns the allocated fd number, or negative error code on failure.
+ */
+#define FD_ADD(_fd_flags, _file_owned) \
+       __FD_ADD(__UNIQUE_ID(fd_prepare), _fd_flags, _file_owned)
+
 #endif /* __LINUX_FILE_H */