+
+#ifdef CONFIG_VSERVER_COWBL
+
+#include <linux/file.h>
+
+struct dentry *cow_break_link(const char *pathname)
+{
+ int ret, mode, pathlen;
+ struct nameidata old_nd, dir_nd;
+ struct dentry *old_dentry, *new_dentry;
+ struct dentry *res = ERR_PTR(-EMLINK);
+ struct vfsmount *old_mnt, *new_mnt;
+ struct file *old_file;
+ struct file *new_file;
+ char *to, *path, pad='\251';
+ loff_t size;
+
+ vxdprintk(VXD_CBIT(misc, 1), "cow_break_link(»%s«)", pathname);
+ path = kmalloc(PATH_MAX, GFP_KERNEL);
+
+ ret = path_lookup(pathname, LOOKUP_FOLLOW, &old_nd);
+ vxdprintk(VXD_CBIT(misc, 2), "path_lookup(old): %d", ret);
+ old_dentry = old_nd.dentry;
+ old_mnt = old_nd.mnt;
+ mode = old_dentry->d_inode->i_mode;
+
+ to = d_path(old_dentry, old_mnt, path, PATH_MAX-2);
+ pathlen = strlen(to);
+ vxdprintk(VXD_CBIT(misc, 2), "old path »%s«", to);
+
+ to[pathlen+1] = 0;
+retry:
+ to[pathlen] = pad--;
+ if (pad <= '\240')
+ goto out_rel_old;
+
+ vxdprintk(VXD_CBIT(misc, 1), "temp copy »%s«", to);
+ ret = path_lookup(to,
+ LOOKUP_PARENT|LOOKUP_OPEN|LOOKUP_CREATE, &dir_nd);
+
+ /* this puppy downs the inode sem */
+ new_dentry = lookup_create(&dir_nd, 0);
+ vxdprintk(VXD_CBIT(misc, 2),
+ "lookup_create(new): %p", new_dentry);
+ if (!new_dentry) {
+ path_release(&dir_nd);
+ goto retry;
+ }
+
+ ret = vfs_create(dir_nd.dentry->d_inode, new_dentry, mode, &dir_nd);
+ vxdprintk(VXD_CBIT(misc, 2),
+ "vfs_create(new): %d", ret);
+ if (ret == -EEXIST) {
+ mutex_unlock(&dir_nd.dentry->d_inode->i_mutex);
+ dput(new_dentry);
+ path_release(&dir_nd);
+ goto retry;
+ }
+ else if (ret < 0) {
+ res = ERR_PTR(ret);
+ goto out_rel_both;
+ }
+
+ new_mnt = dir_nd.mnt;
+
+ dget(old_dentry);
+ mntget(old_mnt);
+ /* this one cleans up the dentry in case of failure */
+ old_file = dentry_open(old_dentry, old_mnt, O_RDONLY);
+ vxdprintk(VXD_CBIT(misc, 2),
+ "dentry_open(old): %p", old_file);
+ if (!old_file)
+ goto out_rel_both;
+
+ dget(new_dentry);
+ mntget(new_mnt);
+ /* this one cleans up the dentry in case of failure */
+ new_file = dentry_open(new_dentry, new_mnt, O_WRONLY);
+ vxdprintk(VXD_CBIT(misc, 2),
+ "dentry_open(new): %p", new_file);
+ if (!new_file)
+ goto out_fput_old;
+
+ size = i_size_read(old_file->f_dentry->d_inode);
+ ret = vfs_sendfile(new_file, old_file, NULL, size, 0);
+ vxdprintk(VXD_CBIT(misc, 2), "vfs_sendfile: %d", ret);
+
+ if (ret < 0)
+ goto out_fput_both;
+
+ ret = vfs_rename(dir_nd.dentry->d_inode, new_dentry,
+ old_nd.dentry->d_parent->d_inode, old_dentry);
+ vxdprintk(VXD_CBIT(misc, 2), "vfs_rename: %d", ret);
+ if (!ret) {
+ res = new_dentry;
+ dget(new_dentry);
+ }
+
+out_fput_both:
+ vxdprintk(VXD_CBIT(misc, 3),
+ "fput(new_file=%p[#%d])", new_file,
+ atomic_read(&new_file->f_count));
+ fput(new_file);
+
+out_fput_old:
+ vxdprintk(VXD_CBIT(misc, 3),
+ "fput(old_file=%p[#%d])", old_file,
+ atomic_read(&old_file->f_count));
+ fput(old_file);
+
+out_rel_both:
+ mutex_unlock(&dir_nd.dentry->d_inode->i_mutex);
+ dput(new_dentry);
+
+ path_release(&dir_nd);
+out_rel_old:
+ path_release(&old_nd);
+ kfree(path);
+ return res;
+}
+
+#endif
+