Merge tag 'powerpc-5.16-5' of git://git.kernel.org/pub/scm/linux/kernel/git/powerpc...
[linux-2.6-microblaze.git] / fs / file.c
index ad4a8bf..97d212a 100644 (file)
--- a/fs/file.c
+++ b/fs/file.c
@@ -841,28 +841,68 @@ void do_close_on_exec(struct files_struct *files)
        spin_unlock(&files->file_lock);
 }
 
-static struct file *__fget_files(struct files_struct *files, unsigned int fd,
-                                fmode_t mask, unsigned int refs)
+static inline struct file *__fget_files_rcu(struct files_struct *files,
+       unsigned int fd, fmode_t mask, unsigned int refs)
 {
-       struct file *file;
+       for (;;) {
+               struct file *file;
+               struct fdtable *fdt = rcu_dereference_raw(files->fdt);
+               struct file __rcu **fdentry;
 
-       rcu_read_lock();
-loop:
-       file = files_lookup_fd_rcu(files, fd);
-       if (file) {
-               /* File object ref couldn't be taken.
-                * dup2() atomicity guarantee is the reason
-                * we loop to catch the new file (or NULL pointer)
+               if (unlikely(fd >= fdt->max_fds))
+                       return NULL;
+
+               fdentry = fdt->fd + array_index_nospec(fd, fdt->max_fds);
+               file = rcu_dereference_raw(*fdentry);
+               if (unlikely(!file))
+                       return NULL;
+
+               if (unlikely(file->f_mode & mask))
+                       return NULL;
+
+               /*
+                * Ok, we have a file pointer. However, because we do
+                * this all locklessly under RCU, we may be racing with
+                * that file being closed.
+                *
+                * Such a race can take two forms:
+                *
+                *  (a) the file ref already went down to zero,
+                *      and get_file_rcu_many() fails. Just try
+                *      again:
                 */
-               if (file->f_mode & mask)
-                       file = NULL;
-               else if (!get_file_rcu_many(file, refs))
-                       goto loop;
-               else if (files_lookup_fd_raw(files, fd) != file) {
+               if (unlikely(!get_file_rcu_many(file, refs)))
+                       continue;
+
+               /*
+                *  (b) the file table entry has changed under us.
+                *       Note that we don't need to re-check the 'fdt->fd'
+                *       pointer having changed, because it always goes
+                *       hand-in-hand with 'fdt'.
+                *
+                * If so, we need to put our refs and try again.
+                */
+               if (unlikely(rcu_dereference_raw(files->fdt) != fdt) ||
+                   unlikely(rcu_dereference_raw(*fdentry) != file)) {
                        fput_many(file, refs);
-                       goto loop;
+                       continue;
                }
+
+               /*
+                * Ok, we have a ref to the file, and checked that it
+                * still exists.
+                */
+               return file;
        }
+}
+
+static struct file *__fget_files(struct files_struct *files, unsigned int fd,
+                                fmode_t mask, unsigned int refs)
+{
+       struct file *file;
+
+       rcu_read_lock();
+       file = __fget_files_rcu(files, fd, mask, refs);
        rcu_read_unlock();
 
        return file;