fs/adfs: dir: add helper to mark directory buffers dirty
[linux-2.6-microblaze.git] / fs / adfs / dir.c
index a54c532..e8aafc6 100644 (file)
@@ -6,6 +6,7 @@
  *
  *  Common directory handling for ADFS
  */
+#include <linux/slab.h>
 #include "adfs.h"
 
 /*
  */
 static DEFINE_RWLOCK(adfs_dir_lock);
 
+int adfs_dir_copyfrom(void *dst, struct adfs_dir *dir, unsigned int offset,
+                     size_t len)
+{
+       struct super_block *sb = dir->sb;
+       unsigned int index, remain;
+
+       index = offset >> sb->s_blocksize_bits;
+       offset &= sb->s_blocksize - 1;
+       remain = sb->s_blocksize - offset;
+       if (index + (remain < len) >= dir->nr_buffers)
+               return -EINVAL;
+
+       if (remain < len) {
+               memcpy(dst, dir->bhs[index]->b_data + offset, remain);
+               dst += remain;
+               len -= remain;
+               index += 1;
+               offset = 0;
+       }
+
+       memcpy(dst, dir->bhs[index]->b_data + offset, len);
+
+       return 0;
+}
+
+int adfs_dir_copyto(struct adfs_dir *dir, unsigned int offset, const void *src,
+                   size_t len)
+{
+       struct super_block *sb = dir->sb;
+       unsigned int index, remain;
+
+       index = offset >> sb->s_blocksize_bits;
+       offset &= sb->s_blocksize - 1;
+       remain = sb->s_blocksize - offset;
+       if (index + (remain < len) >= dir->nr_buffers)
+               return -EINVAL;
+
+       if (remain < len) {
+               memcpy(dir->bhs[index]->b_data + offset, src, remain);
+               src += remain;
+               len -= remain;
+               index += 1;
+               offset = 0;
+       }
+
+       memcpy(dir->bhs[index]->b_data + offset, src, len);
+
+       return 0;
+}
+
+void adfs_dir_relse(struct adfs_dir *dir)
+{
+       unsigned int i;
+
+       for (i = 0; i < dir->nr_buffers; i++)
+               brelse(dir->bhs[i]);
+       dir->nr_buffers = 0;
+
+       if (dir->bhs != dir->bh)
+               kfree(dir->bhs);
+       dir->bhs = NULL;
+       dir->sb = NULL;
+}
+
+int adfs_dir_read_buffers(struct super_block *sb, u32 indaddr,
+                         unsigned int size, struct adfs_dir *dir)
+{
+       struct buffer_head **bhs;
+       unsigned int i, num;
+       int block;
+
+       num = ALIGN(size, sb->s_blocksize) >> sb->s_blocksize_bits;
+       if (num > ARRAY_SIZE(dir->bh)) {
+               /* We only allow one extension */
+               if (dir->bhs != dir->bh)
+                       return -EINVAL;
+
+               bhs = kcalloc(num, sizeof(*bhs), GFP_KERNEL);
+               if (!bhs)
+                       return -ENOMEM;
+
+               if (dir->nr_buffers)
+                       memcpy(bhs, dir->bhs, dir->nr_buffers * sizeof(*bhs));
+
+               dir->bhs = bhs;
+       }
+
+       for (i = dir->nr_buffers; i < num; i++) {
+               block = __adfs_block_map(sb, indaddr, i);
+               if (!block) {
+                       adfs_error(sb, "dir %06x has a hole at offset %u",
+                                  indaddr, i);
+                       goto error;
+               }
+
+               dir->bhs[i] = sb_bread(sb, block);
+               if (!dir->bhs[i]) {
+                       adfs_error(sb,
+                                  "dir %06x failed read at offset %u, mapped block 0x%08x",
+                                  indaddr, i, block);
+                       goto error;
+               }
+
+               dir->nr_buffers++;
+       }
+       return 0;
+
+error:
+       adfs_dir_relse(dir);
+
+       return -EIO;
+}
+
+static int adfs_dir_read(struct super_block *sb, u32 indaddr,
+                        unsigned int size, struct adfs_dir *dir)
+{
+       dir->sb = sb;
+       dir->bhs = dir->bh;
+       dir->nr_buffers = 0;
+
+       return ADFS_SB(sb)->s_dir->read(sb, indaddr, size, dir);
+}
+
+static int adfs_dir_read_inode(struct super_block *sb, struct inode *inode,
+                              struct adfs_dir *dir)
+{
+       int ret;
+
+       ret = adfs_dir_read(sb, inode->i_ino, inode->i_size, dir);
+       if (ret)
+               return ret;
+
+       if (ADFS_I(inode)->parent_id != dir->parent_id) {
+               adfs_error(sb,
+                          "parent directory id changed under me! (%06x but got %06x)\n",
+                          ADFS_I(inode)->parent_id, dir->parent_id);
+               adfs_dir_relse(dir);
+               ret = -EIO;
+       }
+
+       return ret;
+}
+
+static void adfs_dir_mark_dirty(struct adfs_dir *dir)
+{
+       unsigned int i;
+
+       /* Mark the buffers dirty */
+       for (i = 0; i < dir->nr_buffers; i++)
+               mark_buffer_dirty(dir->bhs[i]);
+}
+
+static int adfs_dir_sync(struct adfs_dir *dir)
+{
+       int err = 0;
+       int i;
+
+       for (i = dir->nr_buffers - 1; i >= 0; i--) {
+               struct buffer_head *bh = dir->bhs[i];
+               sync_dirty_buffer(bh);
+               if (buffer_req(bh) && !buffer_uptodate(bh))
+                       err = -EIO;
+       }
+
+       return err;
+}
+
 void adfs_object_fixup(struct adfs_dir *dir, struct object_info *obj)
 {
        unsigned int dots, i;
@@ -64,7 +232,7 @@ adfs_readdir(struct file *file, struct dir_context *ctx)
        if (ctx->pos >> 32)
                return 0;
 
-       ret = ops->read(sb, inode->i_ino, inode->i_size, &dir);
+       ret = adfs_dir_read_inode(sb, inode, &dir);
        if (ret)
                return ret;
 
@@ -95,7 +263,7 @@ unlock_out:
        read_unlock(&adfs_dir_lock);
 
 free_out:
-       ops->free(&dir);
+       adfs_dir_relse(&dir);
        return ret;
 }
 
@@ -110,12 +278,10 @@ adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait)
        printk(KERN_INFO "adfs_dir_update: object %06x in dir %06x\n",
                 obj->indaddr, obj->parent_id);
 
-       if (!ops->update) {
-               ret = -EINVAL;
-               goto out;
-       }
+       if (!ops->update)
+               return -EINVAL;
 
-       ret = ops->read(sb, obj->parent_id, 0, &dir);
+       ret = adfs_dir_read(sb, obj->parent_id, 0, &dir);
        if (ret)
                goto out;
 
@@ -123,13 +289,16 @@ adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait)
        ret = ops->update(&dir, obj);
        write_unlock(&adfs_dir_lock);
 
+       if (ret == 0)
+               adfs_dir_mark_dirty(&dir);
+
        if (wait) {
-               int err = ops->sync(&dir);
+               int err = adfs_dir_sync(&dir);
                if (!ret)
                        ret = err;
        }
 
-       ops->free(&dir);
+       adfs_dir_relse(&dir);
 out:
 #endif
        return ret;
@@ -167,18 +336,10 @@ static int adfs_dir_lookup_byname(struct inode *inode, const struct qstr *qstr,
        u32 name_len;
        int ret;
 
-       ret = ops->read(sb, inode->i_ino, inode->i_size, &dir);
+       ret = adfs_dir_read_inode(sb, inode, &dir);
        if (ret)
                goto out;
 
-       if (ADFS_I(inode)->parent_id != dir.parent_id) {
-               adfs_error(sb,
-                          "parent directory changed under me! (%06x but got %06x)\n",
-                          ADFS_I(inode)->parent_id, dir.parent_id);
-               ret = -EIO;
-               goto free_out;
-       }
-
        obj->parent_id = inode->i_ino;
 
        read_lock(&adfs_dir_lock);
@@ -201,7 +362,7 @@ unlock_out:
        read_unlock(&adfs_dir_lock);
 
 free_out:
-       ops->free(&dir);
+       adfs_dir_relse(&dir);
 out:
        return ret;
 }