struct dentry *cow_break_link(const char *pathname) { int ret, mode, pathlen, redo = 0; struct nameidata old_nd, dir_nd; struct path old_path, dir_path; struct dentry *dir, *old_dentry, *new_dentry; struct file *old_file; struct file *new_file; char *to, *path, pad='\251'; loff_t size; vxdprintk(VXD_CBIT(misc, 1), "cow_break_link(" VS_Q("%s") ")", pathname); path = kmalloc(PATH_MAX, GFP_KERNEL); ret = -ENOMEM; if (!path) goto out; /* old_nd will have refs to dentry and mnt */ ret = do_path_lookup(AT_FDCWD, pathname, LOOKUP_FOLLOW, &old_nd); vxdprintk(VXD_CBIT(misc, 2), "do_path_lookup(old): %d (%d mnt refs)", ret, mnt_get_count(old_nd.path.mnt)); if (ret < 0) goto out_free_path; old_path = old_nd.path; old_dentry = old_path.dentry; mode = old_dentry->d_inode->i_mode; to = d_path(&old_path, path, PATH_MAX-2); pathlen = strlen(to); vxdprintk(VXD_CBIT(misc, 2), "old path " VS_Q("%s") " [%p:" VS_Q("%.*s") ":%d]", to, old_dentry, old_dentry->d_name.len, old_dentry->d_name.name, old_dentry->d_name.len); to[pathlen + 1] = 0; retry: new_dentry = NULL; to[pathlen] = pad--; ret = -ELOOP; if (pad <= '\240') goto out_rel_old; vxdprintk(VXD_CBIT(misc, 1), "temp copy " VS_Q("%s"), to); /* dir_nd will have refs to dentry and mnt */ ret = do_path_lookup(AT_FDCWD, to, LOOKUP_PARENT | LOOKUP_OPEN | LOOKUP_CREATE, &dir_nd); vxdprintk(VXD_CBIT(misc, 2), "do_path_lookup(new): %d", ret); if (ret < 0) goto retry; /* this puppy downs the dir inode mutex if successful */ new_dentry = kern_path_create(AT_FDCWD, to, &dir_path, 0); if (!new_dentry || IS_ERR(new_dentry)) { path_put(&dir_nd.path); vxdprintk(VXD_CBIT(misc, 2), "kern_path_create(new) failed with %d", new_dentry); goto retry; } path_put(&dir_path); vxdprintk(VXD_CBIT(misc, 2), "kern_path_create(new): %p [" VS_Q("%.*s") ":%d]", new_dentry, new_dentry->d_name.len, new_dentry->d_name.name, new_dentry->d_name.len); dir = dir_nd.path.dentry; ret = vfs_create(dir, new_dentry, mode, &dir_nd); vxdprintk(VXD_CBIT(misc, 2), "vfs_create(new): %d", ret); if (ret == -EEXIST) { mutex_unlock(&dir->d_inode->i_mutex); path_put(&dir_nd.path); dput(new_dentry); goto retry; } else if (ret < 0) goto out_unlock_new; /* drop out early, ret passes ENOENT */ ret = -ENOENT; if ((redo = d_unhashed(old_dentry))) goto out_unlock_new; path_get(&old_path); /* this one cleans up the dentry/mnt in case of failure */ old_file = dentry_open(old_dentry, old_path.mnt, O_RDONLY, current_cred()); vxdprintk(VXD_CBIT(misc, 2), "dentry_open(old): %p", old_file); if (IS_ERR(old_file)) { ret = PTR_ERR(old_file); goto out_unlock_new; } dget(&new_dentry); mntget(old_path.mnt); /* this one cleans up the dentry/mnt in case of failure */ new_file = dentry_open(new_dentry, old_path.mnt, O_WRONLY, current_cred()); vxdprintk(VXD_CBIT(misc, 2), "dentry_open(new): %p", new_file); if (IS_ERR(new_file)) { ret = PTR_ERR(new_file); goto out_fput_old; } size = i_size_read(old_file->f_dentry->d_inode); ret = do_cow_splice(old_file, new_file, size); vxdprintk(VXD_CBIT(misc, 2), "do_splice_direct: %d", ret); if (ret < 0) { goto out_fput_both; } else if (ret < size) { ret = -ENOSPC; goto out_fput_both; } else { struct inode *old_inode = old_dentry->d_inode; struct inode *new_inode = new_dentry->d_inode; struct iattr attr = { .ia_uid = old_inode->i_uid, .ia_gid = old_inode->i_gid, .ia_valid = ATTR_UID | ATTR_GID }; setattr_copy(new_inode, &attr); mark_inode_dirty(new_inode); } mutex_lock(&old_dentry->d_inode->i_sb->s_vfs_rename_mutex); /* drop out late */ ret = -ENOENT; if ((redo = d_unhashed(old_dentry))) goto out_unlock; vxdprintk(VXD_CBIT(misc, 2), "vfs_rename: [" VS_Q("%*s") ":%d] -> [" VS_Q("%*s") ":%d]", new_dentry->d_name.len, new_dentry->d_name.name, new_dentry->d_name.len, old_dentry->d_name.len, old_dentry->d_name.name, old_dentry->d_name.len); ret = vfs_rename(dir_nd.path.dentry->d_inode, new_dentry, old_dentry->d_parent->d_inode, old_dentry); vxdprintk(VXD_CBIT(misc, 2), "vfs_rename: %d", ret); out_unlock: mutex_unlock(&old_dentry->d_inode->i_sb->s_vfs_rename_mutex); out_fput_both: vxdprintk(VXD_CBIT(misc, 3), "fput(new_file=%p[#%ld])", new_file, atomic_long_read(&new_file->f_count)); fput(new_file); out_fput_old: vxdprintk(VXD_CBIT(misc, 3), "fput(old_file=%p[#%ld])", old_file, atomic_long_read(&old_file->f_count)); fput(old_file); out_unlock_new: mutex_unlock(&dir->d_inode->i_mutex); if (!ret) goto out_redo; /* error path cleanup */ vfs_unlink(dir->d_inode, new_dentry); out_redo: if (!redo) goto out_rel_both; /* lookup dentry once again */ /* old_nd.path is freed as old_path in out_rel_old */ ret = do_path_lookup(AT_FDCWD, pathname, LOOKUP_FOLLOW, &old_nd); if (ret) goto out_rel_both; dput(new_dentry); new_dentry = old_nd.path.dentry; vxdprintk(VXD_CBIT(misc, 2), "do_path_lookup(redo): %p [" VS_Q("%.*s") ":%d]", new_dentry, new_dentry->d_name.len, new_dentry->d_name.name, new_dentry->d_name.len); dget(new_dentry); out_rel_both: path_put(&dir_nd.path); out_rel_old: path_put(&old_path); out_free_path: kfree(path); out: if (ret) { dput(new_dentry); new_dentry = ERR_PTR(ret); } vxdprintk(VXD_CBIT(misc, 3), "cow_break_link returning with %p (%d mount refs)", new_dentry, mnt_get_count(old_nd.path.mnt)); return new_dentry; }