aboutsummaryrefslogtreecommitdiff
path: root/fs/hfsplus/wrapper.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/hfsplus/wrapper.c')
-rw-r--r--fs/hfsplus/wrapper.c171
1 files changed, 171 insertions, 0 deletions
diff --git a/fs/hfsplus/wrapper.c b/fs/hfsplus/wrapper.c
new file mode 100644
index 00000000000..0c51d6338b0
--- /dev/null
+++ b/fs/hfsplus/wrapper.c
@@ -0,0 +1,171 @@
+/*
+ * linux/fs/hfsplus/wrapper.c
+ *
+ * Copyright (C) 2001
+ * Brad Boyer (flar@allandria.com)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ * Handling of HFS wrappers around HFS+ volumes
+ */
+
+#include <linux/fs.h>
+#include <linux/blkdev.h>
+#include <linux/cdrom.h>
+#include <linux/genhd.h>
+#include <linux/version.h>
+#include <asm/unaligned.h>
+
+#include "hfsplus_fs.h"
+#include "hfsplus_raw.h"
+
+struct hfsplus_wd {
+ u32 ablk_size;
+ u16 ablk_start;
+ u16 embed_start;
+ u16 embed_count;
+};
+
+static int hfsplus_read_mdb(void *bufptr, struct hfsplus_wd *wd)
+{
+ u32 extent;
+ u16 attrib;
+
+ if (be16_to_cpu(*(__be16 *)(bufptr + HFSP_WRAPOFF_EMBEDSIG)) != HFSPLUS_VOLHEAD_SIG)
+ return 0;
+
+ attrib = be16_to_cpu(*(__be16 *)(bufptr + HFSP_WRAPOFF_ATTRIB));
+ if (!(attrib & HFSP_WRAP_ATTRIB_SLOCK) ||
+ !(attrib & HFSP_WRAP_ATTRIB_SPARED))
+ return 0;
+
+ wd->ablk_size = be32_to_cpu(*(__be32 *)(bufptr + HFSP_WRAPOFF_ABLKSIZE));
+ if (wd->ablk_size < HFSPLUS_SECTOR_SIZE)
+ return 0;
+ if (wd->ablk_size % HFSPLUS_SECTOR_SIZE)
+ return 0;
+ wd->ablk_start = be16_to_cpu(*(__be16 *)(bufptr + HFSP_WRAPOFF_ABLKSTART));
+
+ extent = be32_to_cpu(get_unaligned((__be32 *)(bufptr + HFSP_WRAPOFF_EMBEDEXT)));
+ wd->embed_start = (extent >> 16) & 0xFFFF;
+ wd->embed_count = extent & 0xFFFF;
+
+ return 1;
+}
+
+static int hfsplus_get_last_session(struct super_block *sb,
+ sector_t *start, sector_t *size)
+{
+ struct cdrom_multisession ms_info;
+ struct cdrom_tocentry te;
+ int res;
+
+ /* default values */
+ *start = 0;
+ *size = sb->s_bdev->bd_inode->i_size >> 9;
+
+ if (HFSPLUS_SB(sb).session >= 0) {
+ te.cdte_track = HFSPLUS_SB(sb).session;
+ te.cdte_format = CDROM_LBA;
+ res = ioctl_by_bdev(sb->s_bdev, CDROMREADTOCENTRY, (unsigned long)&te);
+ if (!res && (te.cdte_ctrl & CDROM_DATA_TRACK) == 4) {
+ *start = (sector_t)te.cdte_addr.lba << 2;
+ return 0;
+ }
+ printk(KERN_ERR "HFS: Invalid session number or type of track\n");
+ return -EINVAL;
+ }
+ ms_info.addr_format = CDROM_LBA;
+ res = ioctl_by_bdev(sb->s_bdev, CDROMMULTISESSION, (unsigned long)&ms_info);
+ if (!res && ms_info.xa_flag)
+ *start = (sector_t)ms_info.addr.lba << 2;
+ return 0;
+}
+
+/* Find the volume header and fill in some minimum bits in superblock */
+/* Takes in super block, returns true if good data read */
+int hfsplus_read_wrapper(struct super_block *sb)
+{
+ struct buffer_head *bh;
+ struct hfsplus_vh *vhdr;
+ struct hfsplus_wd wd;
+ sector_t part_start, part_size;
+ u32 blocksize;
+
+ blocksize = sb_min_blocksize(sb, HFSPLUS_SECTOR_SIZE);
+ if (!blocksize)
+ return -EINVAL;
+
+ if (hfsplus_get_last_session(sb, &part_start, &part_size))
+ return -EINVAL;
+ while (1) {
+ bh = sb_bread512(sb, part_start + HFSPLUS_VOLHEAD_SECTOR, vhdr);
+ if (!bh)
+ return -EIO;
+
+ if (vhdr->signature == cpu_to_be16(HFSP_WRAP_MAGIC)) {
+ if (!hfsplus_read_mdb(vhdr, &wd))
+ goto error;
+ wd.ablk_size >>= HFSPLUS_SECTOR_SHIFT;
+ part_start += wd.ablk_start + wd.embed_start * wd.ablk_size;
+ part_size = wd.embed_count * wd.ablk_size;
+ brelse(bh);
+ bh = sb_bread512(sb, part_start + HFSPLUS_VOLHEAD_SECTOR, vhdr);
+ if (!bh)
+ return -EIO;
+ }
+ if (vhdr->signature == cpu_to_be16(HFSPLUS_VOLHEAD_SIG))
+ break;
+ brelse(bh);
+
+ /* check for a partition block
+ * (should do this only for cdrom/loop though)
+ */
+ if (hfs_part_find(sb, &part_start, &part_size))
+ return -EINVAL;
+ }
+
+ blocksize = be32_to_cpu(vhdr->blocksize);
+ brelse(bh);
+
+ /* block size must be at least as large as a sector
+ * and a multiple of 2
+ */
+ if (blocksize < HFSPLUS_SECTOR_SIZE ||
+ ((blocksize - 1) & blocksize))
+ return -EINVAL;
+ HFSPLUS_SB(sb).alloc_blksz = blocksize;
+ HFSPLUS_SB(sb).alloc_blksz_shift = 0;
+ while ((blocksize >>= 1) != 0)
+ HFSPLUS_SB(sb).alloc_blksz_shift++;
+ blocksize = min(HFSPLUS_SB(sb).alloc_blksz, (u32)PAGE_SIZE);
+
+ /* align block size to block offset */
+ while (part_start & ((blocksize >> HFSPLUS_SECTOR_SHIFT) - 1))
+ blocksize >>= 1;
+
+ if (sb_set_blocksize(sb, blocksize) != blocksize) {
+ printk("HFS+: unable to blocksize to %u!\n", blocksize);
+ return -EINVAL;
+ }
+
+ HFSPLUS_SB(sb).blockoffset = part_start >>
+ (sb->s_blocksize_bits - HFSPLUS_SECTOR_SHIFT);
+ HFSPLUS_SB(sb).sect_count = part_size;
+ HFSPLUS_SB(sb).fs_shift = HFSPLUS_SB(sb).alloc_blksz_shift -
+ sb->s_blocksize_bits;
+
+ bh = sb_bread512(sb, part_start + HFSPLUS_VOLHEAD_SECTOR, vhdr);
+ if (!bh)
+ return -EIO;
+
+ /* should still be the same... */
+ if (be16_to_cpu(vhdr->signature) != HFSPLUS_VOLHEAD_SIG)
+ goto error;
+ HFSPLUS_SB(sb).s_vhbh = bh;
+ HFSPLUS_SB(sb).s_vhdr = vhdr;
+
+ return 0;
+ error:
+ brelse(bh);
+ return -EINVAL;
+}