Merge branch 'master' of ssh://www.aleph1.co.uk/home/aleph1/git/yaffs2
[yaffs2.git] / yaffs_fs.c
index 15096a9b86afcea8a8b73ba5876940b5c70a3360..ecf2bbe4d48bc458fff4396e663b8b1bc8a3e781 100644 (file)
 #define YAFFS_COMPILE_EXPORTFS
 #endif
 
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,35))
+#define YAFFS_USE_SETATTR_COPY
+#define YAFFS_USE_TRUNCATE_SETSIZE
+#endif
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,35))
+#define YAFFS_HAS_EVICT_INODE
+#endif
+
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,13))
+#define YAFFS_NEW_FOLLOW_LINK 1
+#else
+#define YAFFS_NEW_FOLLOW_LINK 0
+#endif
 
 #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19))
 #include <linux/config.h>
 #include <linux/string.h>
 #include <linux/ctype.h>
 
+#if (YAFFS_NEW_FOLLOW_LINK == 1)
+#include <linux/namei.h>
+#endif
+
 #ifdef YAFFS_COMPILE_EXPORTFS
 #include <linux/exportfs.h>
 #endif
 #define YPROC_ROOT  NULL
 #endif
 
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26))
+#define Y_INIT_TIMER(a)        init_timer(a)
+#else
+#define Y_INIT_TIMER(a)        init_timer_on_stack(a)
+#endif
+
 #if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17))
 #define WRITE_SIZE_STR "writesize"
 #define WRITE_SIZE(mtd) ((mtd)->writesize)
@@ -142,6 +165,7 @@ unsigned int yaffs_traceMask = YAFFS_TRACE_BAD_BLOCKS | YAFFS_TRACE_ALWAYS;
 unsigned int yaffs_wr_attempts = YAFFS_WR_ATTEMPTS;
 unsigned int yaffs_auto_checkpoint = 1;
 unsigned int yaffs_gc_control = 1;
+unsigned int yaffs_bg_enable = 1;
 
 /* Module Parameters */
 #if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 5, 0))
@@ -149,6 +173,7 @@ module_param(yaffs_traceMask, uint, 0644);
 module_param(yaffs_wr_attempts, uint, 0644);
 module_param(yaffs_auto_checkpoint, uint, 0644);
 module_param(yaffs_gc_control, uint, 0644);
+module_param(yaffs_bg_enable, uint, 0644);
 #else
 MODULE_PARM(yaffs_traceMask, "i");
 MODULE_PARM(yaffs_wr_attempts, "i");
@@ -258,8 +283,12 @@ static int yaffs_statfs(struct super_block *sb, struct statfs *buf);
 static void yaffs_put_inode(struct inode *inode);
 #endif
 
+#ifdef YAFFS_HAS_EVICT_INODE
+static void yaffs_evict_inode(struct inode *);
+#else
 static void yaffs_delete_inode(struct inode *);
 static void yaffs_clear_inode(struct inode *);
+#endif
 
 static int yaffs_readpage(struct file *file, struct page *page);
 #if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 5, 0))
@@ -295,13 +324,20 @@ static int yaffs_commit_write(struct file *f, struct page *pg, unsigned offset,
 
 static int yaffs_readlink(struct dentry *dentry, char __user *buffer,
                                int buflen);
-#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 13))
+#if (YAFFS_NEW_FOLLOW_LINK == 1)
+void yaffs_put_link(struct dentry *dentry, struct nameidata *nd, void *alias);
 static void *yaffs_follow_link(struct dentry *dentry, struct nameidata *nd);
 #else
 static int yaffs_follow_link(struct dentry *dentry, struct nameidata *nd);
 #endif
+
+static void yaffs_MarkSuperBlockDirty(yaffs_Device *dev);
+
 static loff_t yaffs_dir_llseek(struct file *file, loff_t offset, int origin);
 
+static int yaffs_vfs_setattr(struct inode *, struct iattr *);
+
+
 static struct address_space_operations yaffs_file_address_operations = {
        .readpage = yaffs_readpage,
        .writepage = yaffs_writepage,
@@ -314,6 +350,7 @@ static struct address_space_operations yaffs_file_address_operations = {
 #endif
 };
 
+
 #if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 22))
 static const struct file_operations yaffs_file_operations = {
        .read = do_sync_read,
@@ -379,6 +416,9 @@ static const struct inode_operations yaffs_file_inode_operations = {
 static const struct inode_operations yaffs_symlink_inode_operations = {
        .readlink = yaffs_readlink,
        .follow_link = yaffs_follow_link,
+#if (YAFFS_NEW_FOLLOW_LINK == 1)
+       .put_link = yaffs_put_link,
+#endif
        .setattr = yaffs_setattr,
 #ifdef CONFIG_YAFFS_XATTR
        .setxattr = yaffs_setxattr,
@@ -424,12 +464,39 @@ static const struct super_operations yaffs_super_ops = {
        .put_inode = yaffs_put_inode,
 #endif
        .put_super = yaffs_put_super,
+#ifdef YAFFS_HAS_EVICT_INODE
+       .evict_inode = yaffs_evict_inode,
+#else
        .delete_inode = yaffs_delete_inode,
        .clear_inode = yaffs_clear_inode,
+#endif
        .sync_fs = yaffs_sync_fs,
        .write_super = yaffs_write_super,
 };
 
+
+static  int yaffs_vfs_setattr(struct inode *inode, struct iattr *attr)
+{
+#ifdef  YAFFS_USE_SETATTR_COPY
+       setattr_copy(inode,attr);
+       return 0;
+#else
+       return inode_setattr(inode, attr);
+#endif
+
+}
+
+static  int yaffs_vfs_setsize(struct inode *inode, loff_t newsize)
+{
+#ifdef  YAFFS_USE_TRUNCATE_SETSIZE
+       truncate_setsize(inode,newsize);
+       return 0;
+#else
+       return simple_setsize(inode, newsize);
+#endif
+
+}
+
 static unsigned yaffs_gc_control_callback(yaffs_Device *dev)
 {
        return yaffs_gc_control;
@@ -651,7 +718,7 @@ static int yaffs_readlink(struct dentry *dentry, char __user *buffer,
        return ret;
 }
 
-#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 13))
+#if (YAFFS_NEW_FOLLOW_LINK == 1)
 static void *yaffs_follow_link(struct dentry *dentry, struct nameidata *nd)
 #else
 static int yaffs_follow_link(struct dentry *dentry, struct nameidata *nd)
@@ -664,7 +731,6 @@ static int yaffs_follow_link(struct dentry *dentry, struct nameidata *nd)
        yaffs_GrossLock(dev);
 
        alias = yaffs_GetSymlinkAlias(yaffs_DentryToObject(dentry));
-
        yaffs_GrossUnlock(dev);
 
        if (!alias) {
@@ -672,16 +738,25 @@ static int yaffs_follow_link(struct dentry *dentry, struct nameidata *nd)
                goto out;
        }
 
-       ret = vfs_follow_link(nd, alias);
-       kfree(alias);
+#if (YAFFS_NEW_FOLLOW_LINK == 1)
+       nd_set_link(nd, alias);
+       ret = (int)alias;
 out:
-#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 13))
        return ERR_PTR(ret);
 #else
+       ret = vfs_follow_link(nd, alias);
+       kfree(alias);
+out:
        return ret;
 #endif
 }
 
+#if (YAFFS_NEW_FOLLOW_LINK == 1)
+void yaffs_put_link(struct dentry *dentry, struct nameidata *nd, void *alias) {
+       kfree(alias);
+}
+#endif
+
 struct inode *yaffs_get_inode(struct super_block *sb, int mode, int dev,
                                yaffs_Object *obj);
 
@@ -764,36 +839,85 @@ static void yaffs_put_inode(struct inode *inode)
 }
 #endif
 
-/* clear is called to tell the fs to release any per-inode data it holds */
-static void yaffs_clear_inode(struct inode *inode)
+
+static void yaffs_UnstitchObject(struct inode *inode, yaffs_Object *obj)
+{
+       /* Clear the association between the inode and
+        * the yaffs_Object.
+        */
+       obj->myInode = NULL;
+       yaffs_InodeToObjectLV(inode) = NULL;
+
+       /* If the object freeing was deferred, then the real
+        * free happens now.
+        * This should fix the inode inconsistency problem.
+        */
+       yaffs_HandleDeferedFree(obj);
+}
+
+#ifdef YAFFS_HAS_EVICT_INODE
+/* yaffs_evict_inode combines into one operation what was previously done in
+ * yaffs_clear_inode() and yaffs_delete_inode()
+ *
+ */
+static void yaffs_evict_inode( struct inode *inode)
 {
        yaffs_Object *obj;
        yaffs_Device *dev;
+       int deleteme = 0;
 
        obj = yaffs_InodeToObject(inode);
 
        T(YAFFS_TRACE_OS,
-               (TSTR("yaffs_clear_inode: ino %d, count %d %s\n"), (int)inode->i_ino,
+               (TSTR("yaffs_evict_inode: ino %d, count %d %s\n"), (int)inode->i_ino,
                atomic_read(&inode->i_count),
                obj ? "object exists" : "null object"));
 
+       if (!inode->i_nlink && !is_bad_inode(inode))
+               deleteme = 1;
+       truncate_inode_pages(&inode->i_data,0);
+       end_writeback(inode);
+
+       if(deleteme && obj){
+               dev = obj->myDev;
+               yaffs_GrossLock(dev);
+               yaffs_DeleteObject(obj);
+               yaffs_GrossUnlock(dev);
+       }
        if (obj) {
                dev = obj->myDev;
                yaffs_GrossLock(dev);
+               yaffs_UnstitchObject(inode,obj);
+               yaffs_GrossUnlock(dev);
+       }
 
-               /* Clear the association between the inode and
-                * the yaffs_Object.
-                */
-               obj->myInode = NULL;
-               yaffs_InodeToObjectLV(inode) = NULL;
 
-               /* If the object freeing was deferred, then the real
-                * free happens now.
-                * This should fix the inode inconsistency problem.
-                */
+}
+#else
 
-               yaffs_HandleDeferedFree(obj);
+/* clear is called to tell the fs to release any per-inode data it holds.
+ * The object might still exist on disk and is just being thrown out of the cache
+ * or else the object has actually been deleted and we're being called via
+ * the chain
+ *   yaffs_delete_inode() -> clear_inode()->yaffs_clear_inode()
+ */
 
+static void yaffs_clear_inode(struct inode *inode)
+{
+       yaffs_Object *obj;
+       yaffs_Device *dev;
+
+       obj = yaffs_InodeToObject(inode);
+
+       T(YAFFS_TRACE_OS,
+               (TSTR("yaffs_clear_inode: ino %d, count %d %s\n"), (int)inode->i_ino,
+               atomic_read(&inode->i_count),
+               obj ? "object exists" : "null object"));
+
+       if (obj) {
+               dev = obj->myDev;
+               yaffs_GrossLock(dev);
+               yaffs_UnstitchObject(inode,obj);
                yaffs_GrossUnlock(dev);
        }
 
@@ -825,6 +949,8 @@ static void yaffs_delete_inode(struct inode *inode)
 #endif
        clear_inode(inode);
 }
+#endif
+
 
 #if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17))
 static int yaffs_file_flush(struct file *file, fl_owner_t id)
@@ -929,6 +1055,7 @@ static int yaffs_writepage(struct page *page, struct writeback_control *wbc)
 static int yaffs_writepage(struct page *page)
 #endif
 {
+       yaffs_Device *dev;
        struct address_space *mapping = page->mapping;
        struct inode *inode;
        unsigned long end_index;
@@ -976,7 +1103,8 @@ static int yaffs_writepage(struct page *page)
        buffer = kmap(page);
 
        obj = yaffs_InodeToObject(inode);
-       yaffs_GrossLock(obj->myDev);
+       dev = obj->myDev;
+       yaffs_GrossLock(dev);
 
        T(YAFFS_TRACE_OS,
                (TSTR("yaffs_writepage at %08x, size %08x\n"),
@@ -988,11 +1116,13 @@ static int yaffs_writepage(struct page *page)
        nWritten = yaffs_WriteDataToFile(obj, buffer,
                        page->index << PAGE_CACHE_SHIFT, nBytes, 0);
 
+       yaffs_MarkSuperBlockDirty(dev);
+
        T(YAFFS_TRACE_OS,
                (TSTR("writepag1: obj = %05x, ino = %05x\n"),
                (int)obj->variant.fileVariant.fileSize, (int)inode->i_size));
 
-       yaffs_GrossUnlock(obj->myDev);
+       yaffs_GrossUnlock(dev);
 
        kunmap(page);
        set_page_writeback(page);
@@ -1328,6 +1458,8 @@ static ssize_t yaffs_file_write(struct file *f, const char *buf, size_t n,
 
        nWritten = yaffs_WriteDataToFile(obj, buf, ipos, n, 0);
 
+       yaffs_MarkSuperBlockDirty(dev);
+
        T(YAFFS_TRACE_OS,
                (TSTR("yaffs_file_write: %d(%x) bytes written\n"),
                (unsigned )n,(unsigned)n));
@@ -1851,10 +1983,12 @@ static int yaffs_setattr(struct dentry *dentry, struct iattr *attr)
        if (error == 0) {
                int result;
                if (!error){
-                       error = inode_setattr(inode, attr);
+                       error = yaffs_vfs_setattr(inode, attr);
                        T(YAFFS_TRACE_OS,(TSTR("inode_setattr called\n")));
-                       if (attr->ia_valid & ATTR_SIZE)
-                               truncate_inode_pages(&inode->i_data,attr->ia_size);
+                       if (attr->ia_valid & ATTR_SIZE){
+                               yaffs_vfs_setsize(inode,attr->ia_size);
+                               inode->i_blocks = (inode->i_size + 511) >> 9;
+                       }
                }
                dev = yaffs_InodeToObject(inode)->myDev;
                if (attr->ia_valid & ATTR_SIZE){
@@ -1920,9 +2054,8 @@ ssize_t yaffs_getxattr(struct dentry *dentry, const char *name, void *buff,
        yaffs_Object *obj = yaffs_InodeToObject(inode);
 
        T(YAFFS_TRACE_OS,
-               (TSTR("yaffs_getxattr of object %d\n"),
-               obj->objectId));
-
+               (TSTR("yaffs_getxattr \"%s\" from object %d\n"),
+               name, obj->objectId));
 
        if (error == 0) {
                dev = obj->myDev;
@@ -2202,12 +2335,12 @@ static int yaffs_BackgroundThread(void *data)
 
                now = jiffies;
 
-               if(time_after(now, next_dir_update)){
+               if(time_after(now, next_dir_update) && yaffs_bg_enable){
                        yaffs_UpdateDirtyDirectories(dev);
                        next_dir_update = now + HZ;
                }
 
-               if(time_after(now,next_gc)){
+               if(time_after(now,next_gc) && yaffs_bg_enable){
                        if(!dev->isCheckpointed){
                                urgency = yaffs_bg_gc_urgency(dev);
                                gcResult = yaffs_BackgroundGarbageCollect(dev, urgency);
@@ -2231,7 +2364,7 @@ static int yaffs_BackgroundThread(void *data)
                if(time_before(expires,now))
                        expires = now + HZ;
 
-               init_timer_on_stack(&timer);
+               Y_INIT_TIMER(&timer);
                timer.expires = expires+1;
                timer.data = (unsigned long) current;
                timer.function = yaffs_background_waker;
@@ -2253,6 +2386,9 @@ static int yaffs_BackgroundStart(yaffs_Device *dev)
        int retval = 0;
        struct yaffs_LinuxContext *context = yaffs_DeviceToLC(dev);
 
+       if(dev->readOnly)
+               return -1;
+
        context->bgRunning = 1;
 
        context->bgThread = kthread_run(yaffs_BackgroundThread,
@@ -2544,6 +2680,8 @@ static struct super_block *yaffs_internal_read_super(int yaffsVersion,
        struct yaffs_LinuxContext *context = NULL;
        yaffs_DeviceParam *param;
 
+       int readOnly = 0;
+
        yaffs_options options;
 
        unsigned mount_id;
@@ -2555,6 +2693,9 @@ static struct super_block *yaffs_internal_read_super(int yaffsVersion,
        sb->s_op = &yaffs_super_ops;
        sb->s_flags |= MS_NOATIME;
 
+       readOnly =((sb->s_flags & MS_RDONLY) != 0);
+
+
 #ifdef YAFFS_COMPILE_EXPORTFS
        sb->s_export_op = &yaffs_export_ops;
 #endif
@@ -2566,9 +2707,10 @@ static struct super_block *yaffs_internal_read_super(int yaffsVersion,
        else if (!yaffs_devname(sb, devname_buf))
                printk(KERN_INFO "yaffs: devname is NULL\n");
        else
-               printk(KERN_INFO "yaffs: dev is %d name is \"%s\"\n",
+               printk(KERN_INFO "yaffs: dev is %d name is \"%s\" %s\n",
                       sb->s_dev,
-                      yaffs_devname(sb, devname_buf));
+                      yaffs_devname(sb, devname_buf),
+                      readOnly ? "ro" : "rw");
 
        if (!data_str)
                data_str = "";
@@ -2708,6 +2850,12 @@ static struct super_block *yaffs_internal_read_super(int yaffsVersion,
         * Set the yaffs_Device up for mtd
         */
 
+       if (!readOnly && !(mtd->flags & MTD_WRITEABLE)){
+               readOnly = 1;
+               printk(KERN_INFO "yaffs: mtd is read only, setting superblock read only");
+               sb->s_flags |= MS_RDONLY;
+       }
+
        dev = kmalloc(sizeof(yaffs_Device), GFP_KERNEL);
        context = kmalloc(sizeof(struct yaffs_LinuxContext),GFP_KERNEL);
        
@@ -2736,7 +2884,7 @@ static struct super_block *yaffs_internal_read_super(int yaffsVersion,
        context->dev = dev;
        context->superBlock = sb;
 
-       
+       dev->readOnly = readOnly;
 
 #if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 5, 0))
        sb->s_fs_info = dev;
@@ -3061,6 +3209,7 @@ static char *yaffs_dump_dev_part1(char *buf, yaffs_Device * dev)
        buf += sprintf(buf, "allGCs............. %u\n", dev->allGCs);
        buf += sprintf(buf, "passiveGCs......... %u\n", dev->passiveGCs);
        buf += sprintf(buf, "oldestDirtyGCs..... %u\n", dev->oldestDirtyGCs);
+       buf += sprintf(buf, "nGCBlocks.......... %u\n", dev->nGCBlocks);
        buf += sprintf(buf, "backgroundGCs...... %u\n", dev->backgroundGCs);
        buf += sprintf(buf, "nRetriedWrites..... %u\n", dev->nRetriedWrites);
        buf += sprintf(buf, "nRetireBlocks...... %u\n", dev->nRetiredBlocks);