summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
authorCharles Manning <cdhmanning@gmail.com>2009-11-17 07:49:32 +1300
committerArve Hjønnevåg <arve@android.com>2010-02-08 15:09:12 -0800
commitef330d560d9c3bb66ab1031af243b3171bba219f (patch)
treeb10f1f170b54c011a214aa20f71a82e0377e691c /fs
parent75e82381df19bcb8bf6681f3fe1a2a8360e6cddc (diff)
yaffs: Fix readdir hang.
This aproach uses a search context iterator to allow yaffs to be unlocked during the filldir. The search context stores the next object to visited. If changes to the directory (ie unlinks) cause the directory to be modified then a callback moves the iterator to the next object if need be, thus preventing the iterator from being corrupted. Change-Id: Ia4735e42732f03a396ced7275e33facdc4f1beac Signed-off-by: Charles Manning <cdhmanning@gmail.com>
Diffstat (limited to 'fs')
-rw-r--r--fs/yaffs2/yaffs_fs.c157
-rw-r--r--fs/yaffs2/yaffs_guts.h9
2 files changed, 155 insertions, 11 deletions
diff --git a/fs/yaffs2/yaffs_fs.c b/fs/yaffs2/yaffs_fs.c
index 45aa158b18d3..4c155aef0a35 100644
--- a/fs/yaffs2/yaffs_fs.c
+++ b/fs/yaffs2/yaffs_fs.c
@@ -378,6 +378,122 @@ static void yaffs_GrossUnlock(yaffs_Device *dev)
up(&dev->grossLock);
}
+
+/*-----------------------------------------------------------------*/
+/* Directory search context allows us to unlock access to yaffs during
+ * filldir without causing problems with the directory being modified.
+ * This is similar to the tried and tested mechanism used in yaffs direct.
+ *
+ * A search context iterates along a doubly linked list of siblings in the
+ * directory. If the iterating object is deleted then this would corrupt
+ * the list iteration, likely causing a crash. The search context avoids
+ * this by using the removeObjectCallback to move the search context to the
+ * next object before the object is deleted.
+ *
+ * Many readdirs (and thus seach conexts) may be alive simulateously so
+ * each yaffs_Device has a list of these.
+ *
+ * A seach context lives for the duration of a readdir.
+ *
+ * All these functions must be called while yaffs is locked.
+ */
+
+struct yaffs_SearchContext {
+ yaffs_Device *dev;
+ yaffs_Object *dirObj;
+ yaffs_Object *nextReturn;
+ struct ylist_head others;
+};
+
+/*
+ * yaffs_NewSearch() creates a new search context, initialises it and
+ * adds it to the device's search context list.
+ *
+ * Called at start of readdir.
+ */
+static struct yaffs_SearchContext * yaffs_NewSearch(yaffs_Object *dir)
+{
+ yaffs_Device *dev = dir->myDev;
+ struct yaffs_SearchContext *sc = YMALLOC(sizeof(struct yaffs_SearchContext));
+ if(sc){
+ sc->dirObj = dir;
+ sc->dev = dev;
+ if( ylist_empty(&sc->dirObj->variant.directoryVariant.children))
+ sc->nextReturn = NULL;
+ else
+ sc->nextReturn = ylist_entry(
+ dir->variant.directoryVariant.children.next,
+ yaffs_Object,siblings);
+ YINIT_LIST_HEAD(&sc->others);
+ ylist_add(&sc->others,&dev->searchContexts);
+ }
+ return sc;
+}
+
+/*
+ * yaffs_EndSearch() disposes of a search context and cleans up.
+ */
+static void yaffs_EndSearch(struct yaffs_SearchContext * sc)
+{
+ if(sc){
+ ylist_del(&sc->others);
+ YFREE(sc);
+ }
+}
+
+/*
+ * yaffs_SearchAdvance() moves a search context to the next object.
+ * Called when the search iterates or when an object removal causes
+ * the search context to be moved to the next object.
+ */
+static void yaffs_SearchAdvance(struct yaffs_SearchContext *sc)
+{
+ if(!sc)
+ return;
+
+ if( sc->nextReturn == NULL ||
+ ylist_empty(&sc->dirObj->variant.directoryVariant.children))
+ sc->nextReturn = NULL;
+ else {
+ struct ylist_head *next = sc->nextReturn->siblings.next;
+
+ if( next == &sc->dirObj->variant.directoryVariant.children)
+ sc->nextReturn = NULL; /* end of list */
+ else
+ sc->nextReturn = ylist_entry(next,yaffs_Object,siblings);
+ }
+}
+
+/*
+ * yaffs_RemoveObjectCallback() is called when an object is unlinked.
+ * We check open search contexts and advance any which are currently
+ * on the object being iterated.
+ */
+static void yaffs_RemoveObjectCallback(yaffs_Object *obj)
+{
+
+ struct ylist_head *i;
+ struct yaffs_SearchContext *sc;
+ struct ylist_head *search_contexts = &obj->myDev->searchContexts;
+
+
+ /* Iterate through the directory search contexts.
+ * If any are currently on the object being removed, then advance
+ * the search context to the next object to prevent a hanging pointer.
+ */
+ ylist_for_each(i, search_contexts) {
+ if (i) {
+ sc = ylist_entry(i, struct yaffs_SearchContext,others);
+ if(sc->nextReturn == obj)
+ yaffs_SearchAdvance(sc);
+ }
+ }
+
+}
+
+
+/*-----------------------------------------------------------------*/
+
static int yaffs_readlink(struct dentry *dentry, char __user *buffer,
int buflen)
{
@@ -1128,10 +1244,11 @@ static int yaffs_readdir(struct file *f, void *dirent, filldir_t filldir)
{
yaffs_Object *obj;
yaffs_Device *dev;
+ struct yaffs_SearchContext *sc;
struct inode *inode = f->f_dentry->d_inode;
unsigned long offset, curoffs;
- struct ylist_head *i;
yaffs_Object *l;
+ int retVal = 0;
char name[YAFFS_MAX_NAME_LENGTH + 1];
@@ -1142,14 +1259,22 @@ static int yaffs_readdir(struct file *f, void *dirent, filldir_t filldir)
offset = f->f_pos;
+ sc = yaffs_NewSearch(obj);
+ if(!sc){
+ retVal = -ENOMEM;
+ goto unlock_out;
+ }
+
T(YAFFS_TRACE_OS, ("yaffs_readdir: starting at %d\n", (int)offset));
if (offset == 0) {
T(YAFFS_TRACE_OS,
("yaffs_readdir: entry . ino %d \n",
(int)inode->i_ino));
+ yaffs_GrossUnlock(dev);
if (filldir(dirent, ".", 1, offset, inode->i_ino, DT_DIR) < 0)
goto out;
+ yaffs_GrossLock(dev);
offset++;
f->f_pos++;
}
@@ -1157,9 +1282,11 @@ static int yaffs_readdir(struct file *f, void *dirent, filldir_t filldir)
T(YAFFS_TRACE_OS,
("yaffs_readdir: entry .. ino %d \n",
(int)f->f_dentry->d_parent->d_inode->i_ino));
+ yaffs_GrossUnlock(dev);
if (filldir(dirent, "..", 2, offset,
f->f_dentry->d_parent->d_inode->i_ino, DT_DIR) < 0)
goto out;
+ yaffs_GrossLock(dev);
offset++;
f->f_pos++;
}
@@ -1175,10 +1302,12 @@ static int yaffs_readdir(struct file *f, void *dirent, filldir_t filldir)
f->f_version = inode->i_version;
}
- ylist_for_each(i, &obj->variant.directoryVariant.children) {
+ while(sc->nextReturn){
curoffs++;
+ l = sc->nextReturn;
if (curoffs >= offset) {
- l = ylist_entry(i, yaffs_Object, siblings);
+ int this_inode = yaffs_GetObjectInode(l);
+ int this_type = yaffs_GetObjectType(l);
yaffs_GetObjectName(l, name,
YAFFS_MAX_NAME_LENGTH + 1);
@@ -1186,24 +1315,30 @@ static int yaffs_readdir(struct file *f, void *dirent, filldir_t filldir)
("yaffs_readdir: %s inode %d\n", name,
yaffs_GetObjectInode(l)));
+ yaffs_GrossUnlock(dev);
+
if (filldir(dirent,
name,
strlen(name),
offset,
- yaffs_GetObjectInode(l),
- yaffs_GetObjectType(l)) < 0)
- goto up_and_out;
+ this_inode,
+ this_type) < 0)
+ goto out;
+
+ yaffs_GrossLock(dev);
offset++;
f->f_pos++;
}
+ yaffs_SearchAdvance(sc);
}
-up_and_out:
-out:
+unlock_out:
yaffs_GrossUnlock(dev);
+out:
+ yaffs_EndSearch(sc);
- return 0;
+ return retVal;
}
/*
@@ -2115,6 +2250,10 @@ static struct super_block *yaffs_internal_read_super(int yaffsVersion,
/* we assume this is protected by lock_kernel() in mount/umount */
ylist_add_tail(&dev->devList, &yaffs_dev_list);
+ /* Directory search handling...*/
+ YINIT_LIST_HEAD(&dev->searchContexts);
+ dev->removeObjectCallback = yaffs_RemoveObjectCallback;
+
init_MUTEX(&dev->grossLock);
yaffs_GrossLock(dev);
diff --git a/fs/yaffs2/yaffs_guts.h b/fs/yaffs2/yaffs_guts.h
index acc2ae71c856..c47a319445ce 100644
--- a/fs/yaffs2/yaffs_guts.h
+++ b/fs/yaffs2/yaffs_guts.h
@@ -594,8 +594,9 @@ struct yaffs_DeviceStruct {
int isYaffs2;
/* The removeObjectCallback function must be supplied by OS flavours that
- * need it. The Linux kernel does not use this, but yaffs direct does use
- * it to implement the faster readdir
+ * need it.
+ * yaffs direct uses it to implement the faster readdir.
+ * Linux uses it to protect the directory during unlocking.
*/
void (*removeObjectCallback)(struct yaffs_ObjectStruct *obj);
@@ -635,10 +636,14 @@ struct yaffs_DeviceStruct {
struct semaphore sem; /* Semaphore for waiting on erasure.*/
struct semaphore grossLock; /* Gross locking semaphore */
+ struct rw_semaphore dirLock; /* Lock the directory structure */
__u8 *spareBuffer; /* For mtdif2 use. Don't know the size of the buffer
* at compile time so we have to allocate it.
+
*/
void (*putSuperFunc) (struct super_block *sb);
+ struct ylist_head searchContexts;
+
#endif
int isMounted;