Merge tag 'pipe-nonblock-2023-05-06' of git://git.kernel.dk/linux
[platform/kernel/linux-rpi.git] / fs / splice.c
index 0af8d15..3e06611 100644 (file)
 #include "internal.h"
 
 /*
+ * Splice doesn't support FMODE_NOWAIT. Since pipes may set this flag to
+ * indicate they support non-blocking reads or writes, we must clear it
+ * here if set to avoid blocking other users of this pipe if splice is
+ * being done on it.
+ */
+static noinline void noinline pipe_clear_nowait(struct file *file)
+{
+       fmode_t fmode = READ_ONCE(file->f_mode);
+
+       do {
+               if (!(fmode & FMODE_NOWAIT))
+                       break;
+       } while (!try_cmpxchg(&file->f_mode, &fmode, fmode & ~FMODE_NOWAIT));
+}
+
+/*
  * Attempt to steal a page from a pipe buffer. This should perhaps go into
  * a vm helper function, it's already simplified quite a bit by the
  * addition of remove_mapping(). If success is returned, the caller may
@@ -1219,10 +1235,16 @@ static long __do_splice(struct file *in, loff_t __user *off_in,
        ipipe = get_pipe_info(in, true);
        opipe = get_pipe_info(out, true);
 
-       if (ipipe && off_in)
-               return -ESPIPE;
-       if (opipe && off_out)
-               return -ESPIPE;
+       if (ipipe) {
+               if (off_in)
+                       return -ESPIPE;
+               pipe_clear_nowait(in);
+       }
+       if (opipe) {
+               if (off_out)
+                       return -ESPIPE;
+               pipe_clear_nowait(out);
+       }
 
        if (off_out) {
                if (copy_from_user(&offset, off_out, sizeof(loff_t)))
@@ -1319,6 +1341,8 @@ static long vmsplice_to_user(struct file *file, struct iov_iter *iter,
        if (!pipe)
                return -EBADF;
 
+       pipe_clear_nowait(file);
+
        if (sd.total_len) {
                pipe_lock(pipe);
                ret = __splice_from_pipe(pipe, &sd, pipe_to_user);
@@ -1347,6 +1371,8 @@ static long vmsplice_to_pipe(struct file *file, struct iov_iter *iter,
        if (!pipe)
                return -EBADF;
 
+       pipe_clear_nowait(file);
+
        pipe_lock(pipe);
        ret = wait_for_space(pipe, flags);
        if (!ret)