diff options
| author | Alexander Viro <viro@www.linux.org.uk> | 2004-03-17 21:24:22 -0800 |
|---|---|---|
| committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2004-03-17 21:24:22 -0800 |
| commit | 9c96c8be372e7e3ce26e7b0e14ab9e7ff22cf068 (patch) | |
| tree | e5979abffc5fc3903a9ca2ac510b25c0ccd6de92 | |
| parent | d9013aaeb1258b30e169e904b5d4229118507b42 (diff) | |
[PATCH] hpfs: fix locking scheme
Fixed the locking scheme. The need of extra locking was caused by
the fact that hpfs_write_inode() must update directory entry; since HPFS
directories are implemented as b-trees, we must provide protection both
against rename() (to make sure that we update the entry in right directory)
and against rebalancing of the parent.
Old scheme had both deadlocks and races - to start with, we had no
protection against rename()/unlink()/rmdir(), since (a) locking parent
was done without any warranties that it will remain our parent and (b)
check that we still have a directory entry (== have positive nlink) was
done before we tried to lock the parent. Moreover, iget serialization
killed two steps ago gave immediate deadlocks if iget() of parent had
triggered another hpfs_write_inode().
New scheme introduces another per-inode semaphore (hpfs-only,
obviously) protecting the reference to parent. It's taken on
rename/rmdir/unlink victims and inode being moved by rename. Old semaphores
are taken only on parent(s) and only after we grab one(s) of the new kind.
hpfs_write_inode() gets the new semaphore on our inode, checks nlink and
if it's non-zero grabs parent and takes the old semaphore on it.
Order among the semaphores of the same kind is arbitrary - the only
function that might take more than one of the same kind is hpfs_rename()
and it's serialized by VFS.
We might get away with only one semaphore, but then the ordering
issues would bite us big way - we would have to make sure that child is
always locked before parent (hpfs_write_inode() leaves no other choice)
and while that's easy to do for almost all operations, rename() is a bitch -
as always. And per-superblock rwsem giving rename() vs. write_inode()
exclusion on hpfs would make the entire thing too baroque for my taste.
->readdir() takes no locks at all (protection against directory
modifications is provided by VFS exclusion), ditto for ->lookup().
->llseek() on directories switched to use of (VFS) ->i_sem, so
it's safe from directory modifications and ->readdir() is safe from it -
no hpfs locks are needed here.
| -rw-r--r-- | fs/hpfs/buffer.c | 16 | ||||
| -rw-r--r-- | fs/hpfs/dir.c | 32 | ||||
| -rw-r--r-- | fs/hpfs/file.c | 11 | ||||
| -rw-r--r-- | fs/hpfs/hpfs_fn.h | 2 | ||||
| -rw-r--r-- | fs/hpfs/inode.c | 11 | ||||
| -rw-r--r-- | fs/hpfs/namei.c | 63 | ||||
| -rw-r--r-- | fs/hpfs/super.c | 1 | ||||
| -rw-r--r-- | include/linux/hpfs_fs_i.h | 3 |
8 files changed, 53 insertions, 86 deletions
diff --git a/fs/hpfs/buffer.c b/fs/hpfs/buffer.c index 7453c07976e7..181e6541eda1 100644 --- a/fs/hpfs/buffer.c +++ b/fs/hpfs/buffer.c @@ -26,22 +26,6 @@ void hpfs_unlock_creation(struct super_block *s) up(&hpfs_sb(s)->hpfs_creation_de); } -void hpfs_lock_inode(struct inode *i) -{ - if (i) { - struct hpfs_inode_info *hpfs_inode = hpfs_i(i); - down(&hpfs_inode->i_sem); - } -} - -void hpfs_unlock_inode(struct inode *i) -{ - if (i) { - struct hpfs_inode_info *hpfs_inode = hpfs_i(i); - up(&hpfs_inode->i_sem); - } -} - /* Map a sector into a buffer and return pointers to it and to the buffer. */ void *hpfs_map_sector(struct super_block *s, unsigned secno, struct buffer_head **bhp, diff --git a/fs/hpfs/dir.c b/fs/hpfs/dir.c index 4a0eb194c138..1e073d51d2bc 100644 --- a/fs/hpfs/dir.c +++ b/fs/hpfs/dir.c @@ -35,19 +35,19 @@ loff_t hpfs_dir_lseek(struct file *filp, loff_t off, int whence) /*printk("dir lseek\n");*/ if (new_off == 0 || new_off == 1 || new_off == 11 || new_off == 12 || new_off == 13) goto ok; - hpfs_lock_inode(i); + down(&i->i_sem); pos = ((loff_t) hpfs_de_as_down_as_possible(s, hpfs_inode->i_dno) << 4) + 1; while (pos != new_off) { if (map_pos_dirent(i, &pos, &qbh)) hpfs_brelse4(&qbh); else goto fail; if (pos == 12) goto fail; } - hpfs_unlock_inode(i); - ok: + up(&i->i_sem); +ok: unlock_kernel(); return filp->f_pos = new_off; - fail: - hpfs_unlock_inode(i); +fail: + up(&i->i_sem); /*printk("illegal lseek: %016llx\n", new_off);*/ unlock_kernel(); return -ESPIPE; @@ -109,8 +109,6 @@ int hpfs_readdir(struct file *filp, void *dirent, filldir_t filldir) goto out; } - hpfs_lock_inode(inode); - while (1) { again: /* This won't work when cycle is longer than number of dirents @@ -118,31 +116,23 @@ int hpfs_readdir(struct file *filp, void *dirent, filldir_t filldir) maybe killall -9 ls helps */ if (hpfs_sb(inode->i_sb)->sb_chk) if (hpfs_stop_cycles(inode->i_sb, filp->f_pos, &c1, &c2, "hpfs_readdir")) { - hpfs_unlock_inode(inode); ret = -EFSERROR; goto out; } - if (filp->f_pos == 12) { - hpfs_unlock_inode(inode); + if (filp->f_pos == 12) goto out; - } if (filp->f_pos == 3 || filp->f_pos == 4 || filp->f_pos == 5) { printk("HPFS: warning: pos==%d\n",(int)filp->f_pos); - hpfs_unlock_inode(inode); goto out; } if (filp->f_pos == 0) { - if (filldir(dirent, ".", 1, filp->f_pos, inode->i_ino, DT_DIR) < 0) { - hpfs_unlock_inode(inode); + if (filldir(dirent, ".", 1, filp->f_pos, inode->i_ino, DT_DIR) < 0) goto out; - } filp->f_pos = 11; } if (filp->f_pos == 11) { - if (filldir(dirent, "..", 2, filp->f_pos, hpfs_inode->i_parent_dir, DT_DIR) < 0) { - hpfs_unlock_inode(inode); + if (filldir(dirent, "..", 2, filp->f_pos, hpfs_inode->i_parent_dir, DT_DIR) < 0) goto out; - } filp->f_pos = 1; } if (filp->f_pos == 1) { @@ -151,13 +141,11 @@ int hpfs_readdir(struct file *filp, void *dirent, filldir_t filldir) filp->f_version = inode->i_version; } /*if (filp->f_version != inode->i_version) { - hpfs_unlock_inode(inode); ret = -ENOENT; goto out; }*/ old_pos = filp->f_pos; if (!(de = map_pos_dirent(inode, &filp->f_pos, &qbh))) { - hpfs_unlock_inode(inode); ret = -EIOERROR; goto out; } @@ -174,7 +162,6 @@ int hpfs_readdir(struct file *filp, void *dirent, filldir_t filldir) filp->f_pos = old_pos; if (tempname != (char *)de->name) kfree(tempname); hpfs_brelse4(&qbh); - hpfs_unlock_inode(inode); goto out; } if (tempname != (char *)de->name) kfree(tempname); @@ -220,7 +207,6 @@ struct dentry *hpfs_lookup(struct inode *dir, struct dentry *dentry, struct name goto end_add; } - hpfs_lock_inode(dir); /* * '.' and '..' will never be passed here. */ @@ -312,7 +298,6 @@ struct dentry *hpfs_lookup(struct inode *dir, struct dentry *dentry, struct name */ end: - hpfs_unlock_inode(dir); end_add: hpfs_set_dentry_operations(dentry); unlock_kernel(); @@ -328,7 +313,6 @@ struct dentry *hpfs_lookup(struct inode *dir, struct dentry *dentry, struct name /*bail:*/ - hpfs_unlock_inode(dir); unlock_kernel(); return ERR_PTR(-ENOENT); } diff --git a/fs/hpfs/file.c b/fs/hpfs/file.c index 0d9c3ec18fa0..5679ac957825 100644 --- a/fs/hpfs/file.c +++ b/fs/hpfs/file.c @@ -15,17 +15,6 @@ #define BLOCKS(size) (((size) + 511) >> 9) -/* HUH? */ -int hpfs_open(struct inode *i, struct file *f) -{ - lock_kernel(); - hpfs_lock_inode(i); - hpfs_unlock_inode(i); /* make sure nobody is deleting the file */ - unlock_kernel(); - if (!i->i_nlink) return -ENOENT; - return 0; -} - int hpfs_file_release(struct inode *inode, struct file *file) { lock_kernel(); diff --git a/fs/hpfs/hpfs_fn.h b/fs/hpfs/hpfs_fn.h index 34b2dd5f811a..3e2d591009e8 100644 --- a/fs/hpfs/hpfs_fn.h +++ b/fs/hpfs/hpfs_fn.h @@ -192,8 +192,6 @@ void hpfs_remove_fnode(struct super_block *, fnode_secno fno); void hpfs_lock_creation(struct super_block *); void hpfs_unlock_creation(struct super_block *); -void hpfs_lock_inode(struct inode *); -void hpfs_unlock_inode(struct inode *); void *hpfs_map_sector(struct super_block *, unsigned, struct buffer_head **, int); void *hpfs_get_sector(struct super_block *, unsigned, struct buffer_head **); void *hpfs_map_4sectors(struct super_block *, unsigned, struct quad_buffer_head *, int); diff --git a/fs/hpfs/inode.c b/fs/hpfs/inode.c index cc51651baa5d..93e47983ec00 100644 --- a/fs/hpfs/inode.c +++ b/fs/hpfs/inode.c @@ -229,13 +229,17 @@ void hpfs_write_inode(struct inode *i) { struct hpfs_inode_info *hpfs_inode = hpfs_i(i); struct inode *parent; - if (!i->i_nlink) return; if (i->i_ino == hpfs_sb(i->i_sb)->sb_root) return; if (hpfs_inode->i_rddir_off && !atomic_read(&i->i_count)) { if (*hpfs_inode->i_rddir_off) printk("HPFS: write_inode: some position still there\n"); kfree(hpfs_inode->i_rddir_off); hpfs_inode->i_rddir_off = NULL; } + down(&hpfs_inode->i_parent); + if (!i->i_nlink) { + up(&hpfs_inode->i_parent); + return; + } parent = iget_locked(i->i_sb, hpfs_inode->i_parent_dir); if (parent) { hpfs_inode->i_dirty = 0; @@ -244,13 +248,14 @@ void hpfs_write_inode(struct inode *i) hpfs_read_inode(parent); unlock_new_inode(parent); } - hpfs_lock_inode(parent); + down(&hpfs_inode->i_sem); hpfs_write_inode_nolock(i); - hpfs_unlock_inode(parent); + up(&hpfs_inode->i_sem); iput(parent); } else { mark_inode_dirty(i); } + up(&hpfs_inode->i_parent); } void hpfs_write_inode_nolock(struct inode *i) diff --git a/fs/hpfs/namei.c b/fs/hpfs/namei.c index c410f0460863..d11a87e14691 100644 --- a/fs/hpfs/namei.c +++ b/fs/hpfs/namei.c @@ -63,7 +63,7 @@ int hpfs_mkdir(struct inode *dir, struct dentry *dentry, int mode) if (dee.read_only) result->i_mode &= ~0222; - hpfs_lock_inode(dir); + down(&hpfs_i(dir)->i_sem); r = hpfs_add_dirent(dir, (char *)name, len, &dee, 0); if (r == 1) goto bail3; @@ -104,11 +104,11 @@ int hpfs_mkdir(struct inode *dir, struct dentry *dentry, int mode) hpfs_write_inode_nolock(result); } d_instantiate(dentry, result); - hpfs_unlock_inode(dir); + up(&hpfs_i(dir)->i_sem); unlock_kernel(); return 0; bail3: - hpfs_unlock_inode(dir); + up(&hpfs_i(dir)->i_sem); iput(result); bail2: hpfs_brelse4(&qbh0); @@ -171,7 +171,7 @@ int hpfs_create(struct inode *dir, struct dentry *dentry, int mode, struct namei result->i_data.a_ops = &hpfs_aops; hpfs_i(result)->mmu_private = 0; - hpfs_lock_inode(dir); + down(&hpfs_i(dir)->i_sem); r = hpfs_add_dirent(dir, (char *)name, len, &dee, 0); if (r == 1) goto bail2; @@ -196,12 +196,12 @@ int hpfs_create(struct inode *dir, struct dentry *dentry, int mode, struct namei hpfs_write_inode_nolock(result); } d_instantiate(dentry, result); - hpfs_unlock_inode(dir); + up(&hpfs_i(dir)->i_sem); unlock_kernel(); return 0; bail2: - hpfs_unlock_inode(dir); + up(&hpfs_i(dir)->i_sem); iput(result); bail1: brelse(bh); @@ -257,7 +257,7 @@ int hpfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t rdev) result->i_blocks = 1; init_special_inode(result, mode, rdev); - hpfs_lock_inode(dir); + down(&hpfs_i(dir)->i_sem); r = hpfs_add_dirent(dir, (char *)name, len, &dee, 0); if (r == 1) goto bail2; @@ -274,12 +274,12 @@ int hpfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t rdev) hpfs_write_inode_nolock(result); d_instantiate(dentry, result); - hpfs_unlock_inode(dir); + up(&hpfs_i(dir)->i_sem); brelse(bh); unlock_kernel(); return 0; bail2: - hpfs_unlock_inode(dir); + up(&hpfs_i(dir)->i_sem); iput(result); bail1: brelse(bh); @@ -338,7 +338,7 @@ int hpfs_symlink(struct inode *dir, struct dentry *dentry, const char *symlink) result->i_op = &page_symlink_inode_operations; result->i_data.a_ops = &hpfs_symlink_aops; - hpfs_lock_inode(dir); + down(&hpfs_i(dir)->i_sem); r = hpfs_add_dirent(dir, (char *)name, len, &dee, 0); if (r == 1) goto bail2; @@ -357,11 +357,11 @@ int hpfs_symlink(struct inode *dir, struct dentry *dentry, const char *symlink) hpfs_write_inode_nolock(result); d_instantiate(dentry, result); - hpfs_unlock_inode(dir); + up(&hpfs_i(dir)->i_sem); unlock_kernel(); return 0; bail2: - hpfs_unlock_inode(dir); + up(&hpfs_i(dir)->i_sem); iput(result); bail1: brelse(bh); @@ -387,8 +387,8 @@ int hpfs_unlink(struct inode *dir, struct dentry *dentry) lock_kernel(); hpfs_adjust_length((char *)name, &len); again: - hpfs_lock_inode(dir); - hpfs_lock_inode(inode); + down(&hpfs_i(inode)->i_parent); + down(&hpfs_i(dir)->i_sem); err = -ENOENT; de = map_dirent(dir, hpfs_i(dir)->i_dno, (char *)name, len, &dno, &qbh); if (!de) @@ -415,8 +415,8 @@ again: if (rep++) break; - hpfs_unlock_inode(inode); - hpfs_unlock_inode(dir); + up(&hpfs_i(dir)->i_sem); + up(&hpfs_i(inode)->i_parent); d_drop(dentry); spin_lock(&dentry->d_lock); if (atomic_read(&dentry->d_count) > 1 || @@ -447,8 +447,8 @@ again: out1: hpfs_brelse4(&qbh); out: - hpfs_unlock_inode(inode); - hpfs_unlock_inode(dir); + up(&hpfs_i(dir)->i_sem); + up(&hpfs_i(inode)->i_parent); unlock_kernel(); return err; } @@ -468,8 +468,8 @@ int hpfs_rmdir(struct inode *dir, struct dentry *dentry) hpfs_adjust_length((char *)name, &len); lock_kernel(); - hpfs_lock_inode(dir); - hpfs_lock_inode(inode); + down(&hpfs_i(inode)->i_parent); + down(&hpfs_i(dir)->i_sem); err = -ENOENT; de = map_dirent(dir, hpfs_i(dir)->i_dno, (char *)name, len, &dno, &qbh); if (!de) @@ -507,8 +507,8 @@ int hpfs_rmdir(struct inode *dir, struct dentry *dentry) out1: hpfs_brelse4(&qbh); out: - hpfs_unlock_inode(inode); - hpfs_unlock_inode(dir); + up(&hpfs_i(dir)->i_sem); + up(&hpfs_i(inode)->i_parent); unlock_kernel(); return err; } @@ -565,10 +565,13 @@ int hpfs_rename(struct inode *old_dir, struct dentry *old_dentry, hpfs_adjust_length((char *)old_name, &old_len); lock_kernel(); - hpfs_lock_inode(old_dir); + /* order doesn't matter, due to VFS exclusion */ + down(&hpfs_i(i)->i_parent); + if (new_inode) + down(&hpfs_i(new_inode)->i_parent); + down(&hpfs_i(old_dir)->i_sem); if (new_dir != old_dir) - hpfs_lock_inode(new_dir); - hpfs_lock_inode(i); + down(&hpfs_i(new_dir)->i_sem); /* Erm? Moving over the empty non-busy directory is perfectly legal */ if (new_inode && S_ISDIR(new_inode->i_mode)) { @@ -646,11 +649,13 @@ int hpfs_rename(struct inode *old_dir, struct dentry *old_dentry, } hpfs_i(i)->i_conv = hpfs_sb(i->i_sb)->sb_conv; hpfs_decide_conv(i, (char *)new_name, new_len); - end1: - hpfs_unlock_inode(i); +end1: if (old_dir != new_dir) - hpfs_unlock_inode(new_dir); - hpfs_unlock_inode(old_dir); + up(&hpfs_i(new_dir)->i_sem); + up(&hpfs_i(old_dir)->i_sem); + up(&hpfs_i(i)->i_parent); + if (new_inode) + up(&hpfs_i(new_inode)->i_parent); unlock_kernel(); return err; } diff --git a/fs/hpfs/super.c b/fs/hpfs/super.c index 886760c4c999..df0cf82c76e9 100644 --- a/fs/hpfs/super.c +++ b/fs/hpfs/super.c @@ -184,6 +184,7 @@ static void init_once(void * foo, kmem_cache_t * cachep, unsigned long flags) if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) == SLAB_CTOR_CONSTRUCTOR) { init_MUTEX(&ei->i_sem); + init_MUTEX(&ei->i_parent); inode_init_once(&ei->vfs_inode); } } diff --git a/include/linux/hpfs_fs_i.h b/include/linux/hpfs_fs_i.h index ab72ea5b5248..2a0d784fea40 100644 --- a/include/linux/hpfs_fs_i.h +++ b/include/linux/hpfs_fs_i.h @@ -16,7 +16,8 @@ struct hpfs_inode_info { unsigned i_ea_uid : 1; /* file's uid is stored in ea */ unsigned i_ea_gid : 1; /* file's gid is stored in ea */ unsigned i_dirty : 1; - struct semaphore i_sem; /* semaphore */ + struct semaphore i_sem; + struct semaphore i_parent; loff_t **i_rddir_off; struct inode vfs_inode; }; |
