Commit 4a30131e authored by NeilBrown's avatar NeilBrown Committed by Linus Torvalds

[PATCH] Fix some problems with truncate and mtime semantics.

SUS requires that when truncating a file to the size that it currently
is:
  truncate and ftruncate should NOT modify ctime or mtime
  O_TRUNC SHOULD modify ctime and mtime.

Currently mtime and ctime are always modified on most local
filesystems (side effect of ->truncate) or never modified (on NFS).

With this patch:
  ATTR_CTIME|ATTR_MTIME are sent with ATTR_SIZE precisely when
    an update of these times is required whether size changes or not
    (via a new argument to do_truncate).  This allows NFS to do
    the right thing for O_TRUNC.
  inode_setattr nolonger forces ATTR_MTIME|ATTR_CTIME when the ATTR_SIZE
    sets the size to it's current value.  This allows local filesystems
    to do the right thing for f?truncate.

Also, the logic in inode_setattr is changed a bit so there are two return
points.  One returns the error from vmtruncate if it failed, the other
returns 0 (there can be no other failure).

Finally, if vmtruncate succeeds, and ATTR_SIZE is the only change
requested, we now fall-through and mark_inode_dirty.  If a filesystem did
not have a ->truncate function, then vmtruncate will have changed i_size,
without marking the inode as 'dirty', and I think this is wrong.
Signed-off-by: default avatarNeil Brown <neilb@suse.de>
Cc: Christoph Hellwig <hch@lst.de>
Cc: Trond Myklebust <trond.myklebust@fys.uio.no>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 78854014
...@@ -67,20 +67,12 @@ EXPORT_SYMBOL(inode_change_ok); ...@@ -67,20 +67,12 @@ EXPORT_SYMBOL(inode_change_ok);
int inode_setattr(struct inode * inode, struct iattr * attr) int inode_setattr(struct inode * inode, struct iattr * attr)
{ {
unsigned int ia_valid = attr->ia_valid; unsigned int ia_valid = attr->ia_valid;
int error = 0;
if (ia_valid & ATTR_SIZE &&
if (ia_valid & ATTR_SIZE) { attr->ia_size != i_size_read(inode)) {
if (attr->ia_size != i_size_read(inode)) { int error = vmtruncate(inode, attr->ia_size);
error = vmtruncate(inode, attr->ia_size); if (error)
if (error || (ia_valid == ATTR_SIZE)) return error;
goto out;
} else {
/*
* We skipped the truncate but must still update
* timestamps
*/
ia_valid |= ATTR_MTIME|ATTR_CTIME;
}
} }
if (ia_valid & ATTR_UID) if (ia_valid & ATTR_UID)
...@@ -104,8 +96,8 @@ int inode_setattr(struct inode * inode, struct iattr * attr) ...@@ -104,8 +96,8 @@ int inode_setattr(struct inode * inode, struct iattr * attr)
inode->i_mode = mode; inode->i_mode = mode;
} }
mark_inode_dirty(inode); mark_inode_dirty(inode);
out:
return error; return 0;
} }
EXPORT_SYMBOL(inode_setattr); EXPORT_SYMBOL(inode_setattr);
......
...@@ -1505,7 +1505,7 @@ int do_coredump(long signr, int exit_code, struct pt_regs * regs) ...@@ -1505,7 +1505,7 @@ int do_coredump(long signr, int exit_code, struct pt_regs * regs)
goto close_fail; goto close_fail;
if (!file->f_op->write) if (!file->f_op->write)
goto close_fail; goto close_fail;
if (do_truncate(file->f_dentry, 0, file) != 0) if (do_truncate(file->f_dentry, 0, 0, file) != 0)
goto close_fail; goto close_fail;
retval = binfmt->core_dump(signr, regs, file); retval = binfmt->core_dump(signr, regs, file);
......
...@@ -1491,7 +1491,7 @@ int may_open(struct nameidata *nd, int acc_mode, int flag) ...@@ -1491,7 +1491,7 @@ int may_open(struct nameidata *nd, int acc_mode, int flag)
if (!error) { if (!error) {
DQUOT_INIT(inode); DQUOT_INIT(inode);
error = do_truncate(dentry, 0, NULL); error = do_truncate(dentry, 0, ATTR_MTIME|ATTR_CTIME, NULL);
} }
put_write_access(inode); put_write_access(inode);
if (error) if (error)
......
...@@ -194,7 +194,8 @@ out: ...@@ -194,7 +194,8 @@ out:
return error; return error;
} }
int do_truncate(struct dentry *dentry, loff_t length, struct file *filp) int do_truncate(struct dentry *dentry, loff_t length, unsigned int time_attrs,
struct file *filp)
{ {
int err; int err;
struct iattr newattrs; struct iattr newattrs;
...@@ -204,7 +205,7 @@ int do_truncate(struct dentry *dentry, loff_t length, struct file *filp) ...@@ -204,7 +205,7 @@ int do_truncate(struct dentry *dentry, loff_t length, struct file *filp)
return -EINVAL; return -EINVAL;
newattrs.ia_size = length; newattrs.ia_size = length;
newattrs.ia_valid = ATTR_SIZE | ATTR_CTIME; newattrs.ia_valid = ATTR_SIZE | time_attrs;
if (filp) { if (filp) {
newattrs.ia_file = filp; newattrs.ia_file = filp;
newattrs.ia_valid |= ATTR_FILE; newattrs.ia_valid |= ATTR_FILE;
...@@ -266,7 +267,7 @@ static inline long do_sys_truncate(const char __user * path, loff_t length) ...@@ -266,7 +267,7 @@ static inline long do_sys_truncate(const char __user * path, loff_t length)
error = locks_verify_truncate(inode, NULL, length); error = locks_verify_truncate(inode, NULL, length);
if (!error) { if (!error) {
DQUOT_INIT(inode); DQUOT_INIT(inode);
error = do_truncate(nd.dentry, length, NULL); error = do_truncate(nd.dentry, length, 0, NULL);
} }
put_write_access(inode); put_write_access(inode);
...@@ -318,7 +319,7 @@ static inline long do_sys_ftruncate(unsigned int fd, loff_t length, int small) ...@@ -318,7 +319,7 @@ static inline long do_sys_ftruncate(unsigned int fd, loff_t length, int small)
error = locks_verify_truncate(inode, file, length); error = locks_verify_truncate(inode, file, length);
if (!error) if (!error)
error = do_truncate(dentry, length, file); error = do_truncate(dentry, length, 0, file);
out_putf: out_putf:
fput(file); fput(file);
out: out:
......
...@@ -1344,7 +1344,8 @@ static inline int break_lease(struct inode *inode, unsigned int mode) ...@@ -1344,7 +1344,8 @@ static inline int break_lease(struct inode *inode, unsigned int mode)
/* fs/open.c */ /* fs/open.c */
extern int do_truncate(struct dentry *, loff_t start, struct file *filp); extern int do_truncate(struct dentry *, loff_t start, unsigned int time_attrs,
struct file *filp);
extern long do_sys_open(const char __user *filename, int flags, int mode); extern long do_sys_open(const char __user *filename, int flags, int mode);
extern struct file *filp_open(const char *, int, int); extern struct file *filp_open(const char *, int, int);
extern struct file * dentry_open(struct dentry *, struct vfsmount *, int); extern struct file * dentry_open(struct dentry *, struct vfsmount *, int);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment