This patch introduces the mount option "swapendian" for cramfs. When this option is set, cramfs is able to mount an image that was created on a machine whose endianness differs from the mounting machine's one. If somebody tries to mount an image with another endianness but forgets to set this option, cramfs will give a hint for it. Signed-off-by: Andi Drebes <andi@xxxxxxxxxxxxxxxxxxx> --- fs/cramfs/inode.c | 112 +++++++++++++++++++++++++++++++---------- include/linux/cramfs_endian.h | 58 +++++++++++++++++++++ include/linux/cramfs_fs_sb.h | 1 + 3 files changed, 144 insertions(+), 27 deletions(-) diff --git a/fs/cramfs/inode.c b/fs/cramfs/inode.c index 350680f..8da03b0 100644 --- a/fs/cramfs/inode.c +++ b/fs/cramfs/inode.c @@ -4,6 +4,10 @@ * Copyright (C) 1999 Linus Torvalds. * * This file is released under the GPL. + * + * Changelog: + * 11/07 - Andi Drebes <andi@xxxxxxxxxxxxxxxxxxx> + * Added mount option "swapendian" */ /* @@ -18,6 +22,7 @@ #include <linux/string.h> #include <linux/blkdev.h> #include <linux/cramfs_fs.h> +#include <linux/cramfs_endian.h> #include <linux/slab.h> #include <linux/cramfs_fs_sb.h> #include <linux/buffer_head.h> @@ -157,19 +162,24 @@ static void *cramfs_read(struct super_block *sb, unsigned int offset, unsigned i blocknr = offset >> PAGE_CACHE_SHIFT; offset &= PAGE_CACHE_SIZE - 1; - /* Check if an existing buffer already has the data.. */ - for (i = 0; i < READ_BUFFERS; i++) { - unsigned int blk_offset; - - if (buffer_dev[i] != sb) - continue; - if (blocknr < buffer_blocknr[i]) - continue; - blk_offset = (blocknr - buffer_blocknr[i]) << PAGE_CACHE_SHIFT; - blk_offset += offset; - if (blk_offset + len > BUFFER_SIZE) - continue; - return read_buffers[i] + blk_offset; + /* Caching is disabled if the filesystem's + and the machine's endianness differ. */ + if(likely(CRAMFS_SB(sb)->endian)) + { + /* Check if an existing buffer already has the data.. */ + for (i = 0; i < READ_BUFFERS; i++) { + unsigned int blk_offset; + + if (buffer_dev[i] != sb) + continue; + if (blocknr < buffer_blocknr[i]) + continue; + blk_offset = (blocknr - buffer_blocknr[i]) << PAGE_CACHE_SHIFT; + blk_offset += offset; + if (blk_offset + len > BUFFER_SIZE) + continue; + return read_buffers[i] + blk_offset; + } } devsize = mapping->host->i_size >> PAGE_CACHE_SHIFT; @@ -246,6 +256,14 @@ static int cramfs_fill_super(struct super_block *sb, void *data, int silent) return -ENOMEM; sb->s_fs_info = sbi; + /* assume the right endianness */ + sbi->endian = 1; + + /* Check mount options: + Does the user want to mount an image with a different endianness? */ + if(strcmp("swapendian", data) == 0) + sbi->endian = 0; + /* Invalidate the read buffers on mount: think disk change.. */ mutex_lock(&read_mutex); for (i = 0; i < READ_BUFFERS; i++) @@ -256,26 +274,49 @@ static int cramfs_fill_super(struct super_block *sb, void *data, int silent) mutex_unlock(&read_mutex); /* Do sanity checks on the superblock */ - if (super.magic != CRAMFS_MAGIC) { - /* check for wrong endianess */ - if (super.magic == CRAMFS_MAGIC_WEND) { - if (!silent) - printk(KERN_ERR "cramfs: wrong endianess\n"); - goto out; - } - + if (super.magic != CRAMFS_MAGIC && super.magic != CRAMFS_MAGIC_WEND) { /* check at 512 byte offset */ mutex_lock(&read_mutex); memcpy(&super, cramfs_read(sb, 512, sizeof(super)), sizeof(super)); mutex_unlock(&read_mutex); - if (super.magic != CRAMFS_MAGIC) { - if (super.magic == CRAMFS_MAGIC_WEND && !silent) - printk(KERN_ERR "cramfs: wrong endianess\n"); - else if (!silent) + + if (super.magic == CRAMFS_MAGIC_WEND) { + goto other_endian; + } + else if (super.magic != CRAMFS_MAGIC) { + if (!silent) printk(KERN_ERR "cramfs: wrong magic\n"); + goto out; } } + /* check for wrong endianess */ + else if (super.magic == CRAMFS_MAGIC_WEND) + { +other_endian: + if (sbi->endian) { + if (!silent) { + printk(KERN_ERR "cramfs: it seems as if you were trying to mount a filesystem " + "whose endianness does not match the host's one. You might want to try " + "the option \"swapendian\" when mounting the filesystem.\n"); + printk(KERN_ERR "cramfs: the filesystem will not be mounted.\n"); + } + goto out; + } + else { + if (!sbi->endian) { + if (!silent) + printk(KERN_INFO "cramfs: mounting cramfs with another endianness\n"); + CRAMFS_CONVERT_SUPER(super); + } + } + } + else if (super.magic == CRAMFS_MAGIC && !sbi->endian) + { + printk(KERN_ERR "cramfs: you are trying to mount a filesystem whose endianness matches the " + "host's one. Do not use the option \"swapendian\".\n"); + goto out; + } /* get feature flags first */ if (super.flags & ~CRAMFS_SUPPORTED_FLAGS) { @@ -352,6 +393,7 @@ static int cramfs_readdir(struct file *filp, void *dirent, filldir_t filldir) char *buf; unsigned int offset; int copied; + unsigned long endian = CRAMFS_SB(filp->f_path.dentry->d_inode->i_sb)->endian; /* Offset within the thing. */ offset = filp->f_pos; @@ -377,6 +419,8 @@ static int cramfs_readdir(struct file *filp, void *dirent, filldir_t filldir) mutex_lock(&read_mutex); de = cramfs_read(sb, OFFSET(inode) + offset, sizeof(*de)+CRAMFS_MAXPATHLEN); name = (char *)(de+1); + if(unlikely(!endian)) + CRAMFS_CONVERT_INODE(de); /* * Namelengths on disk are shifted by two @@ -417,8 +461,10 @@ static struct dentry * cramfs_lookup(struct inode *dir, struct dentry *dentry, s { unsigned int offset = 0; int sorted; + unsigned long endian; mutex_lock(&read_mutex); + endian = CRAMFS_SB(dir->i_sb)->endian; sorted = CRAMFS_SB(dir->i_sb)->flags & CRAMFS_FLAG_SORTED_DIRS; while (offset < dir->i_size) { struct cramfs_inode *de; @@ -427,6 +473,8 @@ static struct dentry * cramfs_lookup(struct inode *dir, struct dentry *dentry, s de = cramfs_read(dir->i_sb, OFFSET(dir) + offset, sizeof(*de)+CRAMFS_MAXPATHLEN); name = (char *)(de+1); + if(unlikely(!endian)) + CRAMFS_CONVERT_INODE(de); /* Try to take advantage of sorted directories */ if (sorted && (dentry->d_name.name[0] < name[0])) @@ -473,6 +521,7 @@ static int cramfs_readpage(struct file *file, struct page * page) struct inode *inode = page->mapping->host; u32 maxblock, bytes_filled; void *pgdata; + unsigned long endian = CRAMFS_SB(inode->i_sb)->endian; maxblock = (inode->i_size + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT; bytes_filled = 0; @@ -483,9 +532,18 @@ static int cramfs_readpage(struct file *file, struct page * page) start_offset = OFFSET(inode) + maxblock*4; mutex_lock(&read_mutex); - if (page->index) + if (page->index) { start_offset = *(u32 *) cramfs_read(sb, blkptr_offset-4, 4); - compr_len = (*(u32 *) cramfs_read(sb, blkptr_offset, 4) - start_offset); + + if(unlikely(!endian)) + start_offset = CPU_ENDIAN_32(start_offset); + } + + if(unlikely(!endian)) + compr_len = CPU_ENDIAN_32(*(u32 *) cramfs_read(sb, blkptr_offset, 4)) - start_offset; + else + compr_len = (*(u32 *) cramfs_read(sb, blkptr_offset, 4) - start_offset); + mutex_unlock(&read_mutex); pgdata = kmap(page); if (compr_len == 0) diff --git a/include/linux/cramfs_endian.h b/include/linux/cramfs_endian.h new file mode 100644 index 0000000..9b90b26 --- /dev/null +++ b/include/linux/cramfs_endian.h @@ -0,0 +1,58 @@ +/* + * cramfs_endian.h provides definitions used when mounting + * a cram filesystem whose endianness doesn't match the host's + * one. + * + * Copyright 2007 (C) Andi Drebes <andi@xxxxxxxxxxxxxxxxxxx> + * + * This file is released under the GPLv2. + */ + +#ifndef __CRAMFS_ENDIAN_H +#define __CRAMFS_ENDIAN_H + +#ifdef __LITTLE_ENDIAN + #define CPU_ENDIAN_32(x) (be32_to_cpu(x)) + #define CPU_ENDIAN_16(x) (be16_to_cpu(x)) +#elif defined __BIG_ENDIAN + #define CPU_ENDIAN_32(x) (le32_to_cpu(x)) + #define CPU_ENDIAN_16(x) (le16_to_cpu(x)) +#else + #error "Neither __LITTLE_ENDIAN nor __BIG_ENDIAN is defined!" +#endif + +/* Converts a cramfs_info from the wrong endianess + to host endianess. */ +#define CRAMFS_CONVERT_INFO(info) \ + do { \ + (info).crc = CPU_ENDIAN_32((info).crc); \ + (info).edition = CPU_ENDIAN_32((info).edition); \ + (info).blocks = CPU_ENDIAN_32((info).blocks); \ + (info).files = CPU_ENDIAN_32((info).files); \ + } while(0) + +/* Converts a cramfs_info from the wrong endianess + to host endianess. */ +#define CRAMFS_CONVERT_INODE(inode) \ + do { \ + __u8* ptr = (__u8*)(inode);\ + (inode)->mode = CPU_ENDIAN_16((inode)->mode); \ + (inode)->uid = CPU_ENDIAN_16((inode)->uid); \ + (inode)->size = (ptr[4] << 16) | (ptr[5] << 8) | (ptr[6]) ; \ + (inode)->offset = ((ptr[8] & 0x03) << 24) | (ptr[9] << 16) | (ptr[10] << 8) | ptr[11]; \ + (inode)->namelen = (ptr[8] & 0x3f) >> 2; \ + } while(0) + +/* Converts a cramfs superblock from the wrong endianess + to host endianess. */ +#define CRAMFS_CONVERT_SUPER(super) \ + do { \ + (super).magic = CPU_ENDIAN_32((super).magic); \ + (super).size = CPU_ENDIAN_32((super).size); \ + (super).flags = CPU_ENDIAN_32((super).flags); \ + (super).future = CPU_ENDIAN_32((super).future); \ + CRAMFS_CONVERT_INFO((super).fsid); \ + CRAMFS_CONVERT_INODE(&(super).root); \ + } while(0) + +#endif //__CRAMFS_ENDIAN_H diff --git a/include/linux/cramfs_fs_sb.h b/include/linux/cramfs_fs_sb.h index 8390693..dda8e09 100644 --- a/include/linux/cramfs_fs_sb.h +++ b/include/linux/cramfs_fs_sb.h @@ -10,6 +10,7 @@ struct cramfs_sb_info { unsigned long blocks; unsigned long files; unsigned long flags; + unsigned long endian; /* 1: host endian */ }; static inline struct cramfs_sb_info *CRAMFS_SB(struct super_block *sb) - To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html