union-mount: Implement union-aware truncate() Original-author: Valerie Aurora <vaurora@redhat.com> Signed-off-by: David Howells <dhowells@redhat.com> (Further development)
diff --git a/fs/namei.c b/fs/namei.c index bab3a57..5cc70c7 100644 --- a/fs/namei.c +++ b/fs/namei.c
@@ -1237,7 +1237,8 @@ goto out_err; } - if (nd->flags & (LOOKUP_COPY_UP_TRUNC | LOOKUP_OPEN | LOOKUP_CREATE)) { + if (nd->flags & (LOOKUP_COPY_UP_TRUNC | LOOKUP_OPEN | LOOKUP_CREATE | + LOOKUP_FOR_TRUNCATE)) { err = __inode_permission(lower.dentry->d_inode, MAY_WRITE); if (err < 0) goto out_err;
diff --git a/fs/open.c b/fs/open.c index dbaac6a..5ef49c0 100644 --- a/fs/open.c +++ b/fs/open.c
@@ -65,14 +65,19 @@ static long do_sys_truncate(const char __user *pathname, loff_t length) { struct path path; + struct nameidata nd; + struct vfsmount *mnt; struct inode *inode; + char *tmp; int error; error = -EINVAL; if (length < 0) /* sorry, but loff_t says... */ goto out; - error = user_path(pathname, &path); + error = user_path_nd(AT_FDCWD, pathname, + LOOKUP_COPY_UP | LOOKUP_FOR_TRUNCATE, + &nd, &path, &tmp); if (error) goto out; inode = path.dentry->d_inode; @@ -86,18 +91,45 @@ if (!S_ISREG(inode->i_mode)) goto dput_and_out; - error = mnt_want_write(path.mnt); + /* If we're looking at the lower layer of a union mount, then we need + * to create the file on the upperfs and truncate that. + */ + if (IS_MNT_LOWER(path.mnt)) + mnt = nd.path.mnt; + else + mnt = path.mnt; + + error = mnt_want_write(mnt); if (error) goto dput_and_out; - error = inode_permission(inode, MAY_WRITE); - if (error) - goto mnt_drop_write_and_out; + if (unlikely(IS_MNT_UNION(mnt))) { + /* We have to be able to write to the upperfs. */ + error = -EROFS; + if (mnt->mnt_sb->s_flags & MS_RDONLY) + goto mnt_drop_write_and_out; + + /* But the lowerfs inode must offer write permission - if the + * lowerfs was mounted writably. */ + error = __inode_permission(inode, MAY_WRITE); + if (error) + goto mnt_drop_write_and_out; + } else { + error = inode_permission(inode, MAY_WRITE); + if (error) + goto mnt_drop_write_and_out; + } error = -EPERM; if (IS_APPEND(inode)) goto mnt_drop_write_and_out; + error = union_copyup_len(&nd, &path, length); + if (error) + goto mnt_drop_write_and_out; + + /* path may have changed after copyup */ + inode = path.dentry->d_inode; error = get_write_access(inode); if (error) goto mnt_drop_write_and_out; @@ -119,9 +151,11 @@ put_write_and_out: put_write_access(inode); mnt_drop_write_and_out: - mnt_drop_write(path.mnt); + mnt_drop_write(mnt); dput_and_out: path_put(&path); + path_put(&nd.path); + putname(tmp); out: return error; }
diff --git a/fs/union.c b/fs/union.c index 2f1d95c..52f54e0 100644 --- a/fs/union.c +++ b/fs/union.c
@@ -595,7 +595,7 @@ return error; } -#if 0 +#if 1 /** * __union_copyup_len - Copy up a file and len bytes of data * @nd: nameidata for topmost parent dir
diff --git a/fs/union.h b/fs/union.h index b77b3a4..19c5228 100644 --- a/fs/union.h +++ b/fs/union.h
@@ -182,6 +182,7 @@ { return do_union_copyup_len(nd, path, true, 0); } +#endif /* * Helper function to copy up part of a file for truncate and O_TRUNC. @@ -190,4 +191,3 @@ { return do_union_copyup_len(nd, path, false, len); } -#endif
diff --git a/include/linux/namei.h b/include/linux/namei.h index c4802ef..9648361 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h
@@ -56,6 +56,7 @@ #define LOOKUP_EMPTY 0x4000 #define LOOKUP_COPY_UP 0x8000 /* Copy up from lower mount if unionmounted */ #define LOOKUP_COPY_UP_TRUNC 0x10000 /* Copy up truncated to 0 (need file attributes) */ +#define LOOKUP_FOR_TRUNCATE 0x20000 /* truncate() is doing the lookup */ extern int user_path_at(int, const char __user *, unsigned, struct path *); extern int user_path_at_empty(int, const char __user *, unsigned, struct path *, int *empty);