diff options
| author | Linus Torvalds <torvalds@home.transmeta.com> | 2002-02-17 17:02:13 -0800 |
|---|---|---|
| committer | Linus Torvalds <torvalds@home.transmeta.com> | 2002-02-17 17:02:13 -0800 |
| commit | 2796576e0cff656d34368221121729846b4e1de5 (patch) | |
| tree | f5dc91b6f800bb80d201e5abea8238ddd89612f5 | |
| parent | 98d809e7d3f9f3c04a8b67d3de21a2d4f50af26b (diff) | |
| parent | 071c9b22a256c3a08aeb73dc64f7b8613a2fc02c (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.c | 14 | ||||
| -rw-r--r-- | fs/buffer.c | 135 | ||||
| -rw-r--r-- | fs/ext2/dir.c | 9 | ||||
| -rw-r--r-- | fs/minix/dir.c | 25 | ||||
| -rw-r--r-- | fs/nfs/file.c | 11 | ||||
| -rw-r--r-- | fs/sysv/ChangeLog | 7 | ||||
| -rw-r--r-- | fs/sysv/dir.c | 9 | ||||
| -rw-r--r-- | include/linux/fs.h | 7 | ||||
| -rw-r--r-- | kernel/ksyms.c | 1 | ||||
| -rw-r--r-- | mm/filemap.c | 113 |
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) { |
