2005-04-17 02:20:36 +04:00
/*
* 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 <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 ;
2006-01-19 04:43:08 +03:00
__be16 sig ;
2005-04-17 02:20:36 +04:00
2006-01-19 04:43:08 +03:00
sig = * ( __be16 * ) ( bufptr + HFSP_WRAPOFF_EMBEDSIG ) ;
if ( sig ! = cpu_to_be16 ( HFSPLUS_VOLHEAD_SIG ) & &
sig ! = cpu_to_be16 ( HFSPLUS_VOLHEAD_SIGX ) )
2005-04-17 02:20:36 +04:00
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 ) ) ;
2008-04-29 12:03:44 +04:00
extent = get_unaligned_be32 ( bufptr + HFSP_WRAPOFF_EMBEDEXT ) ;
2005-04-17 02:20:36 +04:00
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 ;
}
2006-01-19 04:43:05 +03:00
printk ( KERN_ERR " hfs: invalid session number or type of track \n " ) ;
2005-04-17 02:20:36 +04:00
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 ;
2006-01-19 04:43:08 +03:00
if ( vhdr - > signature = = cpu_to_be16 ( HFSPLUS_VOLHEAD_SIGX ) ) {
HFSPLUS_SB ( sb ) . flags | = HFSPLUS_SB_HFSX ;
break ;
}
2005-04-17 02:20:36 +04:00
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 ) {
2006-01-19 04:43:05 +03:00
printk ( KERN_ERR " hfs: unable to set blocksize to %u! \n " , blocksize ) ;
2005-04-17 02:20:36 +04:00
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... */
2006-01-19 04:43:08 +03:00
if ( vhdr - > signature ! = ( HFSPLUS_SB ( sb ) . flags & HFSPLUS_SB_HFSX ?
cpu_to_be16 ( HFSPLUS_VOLHEAD_SIGX ) :
cpu_to_be16 ( HFSPLUS_VOLHEAD_SIG ) ) )
2005-04-17 02:20:36 +04:00
goto error ;
HFSPLUS_SB ( sb ) . s_vhbh = bh ;
HFSPLUS_SB ( sb ) . s_vhdr = vhdr ;
return 0 ;
error :
brelse ( bh ) ;
return - EINVAL ;
}