Merge tag 'writeback-proportions' of git://git.kernel.org/pub/scm/linux/kernel/git...
[firefly-linux-kernel-4.4.55.git] / kernel / kmod.c
index 21a0f8e99102a4a8efba1032bb7a13e9d083104a..6f99aead66c6b897fbe14b9fb03b2a77bd17bc47 100644 (file)
@@ -45,6 +45,13 @@ extern int max_threads;
 
 static struct workqueue_struct *khelper_wq;
 
+/*
+ * kmod_thread_locker is used for deadlock avoidance.  There is no explicit
+ * locking to protect this global - it is private to the singleton khelper
+ * thread and should only ever be modified by that thread.
+ */
+static const struct task_struct *kmod_thread_locker;
+
 #define CAP_BSET       (void *)1
 #define CAP_PI         (void *)2
 
@@ -221,6 +228,13 @@ fail:
        return 0;
 }
 
+static int call_helper(void *data)
+{
+       /* Worker thread started blocking khelper thread. */
+       kmod_thread_locker = current;
+       return ____call_usermodehelper(data);
+}
+
 static void call_usermodehelper_freeinfo(struct subprocess_info *info)
 {
        if (info->cleanup)
@@ -295,9 +309,12 @@ static void __call_usermodehelper(struct work_struct *work)
        if (wait == UMH_WAIT_PROC)
                pid = kernel_thread(wait_for_helper, sub_info,
                                    CLONE_FS | CLONE_FILES | SIGCHLD);
-       else
-               pid = kernel_thread(____call_usermodehelper, sub_info,
+       else {
+               pid = kernel_thread(call_helper, sub_info,
                                    CLONE_VFORK | SIGCHLD);
+               /* Worker thread stopped blocking khelper thread. */
+               kmod_thread_locker = NULL;
+       }
 
        switch (wait) {
        case UMH_NO_WAIT:
@@ -409,7 +426,7 @@ EXPORT_SYMBOL_GPL(usermodehelper_read_unlock);
 
 /**
  * __usermodehelper_set_disable_depth - Modify usermodehelper_disabled.
- * depth: New value to assign to usermodehelper_disabled.
+ * @depth: New value to assign to usermodehelper_disabled.
  *
  * Change the value of usermodehelper_disabled (under umhelper_sem locked for
  * writing) and wakeup tasks waiting for it to change.
@@ -478,6 +495,7 @@ static void helper_unlock(void)
  * structure.  This should be passed to call_usermodehelper_exec to
  * exec the process and free the structure.
  */
+static
 struct subprocess_info *call_usermodehelper_setup(char *path, char **argv,
                                                  char **envp, gfp_t gfp_mask)
 {
@@ -493,7 +511,6 @@ struct subprocess_info *call_usermodehelper_setup(char *path, char **argv,
   out:
        return sub_info;
 }
-EXPORT_SYMBOL(call_usermodehelper_setup);
 
 /**
  * call_usermodehelper_setfns - set a cleanup/init function
@@ -511,6 +528,7 @@ EXPORT_SYMBOL(call_usermodehelper_setup);
  * Function must be runnable in either a process context or the
  * context in which call_usermodehelper_exec is called.
  */
+static
 void call_usermodehelper_setfns(struct subprocess_info *info,
                    int (*init)(struct subprocess_info *info, struct cred *new),
                    void (*cleanup)(struct subprocess_info *info),
@@ -520,7 +538,6 @@ void call_usermodehelper_setfns(struct subprocess_info *info,
        info->init = init;
        info->data = data;
 }
-EXPORT_SYMBOL(call_usermodehelper_setfns);
 
 /**
  * call_usermodehelper_exec - start a usermode application
@@ -534,6 +551,7 @@ EXPORT_SYMBOL(call_usermodehelper_setfns);
  * asynchronously if wait is not set, and runs as a child of keventd.
  * (ie. it runs with full root capabilities).
  */
+static
 int call_usermodehelper_exec(struct subprocess_info *sub_info, int wait)
 {
        DECLARE_COMPLETION_ONSTACK(done);
@@ -547,6 +565,16 @@ int call_usermodehelper_exec(struct subprocess_info *sub_info, int wait)
                retval = -EBUSY;
                goto out;
        }
+       /*
+        * Worker thread must not wait for khelper thread at below
+        * wait_for_completion() if the thread was created with CLONE_VFORK
+        * flag, for khelper thread is already waiting for the thread at
+        * wait_for_completion() in do_fork().
+        */
+       if (wait != UMH_NO_WAIT && current == kmod_thread_locker) {
+               retval = -EBUSY;
+               goto out;
+       }
 
        sub_info->complete = &done;
        sub_info->wait = wait;
@@ -575,7 +603,31 @@ unlock:
        helper_unlock();
        return retval;
 }
-EXPORT_SYMBOL(call_usermodehelper_exec);
+
+/*
+ * call_usermodehelper_fns() will not run the caller-provided cleanup function
+ * if a memory allocation failure is experienced.  So the caller might need to
+ * check the call_usermodehelper_fns() return value: if it is -ENOMEM, perform
+ * the necessaary cleanup within the caller.
+ */
+int call_usermodehelper_fns(
+       char *path, char **argv, char **envp, int wait,
+       int (*init)(struct subprocess_info *info, struct cred *new),
+       void (*cleanup)(struct subprocess_info *), void *data)
+{
+       struct subprocess_info *info;
+       gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL;
+
+       info = call_usermodehelper_setup(path, argv, envp, gfp_mask);
+
+       if (info == NULL)
+               return -ENOMEM;
+
+       call_usermodehelper_setfns(info, init, cleanup, data);
+
+       return call_usermodehelper_exec(info, wait);
+}
+EXPORT_SYMBOL(call_usermodehelper_fns);
 
 static int proc_cap_handler(struct ctl_table *table, int write,
                         void __user *buffer, size_t *lenp, loff_t *ppos)