]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - fs/configfs/symlink.c
Merge tag 'libnvdimm-for-5.4' of git://git.kernel.org/pub/scm/linux/kernel/git/nvdimm...
[linux.git] / fs / configfs / symlink.c
index 91eac6c55e07dd5aa7b95ddd0c8c3b4d36e729a1..dc5dbf6a81d77bde5bcaad94cb2f4e37a6296ede 100644 (file)
@@ -55,41 +55,63 @@ static void fill_item_path(struct config_item * item, char * buffer, int length)
        }
 }
 
+static int configfs_get_target_path(struct config_item *item,
+               struct config_item *target, char *path)
+{
+       int depth, size;
+       char *s;
+
+       depth = item_depth(item);
+       size = item_path_length(target) + depth * 3 - 1;
+       if (size > PATH_MAX)
+               return -ENAMETOOLONG;
+
+       pr_debug("%s: depth = %d, size = %d\n", __func__, depth, size);
+
+       for (s = path; depth--; s += 3)
+               strcpy(s,"../");
+
+       fill_item_path(target, path, size);
+       pr_debug("%s: path = '%s'\n", __func__, path);
+       return 0;
+}
+
 static int create_link(struct config_item *parent_item,
                       struct config_item *item,
                       struct dentry *dentry)
 {
        struct configfs_dirent *target_sd = item->ci_dentry->d_fsdata;
-       struct configfs_symlink *sl;
+       char *body;
        int ret;
 
-       ret = -ENOENT;
        if (!configfs_dirent_is_ready(target_sd))
-               goto out;
-       ret = -ENOMEM;
-       sl = kmalloc(sizeof(struct configfs_symlink), GFP_KERNEL);
-       if (sl) {
+               return -ENOENT;
+
+       body = kzalloc(PAGE_SIZE, GFP_KERNEL);
+       if (!body)
+               return -ENOMEM;
+
+       configfs_get(target_sd);
+       spin_lock(&configfs_dirent_lock);
+       if (target_sd->s_type & CONFIGFS_USET_DROPPING) {
+               spin_unlock(&configfs_dirent_lock);
+               configfs_put(target_sd);
+               kfree(body);
+               return -ENOENT;
+       }
+       target_sd->s_links++;
+       spin_unlock(&configfs_dirent_lock);
+       ret = configfs_get_target_path(item, item, body);
+       if (!ret)
+               ret = configfs_create_link(target_sd, parent_item->ci_dentry,
+                                          dentry, body);
+       if (ret) {
                spin_lock(&configfs_dirent_lock);
-               if (target_sd->s_type & CONFIGFS_USET_DROPPING) {
-                       spin_unlock(&configfs_dirent_lock);
-                       kfree(sl);
-                       return -ENOENT;
-               }
-               sl->sl_target = config_item_get(item);
-               list_add(&sl->sl_list, &target_sd->s_links);
+               target_sd->s_links--;
                spin_unlock(&configfs_dirent_lock);
-               ret = configfs_create_link(sl, parent_item->ci_dentry,
-                                          dentry);
-               if (ret) {
-                       spin_lock(&configfs_dirent_lock);
-                       list_del_init(&sl->sl_list);
-                       spin_unlock(&configfs_dirent_lock);
-                       config_item_put(item);
-                       kfree(sl);
-               }
+               configfs_put(target_sd);
+               kfree(body);
        }
-
-out:
        return ret;
 }
 
@@ -131,9 +153,8 @@ int configfs_symlink(struct inode *dir, struct dentry *dentry, const char *symna
         * Fake invisibility if dir belongs to a group/default groups hierarchy
         * being attached
         */
-       ret = -ENOENT;
        if (!configfs_dirent_is_ready(sd))
-               goto out;
+               return -ENOENT;
 
        parent_item = configfs_get_config_item(dentry->d_parent);
        type = parent_item->ci_type;
@@ -143,11 +164,42 @@ int configfs_symlink(struct inode *dir, struct dentry *dentry, const char *symna
            !type->ct_item_ops->allow_link)
                goto out_put;
 
+       /*
+        * This is really sick.  What they wanted was a hybrid of
+        * link(2) and symlink(2) - they wanted the target resolved
+        * at syscall time (as link(2) would've done), be a directory
+        * (which link(2) would've refused to do) *AND* be a deep
+        * fucking magic, making the target busy from rmdir POV.
+        * symlink(2) is nothing of that sort, and the locking it
+        * gets matches the normal symlink(2) semantics.  Without
+        * attempts to resolve the target (which might very well
+        * not even exist yet) done prior to locking the parent
+        * directory.  This perversion, OTOH, needs to resolve
+        * the target, which would lead to obvious deadlocks if
+        * attempted with any directories locked.
+        *
+        * Unfortunately, that garbage is userland ABI and we should've
+        * said "no" back in 2005.  Too late now, so we get to
+        * play very ugly games with locking.
+        *
+        * Try *ANYTHING* of that sort in new code, and you will
+        * really regret it.  Just ask yourself - what could a BOFH
+        * do to me and do I want to find it out first-hand?
+        *
+        *  AV, a thoroughly annoyed bastard.
+        */
+       inode_unlock(dir);
        ret = get_target(symname, &path, &target_item, dentry->d_sb);
+       inode_lock(dir);
        if (ret)
                goto out_put;
 
-       ret = type->ct_item_ops->allow_link(parent_item, target_item);
+       if (dentry->d_inode || d_unhashed(dentry))
+               ret = -EEXIST;
+       else
+               ret = inode_permission(dir, MAY_WRITE | MAY_EXEC);
+       if (!ret)
+               ret = type->ct_item_ops->allow_link(parent_item, target_item);
        if (!ret) {
                mutex_lock(&configfs_symlink_mutex);
                ret = create_link(parent_item, target_item, dentry);
@@ -162,15 +214,12 @@ int configfs_symlink(struct inode *dir, struct dentry *dentry, const char *symna
 
 out_put:
        config_item_put(parent_item);
-
-out:
        return ret;
 }
 
 int configfs_unlink(struct inode *dir, struct dentry *dentry)
 {
-       struct configfs_dirent *sd = dentry->d_fsdata;
-       struct configfs_symlink *sl;
+       struct configfs_dirent *sd = dentry->d_fsdata, *target_sd;
        struct config_item *parent_item;
        const struct config_item_type *type;
        int ret;
@@ -179,7 +228,7 @@ int configfs_unlink(struct inode *dir, struct dentry *dentry)
        if (!(sd->s_type & CONFIGFS_ITEM_LINK))
                goto out;
 
-       sl = sd->s_element;
+       target_sd = sd->s_element;
 
        parent_item = configfs_get_config_item(dentry->d_parent);
        type = parent_item->ci_type;
@@ -193,21 +242,18 @@ int configfs_unlink(struct inode *dir, struct dentry *dentry)
 
        /*
         * drop_link() must be called before
-        * list_del_init(&sl->sl_list), so that the order of
+        * decrementing target's ->s_links, so that the order of
         * drop_link(this, target) and drop_item(target) is preserved.
         */
        if (type && type->ct_item_ops &&
            type->ct_item_ops->drop_link)
                type->ct_item_ops->drop_link(parent_item,
-                                              sl->sl_target);
+                                              target_sd->s_element);
 
        spin_lock(&configfs_dirent_lock);
-       list_del_init(&sl->sl_list);
+       target_sd->s_links--;
        spin_unlock(&configfs_dirent_lock);
-
-       /* Put reference from create_link() */
-       config_item_put(sl->sl_target);
-       kfree(sl);
+       configfs_put(target_sd);
 
        config_item_put(parent_item);
 
@@ -217,79 +263,8 @@ int configfs_unlink(struct inode *dir, struct dentry *dentry)
        return ret;
 }
 
-static int configfs_get_target_path(struct config_item * item, struct config_item * target,
-                                  char *path)
-{
-       char * s;
-       int depth, size;
-
-       depth = item_depth(item);
-       size = item_path_length(target) + depth * 3 - 1;
-       if (size > PATH_MAX)
-               return -ENAMETOOLONG;
-
-       pr_debug("%s: depth = %d, size = %d\n", __func__, depth, size);
-
-       for (s = path; depth--; s += 3)
-               strcpy(s,"../");
-
-       fill_item_path(target, path, size);
-       pr_debug("%s: path = '%s'\n", __func__, path);
-
-       return 0;
-}
-
-static int configfs_getlink(struct dentry *dentry, char * path)
-{
-       struct config_item *item, *target_item;
-       int error = 0;
-
-       item = configfs_get_config_item(dentry->d_parent);
-       if (!item)
-               return -EINVAL;
-
-       target_item = configfs_get_config_item(dentry);
-       if (!target_item) {
-               config_item_put(item);
-               return -EINVAL;
-       }
-
-       down_read(&configfs_rename_sem);
-       error = configfs_get_target_path(item, target_item, path);
-       up_read(&configfs_rename_sem);
-
-       config_item_put(item);
-       config_item_put(target_item);
-       return error;
-
-}
-
-static const char *configfs_get_link(struct dentry *dentry,
-                                    struct inode *inode,
-                                    struct delayed_call *done)
-{
-       char *body;
-       int error;
-
-       if (!dentry)
-               return ERR_PTR(-ECHILD);
-
-       body = kzalloc(PAGE_SIZE, GFP_KERNEL);
-       if (!body)
-               return ERR_PTR(-ENOMEM);
-
-       error = configfs_getlink(dentry, body);
-       if (!error) {
-               set_delayed_call(done, kfree_link, body);
-               return body;
-       }
-
-       kfree(body);
-       return ERR_PTR(error);
-}
-
 const struct inode_operations configfs_symlink_inode_operations = {
-       .get_link = configfs_get_link,
+       .get_link = simple_get_link,
        .setattr = configfs_setattr,
 };