--- linux-2.6.2-rc2/fs/devfs/base.c.nd 2004-01-26 13:25:38.000000000 +0300 +++ linux-2.6.2-rc2/fs/devfs/base.c 2004-01-26 15:52:11.000000000 +0300 @@ -676,6 +676,7 @@ #include #include #include +#include #include #include @@ -2223,6 +2224,34 @@ static int devfs_d_revalidate_wait (stru devfs_handle_t parent = get_devfs_entry_from_vfs_inode (dir); struct devfs_lookup_struct *lookup_info = dentry->d_fsdata; DECLARE_WAITQUEUE (wait, current); + int need_lock; + + /* + * FIXME HACK + * + * make sure that + * d_instantiate always runs under lock + * we release i_sem lock before going to sleep + * + * unfortunately sometimes d_revalidate is called with + * and sometimes without i_sem lock held. The following checks + * attempt to deduce when we need to add (and drop resp.) lock + * here. This relies on current (2.6.2) calling coventions: + * + * lookup_hash is always run under i_sem and is passing NULL + * as nd + * + * open(...,O_CREATE,...) calls _lookup_hash under i_sem + * and sets flags to LOOKUP_OPEN|LOOKUP_CREATE + * + * all other invocations of ->d_revalidate seem to happen + * outside of i_sem + */ + need_lock = nd && + (!(nd->flags & LOOKUP_CREATE) || (nd->flags & LOOKUP_PARENT)); + + if (need_lock) + down(&dir->i_sem); if ( is_devfsd_or_child (fs_info) ) { @@ -2233,33 +2262,40 @@ static int devfs_d_revalidate_wait (stru "(%s): dentry: %p inode: %p de: %p by: \"%s\"\n", dentry->d_name.name, dentry, dentry->d_inode, de, current->comm); - if (dentry->d_inode) return 1; + if (dentry->d_inode) + goto out; if (de == NULL) { read_lock (&parent->u.dir.lock); de = _devfs_search_dir (parent, dentry->d_name.name, dentry->d_name.len); read_unlock (&parent->u.dir.lock); - if (de == NULL) return 1; + if (de == NULL) + goto out; lookup_info->de = de; } /* Create an inode, now that the driver information is available */ inode = _devfs_get_vfs_inode (dir->i_sb, de, dentry); - if (!inode) return 1; + if (!inode) + goto out; DPRINTK (DEBUG_I_LOOKUP, "(%s): new VFS inode(%u): %p de: %p by: \"%s\"\n", de->name, de->inode.ino, inode, de, current->comm); d_instantiate (dentry, inode); - return 1; + goto out; } - if (lookup_info == NULL) return 1; /* Early termination */ + if (lookup_info == NULL) + goto out; /* Early termination */ read_lock (&parent->u.dir.lock); if (dentry->d_fsdata) { set_current_state (TASK_UNINTERRUPTIBLE); add_wait_queue (&lookup_info->wait_queue, &wait); read_unlock (&parent->u.dir.lock); + /* at this point it is always (hopefully) locked */ + up(&dir->i_sem); schedule (); + down(&dir->i_sem); /* * This does not need nor should remove wait from wait_queue. * Wait queue head is never reused - nothing is ever added to it @@ -2271,6 +2307,10 @@ static int devfs_d_revalidate_wait (stru } else read_unlock (&parent->u.dir.lock); + +out: + if (need_lock) + up(&dir->i_sem); return 1; } /* End Function devfs_d_revalidate_wait */ @@ -2320,6 +2360,7 @@ static struct dentry *devfs_lookup (stru revalidation */ up (&dir->i_sem); wait_for_devfsd_finished (fs_info); /* If I'm not devfsd, must wait */ + down (&dir->i_sem); /* Grab it again because them's the rules */ de = lookup_info.de; /* If someone else has been so kind as to make the inode, we go home early */ @@ -2349,7 +2390,6 @@ out: dentry->d_fsdata = NULL; wake_up (&lookup_info.wait_queue); write_unlock (&parent->u.dir.lock); - down (&dir->i_sem); /* Grab it again because them's the rules */ devfs_put (de); return retval; } /* End Function devfs_lookup */