Merge branch 'work.recursive_removal' of git://git.kernel.org/pub/scm/linux/kernel...
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 5 Feb 2020 05:09:46 +0000 (05:09 +0000)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 5 Feb 2020 05:09:46 +0000 (05:09 +0000)
Pull vfs recursive removal updates from Al Viro:
 "We have quite a few places where synthetic filesystems do an
  equivalent of 'rm -rf', with varying amounts of code duplication,
  wrong locking, etc. That really ought to be a library helper.

  Only debugfs (and very similar tracefs) are converted here - I have
  more conversions, but they'd never been in -next, so they'll have to
  wait"

* 'work.recursive_removal' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs:
  simple_recursive_removal(): kernel-side rm -rf for ramfs-style filesystems

1  2 
fs/debugfs/inode.c
include/linux/fs.h
kernel/trace/trace.c
kernel/trace/trace_events.c

diff --combined fs/debugfs/inode.c
@@@ -332,7 -332,10 +332,10 @@@ static struct dentry *start_creating(co
                parent = debugfs_mount->mnt_root;
  
        inode_lock(d_inode(parent));
-       dentry = lookup_one_len(name, parent, strlen(name));
+       if (unlikely(IS_DEADDIR(d_inode(parent))))
+               dentry = ERR_PTR(-ENOENT);
+       else
+               dentry = lookup_one_len(name, parent, strlen(name));
        if (!IS_ERR(dentry) && d_really_is_positive(dentry)) {
                if (d_is_dir(dentry))
                        pr_err("Directory '%s' with parent '%s' already present!\n",
@@@ -423,7 -426,7 +426,7 @@@ static struct dentry *__debugfs_create_
   * This function will return a pointer to a dentry if it succeeds.  This
   * pointer must be passed to the debugfs_remove() function when the file is
   * to be removed (no automatic cleanup happens if your module is unloaded,
 - * you are responsible here.)  If an error occurs, %ERR_PTR(-ERROR) will be
 + * you are responsible here.)  If an error occurs, ERR_PTR(-ERROR) will be
   * returned.
   *
   * If debugfs is not enabled in the kernel, the value -%ENODEV will be
@@@ -502,7 -505,7 +505,7 @@@ EXPORT_SYMBOL_GPL(debugfs_create_file_u
   * This function will return a pointer to a dentry if it succeeds.  This
   * pointer must be passed to the debugfs_remove() function when the file is
   * to be removed (no automatic cleanup happens if your module is unloaded,
 - * you are responsible here.)  If an error occurs, %ERR_PTR(-ERROR) will be
 + * you are responsible here.)  If an error occurs, ERR_PTR(-ERROR) will be
   * returned.
   *
   * If debugfs is not enabled in the kernel, the value -%ENODEV will be
@@@ -534,7 -537,7 +537,7 @@@ EXPORT_SYMBOL_GPL(debugfs_create_file_s
   * This function will return a pointer to a dentry if it succeeds.  This
   * pointer must be passed to the debugfs_remove() function when the file is
   * to be removed (no automatic cleanup happens if your module is unloaded,
 - * you are responsible here.)  If an error occurs, %ERR_PTR(-ERROR) will be
 + * you are responsible here.)  If an error occurs, ERR_PTR(-ERROR) will be
   * returned.
   *
   * If debugfs is not enabled in the kernel, the value -%ENODEV will be
@@@ -627,7 -630,7 +630,7 @@@ EXPORT_SYMBOL(debugfs_create_automount)
   * This function will return a pointer to a dentry if it succeeds.  This
   * pointer must be passed to the debugfs_remove() function when the symbolic
   * link is to be removed (no automatic cleanup happens if your module is
 - * unloaded, you are responsible here.)  If an error occurs, %ERR_PTR(-ERROR)
 + * unloaded, you are responsible here.)  If an error occurs, ERR_PTR(-ERROR)
   * will be returned.
   *
   * If debugfs is not enabled in the kernel, the value -%ENODEV will be
@@@ -681,62 -684,15 +684,15 @@@ static void __debugfs_file_removed(stru
                wait_for_completion(&fsd->active_users_drained);
  }
  
- static int __debugfs_remove(struct dentry *dentry, struct dentry *parent)
+ static void remove_one(struct dentry *victim)
  {
-       int ret = 0;
-       if (simple_positive(dentry)) {
-               dget(dentry);
-               if (d_is_dir(dentry)) {
-                       ret = simple_rmdir(d_inode(parent), dentry);
-                       if (!ret)
-                               fsnotify_rmdir(d_inode(parent), dentry);
-               } else {
-                       simple_unlink(d_inode(parent), dentry);
-                       fsnotify_unlink(d_inode(parent), dentry);
-               }
-               if (!ret)
-                       d_delete(dentry);
-               if (d_is_reg(dentry))
-                       __debugfs_file_removed(dentry);
-               dput(dentry);
-       }
-       return ret;
- }
- /**
-  * debugfs_remove - removes a file or directory from the debugfs filesystem
-  * @dentry: a pointer to a the dentry of the file or directory to be
-  *          removed.  If this parameter is NULL or an error value, nothing
-  *          will be done.
-  *
-  * This function removes a file or directory in debugfs that was previously
-  * created with a call to another debugfs function (like
-  * debugfs_create_file() or variants thereof.)
-  *
-  * This function is required to be called in order for the file to be
-  * removed, no automatic cleanup of files will happen when a module is
-  * removed, you are responsible here.
-  */
- void debugfs_remove(struct dentry *dentry)
- {
-       struct dentry *parent;
-       int ret;
-       if (IS_ERR_OR_NULL(dentry))
-               return;
-       parent = dentry->d_parent;
-       inode_lock(d_inode(parent));
-       ret = __debugfs_remove(dentry, parent);
-       inode_unlock(d_inode(parent));
-       if (!ret)
-               simple_release_fs(&debugfs_mount, &debugfs_mount_count);
+         if (d_is_reg(victim))
+               __debugfs_file_removed(victim);
+       simple_release_fs(&debugfs_mount, &debugfs_mount_count);
  }
- EXPORT_SYMBOL_GPL(debugfs_remove);
  
  /**
-  * debugfs_remove_recursive - recursively removes a directory
+  * debugfs_remove - recursively removes a directory
   * @dentry: a pointer to a the dentry of the directory to be removed.  If this
   *          parameter is NULL or an error value, nothing will be done.
   *
   * removed, no automatic cleanup of files will happen when a module is
   * removed, you are responsible here.
   */
- void debugfs_remove_recursive(struct dentry *dentry)
+ void debugfs_remove(struct dentry *dentry)
  {
-       struct dentry *child, *parent;
        if (IS_ERR_OR_NULL(dentry))
                return;
  
-       parent = dentry;
-  down:
-       inode_lock(d_inode(parent));
-  loop:
-       /*
-        * The parent->d_subdirs is protected by the d_lock. Outside that
-        * lock, the child can be unlinked and set to be freed which can
-        * use the d_u.d_child as the rcu head and corrupt this list.
-        */
-       spin_lock(&parent->d_lock);
-       list_for_each_entry(child, &parent->d_subdirs, d_child) {
-               if (!simple_positive(child))
-                       continue;
-               /* perhaps simple_empty(child) makes more sense */
-               if (!list_empty(&child->d_subdirs)) {
-                       spin_unlock(&parent->d_lock);
-                       inode_unlock(d_inode(parent));
-                       parent = child;
-                       goto down;
-               }
-               spin_unlock(&parent->d_lock);
-               if (!__debugfs_remove(child, parent))
-                       simple_release_fs(&debugfs_mount, &debugfs_mount_count);
-               /*
-                * The parent->d_lock protects agaist child from unlinking
-                * from d_subdirs. When releasing the parent->d_lock we can
-                * no longer trust that the next pointer is valid.
-                * Restart the loop. We'll skip this one with the
-                * simple_positive() check.
-                */
-               goto loop;
-       }
-       spin_unlock(&parent->d_lock);
-       inode_unlock(d_inode(parent));
-       child = parent;
-       parent = parent->d_parent;
-       inode_lock(d_inode(parent));
-       if (child != dentry)
-               /* go up */
-               goto loop;
-       if (!__debugfs_remove(child, parent))
-               simple_release_fs(&debugfs_mount, &debugfs_mount_count);
-       inode_unlock(d_inode(parent));
+       simple_pin_fs(&debug_fs_type, &debugfs_mount, &debugfs_mount_count);
+       simple_recursive_removal(dentry, remove_one);
+       simple_release_fs(&debugfs_mount, &debugfs_mount_count);
  }
- EXPORT_SYMBOL_GPL(debugfs_remove_recursive);
+ EXPORT_SYMBOL_GPL(debugfs_remove);
  
  /**
   * debugfs_rename - rename a file/directory in the debugfs filesystem
@@@ -906,3 -813,4 +813,3 @@@ static int __init debugfs_init(void
        return retval;
  }
  core_initcall(debugfs_init);
 -
diff --combined include/linux/fs.h
@@@ -855,7 -855,7 +855,7 @@@ static inline loff_t i_size_read(const 
                i_size = inode->i_size;
        } while (read_seqcount_retry(&inode->i_size_seqcount, seq));
        return i_size;
 -#elif BITS_PER_LONG==32 && defined(CONFIG_PREEMPT)
 +#elif BITS_PER_LONG==32 && defined(CONFIG_PREEMPTION)
        loff_t i_size;
  
        preempt_disable();
@@@ -880,7 -880,7 +880,7 @@@ static inline void i_size_write(struct 
        inode->i_size = i_size;
        write_seqcount_end(&inode->i_size_seqcount);
        preempt_enable();
 -#elif BITS_PER_LONG==32 && defined(CONFIG_PREEMPT)
 +#elif BITS_PER_LONG==32 && defined(CONFIG_PREEMPTION)
        preempt_disable();
        inode->i_size = i_size;
        preempt_enable();
@@@ -1575,6 -1575,7 +1575,6 @@@ static inline void i_gid_write(struct i
        inode->i_gid = make_kgid(inode->i_sb->s_user_ns, gid);
  }
  
 -extern struct timespec64 timespec64_trunc(struct timespec64 t, unsigned gran);
  extern struct timespec64 current_time(struct inode *inode);
  
  /*
@@@ -2077,18 -2078,6 +2077,18 @@@ static inline void init_sync_kiocb(stru
        };
  }
  
 +static inline void kiocb_clone(struct kiocb *kiocb, struct kiocb *kiocb_src,
 +                             struct file *filp)
 +{
 +      *kiocb = (struct kiocb) {
 +              .ki_filp = filp,
 +              .ki_flags = kiocb_src->ki_flags,
 +              .ki_hint = kiocb_src->ki_hint,
 +              .ki_ioprio = kiocb_src->ki_ioprio,
 +              .ki_pos = kiocb_src->ki_pos,
 +      };
 +}
 +
  /*
   * Inode state bits.  Protected by inode->i_lock
   *
@@@ -2563,6 -2552,10 +2563,6 @@@ extern int finish_open(struct file *fil
                        int (*open)(struct inode *, struct file *));
  extern int finish_no_open(struct file *file, struct dentry *dentry);
  
 -/* fs/ioctl.c */
 -
 -extern int ioctl_preallocate(struct file *filp, int mode, void __user *argp);
 -
  /* fs/dcache.c */
  extern void __init vfs_caches_init_early(void);
  extern void __init vfs_caches_init(void);
@@@ -2748,6 -2741,7 +2748,6 @@@ static inline int filemap_fdatawait(str
  
  extern bool filemap_range_has_page(struct address_space *, loff_t lstart,
                                  loff_t lend);
 -extern int filemap_write_and_wait(struct address_space *mapping);
  extern int filemap_write_and_wait_range(struct address_space *mapping,
                                        loff_t lstart, loff_t lend);
  extern int __filemap_fdatawrite_range(struct address_space *mapping,
@@@ -2757,11 -2751,6 +2757,11 @@@ extern int filemap_fdatawrite_range(str
  extern int filemap_check_errors(struct address_space *mapping);
  extern void __filemap_set_wb_err(struct address_space *mapping, int err);
  
 +static inline int filemap_write_and_wait(struct address_space *mapping)
 +{
 +      return filemap_write_and_wait_range(mapping, 0, LLONG_MAX);
 +}
 +
  extern int __must_check file_fdatawait_range(struct file *file, loff_t lstart,
                                                loff_t lend);
  extern int __must_check file_check_and_advance_wb_err(struct file *file);
@@@ -3119,10 -3108,6 +3119,10 @@@ ssize_t vfs_iter_read(struct file *file
                rwf_t flags);
  ssize_t vfs_iter_write(struct file *file, struct iov_iter *iter, loff_t *ppos,
                rwf_t flags);
 +ssize_t vfs_iocb_iter_read(struct file *file, struct kiocb *iocb,
 +                         struct iov_iter *iter);
 +ssize_t vfs_iocb_iter_write(struct file *file, struct kiocb *iocb,
 +                          struct iov_iter *iter);
  
  /* fs/block_dev.c */
  extern ssize_t blkdev_read_iter(struct kiocb *iocb, struct iov_iter *to);
@@@ -3318,6 -3303,8 +3318,8 @@@ extern int simple_unlink(struct inode *
  extern int simple_rmdir(struct inode *, struct dentry *);
  extern int simple_rename(struct inode *, struct dentry *,
                         struct inode *, struct dentry *, unsigned int);
+ extern void simple_recursive_removal(struct dentry *,
+                               void (*callback)(struct dentry *));
  extern int noop_fsync(struct file *, loff_t, loff_t, int);
  extern int noop_set_page_dirty(struct page *page);
  extern void noop_invalidatepage(struct page *page, unsigned int offset,
diff --combined kernel/trace/trace.c
@@@ -1889,7 -1889,7 +1889,7 @@@ int __init register_tracer(struct trace
        }
  
        if (security_locked_down(LOCKDOWN_TRACEFS)) {
 -              pr_warning("Can not register tracer %s due to lockdown\n",
 +              pr_warn("Can not register tracer %s due to lockdown\n",
                           type->name);
                return -EPERM;
        }
@@@ -4685,10 -4685,6 +4685,10 @@@ int trace_keep_overwrite(struct tracer 
  
  int set_tracer_flag(struct trace_array *tr, unsigned int mask, int enabled)
  {
 +      if ((mask == TRACE_ITER_RECORD_TGID) ||
 +          (mask == TRACE_ITER_RECORD_CMD))
 +              lockdep_assert_held(&event_mutex);
 +
        /* do nothing if flag is already set */
        if (!!(tr->trace_flags & mask) == !!enabled)
                return 0;
@@@ -4756,7 -4752,6 +4756,7 @@@ static int trace_set_options(struct tra
  
        cmp += len;
  
 +      mutex_lock(&event_mutex);
        mutex_lock(&trace_types_lock);
  
        ret = match_string(trace_options, -1, cmp);
                ret = set_tracer_flag(tr, 1 << ret, !neg);
  
        mutex_unlock(&trace_types_lock);
 +      mutex_unlock(&event_mutex);
  
        /*
         * If the first trailing whitespace is replaced with '\0' by strstrip,
@@@ -8082,11 -8076,9 +8082,11 @@@ trace_options_core_write(struct file *f
        if (val != 0 && val != 1)
                return -EINVAL;
  
 +      mutex_lock(&event_mutex);
        mutex_lock(&trace_types_lock);
        ret = set_tracer_flag(tr, 1 << index, val);
        mutex_unlock(&trace_types_lock);
 +      mutex_unlock(&event_mutex);
  
        if (ret < 0)
                return ret;
@@@ -8504,7 -8496,7 +8504,7 @@@ static struct trace_array *trace_array_
  
        ret = event_trace_add_tracer(tr->dir, tr);
        if (ret) {
-               tracefs_remove_recursive(tr->dir);
+               tracefs_remove(tr->dir);
                goto out_free_tr;
        }
  
@@@ -8613,7 -8605,7 +8613,7 @@@ static int __remove_instance(struct tra
        event_trace_del_tracer(tr);
        ftrace_clear_pids(tr);
        ftrace_destroy_function_files(tr);
-       tracefs_remove_recursive(tr->dir);
+       tracefs_remove(tr->dir);
        free_trace_buffers(tr);
  
        for (i = 0; i < tr->nr_topts; i++) {
@@@ -8804,7 -8796,7 +8804,7 @@@ struct dentry *tracing_init_dentry(void
        struct trace_array *tr = &global_trace;
  
        if (security_locked_down(LOCKDOWN_TRACEFS)) {
 -              pr_warning("Tracing disabled due to lockdown\n");
 +              pr_warn("Tracing disabled due to lockdown\n");
                return ERR_PTR(-EPERM);
        }
  
@@@ -9252,7 -9244,7 +9252,7 @@@ __init static int tracer_alloc_buffers(
  
  
        if (security_locked_down(LOCKDOWN_TRACEFS)) {
 -              pr_warning("Tracing disabled due to lockdown\n");
 +              pr_warn("Tracing disabled due to lockdown\n");
                return -EPERM;
        }
  
@@@ -9420,11 -9412,6 +9420,11 @@@ __init static int tracing_set_default_c
  {
        /* sched_clock_stable() is determined in late_initcall */
        if (!trace_boot_clock && !sched_clock_stable()) {
 +              if (security_locked_down(LOCKDOWN_TRACEFS)) {
 +                      pr_warn("Can not set tracing clock due to lockdown\n");
 +                      return -EPERM;
 +              }
 +
                printk(KERN_WARNING
                       "Unstable clock detected, switching default tracing clock to \"global\"\n"
                       "If you want to keep using the local clock, then add:\n"
@@@ -24,7 -24,6 +24,7 @@@
  #include <linux/delay.h>
  
  #include <trace/events/sched.h>
 +#include <trace/syscall.h>
  
  #include <asm/setup.h>
  
@@@ -321,8 -320,7 +321,8 @@@ void trace_event_enable_cmd_record(boo
        struct trace_event_file *file;
        struct trace_array *tr;
  
 -      mutex_lock(&event_mutex);
 +      lockdep_assert_held(&event_mutex);
 +
        do_for_each_event_file(tr, file) {
  
                if (!(file->flags & EVENT_FILE_FL_ENABLED))
                        clear_bit(EVENT_FILE_FL_RECORDED_CMD_BIT, &file->flags);
                }
        } while_for_each_event_file();
 -      mutex_unlock(&event_mutex);
  }
  
  void trace_event_enable_tgid_record(bool enable)
        struct trace_event_file *file;
        struct trace_array *tr;
  
 -      mutex_lock(&event_mutex);
 +      lockdep_assert_held(&event_mutex);
 +
        do_for_each_event_file(tr, file) {
                if (!(file->flags & EVENT_FILE_FL_ENABLED))
                        continue;
                                  &file->flags);
                }
        } while_for_each_event_file();
 -      mutex_unlock(&event_mutex);
  }
  
  static int __ftrace_event_enable_disable(struct trace_event_file *file,
@@@ -698,7 -697,7 +698,7 @@@ static void remove_subsystem(struct tra
                return;
  
        if (!--dir->nr_events) {
-               tracefs_remove_recursive(dir->entry);
+               tracefs_remove(dir->entry);
                list_del(&dir->list);
                __put_system_dir(dir);
        }
@@@ -717,7 -716,7 +717,7 @@@ static void remove_event_file_dir(struc
                }
                spin_unlock(&dir->d_lock);
  
-               tracefs_remove_recursive(dir);
+               tracefs_remove(dir);
        }
  
        list_del(&file->list);
@@@ -2018,24 -2017,7 +2018,24 @@@ event_create_dir(struct dentry *parent
         */
        head = trace_get_fields(call);
        if (list_empty(head)) {
 -              ret = call->class->define_fields(call);
 +              struct trace_event_fields *field = call->class->fields_array;
 +              unsigned int offset = sizeof(struct trace_entry);
 +
 +              for (; field->type; field++) {
 +                      if (field->type == TRACE_FUNCTION_TYPE) {
 +                              ret = field->define_fields(call);
 +                              break;
 +                      }
 +
 +                      offset = ALIGN(offset, field->align);
 +                      ret = trace_define_field(call, field->type, field->name,
 +                                               offset, field->size,
 +                                               field->is_signed, field->filter_type);
 +                      if (ret)
 +                              break;
 +
 +                      offset += field->size;
 +              }
                if (ret < 0) {
                        pr_warn("Could not initialize trace point events/%s\n",
                                name);
@@@ -3082,7 -3064,7 +3082,7 @@@ int event_trace_del_tracer(struct trace
  
        down_write(&trace_event_sem);
        __trace_remove_event_dirs(tr);
-       tracefs_remove_recursive(tr->event_dir);
+       tracefs_remove(tr->event_dir);
        up_write(&trace_event_sem);
  
        tr->event_dir = NULL;