diff options
author | J. Bruce Fields <bfields@redhat.com> | 2011-09-20 17:14:31 -0400 |
---|---|---|
committer | Al Viro <viro@zeniv.linux.org.uk> | 2013-11-09 00:16:43 -0500 |
commit | 146a8595c6399ee6ab4b5cc34c0d28aa4835fdc5 (patch) | |
tree | a16a703ef40d72912247d496cd2e3cb7c0f3b9a7 | |
parent | 8e6d782cab50884ba94324632700e6233a252f6a (diff) |
locks: break delegations on link
Cc: Tyler Hicks <tyhicks@canonical.com>
Cc: Dustin Kirkland <dustin.kirkland@gazzang.com>
Acked-by: Jeff Layton <jlayton@redhat.com>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
-rw-r--r-- | fs/ecryptfs/inode.c | 2 | ||||
-rw-r--r-- | fs/namei.c | 36 | ||||
-rw-r--r-- | fs/nfsd/vfs.c | 2 | ||||
-rw-r--r-- | include/linux/fs.h | 2 |
4 files changed, 35 insertions, 7 deletions
diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index c23b01bb7e04..1c628f023041 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c | |||
@@ -475,7 +475,7 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir, | |||
475 | dget(lower_new_dentry); | 475 | dget(lower_new_dentry); |
476 | lower_dir_dentry = lock_parent(lower_new_dentry); | 476 | lower_dir_dentry = lock_parent(lower_new_dentry); |
477 | rc = vfs_link(lower_old_dentry, lower_dir_dentry->d_inode, | 477 | rc = vfs_link(lower_old_dentry, lower_dir_dentry->d_inode, |
478 | lower_new_dentry); | 478 | lower_new_dentry, NULL); |
479 | if (rc || !lower_new_dentry->d_inode) | 479 | if (rc || !lower_new_dentry->d_inode) |
480 | goto out_lock; | 480 | goto out_lock; |
481 | rc = ecryptfs_interpose(lower_new_dentry, new_dentry, dir->i_sb); | 481 | rc = ecryptfs_interpose(lower_new_dentry, new_dentry, dir->i_sb); |
diff --git a/fs/namei.c b/fs/namei.c index ce7e580e4e14..251178a1e383 100644 --- a/fs/namei.c +++ b/fs/namei.c | |||
@@ -3819,7 +3819,26 @@ SYSCALL_DEFINE2(symlink, const char __user *, oldname, const char __user *, newn | |||
3819 | return sys_symlinkat(oldname, AT_FDCWD, newname); | 3819 | return sys_symlinkat(oldname, AT_FDCWD, newname); |
3820 | } | 3820 | } |
3821 | 3821 | ||
3822 | int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) | 3822 | /** |
3823 | * vfs_link - create a new link | ||
3824 | * @old_dentry: object to be linked | ||
3825 | * @dir: new parent | ||
3826 | * @new_dentry: where to create the new link | ||
3827 | * @delegated_inode: returns inode needing a delegation break | ||
3828 | * | ||
3829 | * The caller must hold dir->i_mutex | ||
3830 | * | ||
3831 | * If vfs_link discovers a delegation on the to-be-linked file in need | ||
3832 | * of breaking, it will return -EWOULDBLOCK and return a reference to the | ||
3833 | * inode in delegated_inode. The caller should then break the delegation | ||
3834 | * and retry. Because breaking a delegation may take a long time, the | ||
3835 | * caller should drop the i_mutex before doing so. | ||
3836 | * | ||
3837 | * Alternatively, a caller may pass NULL for delegated_inode. This may | ||
3838 | * be appropriate for callers that expect the underlying filesystem not | ||
3839 | * to be NFS exported. | ||
3840 | */ | ||
3841 | int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry, struct inode **delegated_inode) | ||
3823 | { | 3842 | { |
3824 | struct inode *inode = old_dentry->d_inode; | 3843 | struct inode *inode = old_dentry->d_inode; |
3825 | unsigned max_links = dir->i_sb->s_max_links; | 3844 | unsigned max_links = dir->i_sb->s_max_links; |
@@ -3855,8 +3874,11 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de | |||
3855 | error = -ENOENT; | 3874 | error = -ENOENT; |
3856 | else if (max_links && inode->i_nlink >= max_links) | 3875 | else if (max_links && inode->i_nlink >= max_links) |
3857 | error = -EMLINK; | 3876 | error = -EMLINK; |
3858 | else | 3877 | else { |
3859 | error = dir->i_op->link(old_dentry, dir, new_dentry); | 3878 | error = try_break_deleg(inode, delegated_inode); |
3879 | if (!error) | ||
3880 | error = dir->i_op->link(old_dentry, dir, new_dentry); | ||
3881 | } | ||
3860 | 3882 | ||
3861 | if (!error && (inode->i_state & I_LINKABLE)) { | 3883 | if (!error && (inode->i_state & I_LINKABLE)) { |
3862 | spin_lock(&inode->i_lock); | 3884 | spin_lock(&inode->i_lock); |
@@ -3883,6 +3905,7 @@ SYSCALL_DEFINE5(linkat, int, olddfd, const char __user *, oldname, | |||
3883 | { | 3905 | { |
3884 | struct dentry *new_dentry; | 3906 | struct dentry *new_dentry; |
3885 | struct path old_path, new_path; | 3907 | struct path old_path, new_path; |
3908 | struct inode *delegated_inode = NULL; | ||
3886 | int how = 0; | 3909 | int how = 0; |
3887 | int error; | 3910 | int error; |
3888 | 3911 | ||
@@ -3921,9 +3944,14 @@ retry: | |||
3921 | error = security_path_link(old_path.dentry, &new_path, new_dentry); | 3944 | error = security_path_link(old_path.dentry, &new_path, new_dentry); |
3922 | if (error) | 3945 | if (error) |
3923 | goto out_dput; | 3946 | goto out_dput; |
3924 | error = vfs_link(old_path.dentry, new_path.dentry->d_inode, new_dentry); | 3947 | error = vfs_link(old_path.dentry, new_path.dentry->d_inode, new_dentry, &delegated_inode); |
3925 | out_dput: | 3948 | out_dput: |
3926 | done_path_create(&new_path, new_dentry); | 3949 | done_path_create(&new_path, new_dentry); |
3950 | if (delegated_inode) { | ||
3951 | error = break_deleg_wait(&delegated_inode); | ||
3952 | if (!error) | ||
3953 | goto retry; | ||
3954 | } | ||
3927 | if (retry_estale(error, how)) { | 3955 | if (retry_estale(error, how)) { |
3928 | how |= LOOKUP_REVAL; | 3956 | how |= LOOKUP_REVAL; |
3929 | goto retry; | 3957 | goto retry; |
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 45bf0295894d..27ba21b5f383 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c | |||
@@ -1736,7 +1736,7 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp, | |||
1736 | err = nfserrno(host_err); | 1736 | err = nfserrno(host_err); |
1737 | goto out_dput; | 1737 | goto out_dput; |
1738 | } | 1738 | } |
1739 | host_err = vfs_link(dold, dirp, dnew); | 1739 | host_err = vfs_link(dold, dirp, dnew, NULL); |
1740 | if (!host_err) { | 1740 | if (!host_err) { |
1741 | err = nfserrno(commit_metadata(ffhp)); | 1741 | err = nfserrno(commit_metadata(ffhp)); |
1742 | if (!err) | 1742 | if (!err) |
diff --git a/include/linux/fs.h b/include/linux/fs.h index 5bcff883fa90..6e36e7118ec1 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h | |||
@@ -1453,7 +1453,7 @@ extern int vfs_create(struct inode *, struct dentry *, umode_t, bool); | |||
1453 | extern int vfs_mkdir(struct inode *, struct dentry *, umode_t); | 1453 | extern int vfs_mkdir(struct inode *, struct dentry *, umode_t); |
1454 | extern int vfs_mknod(struct inode *, struct dentry *, umode_t, dev_t); | 1454 | extern int vfs_mknod(struct inode *, struct dentry *, umode_t, dev_t); |
1455 | extern int vfs_symlink(struct inode *, struct dentry *, const char *); | 1455 | extern int vfs_symlink(struct inode *, struct dentry *, const char *); |
1456 | extern int vfs_link(struct dentry *, struct inode *, struct dentry *); | 1456 | extern int vfs_link(struct dentry *, struct inode *, struct dentry *, struct inode **); |
1457 | extern int vfs_rmdir(struct inode *, struct dentry *); | 1457 | extern int vfs_rmdir(struct inode *, struct dentry *); |
1458 | extern int vfs_unlink(struct inode *, struct dentry *, struct inode **); | 1458 | extern int vfs_unlink(struct inode *, struct dentry *, struct inode **); |
1459 | extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *, struct inode **); | 1459 | extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *, struct inode **); |