ChangeSet 1.1018.1.11, 2003/04/04 17:06:13-08:00, greg@kroah.com

kobject: cause /sbin/hotplug to be called when kobjects are added and removed

This only happens if a kobject belongs to a subsystem that has specified
a set of hotplug operations.

Based on work done by Kevin Fleming <kpfleming@cox.net>


 include/linux/kobject.h |   22 +++++-
 lib/kobject.c           |  170 +++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 183 insertions(+), 9 deletions(-)


diff -Nru a/include/linux/kobject.h b/include/linux/kobject.h
--- a/include/linux/kobject.h	Mon Apr  7 15:13:43 2003
+++ b/include/linux/kobject.h	Mon Apr  7 15:13:43 2003
@@ -57,12 +57,24 @@
  *	of object; multiple ksets can belong to one subsystem. All 
  *	ksets of a subsystem share the subsystem's lock.
  *
+ *      Each kset can support hotplugging; if it does, it will be given
+ *      the opportunity to filter out specific kobjects from being
+ *      reported, as well as to add its own "data" elements to the
+ *      environment being passed to the hotplug helper.
  */
+struct kset_hotplug_ops {
+	int (*filter)(struct kset *kset, struct kobject *kobj);
+	char *(*name)(struct kset *kset, struct kobject *kobj);
+	int (*hotplug)(struct kset *kset, struct kobject *kobj, char **envp,
+			int num_envp, char *buffer, int buffer_size);
+};
+
 struct kset {
 	struct subsystem	* subsys;
 	struct kobj_type	* ktype;
 	struct list_head	list;
 	struct kobject		kobj;
+	struct kset_hotplug_ops	* hotplug_ops;
 };
 
 
@@ -86,6 +98,13 @@
 	kobject_put(&k->kobj);
 }
 
+static inline struct kobj_type * get_ktype(struct kobject * k)
+{
+	if (k->kset && k->kset->ktype)
+		return k->kset->ktype;
+	else 
+		return k->ktype;
+}
 
 extern struct kobject * kset_find_obj(struct kset *, const char *);
 
@@ -95,11 +114,12 @@
 	struct rw_semaphore	rwsem;
 };
 
-#define decl_subsys(_name,_type) \
+#define decl_subsys(_name,_type,_hotplug_ops) \
 struct subsystem _name##_subsys = { \
 	.kset = { \
 		.kobj = { .name = __stringify(_name) }, \
 		.ktype = _type, \
+		.hotplug_ops =_hotplug_ops, \
 	} \
 }
 
diff -Nru a/lib/kobject.c b/lib/kobject.c
--- a/lib/kobject.c	Mon Apr  7 15:13:43 2003
+++ b/lib/kobject.c	Mon Apr  7 15:13:43 2003
@@ -11,14 +11,6 @@
 
 static spinlock_t kobj_lock = SPIN_LOCK_UNLOCKED;
 
-static inline struct kobj_type * get_ktype(struct kobject * k)
-{
-	if (k->kset && k->kset->ktype)
-		return k->kset->ktype;
-	else 
-		return k->ktype;
-}
-
 /**
  *	populate_dir - populate directory with attributes.
  *	@kobj:	object we're working on.
@@ -67,6 +59,140 @@
 }
 
 
+#ifdef CONFIG_HOTPLUG
+static int get_kobj_path_length(struct kset *kset, struct kobject *kobj)
+{
+	int length = 1;
+	struct kobject * parent = kobj;
+
+	/* walk up the ancestors until we hit the one pointing to the 
+	 * root.
+	 * Add 1 to strlen for leading '/' of each level.
+	 */
+	do {
+		length += strlen (parent->name) + 1;
+		parent = parent->parent;
+	} while (parent);
+	return length;
+}
+
+static void fill_kobj_path(struct kset *kset, struct kobject *kobj, char *path, int length)
+{
+	struct kobject * parent;
+
+	--length;
+	for (parent = kobj; parent; parent = parent->parent) {
+		int cur = strlen (parent->name);
+		/* back up enough to print this name with '/' */
+		length -= cur;
+		strncpy (path + length, parent->name, cur);
+		*(path + --length) = '/';
+	}
+
+	pr_debug("%s: path = '%s'\n",__FUNCTION__,path);
+}
+
+#define BUFFER_SIZE	1024	/* should be enough memory for the env */
+#define NUM_ENVP	32	/* number of env pointers */
+static void kset_hotplug(const char *action, struct kset *kset,
+			 struct kobject *kobj)
+{
+	char *argv [3];
+	char **envp;
+	char *buffer;
+	char *scratch;
+	int i = 0;
+	int retval;
+	int kobj_path_length;
+	char *kobj_path;
+	char *name = NULL;
+
+	/* If the kset has a filter operation, call it. If it returns
+	   failure, no hotplug event is required. */
+	if (kset->hotplug_ops->filter) {
+		if (!kset->hotplug_ops->filter(kset, kobj))
+			return;
+	}
+
+	pr_debug ("%s\n", __FUNCTION__);
+
+	if (!hotplug_path[0])
+		return;
+
+	envp = (char **)kmalloc(NUM_ENVP * sizeof (char *), GFP_KERNEL);
+	if (!envp)
+		return;
+	memset (envp, 0x00, NUM_ENVP * sizeof (char *));
+
+	buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);
+	if (!buffer) {
+		kfree(envp);
+		return;
+	}
+
+	if (kset->hotplug_ops->name)
+		name = kset->hotplug_ops->name(kset, kobj);
+	if (name == NULL)
+		name = kset->kobj.name;
+
+	argv [0] = hotplug_path;
+	argv [1] = name;
+	argv [2] = 0;
+
+	/* minimal command environment */
+	envp [i++] = "HOME=/";
+	envp [i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
+
+	scratch = buffer;
+
+	envp [i++] = scratch;
+	scratch += sprintf(scratch, "ACTION=%s", action) + 1;
+
+	kobj_path_length = get_kobj_path_length (kset, kobj);
+	kobj_path = kmalloc (kobj_path_length, GFP_KERNEL);
+	if (!kobj_path) {
+		kfree (buffer);
+		kfree (envp);
+		return;
+	}
+	memset (kobj_path, 0x00, kobj_path_length);
+	fill_kobj_path (kset, kobj, kobj_path, kobj_path_length);
+
+	envp [i++] = scratch;
+	scratch += sprintf (scratch, "DEVPATH=%s", kobj_path) + 1;
+
+	if (kset->hotplug_ops->hotplug) {
+		/* have the kset specific function add its stuff */
+		retval = kset->hotplug_ops->hotplug (kset, kobj,
+				  &envp[i], NUM_ENVP - i, scratch,
+				  BUFFER_SIZE - (scratch - buffer));
+		if (retval) {
+			pr_debug ("%s - hotplug() returned %d\n",
+				  __FUNCTION__, retval);
+			goto exit;
+		}
+	}
+
+	pr_debug ("%s: %s %s %s %s %s %s\n", __FUNCTION__, argv[0], argv[1],
+		  envp[0], envp[1], envp[2], envp[3]);
+	retval = call_usermodehelper (argv[0], argv, envp, 0);
+	if (retval)
+		pr_debug ("%s - call_usermodehelper returned %d\n",
+			  __FUNCTION__, retval);
+
+exit:
+	kfree (kobj_path);
+	kfree (buffer);
+	return;
+}
+#else
+static void kset_hotplug(const char *action, struct kset *kset,
+			 struct kobject *kobj)
+{
+	return 0;
+}
+#endif	/* CONFIG_HOTPLUG */
+
 /**
  *	kobject_init - initialize object.
  *	@kobj:	object in question.
@@ -111,6 +237,7 @@
 {
 	int error = 0;
 	struct kobject * parent;
+	struct kobject * top_kobj;
 
 	if (!(kobj = kobject_get(kobj)))
 		return -ENOENT;
@@ -134,6 +261,19 @@
 	error = create_dir(kobj);
 	if (error)
 		unlink(kobj);
+	else {
+		/* If this kobj does not belong to a kset,
+		   try to find a parent that does. */
+		top_kobj = kobj;
+		if (!top_kobj->kset && top_kobj->parent) {
+			do {
+				top_kobj = top_kobj->parent;
+			} while (!top_kobj->kset && top_kobj->parent);
+		}
+	
+		if (top_kobj->kset && top_kobj->kset->hotplug_ops)
+			kset_hotplug("add", top_kobj->kset, kobj);
+	}
 	return error;
 }
 
@@ -162,6 +302,20 @@
 
 void kobject_del(struct kobject * kobj)
 {
+	struct kobject * top_kobj;
+
+	/* If this kobj does not belong to a kset,
+	   try to find a parent that does. */
+	top_kobj = kobj;
+	if (!top_kobj->kset && top_kobj->parent) {
+		do {
+			top_kobj = top_kobj->parent;
+		} while (!top_kobj->kset && top_kobj->parent);
+	}
+
+	if (top_kobj->kset && top_kobj->kset->hotplug_ops)
+		kset_hotplug("remove", top_kobj->kset, kobj);
+
 	sysfs_remove_dir(kobj);
 	unlink(kobj);
 }
