driver core: Introduce device_move(): move a device to a new parent.
authorCornelia Huck <cornelia.huck@de.ibm.com>
Mon, 20 Nov 2006 16:07:51 +0000 (17:07 +0100)
committerGreg Kroah-Hartman <gregkh@suse.de>
Fri, 1 Dec 2006 22:52:01 +0000 (14:52 -0800)
Provide a function device_move() to move a device to a new parent device. Add
auxilliary functions kobject_move() and sysfs_move_dir().
kobject_move() generates a new uevent of type KOBJ_MOVE, containing the
previous path (DEVPATH_OLD) in addition to the usual values. For this, a new
interface kobject_uevent_env() is created that allows to add further
environmental data to the uevent at the kobject layer.

Signed-off-by: Cornelia Huck <cornelia.huck@de.ibm.com>
Acked-by: Kay Sievers <kay.sievers@vrfy.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/base/core.c
fs/sysfs/dir.c
include/linux/device.h
include/linux/kobject.h
include/linux/sysfs.h
lib/kobject.c
lib/kobject_uevent.c

index 75b45a10935a009617fcc830ad9d0111f58f394e..e4eaf46c4d9337cb1c0dbbd9c6d5cd8b9afa66fb 100644 (file)
@@ -955,3 +955,95 @@ int device_rename(struct device *dev, char *new_name)
 
        return error;
 }
+
+
+static int device_move_class_links(struct device *dev,
+                                  struct device *old_parent,
+                                  struct device *new_parent)
+{
+#ifdef CONFIG_SYSFS_DEPRECATED
+       int error;
+       char *class_name;
+
+       class_name = make_class_name(dev->class->name, &dev->kobj);
+       if (!class_name) {
+               error = PTR_ERR(class_name);
+               class_name = NULL;
+               goto out;
+       }
+       if (old_parent) {
+               sysfs_remove_link(&dev->kobj, "device");
+               sysfs_remove_link(&old_parent->kobj, class_name);
+       }
+       error = sysfs_create_link(&dev->kobj, &new_parent->kobj, "device");
+       if (error)
+               goto out;
+       error = sysfs_create_link(&new_parent->kobj, &dev->kobj, class_name);
+       if (error)
+               sysfs_remove_link(&dev->kobj, "device");
+out:
+       kfree(class_name);
+       return error;
+#else
+       return 0;
+#endif
+}
+
+/**
+ * device_move - moves a device to a new parent
+ * @dev: the pointer to the struct device to be moved
+ * @new_parent: the new parent of the device
+ */
+int device_move(struct device *dev, struct device *new_parent)
+{
+       int error;
+       struct device *old_parent;
+
+       dev = get_device(dev);
+       if (!dev)
+               return -EINVAL;
+
+       if (!device_is_registered(dev)) {
+               error = -EINVAL;
+               goto out;
+       }
+       new_parent = get_device(new_parent);
+       if (!new_parent) {
+               error = -EINVAL;
+               goto out;
+       }
+       pr_debug("DEVICE: moving '%s' to '%s'\n", dev->bus_id,
+               new_parent->bus_id);
+       error = kobject_move(&dev->kobj, &new_parent->kobj);
+       if (error) {
+               put_device(new_parent);
+               goto out;
+       }
+       old_parent = dev->parent;
+       dev->parent = new_parent;
+       if (old_parent)
+               klist_del(&dev->knode_parent);
+       klist_add_tail(&dev->knode_parent, &new_parent->klist_children);
+       if (!dev->class)
+               goto out_put;
+       error = device_move_class_links(dev, old_parent, new_parent);
+       if (error) {
+               /* We ignore errors on cleanup since we're hosed anyway... */
+               device_move_class_links(dev, new_parent, old_parent);
+               if (!kobject_move(&dev->kobj, &old_parent->kobj)) {
+                       klist_del(&dev->knode_parent);
+                       if (old_parent)
+                               klist_add_tail(&dev->knode_parent,
+                                              &old_parent->klist_children);
+               }
+               put_device(new_parent);
+               goto out;
+       }
+out_put:
+       put_device(old_parent);
+out:
+       put_device(dev);
+       return error;
+}
+
+EXPORT_SYMBOL_GPL(device_move);
index 3aa3434621ca89296b3c0b0598a663807977f1eb..a5782e8c7f07143262085dda013f62cbbd9c8ecb 100644 (file)
@@ -372,6 +372,51 @@ int sysfs_rename_dir(struct kobject * kobj, const char *new_name)
        return error;
 }
 
+int sysfs_move_dir(struct kobject *kobj, struct kobject *new_parent)
+{
+       struct dentry *old_parent_dentry, *new_parent_dentry, *new_dentry;
+       struct sysfs_dirent *new_parent_sd, *sd;
+       int error;
+
+       if (!new_parent)
+               return -EINVAL;
+
+       old_parent_dentry = kobj->parent ?
+               kobj->parent->dentry : sysfs_mount->mnt_sb->s_root;
+       new_parent_dentry = new_parent->dentry;
+
+again:
+       mutex_lock(&old_parent_dentry->d_inode->i_mutex);
+       if (!mutex_trylock(&new_parent_dentry->d_inode->i_mutex)) {
+               mutex_unlock(&old_parent_dentry->d_inode->i_mutex);
+               goto again;
+       }
+
+       new_parent_sd = new_parent_dentry->d_fsdata;
+       sd = kobj->dentry->d_fsdata;
+
+       new_dentry = lookup_one_len(kobj->name, new_parent_dentry,
+                                   strlen(kobj->name));
+       if (IS_ERR(new_dentry)) {
+               error = PTR_ERR(new_dentry);
+               goto out;
+       } else
+               error = 0;
+       d_add(new_dentry, NULL);
+       d_move(kobj->dentry, new_dentry);
+       dput(new_dentry);
+
+       /* Remove from old parent's list and insert into new parent's list. */
+       list_del_init(&sd->s_sibling);
+       list_add(&sd->s_sibling, &new_parent_sd->s_children);
+
+out:
+       mutex_unlock(&new_parent_dentry->d_inode->i_mutex);
+       mutex_unlock(&old_parent_dentry->d_inode->i_mutex);
+
+       return error;
+}
+
 static int sysfs_dir_open(struct inode *inode, struct file *file)
 {
        struct dentry * dentry = file->f_dentry;
index 0a0370c74181a1d0cabcacd680725020497c7c40..583a341e016cea229ad8b04d4213d59974e3cbd3 100644 (file)
@@ -424,6 +424,7 @@ extern int device_for_each_child(struct device *, void *,
 extern struct device *device_find_child(struct device *, void *data,
                                        int (*match)(struct device *, void *));
 extern int device_rename(struct device *dev, char *new_name);
+extern int device_move(struct device *dev, struct device *new_parent);
 
 /*
  * Manual binding of a device to driver. See drivers/base/bus.c
index bcd9cd173c2c80ffce52af4ea925c24e7f22dc0f..d1c8d28fa92ebc844906c88d9081c78af719dcdb 100644 (file)
@@ -47,6 +47,7 @@ enum kobject_action {
        KOBJ_UMOUNT     = (__force kobject_action_t) 0x05,      /* umount event for block devices (broken) */
        KOBJ_OFFLINE    = (__force kobject_action_t) 0x06,      /* device offline */
        KOBJ_ONLINE     = (__force kobject_action_t) 0x07,      /* device online */
+       KOBJ_MOVE       = (__force kobject_action_t) 0x08,      /* device move */
 };
 
 struct kobject {
@@ -76,6 +77,7 @@ extern int __must_check kobject_add(struct kobject *);
 extern void kobject_del(struct kobject *);
 
 extern int __must_check kobject_rename(struct kobject *, const char *new_name);
+extern int __must_check kobject_move(struct kobject *, struct kobject *);
 
 extern int __must_check kobject_register(struct kobject *);
 extern void kobject_unregister(struct kobject *);
@@ -264,6 +266,8 @@ extern int __must_check subsys_create_file(struct subsystem * ,
 
 #if defined(CONFIG_HOTPLUG)
 void kobject_uevent(struct kobject *kobj, enum kobject_action action);
+void kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
+                       char *envp[]);
 
 int add_uevent_var(char **envp, int num_envp, int *cur_index,
                        char *buffer, int buffer_size, int *cur_len,
@@ -271,6 +275,10 @@ int add_uevent_var(char **envp, int num_envp, int *cur_index,
        __attribute__((format (printf, 7, 8)));
 #else
 static inline void kobject_uevent(struct kobject *kobj, enum kobject_action action) { }
+static inline void kobject_uevent_env(struct kobject *kobj,
+                                     enum kobject_action action,
+                                     char *envp[])
+{ }
 
 static inline int add_uevent_var(char **envp, int num_envp, int *cur_index,
                                      char *buffer, int buffer_size, int *cur_len, 
index 6d5c43d31dec27e7b0a0131c5cc1c03a5b030612..2129d1b6c8749fc06274784d3ce7b8925533b962 100644 (file)
@@ -96,6 +96,9 @@ sysfs_remove_dir(struct kobject *);
 extern int __must_check
 sysfs_rename_dir(struct kobject *, const char *new_name);
 
+extern int __must_check
+sysfs_move_dir(struct kobject *, struct kobject *);
+
 extern int __must_check
 sysfs_create_file(struct kobject *, const struct attribute *);
 
@@ -142,6 +145,11 @@ static inline int sysfs_rename_dir(struct kobject * k, const char *new_name)
        return 0;
 }
 
+static inline int sysfs_move_dir(struct kobject * k, struct kobject * new_parent)
+{
+       return 0;
+}
+
 static inline int sysfs_create_file(struct kobject * k, const struct attribute * a)
 {
        return 0;
index 7dd5c0e9d996adb07681e02037e746b68a2a2f50..744a4b102c7fa595b09808f8a6f949318a1f57a1 100644 (file)
@@ -310,6 +310,56 @@ int kobject_rename(struct kobject * kobj, const char *new_name)
        return error;
 }
 
+/**
+ *     kobject_move - move object to another parent
+ *     @kobj:  object in question.
+ *     @new_parent: object's new parent
+ */
+
+int kobject_move(struct kobject *kobj, struct kobject *new_parent)
+{
+       int error;
+       struct kobject *old_parent;
+       const char *devpath = NULL;
+       char *devpath_string = NULL;
+       char *envp[2];
+
+       kobj = kobject_get(kobj);
+       if (!kobj)
+               return -EINVAL;
+       new_parent = kobject_get(new_parent);
+       if (!new_parent) {
+               error = -EINVAL;
+               goto out;
+       }
+       /* old object path */
+       devpath = kobject_get_path(kobj, GFP_KERNEL);
+       if (!devpath) {
+               error = -ENOMEM;
+               goto out;
+       }
+       devpath_string = kmalloc(strlen(devpath) + 15, GFP_KERNEL);
+       if (!devpath_string) {
+               error = -ENOMEM;
+               goto out;
+       }
+       sprintf(devpath_string, "DEVPATH_OLD=%s", devpath);
+       envp[0] = devpath_string;
+       envp[1] = NULL;
+       error = sysfs_move_dir(kobj, new_parent);
+       if (error)
+               goto out;
+       old_parent = kobj->parent;
+       kobj->parent = new_parent;
+       kobject_put(old_parent);
+       kobject_uevent_env(kobj, KOBJ_MOVE, envp);
+out:
+       kobject_put(kobj);
+       kfree(devpath_string);
+       kfree(devpath);
+       return error;
+}
+
 /**
  *     kobject_del - unlink kobject from hierarchy.
  *     @kobj:  object.
index 7f20e7b857cb559b7624d89ca4c52bbf86bc1b65..a1922765ff315dfe1a90879f567c82e08521eaa2 100644 (file)
@@ -50,18 +50,22 @@ static char *action_to_string(enum kobject_action action)
                return "offline";
        case KOBJ_ONLINE:
                return "online";
+       case KOBJ_MOVE:
+               return "move";
        default:
                return NULL;
        }
 }
 
 /**
- * kobject_uevent - notify userspace by ending an uevent
+ * kobject_uevent_env - send an uevent with environmental data
  *
- * @action: action that is happening (usually KOBJ_ADD and KOBJ_REMOVE)
+ * @action: action that is happening (usually KOBJ_MOVE)
  * @kobj: struct kobject that the action is happening to
+ * @envp_ext: pointer to environmental data
  */
-void kobject_uevent(struct kobject *kobj, enum kobject_action action)
+void kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
+                       char *envp_ext[])
 {
        char **envp;
        char *buffer;
@@ -76,6 +80,7 @@ void kobject_uevent(struct kobject *kobj, enum kobject_action action)
        char *seq_buff;
        int i = 0;
        int retval;
+       int j;
 
        pr_debug("%s\n", __FUNCTION__);
 
@@ -134,7 +139,8 @@ void kobject_uevent(struct kobject *kobj, enum kobject_action action)
        scratch += sprintf (scratch, "DEVPATH=%s", devpath) + 1;
        envp [i++] = scratch;
        scratch += sprintf(scratch, "SUBSYSTEM=%s", subsystem) + 1;
-
+       for (j = 0; envp_ext && envp_ext[j]; j++)
+               envp[i++] = envp_ext[j];
        /* just reserve the space, overwrite it after kset call has returned */
        envp[i++] = seq_buff = scratch;
        scratch += strlen("SEQNUM=18446744073709551616") + 1;
@@ -200,6 +206,20 @@ exit:
        kfree(envp);
        return;
 }
+
+EXPORT_SYMBOL_GPL(kobject_uevent_env);
+
+/**
+ * kobject_uevent - notify userspace by ending an uevent
+ *
+ * @action: action that is happening (usually KOBJ_ADD and KOBJ_REMOVE)
+ * @kobj: struct kobject that the action is happening to
+ */
+void kobject_uevent(struct kobject *kobj, enum kobject_action action)
+{
+       kobject_uevent_env(kobj, action, NULL);
+}
+
 EXPORT_SYMBOL_GPL(kobject_uevent);
 
 /**