Fix race yaffs_flush_inodes against iput
[yaffs2.git] / yaffs_vfs_single.c
index d8379d45964aa2f8dc8a82bc0e2c83c52386bcaa..1abbfd859a4217767a90d6af13d9a9862a091e19 100644 (file)
@@ -1,8 +1,7 @@
 /*
  * YAFFS: Yet Another Flash File System. A NAND-flash specific file system.
  *
- * Copyright (C) 2002-2011 Aleph One Ltd.
- *   for Toby Churchill Ltd and Brightstar Engineering
+ * Copyright (C) 2002-2018 Aleph One Ltd.
  *
  * Created by Charles Manning <charles@aleph1.co.uk>
  * Acknowledgements:
@@ -436,7 +435,8 @@ static int yaffs_unlink(struct inode *dir, struct dentry *dentry)
        return -ENOTEMPTY;
 }
 
-static int yaffs_sync_object(struct file *file, loff_t start, loff_t end, int datasync)
+static int yaffs_sync_object(struct file *file,
+                               loff_t start, loff_t end, int datasync)
 {
 
        struct yaffs_obj *obj;
@@ -449,7 +449,7 @@ static int yaffs_sync_object(struct file *file, loff_t start, loff_t end, int da
 
        yaffs_trace(YAFFS_TRACE_OS | YAFFS_TRACE_SYNC, "yaffs_sync_object");
        yaffs_gross_lock(dev);
-       yaffs_flush_file(obj, 1, datasync);
+       yaffs_flush_file(obj, 1, datasync, 0);
        yaffs_gross_unlock(dev);
        return 0;
 }
@@ -889,7 +889,7 @@ static int yaffs_file_flush(struct file *file, fl_owner_t id)
 
        yaffs_gross_lock(dev);
 
-       yaffs_flush_file(obj, 1, 0);
+       yaffs_flush_file(obj, 1, 0, 0);
 
        yaffs_gross_unlock(dev);
 
@@ -1097,12 +1097,14 @@ static int yaffs_readpage_nolock(struct file *f, struct page *pg)
        unsigned char *pg_buf;
        int ret;
        struct yaffs_dev *dev;
+       loff_t pos = ((loff_t) pg->index) << PAGE_CACHE_SHIFT;
 
        yaffs_trace(YAFFS_TRACE_OS,
-               "yaffs_readpage_nolock at %08x, size %08x",
-               (unsigned)(pg->index << PAGE_CACHE_SHIFT),
+               "yaffs_readpage_nolock at %lld, size %08x",
+               (long long)pos,
                (unsigned)PAGE_CACHE_SIZE);
 
+
        obj = yaffs_dentry_to_obj(f->f_dentry);
 
        dev = obj->my_dev;
@@ -1114,8 +1116,7 @@ static int yaffs_readpage_nolock(struct file *f, struct page *pg)
 
        yaffs_gross_lock(dev);
 
-       ret = yaffs_file_rd(obj, pg_buf,
-                           pg->index << PAGE_CACHE_SHIFT, PAGE_CACHE_SIZE);
+       ret = yaffs_file_rd(obj, pg_buf, pos, PAGE_CACHE_SIZE);
 
        yaffs_gross_unlock(dev);
 
@@ -1218,7 +1219,8 @@ static int yaffs_writepage(struct page *page, struct writeback_control *wbc)
                (int)obj->variant.file_variant.file_size, (int)inode->i_size);
 
        n_written = yaffs_wr_file(obj, buffer,
-                                 page->index << PAGE_CACHE_SHIFT, n_bytes, 0);
+                                 ((loff_t)page->index) << PAGE_CACHE_SHIFT,
+                                 n_bytes, 0);
 
        yaffs_touch_super(dev);
 
@@ -1487,15 +1489,34 @@ static void yaffs_flush_inodes(struct super_block *sb)
 {
        struct inode *iptr;
        struct yaffs_obj *obj;
+       struct yaffs_dev *dev = yaffs_super_to_dev(sb);
 
+       spin_lock(&sb->s_inode_list_lock);
        list_for_each_entry(iptr, &sb->s_inodes, i_sb_list) {
+               spin_lock(&inode->i_lock);
+               if (iptr->i_state & (I_FREEING|I_WILL_FREE|I_NEW)) {
+                       spin_unlock(&inode->i_lock);
+                       continue;
+               }
+
+               __iget(iptr);
+               spin_unlock(&inode->i_lock);
+               spin_unlock(&sb->s_inode_list_lock);
+
                obj = yaffs_inode_to_obj(iptr);
                if (obj) {
                        yaffs_trace(YAFFS_TRACE_OS,
                                "flushing obj %d", obj->obj_id);
-                       yaffs_flush_file(obj, 1, 0);
+                       yaffs_flush_file(obj, 1, 0, 0);
                }
+
+               yaffs_gross_unlock(dev);
+               iput(iptr);
+               yaffs_gross_lock(dev);
+
+               spin_lock(&sb->s_inode_list_lock);
        }
+       spin_unlock(&sb->s_inode_list_lock);
 }
 
 static void yaffs_flush_super(struct super_block *sb, int do_checkpoint)
@@ -1507,7 +1528,7 @@ static void yaffs_flush_super(struct super_block *sb, int do_checkpoint)
 
        yaffs_flush_inodes(sb);
        yaffs_update_dirty_dirs(dev);
-       yaffs_flush_whole_cache(dev);
+       yaffs_flush_whole_cache(dev, 1);
        if (do_checkpoint)
                yaffs_checkpoint_save(dev);
 }
@@ -1909,6 +1930,7 @@ static void yaffs_fill_inode_from_obj(struct inode *inode,
 static void yaffs_put_super(struct super_block *sb)
 {
        struct yaffs_dev *dev = yaffs_super_to_dev(sb);
+       struct mtd_info *mtd = yaffs_dev_to_mtd(dev);
 
        yaffs_trace(YAFFS_TRACE_OS, "yaffs_put_super");
 
@@ -1938,24 +1960,62 @@ static void yaffs_put_super(struct super_block *sb)
        }
 
        kfree(dev);
+
+       if (mtd && mtd->sync)
+               mtd->sync(mtd);
+
+       if (mtd)
+               put_mtd_device(mtd);
 }
 
-static void yaffs_mtd_put_super(struct super_block *sb)
+/* the function only is used to change dev->read_only when this file system
+ * is remounted.
+ */
+static int yaffs_remount_fs(struct super_block *sb, int *flags, char *data)
 {
-       struct mtd_info *mtd = yaffs_dev_to_mtd(yaffs_super_to_dev(sb));
+       int read_only = 0;
+       struct mtd_info *mtd;
+       struct yaffs_dev *dev = 0;
 
-       if (mtd->sync)
-               mtd->sync(mtd);
+       /* Get the device */
+       mtd = get_mtd_device(NULL, MINOR(sb->s_dev));
+       if (!mtd) {
+               yaffs_trace(YAFFS_TRACE_ALWAYS,
+                       "MTD device #%u doesn't appear to exist",
+                       MINOR(sb->s_dev));
+               return 1;
+       }
+
+       /* Check it's NAND */
+       if (mtd->type != MTD_NANDFLASH) {
+               yaffs_trace(YAFFS_TRACE_ALWAYS,
+                       "MTD device is not NAND it's type %d",
+                       mtd->type);
+               return 1;
+       }
 
-       put_mtd_device(mtd);
+       read_only = ((*flags & MS_RDONLY) != 0);
+       if (!read_only && !(mtd->flags & MTD_WRITEABLE)) {
+               read_only = 1;
+               printk(KERN_INFO
+                       "yaffs: mtd is read only, setting superblock read only");
+               *flags |= MS_RDONLY;
+       }
+
+       dev = sb->s_fs_info;
+       dev->read_only = read_only;
+
+       return 0;
 }
 
+
 static const struct super_operations yaffs_super_ops = {
        .statfs = yaffs_statfs,
        .put_super = yaffs_put_super,
        .evict_inode = yaffs_evict_inode,
        .sync_fs = yaffs_sync_fs,
        .write_super = yaffs_write_super,
+       .remount_fs = yaffs_remount_fs,
 };
 
 static struct super_block *yaffs_internal_read_super(int yaffs_version,
@@ -2031,7 +2091,7 @@ static struct super_block *yaffs_internal_read_super(int yaffs_version,
 
        /* Get the device */
        mtd = get_mtd_device(NULL, MINOR(sb->s_dev));
-       if (!mtd) {
+       if (IS_ERR(mtd)) {
                yaffs_trace(YAFFS_TRACE_ALWAYS,
                        "MTD device #%u doesn't appear to exist",
                        MINOR(sb->s_dev));
@@ -2125,9 +2185,7 @@ static struct super_block *yaffs_internal_read_super(int yaffs_version,
                kfree(context);
                dev = NULL;
                context = NULL;
-       }
 
-       if (!dev) {
                /* Deep shit could not allocate device structure */
                yaffs_trace(YAFFS_TRACE_ALWAYS,
                        "yaffs_read_super failed trying to allocate yaffs_dev");
@@ -2210,7 +2268,7 @@ static struct super_block *yaffs_internal_read_super(int yaffs_version,
        yaffs_dev_to_lc(dev)->put_super_fn = yaffs_mtd_put_super;
 
        param->sb_dirty_fn = yaffs_touch_super;
-       param->gc_control = yaffs_gc_control_callback;
+       param->gc_control_fn = yaffs_gc_control_callback;
 
        yaffs_dev_to_lc(dev)->super = sb;
 
@@ -2257,6 +2315,8 @@ static struct super_block *yaffs_internal_read_super(int yaffs_version,
        if (!context->bg_thread)
                param->defered_dir_update = 0;
 
+       sb->s_maxbytes = yaffs_max_file_size(dev);
+
        /* Release lock before yaffs_get_inode() */
        yaffs_gross_unlock(dev);
 
@@ -2366,9 +2426,10 @@ static char *yaffs_dump_dev_part0(char *buf, struct yaffs_dev *dev)
 
 static char *yaffs_dump_dev_part1(char *buf, struct yaffs_dev *dev)
 {
-       buf +=
-           sprintf(buf, "data_bytes_per_chunk.. %d\n",
-                   dev->data_bytes_per_chunk);
+       buf += sprintf(buf, "max file size......... %lld\n",
+                       (long long) yaffs_max_file_size(dev));
+       buf += sprintf(buf, "data_bytes_per_chunk.. %d\n",
+                       dev->data_bytes_per_chunk);
        buf += sprintf(buf, "chunk_grp_bits........ %d\n", dev->chunk_grp_bits);
        buf += sprintf(buf, "chunk_grp_size........ %d\n", dev->chunk_grp_size);
        buf +=