New fix for readdir hang
authorcharles <charles>
Sun, 29 Nov 2009 21:50:10 +0000 (21:50 +0000)
committercharles <charles>
Sun, 29 Nov 2009 21:50:10 +0000 (21:50 +0000)
yaffs_fs.c
yaffs_guts.h

index 625683ca8bcdc4988616bacb261a8efbc77bf6b4..ab123f7bd5ee04064a9e1f57d0d02c2f6def73de 100644 (file)
@@ -32,7 +32,7 @@
  */
 
 const char *yaffs_fs_c_version =
-    "$Id: yaffs_fs.c,v 1.88 2009-11-29 21:42:47 charles Exp $";
+    "$Id: yaffs_fs.c,v 1.89 2009-11-29 21:50:10 charles Exp $";
 extern const char *yaffs_guts_c_version;
 
 #include <linux/version.h>
@@ -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;
 }
 
 /*
@@ -2158,6 +2293,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);
index f47c65edda47f8362c2569b4de74e8a7782438da..c58c84bf1f29962c1fcc74ab7596e7942a35330f 100644 (file)
@@ -599,8 +599,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);
 
@@ -640,10 +641,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;