summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@home.transmeta.com>2002-02-17 17:02:13 -0800
committerLinus Torvalds <torvalds@home.transmeta.com>2002-02-17 17:02:13 -0800
commit2796576e0cff656d34368221121729846b4e1de5 (patch)
treef5dc91b6f800bb80d201e5abea8238ddd89612f5
parent98d809e7d3f9f3c04a8b67d3de21a2d4f50af26b (diff)
parent071c9b22a256c3a08aeb73dc64f7b8613a2fc02c (diff)
Merge home.transmeta.com:/home/torvalds/v2.5/morton
into home.transmeta.com:/home/torvalds/v2.5/linux
-rw-r--r--fs/block_dev.c14
-rw-r--r--fs/buffer.c135
-rw-r--r--fs/ext2/dir.c9
-rw-r--r--fs/minix/dir.c25
-rw-r--r--fs/nfs/file.c11
-rw-r--r--fs/sysv/ChangeLog7
-rw-r--r--fs/sysv/dir.c9
-rw-r--r--include/linux/fs.h7
-rw-r--r--kernel/ksyms.c1
-rw-r--r--mm/filemap.c113
10 files changed, 237 insertions, 94 deletions
diff --git a/fs/block_dev.c b/fs/block_dev.c
index 5bdffee5760e..08690495426d 100644
--- a/fs/block_dev.c
+++ b/fs/block_dev.c
@@ -195,11 +195,15 @@ static loff_t block_llseek(struct file *file, loff_t offset, int origin)
static int __block_fsync(struct inode * inode)
{
- int ret;
-
- filemap_fdatasync(inode->i_mapping);
- ret = sync_buffers(inode->i_rdev, 1);
- filemap_fdatawait(inode->i_mapping);
+ int ret, err;
+
+ ret = filemap_fdatasync(inode->i_mapping);
+ err = sync_buffers(inode->i_rdev, 1);
+ if (err && !ret)
+ ret = err;
+ err = filemap_fdatawait(inode->i_mapping);
+ if (err && !ret)
+ ret = err;
return ret;
}
diff --git a/fs/buffer.c b/fs/buffer.c
index 216490fb8b70..612ea5e807f7 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -410,9 +410,9 @@ asmlinkage long sys_fsync(unsigned int fd)
struct file * file;
struct dentry * dentry;
struct inode * inode;
- int err;
+ int ret, err;
- err = -EBADF;
+ ret = -EBADF;
file = fget(fd);
if (!file)
goto out;
@@ -420,21 +420,27 @@ asmlinkage long sys_fsync(unsigned int fd)
dentry = file->f_dentry;
inode = dentry->d_inode;
- err = -EINVAL;
- if (!file->f_op || !file->f_op->fsync)
+ ret = -EINVAL;
+ if (!file->f_op || !file->f_op->fsync) {
+ /* Why? We can still call filemap_fdatasync */
goto out_putf;
+ }
/* We need to protect against concurrent writers.. */
down(&inode->i_sem);
- filemap_fdatasync(inode->i_mapping);
+ ret = filemap_fdatasync(inode->i_mapping);
err = file->f_op->fsync(file, dentry, 0);
- filemap_fdatawait(inode->i_mapping);
+ if (err && !ret)
+ ret = err;
+ err = filemap_fdatawait(inode->i_mapping);
+ if (err && !ret)
+ ret = err;
up(&inode->i_sem);
out_putf:
fput(file);
out:
- return err;
+ return ret;
}
asmlinkage long sys_fdatasync(unsigned int fd)
@@ -442,9 +448,9 @@ asmlinkage long sys_fdatasync(unsigned int fd)
struct file * file;
struct dentry * dentry;
struct inode * inode;
- int err;
+ int ret, err;
- err = -EBADF;
+ ret = -EBADF;
file = fget(fd);
if (!file)
goto out;
@@ -452,20 +458,24 @@ asmlinkage long sys_fdatasync(unsigned int fd)
dentry = file->f_dentry;
inode = dentry->d_inode;
- err = -EINVAL;
+ ret = -EINVAL;
if (!file->f_op || !file->f_op->fsync)
goto out_putf;
down(&inode->i_sem);
- filemap_fdatasync(inode->i_mapping);
+ ret = filemap_fdatasync(inode->i_mapping);
err = file->f_op->fsync(file, dentry, 1);
- filemap_fdatawait(inode->i_mapping);
+ if (err && !ret)
+ ret = err;
+ err = filemap_fdatawait(inode->i_mapping);
+ if (err && !ret)
+ ret = err;
up(&inode->i_sem);
out_putf:
fput(file);
out:
- return err;
+ return ret;
}
/* After several hours of tedious analysis, the following hash
@@ -1431,6 +1441,7 @@ static int __block_write_full_page(struct inode *inode, struct page *page, get_b
int err, i;
unsigned long block;
struct buffer_head *bh, *head;
+ int need_unlock;
if (!PageLocked(page))
BUG();
@@ -1486,8 +1497,34 @@ static int __block_write_full_page(struct inode *inode, struct page *page, get_b
return 0;
out:
+ /*
+ * ENOSPC, or some other error. We may already have added some
+ * blocks to the file, so we need to write these out to avoid
+ * exposing stale data.
+ */
ClearPageUptodate(page);
- UnlockPage(page);
+ bh = head;
+ need_unlock = 1;
+ /* Recovery: lock and submit the mapped buffers */
+ do {
+ if (buffer_mapped(bh)) {
+ lock_buffer(bh);
+ set_buffer_async_io(bh);
+ need_unlock = 0;
+ }
+ bh = bh->b_this_page;
+ } while (bh != head);
+ do {
+ struct buffer_head *next = bh->b_this_page;
+ if (buffer_mapped(bh)) {
+ set_bit(BH_Uptodate, &bh->b_state);
+ clear_bit(BH_Dirty, &bh->b_state);
+ submit_bh(WRITE, bh);
+ }
+ bh = next;
+ } while (bh != head);
+ if (need_unlock)
+ UnlockPage(page);
return err;
}
@@ -1518,6 +1555,7 @@ static int __block_prepare_write(struct inode *inode, struct page *page,
continue;
if (block_start >= to)
break;
+ clear_bit(BH_New, &bh->b_state);
if (!buffer_mapped(bh)) {
err = get_block(inode, block, bh, 1);
if (err)
@@ -1552,12 +1590,35 @@ static int __block_prepare_write(struct inode *inode, struct page *page,
*/
while(wait_bh > wait) {
wait_on_buffer(*--wait_bh);
- err = -EIO;
if (!buffer_uptodate(*wait_bh))
- goto out;
+ return -EIO;
}
return 0;
out:
+ /*
+ * Zero out any newly allocated blocks to avoid exposing stale
+ * data. If BH_New is set, we know that the block was newly
+ * allocated in the above loop.
+ */
+ bh = head;
+ block_start = 0;
+ do {
+ block_end = block_start+blocksize;
+ if (block_end <= from)
+ goto next_bh;
+ if (block_start >= to)
+ break;
+ if (buffer_new(bh)) {
+ if (buffer_uptodate(bh))
+ printk(KERN_ERR "%s: zeroing uptodate buffer!\n", __FUNCTION__);
+ memset(kaddr+block_start, 0, bh->b_size);
+ set_bit(BH_Uptodate, &bh->b_state);
+ mark_buffer_dirty(bh);
+ }
+next_bh:
+ block_start = block_end;
+ bh = bh->b_this_page;
+ } while (bh != head);
return err;
}
@@ -1954,6 +2015,48 @@ done:
goto done;
}
+/*
+ * Commence writeout of all the buffers against a page. The
+ * page must be locked. Returns zero on success or a negative
+ * errno.
+ */
+int writeout_one_page(struct page *page)
+{
+ struct buffer_head *bh, *head = page->buffers;
+
+ if (!PageLocked(page))
+ BUG();
+ bh = head;
+ do {
+ if (buffer_locked(bh) || !buffer_dirty(bh) || !buffer_uptodate(bh))
+ continue;
+
+ bh->b_flushtime = jiffies;
+ ll_rw_block(WRITE, 1, &bh);
+ } while ((bh = bh->b_this_page) != head);
+ return 0;
+}
+EXPORT_SYMBOL(writeout_one_page);
+
+/*
+ * Wait for completion of I/O of all buffers against a page. The page
+ * must be locked. Returns zero on success or a negative errno.
+ */
+int waitfor_one_page(struct page *page)
+{
+ int error = 0;
+ struct buffer_head *bh, *head = page->buffers;
+
+ bh = head;
+ do {
+ wait_on_buffer(bh);
+ if (buffer_req(bh) && !buffer_uptodate(bh))
+ error = -EIO;
+ } while ((bh = bh->b_this_page) != head);
+ return error;
+}
+EXPORT_SYMBOL(waitfor_one_page);
+
sector_t generic_block_bmap(struct address_space *mapping, sector_t block,
get_block_t *get_block)
{
diff --git a/fs/ext2/dir.c b/fs/ext2/dir.c
index d6416b9484f3..c11482c94ed5 100644
--- a/fs/ext2/dir.c
+++ b/fs/ext2/dir.c
@@ -52,8 +52,13 @@ static int ext2_commit_chunk(struct page *page, unsigned from, unsigned to)
int err = 0;
dir->i_version = ++event;
page->mapping->a_ops->commit_write(NULL, page, from, to);
- if (IS_SYNC(dir))
- err = waitfor_one_page(page);
+ if (IS_SYNC(dir)) {
+ int err2;
+ err = writeout_one_page(page);
+ err2 = waitfor_one_page(page);
+ if (err == 0)
+ err = err2;
+ }
return err;
}
diff --git a/fs/minix/dir.c b/fs/minix/dir.c
index d8b5f8469c10..c363a6d00925 100644
--- a/fs/minix/dir.c
+++ b/fs/minix/dir.c
@@ -36,8 +36,13 @@ static int dir_commit_chunk(struct page *page, unsigned from, unsigned to)
struct inode *dir = (struct inode *)page->mapping->host;
int err = 0;
page->mapping->a_ops->commit_write(NULL, page, from, to);
- if (IS_SYNC(dir))
- err = waitfor_one_page(page);
+ if (IS_SYNC(dir)) {
+ int err2;
+ err = writeout_one_page(page);
+ err2 = waitfor_one_page(page);
+ if (err == 0)
+ err = err2;
+ }
return err;
}
@@ -236,10 +241,10 @@ int minix_delete_entry(struct minix_dir_entry *de, struct page *page)
lock_page(page);
err = mapping->a_ops->prepare_write(NULL, page, from, to);
- if (err)
- BUG();
- de->inode = 0;
- err = dir_commit_chunk(page, from, to);
+ if (err == 0) {
+ de->inode = 0;
+ err = dir_commit_chunk(page, from, to);
+ }
UnlockPage(page);
dir_put_page(page);
inode->i_ctime = inode->i_mtime = CURRENT_TIME;
@@ -336,10 +341,10 @@ void minix_set_link(struct minix_dir_entry *de, struct page *page,
lock_page(page);
err = page->mapping->a_ops->prepare_write(NULL, page, from, to);
- if (err)
- BUG();
- de->inode = inode->i_ino;
- err = dir_commit_chunk(page, from, to);
+ if (err == 0) {
+ de->inode = inode->i_ino;
+ err = dir_commit_chunk(page, from, to);
+ }
UnlockPage(page);
dir_put_page(page);
dir->i_mtime = dir->i_ctime = CURRENT_TIME;
diff --git a/fs/nfs/file.c b/fs/nfs/file.c
index f7fa8ac1972f..d8151c11eb1a 100644
--- a/fs/nfs/file.c
+++ b/fs/nfs/file.c
@@ -244,6 +244,7 @@ nfs_lock(struct file *filp, int cmd, struct file_lock *fl)
{
struct inode * inode = filp->f_dentry->d_inode;
int status = 0;
+ int status2;
dprintk("NFS: nfs_lock(f=%s/%ld, t=%x, fl=%x, r=%Ld:%Ld)\n",
inode->i_sb->s_id, inode->i_ino,
@@ -278,11 +279,15 @@ nfs_lock(struct file *filp, int cmd, struct file_lock *fl)
* Flush all pending writes before doing anything
* with locks..
*/
- filemap_fdatasync(inode->i_mapping);
+ status = filemap_fdatasync(inode->i_mapping);
down(&inode->i_sem);
- status = nfs_wb_all(inode);
+ status2 = nfs_wb_all(inode);
+ if (status2 && !status)
+ status = status2;
up(&inode->i_sem);
- filemap_fdatawait(inode->i_mapping);
+ status2 = filemap_fdatawait(inode->i_mapping);
+ if (status2 && !status)
+ status = status2;
if (status < 0)
return status;
diff --git a/fs/sysv/ChangeLog b/fs/sysv/ChangeLog
index adda37067320..1776aae9bd78 100644
--- a/fs/sysv/ChangeLog
+++ b/fs/sysv/ChangeLog
@@ -1,3 +1,9 @@
+Thu Feb 14 2002 Andrew Morton <akpm@zip.com.au>
+
+ * dir_commit_chunk(): call writeout_one_page() as well as
+ waitfor_one_page() for IS_SYNC directories, so that we
+ actually do sync the directory. (forward-port from 2.4).
+
Thu Feb 7 2002 Alexander Viro <viro@math.psu.edu>
* super.c: switched to ->get_sb()
@@ -97,3 +103,4 @@ Fri Oct 26 2001 Christoph Hellwig <hch@caldera.de>
Remove symlink faking. Noone really wants to use these as
linux filesystems and native OSes don't support it anyway.
+
diff --git a/fs/sysv/dir.c b/fs/sysv/dir.c
index 99cd284659af..41e13fe81c8b 100644
--- a/fs/sysv/dir.c
+++ b/fs/sysv/dir.c
@@ -42,8 +42,13 @@ static int dir_commit_chunk(struct page *page, unsigned from, unsigned to)
int err = 0;
page->mapping->a_ops->commit_write(NULL, page, from, to);
- if (IS_SYNC(dir))
- err = waitfor_one_page(page);
+ if (IS_SYNC(dir)) {
+ int err2;
+ err = writeout_one_page(page);
+ err2 = waitfor_one_page(page);
+ if (err == 0)
+ err = err2;
+ }
return err;
}
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 561b075e9a4b..18cdb72c8d3c 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1230,8 +1230,8 @@ static inline int fsync_inode_data_buffers(struct inode *inode)
return fsync_buffers_list(&inode->i_dirty_data_buffers);
}
extern int inode_has_buffers(struct inode *);
-extern void filemap_fdatasync(struct address_space *);
-extern void filemap_fdatawait(struct address_space *);
+extern int filemap_fdatasync(struct address_space *);
+extern int filemap_fdatawait(struct address_space *);
extern void sync_supers(kdev_t);
extern int bmap(struct inode *, int);
extern int notify_change(struct dentry *, struct iattr *);
@@ -1440,8 +1440,9 @@ sector_t generic_block_bmap(struct address_space *, sector_t, get_block_t *);
int generic_commit_write(struct file *, struct page *, unsigned, unsigned);
int block_truncate_page(struct address_space *, loff_t, get_block_t *);
extern int generic_direct_IO(int, struct inode *, struct kiobuf *, unsigned long, int, get_block_t *);
+extern int waitfor_one_page(struct page *);
+extern int writeout_one_page(struct page *);
-extern int waitfor_one_page(struct page*);
extern int generic_file_mmap(struct file *, struct vm_area_struct *);
extern int file_read_actor(read_descriptor_t * desc, struct page *page, unsigned long offset, unsigned long size);
extern ssize_t generic_file_read(struct file *, char *, size_t, loff_t *);
diff --git a/kernel/ksyms.c b/kernel/ksyms.c
index 0d8d867bbd66..7b9510813a51 100644
--- a/kernel/ksyms.c
+++ b/kernel/ksyms.c
@@ -213,7 +213,6 @@ EXPORT_SYMBOL(cont_prepare_write);
EXPORT_SYMBOL(generic_commit_write);
EXPORT_SYMBOL(block_truncate_page);
EXPORT_SYMBOL(generic_block_bmap);
-EXPORT_SYMBOL(waitfor_one_page);
EXPORT_SYMBOL(generic_file_read);
EXPORT_SYMBOL(do_generic_file_read);
EXPORT_SYMBOL(generic_file_write);
diff --git a/mm/filemap.c b/mm/filemap.c
index 16b924b0c5ba..d93017858697 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -449,41 +449,6 @@ not_found:
return page;
}
-/*
- * By the time this is called, the page is locked and
- * we don't have to worry about any races any more.
- *
- * Start the IO..
- */
-static int writeout_one_page(struct page *page)
-{
- struct buffer_head *bh, *head = page->buffers;
-
- bh = head;
- do {
- if (buffer_locked(bh) || !buffer_dirty(bh) || !buffer_uptodate(bh))
- continue;
-
- bh->b_flushtime = jiffies;
- ll_rw_block(WRITE, 1, &bh);
- } while ((bh = bh->b_this_page) != head);
- return 0;
-}
-
-int waitfor_one_page(struct page *page)
-{
- int error = 0;
- struct buffer_head *bh, *head = page->buffers;
-
- bh = head;
- do {
- wait_on_buffer(bh);
- if (buffer_req(bh) && !buffer_uptodate(bh))
- error = -EIO;
- } while ((bh = bh->b_this_page) != head);
- return error;
-}
-
static int do_buffer_fdatasync(struct list_head *head, unsigned long start, unsigned long end, int (*fn)(struct page *))
{
struct list_head *curr;
@@ -577,8 +542,9 @@ EXPORT_SYMBOL(fail_writepage);
* @mapping: address space structure to write
*
*/
-void filemap_fdatasync(struct address_space * mapping)
+int filemap_fdatasync(struct address_space * mapping)
{
+ int ret = 0;
int (*writepage)(struct page *) = mapping->a_ops->writepage;
spin_lock(&pagecache_lock);
@@ -598,8 +564,11 @@ void filemap_fdatasync(struct address_space * mapping)
lock_page(page);
if (PageDirty(page)) {
+ int err;
ClearPageDirty(page);
- writepage(page);
+ err = writepage(page);
+ if (err && !ret)
+ ret = err;
} else
UnlockPage(page);
@@ -607,6 +576,7 @@ void filemap_fdatasync(struct address_space * mapping)
spin_lock(&pagecache_lock);
}
spin_unlock(&pagecache_lock);
+ return ret;
}
/**
@@ -616,8 +586,10 @@ void filemap_fdatasync(struct address_space * mapping)
* @mapping: address space structure to wait for
*
*/
-void filemap_fdatawait(struct address_space * mapping)
+int filemap_fdatawait(struct address_space * mapping)
{
+ int ret = 0;
+
spin_lock(&pagecache_lock);
while (!list_empty(&mapping->locked_pages)) {
@@ -633,11 +605,14 @@ void filemap_fdatawait(struct address_space * mapping)
spin_unlock(&pagecache_lock);
___wait_on_page(page);
+ if (PageError(page))
+ ret = -EIO;
page_cache_release(page);
spin_lock(&pagecache_lock);
}
spin_unlock(&pagecache_lock);
+ return ret;
}
/*
@@ -1514,12 +1489,14 @@ static ssize_t generic_file_direct_IO(int rw, struct file * filp, char * buf, si
goto out_free;
/*
- * Flush to disk exlusively the _data_, metadata must remains
+ * Flush to disk exclusively the _data_, metadata must remain
* completly asynchronous or performance will go to /dev/null.
*/
- filemap_fdatasync(mapping);
- retval = fsync_inode_data_buffers(inode);
- filemap_fdatawait(mapping);
+ retval = filemap_fdatasync(mapping);
+ if (retval == 0)
+ retval = fsync_inode_data_buffers(inode);
+ if (retval == 0)
+ retval = filemap_fdatawait(mapping);
if (retval < 0)
goto out_free;
@@ -2136,26 +2113,45 @@ int generic_file_mmap(struct file * file, struct vm_area_struct * vma)
* The msync() system call.
*/
+/*
+ * MS_SYNC syncs the entire file - including mappings.
+ *
+ * MS_ASYNC initiates writeout of just the dirty mapped data.
+ * This provides no guarantee of file integrity - things like indirect
+ * blocks may not have started writeout. MS_ASYNC is primarily useful
+ * where the application knows that it has finished with the data and
+ * wishes to intelligently schedule its own I/O traffic.
+ */
static int msync_interval(struct vm_area_struct * vma,
unsigned long start, unsigned long end, int flags)
{
+ int ret = 0;
struct file * file = vma->vm_file;
+
if (file && (vma->vm_flags & VM_SHARED)) {
- int error;
- error = filemap_sync(vma, start, end-start, flags);
+ ret = filemap_sync(vma, start, end-start, flags);
- if (!error && (flags & MS_SYNC)) {
+ if (!ret && (flags & (MS_SYNC|MS_ASYNC))) {
struct inode * inode = file->f_dentry->d_inode;
+
down(&inode->i_sem);
- filemap_fdatasync(inode->i_mapping);
- if (file->f_op && file->f_op->fsync)
- error = file->f_op->fsync(file, file->f_dentry, 1);
- filemap_fdatawait(inode->i_mapping);
+ ret = filemap_fdatasync(inode->i_mapping);
+ if (flags & MS_SYNC) {
+ int err;
+
+ if (file->f_op && file->f_op->fsync) {
+ err = file->f_op->fsync(file, file->f_dentry, 1);
+ if (err && !ret)
+ ret = err;
+ }
+ err = filemap_fdatawait(inode->i_mapping);
+ if (err && !ret)
+ ret = err;
+ }
up(&inode->i_sem);
}
- return error;
}
- return 0;
+ return ret;
}
asmlinkage long sys_msync(unsigned long start, size_t len, int flags)
@@ -2999,7 +2995,7 @@ generic_file_write(struct file *file,const char *buf,size_t count, loff_t *ppos)
kaddr = kmap(page);
status = mapping->a_ops->prepare_write(file, page, offset, offset+bytes);
if (status)
- goto unlock;
+ goto sync_failure;
page_fault = __copy_from_user(kaddr+offset, buf, bytes);
flush_dcache_page(page);
status = mapping->a_ops->commit_write(file, page, offset, offset+bytes);
@@ -3024,6 +3020,7 @@ unlock:
if (status < 0)
break;
} while (count);
+done:
*ppos = pos;
if (cached_page)
@@ -3046,6 +3043,18 @@ fail_write:
status = -EFAULT;
goto unlock;
+sync_failure:
+ /*
+ * If blocksize < pagesize, prepare_write() may have instantiated a
+ * few blocks outside i_size. Trim these off again.
+ */
+ kunmap(page);
+ UnlockPage(page);
+ page_cache_release(page);
+ if (pos + bytes > inode->i_size)
+ vmtruncate(inode, inode->i_size);
+ goto done;
+
o_direct:
written = generic_file_direct_IO(WRITE, file, (char *) buf, count, pos);
if (written > 0) {