Merge tag 'ib-mfd-many-v4.14' of git://git.kernel.org/pub/scm/linux/kernel/git/lee...
[linux-2.6-microblaze.git] / lib / kobject_uevent.c
index 4682e85..e590523 100644 (file)
@@ -23,6 +23,8 @@
 #include <linux/socket.h>
 #include <linux/skbuff.h>
 #include <linux/netlink.h>
+#include <linux/uuid.h>
+#include <linux/ctype.h>
 #include <net/sock.h>
 #include <net/net_namespace.h>
 
@@ -54,19 +56,13 @@ static const char *kobject_actions[] = {
        [KOBJ_UNBIND] =         "unbind",
 };
 
-/**
- * kobject_action_type - translate action string to numeric type
- *
- * @buf: buffer containing the action string, newline is ignored
- * @count: length of buffer
- * @type: pointer to the location to store the action type
- *
- * Returns 0 if the action string was recognized.
- */
-int kobject_action_type(const char *buf, size_t count,
-                       enum kobject_action *type)
+static int kobject_action_type(const char *buf, size_t count,
+                              enum kobject_action *type,
+                              const char **args)
 {
        enum kobject_action action;
+       size_t count_first;
+       const char *args_start;
        int ret = -EINVAL;
 
        if (count && (buf[count-1] == '\n' || buf[count-1] == '\0'))
@@ -75,11 +71,20 @@ int kobject_action_type(const char *buf, size_t count,
        if (!count)
                goto out;
 
+       args_start = strnchr(buf, count, ' ');
+       if (args_start) {
+               count_first = args_start - buf;
+               args_start = args_start + 1;
+       } else
+               count_first = count;
+
        for (action = 0; action < ARRAY_SIZE(kobject_actions); action++) {
-               if (strncmp(kobject_actions[action], buf, count) != 0)
+               if (strncmp(kobject_actions[action], buf, count_first) != 0)
                        continue;
-               if (kobject_actions[action][count] != '\0')
+               if (kobject_actions[action][count_first] != '\0')
                        continue;
+               if (args)
+                       *args = args_start;
                *type = action;
                ret = 0;
                break;
@@ -88,6 +93,142 @@ out:
        return ret;
 }
 
+static const char *action_arg_word_end(const char *buf, const char *buf_end,
+                                      char delim)
+{
+       const char *next = buf;
+
+       while (next <= buf_end && *next != delim)
+               if (!isalnum(*next++))
+                       return NULL;
+
+       if (next == buf)
+               return NULL;
+
+       return next;
+}
+
+static int kobject_action_args(const char *buf, size_t count,
+                              struct kobj_uevent_env **ret_env)
+{
+       struct kobj_uevent_env *env = NULL;
+       const char *next, *buf_end, *key;
+       int key_len;
+       int r = -EINVAL;
+
+       if (count && (buf[count - 1] == '\n' || buf[count - 1] == '\0'))
+               count--;
+
+       if (!count)
+               return -EINVAL;
+
+       env = kzalloc(sizeof(*env), GFP_KERNEL);
+       if (!env)
+               return -ENOMEM;
+
+       /* first arg is UUID */
+       if (count < UUID_STRING_LEN || !uuid_is_valid(buf) ||
+           add_uevent_var(env, "SYNTH_UUID=%.*s", UUID_STRING_LEN, buf))
+               goto out;
+
+       /*
+        * the rest are custom environment variables in KEY=VALUE
+        * format with ' ' delimiter between each KEY=VALUE pair
+        */
+       next = buf + UUID_STRING_LEN;
+       buf_end = buf + count - 1;
+
+       while (next <= buf_end) {
+               if (*next != ' ')
+                       goto out;
+
+               /* skip the ' ', key must follow */
+               key = ++next;
+               if (key > buf_end)
+                       goto out;
+
+               buf = next;
+               next = action_arg_word_end(buf, buf_end, '=');
+               if (!next || next > buf_end || *next != '=')
+                       goto out;
+               key_len = next - buf;
+
+               /* skip the '=', value must follow */
+               if (++next > buf_end)
+                       goto out;
+
+               buf = next;
+               next = action_arg_word_end(buf, buf_end, ' ');
+               if (!next)
+                       goto out;
+
+               if (add_uevent_var(env, "SYNTH_ARG_%.*s=%.*s",
+                                  key_len, key, (int) (next - buf), buf))
+                       goto out;
+       }
+
+       r = 0;
+out:
+       if (r)
+               kfree(env);
+       else
+               *ret_env = env;
+       return r;
+}
+
+/**
+ * kobject_synth_uevent - send synthetic uevent with arguments
+ *
+ * @kobj: struct kobject for which synthetic uevent is to be generated
+ * @buf: buffer containing action type and action args, newline is ignored
+ * @count: length of buffer
+ *
+ * Returns 0 if kobject_synthetic_uevent() is completed with success or the
+ * corresponding error when it fails.
+ */
+int kobject_synth_uevent(struct kobject *kobj, const char *buf, size_t count)
+{
+       char *no_uuid_envp[] = { "SYNTH_UUID=0", NULL };
+       enum kobject_action action;
+       const char *action_args;
+       struct kobj_uevent_env *env;
+       const char *msg = NULL, *devpath;
+       int r;
+
+       r = kobject_action_type(buf, count, &action, &action_args);
+       if (r) {
+               msg = "unknown uevent action string\n";
+               goto out;
+       }
+
+       if (!action_args) {
+               r = kobject_uevent_env(kobj, action, no_uuid_envp);
+               goto out;
+       }
+
+       r = kobject_action_args(action_args,
+                               count - (action_args - buf), &env);
+       if (r == -EINVAL) {
+               msg = "incorrect uevent action arguments\n";
+               goto out;
+       }
+
+       if (r)
+               goto out;
+
+       r = kobject_uevent_env(kobj, action, env->envp);
+       kfree(env);
+out:
+       if (r) {
+               devpath = kobject_get_path(kobj, GFP_KERNEL);
+               printk(KERN_WARNING "synth uevent: %s: %s",
+                      devpath ?: "unknown device",
+                      msg ?: "failed to send uevent");
+               kfree(devpath);
+       }
+       return r;
+}
+
 #ifdef CONFIG_NET
 static int kobj_bcast_filter(struct sock *dsk, struct sk_buff *skb, void *data)
 {