[PATCH] fat: Editions to support fat_fallocate()

From: Steven Cavanagh
Date: Tue Dec 11 2007 - 21:55:53 EST


From: Steven Cavanagh <steven.cavanagh@xxxxxxxxxxxx>

Added support for fallocate for a msdos fat driver. This allows
preallocation of clusters to an inode before writes to reduce
file fragmentation

Signed-off-by: Steven.Cavanagh <steven.cavanagh@xxxxxxxxxxxx>
---

fs/fat/cache.c | 9 ++
fs/fat/file.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++
fs/fat/inode.c | 21 ++++++
include/linux/msdos_fs.h | 5 +
4 files changed, 201 insertions(+), 1 deletions(-)

diff --git a/fs/fat/cache.c b/fs/fat/cache.c
index 639b3b4..1a69ce4 100644
--- a/fs/fat/cache.c
+++ b/fs/fat/cache.c
@@ -8,6 +8,8 @@
* May 1999. AV. Fixed the bogosity with FAT32 (read "FAT28"). Fscking lusers.
*/

+#undef DEBUG
+
#include <linux/fs.h>
#include <linux/msdos_fs.h>
#include <linux/buffer_head.h>
@@ -316,6 +318,10 @@ int fat_bmap(struct inode *inode, sector

cluster = sector >> (sbi->cluster_bits - sb->s_blocksize_bits);
offset = sector & (sbi->sec_per_clus - 1);
+
+ pr_debug("fat_bmap():cluster:%d, offset:%d, last_block:%llu\n",
+ cluster, offset, last_block);
+
cluster = fat_bmap_cluster(inode, cluster);
if (cluster < 0)
return cluster;
@@ -324,6 +330,9 @@ int fat_bmap(struct inode *inode, sector
*mapped_blocks = sbi->sec_per_clus - offset;
if (*mapped_blocks > last_block - sector)
*mapped_blocks = last_block - sector;
+
+ pr_debug("fat_bmap():cluster:%d, phys:%llu\n",
+ cluster, *phys);
}
return 0;
}
diff --git a/fs/fat/file.c b/fs/fat/file.c
index 69a83b5..9698d42 100644
--- a/fs/fat/file.c
+++ b/fs/fat/file.c
@@ -6,6 +6,8 @@
* regular file handling primitives for fat-based filesystems
*/

+#undef DEBUG
+
#include <linux/capability.h>
#include <linux/module.h>
#include <linux/time.h>
@@ -15,6 +17,7 @@ #include <linux/buffer_head.h>
#include <linux/writeback.h>
#include <linux/backing-dev.h>
#include <linux/blkdev.h>
+#include <linux/falloc.h>

int fat_generic_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
@@ -312,8 +315,172 @@ int fat_getattr(struct vfsmount *mnt, st
}
EXPORT_SYMBOL_GPL(fat_getattr);

+/*
+ * preallocate space for a file. This implements fat fallocate inode
+ * operation, which gets called from sys_fallocate system call. User
+ * space requests len bytes at offset.
+ */
+long fat_fallocate(struct inode *inode, int mode, loff_t offset, loff_t len)
+{
+ unsigned int blkbits = inode->i_blkbits;
+ int ret = 0, err;
+ unsigned long offset_block, new_blocks;
+ unsigned long max_blocks, nblocks = 0;
+ unsigned long mapped_blocks = 0, cluster_offset = 0;
+
+ struct buffer_head bh;
+
+ loff_t newsize = 0;
+
+ struct super_block *sb = inode->i_sb;
+ struct msdos_sb_info *sbi = MSDOS_SB(sb);
+ sector_t phys;
+ struct timespec now;
+
+ /* preallocation to directories is currently not supported */
+ if (S_ISDIR(inode->i_mode)) {
+ printk(KERN_ERR
+ "fat_fallocate(): Directory prealloc not supported\n");
+ return -ENODEV;
+ }
+
+ offset_block = offset >> blkbits;
+ pr_debug("fat_fallocate:offset block:%lu\n", offset_block);
+
+ /* Determine new allocation block */
+ new_blocks = (MSDOS_BLOCK_ALIGN(len + offset, blkbits) >> blkbits)
+ - offset_block;
+ pr_debug("fat_fallocate:allocate block:%lu\n", new_blocks);
+
+ if ((offset_block + new_blocks) <=
+ (MSDOS_BLOCK_ALIGN(i_size_read(inode), blkbits)
+ >> blkbits)) {
+ printk(KERN_ERR
+ "fat_fallocate():Blocks already allocated\n");
+ return -EIO;
+ }
+ if (offset_block >
+ (MSDOS_BLOCK_ALIGN(i_size_read(inode), blkbits)
+ >> blkbits)) {
+ printk(KERN_ERR
+ "fat_fallocate():Offset error\n");
+ return -EIO;
+ }
+ while (ret >= 0 && nblocks < new_blocks) {
+
+ /* Allocate a new cluster after all the available
+ * blocks(sectors) have been allocated.
+ */
+ cluster_offset =
+ (unsigned long)offset_block & (sbi->sec_per_clus - 1);
+ if (!cluster_offset) {
+
+ ret = fat_add_cluster(inode);
+ if (ret) {
+ pr_debug("msdos_fallocate():Add cluster err\
+ inode#%lu, block = %lu, max_blocks = %lu\n",
+ inode->i_ino, offset_block, new_blocks);
+ break;
+ }
+ }
+
+ /* mapped blocks = 4 blocks/sector - offset into cluster */
+ mapped_blocks = sbi->sec_per_clus - cluster_offset;
+ pr_debug("msdos_fallocate():mapped_blocks:%lu\n",
+ mapped_blocks);
+
+ /*mapped_blocks and dos_max_blocks should
+ *be the same because mapped_blocks used to
+ *be calculated in __fat_get_block() as
+ * sbi->sec_per_clus - offset(sector offset)
+ */
+ max_blocks = min(mapped_blocks, (new_blocks-nblocks));
+
+ MSDOS_I(inode)->mmu_private +=
+ max_blocks << sb->s_blocksize_bits;
+
+ pr_debug("msdos_fallocate():MSDOS_I(inode)->mmu_private:%llu\n",
+ MSDOS_I(inode)->mmu_private);
+
+ pr_debug("msdos_fallocate():mapped_blocks:%lu,max_blocks:%lu\n",
+ mapped_blocks, max_blocks);
+
+ err = fat_bmap(inode, offset_block, &phys, &mapped_blocks);
+ if (err) {
+ printk(KERN_ERR
+ "msdos_fallocate():fat_bmap() error:%d\n", err);
+ return err;
+ }
+ pr_debug("msdos_fallocate():sector: %lu, phys: %llu\n",
+ offset_block, phys);
+
+ if (!phys) {
+ printk(KERN_ERR
+ "msdos_fallocate():Bad disk sector number\n");
+ return -EIO;
+ }
+ if (!mapped_blocks) {
+ printk(KERN_ERR
+ "msdos_fallocate():Block mapping error\n");
+ return -EIO;
+ }
+ set_buffer_new(&bh);
+ map_bh(&bh, sb, phys);
+ pr_debug("msdos_fallocate():b_size:%d, b_blocknr:%llu\n",
+ bh.b_size, bh.b_blocknr);
+
+ now = current_fs_time(inode->i_sb);
+ if (!timespec_equal(&inode->i_ctime, &now))
+ inode->i_ctime = now;
+
+ /*Increment the cluster block count*/
+ nblocks += mapped_blocks;
+ offset_block += mapped_blocks;
+ }
+
+ pr_debug("msdos_fallocate():fat_bmap():nblocks:%lu\n",
+ nblocks);
+ mark_inode_dirty(inode);
+
+ /*
+ * Time to update the file size.
+ * Update only when preallocation was requested beyond the file size.
+ */
+ if (!(mode & FALLOC_FL_KEEP_SIZE) &&
+ (offset + len) > i_size_read(inode)) {
+ if (!ret) {
+ /*
+ * if no error, we assume preallocation succeeded
+ * completely
+ */
+ mutex_lock(&inode->i_mutex);
+ i_size_write(inode, offset + len);
+ mutex_unlock(&inode->i_mutex);
+ pr_debug("msdos_fallocate():INODE SIZE:%llu\n",
+ i_size_read(inode));
+
+ } else if (ret && nblocks) {
+
+ printk(KERN_ERR
+ "msdos_fallocate(): Errors, updating inode size\n");
+ /* Handle partial allocation scenario */
+ mutex_lock(&inode->i_mutex);
+ newsize = (nblocks << blkbits) + i_size_read(inode);
+ i_size_write(inode,
+ MSDOS_BLOCK_ALIGN(newsize, blkbits));
+ mutex_unlock(&inode->i_mutex);
+ pr_debug("msdos_fallocate():INODE SIZE:%llu\n",
+ i_size_read(inode));
+ }
+ }
+
+ return ret;
+
+}
+
const struct inode_operations fat_file_inode_operations = {
.truncate = fat_truncate,
.setattr = fat_notify_change,
.getattr = fat_getattr,
+ .fallocate = fat_fallocate,
};
diff --git a/fs/fat/inode.c b/fs/fat/inode.c
index 920a576..463ead5 100644
--- a/fs/fat/inode.c
+++ b/fs/fat/inode.c
@@ -10,6 +10,8 @@
* Max Cohan: Fixed invalid FSINFO offset when info_sector is 0
*/

+#undef DEBUG
+
#include <linux/module.h>
#include <linux/init.h>
#include <linux/time.h>
@@ -38,7 +40,7 @@ static int fat_default_codepage = CONFIG
static char fat_default_iocharset[] = CONFIG_FAT_DEFAULT_IOCHARSET;


-static int fat_add_cluster(struct inode *inode)
+int fat_add_cluster(struct inode *inode)
{
int err, cluster;

@@ -64,6 +66,11 @@ static inline int __fat_get_block(struct
int err, offset;

err = fat_bmap(inode, iblock, &phys, &mapped_blocks);
+
+ pr_debug(
+ "__fat_get_block():phys: %llu, mapped_blocks:%lu,sector: %llu\n",
+ phys, mapped_blocks, iblock);
+
if (err)
return err;
if (phys) {
@@ -90,10 +97,18 @@ static inline int __fat_get_block(struct
/* available blocks on this cluster */
mapped_blocks = sbi->sec_per_clus - offset;

+ pr_debug(
+ "__fat_get_block(): max_blocks: %lu, mapped_blocks:%lu\n",
+ *max_blocks, mapped_blocks);
+
*max_blocks = min(mapped_blocks, *max_blocks);
MSDOS_I(inode)->mmu_private += *max_blocks << sb->s_blocksize_bits;

err = fat_bmap(inode, iblock, &phys, &mapped_blocks);
+ pr_debug(
+ "__fat_get_block():phys: %llu, mapped_blocks:%lu,sector: %llu\n",
+ phys, mapped_blocks, iblock);
+
if (err)
return err;

@@ -116,6 +131,10 @@ static int fat_get_block(struct inode *i
if (err)
return err;
bh_result->b_size = max_blocks << sb->s_blocksize_bits;
+
+ printk(KERN_INFO "fat_get_block():bh_result->b_size:%d\n",
+ bh_result->b_size);
+
return 0;
}

diff --git a/include/linux/msdos_fs.h b/include/linux/msdos_fs.h
index f950921..034a2a4 100644
--- a/include/linux/msdos_fs.h
+++ b/include/linux/msdos_fs.h
@@ -109,6 +109,8 @@ #define VFAT_SFN_DISPLAY_WINNT 0x0004 /*
#define VFAT_SFN_CREATE_WIN95 0x0100 /* emulate win95 rule for create */
#define VFAT_SFN_CREATE_WINNT 0x0200 /* emulate winnt rule for create */

+#define MSDOS_BLOCK_ALIGN(size, blkbits) ALIGN((size), (1 << (blkbits)))
+
struct fat_boot_sector {
__u8 ignored[3]; /* Boot strap short or near jump */
__u8 system_id[8]; /* Name - can be used to special case
@@ -350,6 +352,9 @@ extern int fat_add_entries(struct inode
struct fat_slot_info *sinfo);
extern int fat_remove_entries(struct inode *dir, struct fat_slot_info *sinfo);

+/* fat/inode.c */
+extern int fat_add_cluster(struct inode *inode);
+
/* fat/fatent.c */
struct fat_entry {
int entry;
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/