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 <[EMAIL PROTECTED]>
---
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 <[EMAIL PROTECTED]>
+ * 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 <[EMAIL PROTECTED]>
+ *
+ * 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 [EMAIL PROTECTED]
More majordomo info at http://vger.kernel.org/majordomo-info.html