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 ;
} ;
2014-06-07 01:36:27 +04:00
/**
* hfsplus_submit_bio - Perform block I / O
2011-07-18 19:06:23 +04:00
* @ sb : super block of volume for I / O
* @ sector : block to read or write , for blocks of HFSPLUS_SECTOR_SIZE bytes
* @ buf : buffer for I / O
* @ data : output pointer for location of requested data
* @ rw : direction of I / O
*
* The unit of I / O is hfsplus_min_io_size ( sb ) , which may be bigger than
* HFSPLUS_SECTOR_SIZE , and @ buf must be sized accordingly . On reads
* @ data will return a pointer to the start of the requested sector ,
* which may not be the same location as @ buf .
*
* If @ sector is not aligned to the bdev logical block size it will
* be rounded down . For writes this means that @ buf should contain data
* that starts at the rounded - down address . As long as the data was
* read using hfsplus_submit_bio ( ) and the same buffer is used things
* will work correctly .
*/
int hfsplus_submit_bio ( struct super_block * sb , sector_t sector ,
void * buf , void * * data , int rw )
2010-11-23 16:37:47 +03:00
{
struct bio * bio ;
2011-06-01 01:35:50 +04:00
int ret = 0 ;
2012-06-18 01:05:24 +04:00
u64 io_size ;
2011-07-18 19:06:23 +04:00
loff_t start ;
int offset ;
/*
* Align sector to hardware sector size and find offset . We
* assume that io_size is a power of two , which _should_
* be true .
*/
io_size = hfsplus_min_io_size ( sb ) ;
start = ( loff_t ) sector < < HFSPLUS_SECTOR_SHIFT ;
offset = start & ( io_size - 1 ) ;
sector & = ~ ( ( io_size > > HFSPLUS_SECTOR_SHIFT ) - 1 ) ;
2010-11-23 16:37:47 +03:00
bio = bio_alloc ( GFP_NOIO , 1 ) ;
2013-10-12 02:44:27 +04:00
bio - > bi_iter . bi_sector = sector ;
2011-07-18 19:06:23 +04:00
bio - > bi_bdev = sb - > s_bdev ;
2010-11-23 16:37:47 +03:00
2011-07-18 19:06:23 +04:00
if ( ! ( rw & WRITE ) & & data )
* data = ( u8 * ) buf + offset ;
while ( io_size > 0 ) {
unsigned int page_offset = offset_in_page ( buf ) ;
unsigned int len = min_t ( unsigned int , PAGE_SIZE - page_offset ,
io_size ) ;
ret = bio_add_page ( bio , virt_to_page ( buf ) , len , page_offset ) ;
if ( ret ! = len ) {
ret = - EIO ;
goto out ;
}
io_size - = len ;
buf = ( u8 * ) buf + len ;
}
2010-11-23 16:37:47 +03:00
2013-11-24 10:33:32 +04:00
ret = submit_bio_wait ( rw , bio ) ;
2011-07-18 19:06:23 +04:00
out :
2011-06-01 01:35:50 +04:00
bio_put ( bio ) ;
2011-07-18 19:06:23 +04:00
return ret < 0 ? ret : 0 ;
2010-11-23 16:37:47 +03:00
}
2005-04-17 02:20:36 +04:00
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 ;
2010-12-16 19:08:38 +03:00
wd - > ablk_size =
be32_to_cpu ( * ( __be32 * ) ( bufptr + HFSP_WRAPOFF_ABLKSIZE ) ) ;
2005-04-17 02:20:36 +04:00
if ( wd - > ablk_size < HFSPLUS_SECTOR_SIZE )
return 0 ;
if ( wd - > ablk_size % HFSPLUS_SECTOR_SIZE )
return 0 ;
2010-12-16 19:08:38 +03:00
wd - > ablk_start =
be16_to_cpu ( * ( __be16 * ) ( bufptr + HFSP_WRAPOFF_ABLKSTART ) ) ;
2005-04-17 02:20:36 +04:00
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 ;
2010-10-01 07:42:59 +04:00
if ( HFSPLUS_SB ( sb ) - > session > = 0 ) {
te . cdte_track = HFSPLUS_SB ( sb ) - > session ;
2005-04-17 02:20:36 +04:00
te . cdte_format = CDROM_LBA ;
2010-12-16 19:08:38 +03:00
res = ioctl_by_bdev ( sb - > s_bdev ,
CDROMREADTOCENTRY , ( unsigned long ) & te ) ;
2005-04-17 02:20:36 +04:00
if ( ! res & & ( te . cdte_ctrl & CDROM_DATA_TRACK ) = = 4 ) {
* start = ( sector_t ) te . cdte_addr . lba < < 2 ;
return 0 ;
}
2013-05-01 02:27:55 +04:00
pr_err ( " invalid session number or type of track \n " ) ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
ms_info . addr_format = CDROM_LBA ;
2010-12-16 19:08:38 +03:00
res = ioctl_by_bdev ( sb - > s_bdev , CDROMMULTISESSION ,
( unsigned long ) & ms_info ) ;
2005-04-17 02:20:36 +04:00
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 )
{
2010-10-01 07:42:59 +04:00
struct hfsplus_sb_info * sbi = HFSPLUS_SB ( sb ) ;
2005-04-17 02:20:36 +04:00
struct hfsplus_wd wd ;
sector_t part_start , part_size ;
u32 blocksize ;
2010-11-23 16:37:47 +03:00
int error = 0 ;
2005-04-17 02:20:36 +04:00
2010-11-23 16:37:47 +03:00
error = - EINVAL ;
2005-04-17 02:20:36 +04:00
blocksize = sb_min_blocksize ( sb , HFSPLUS_SECTOR_SIZE ) ;
if ( ! blocksize )
2010-11-23 16:37:47 +03:00
goto out ;
2005-04-17 02:20:36 +04:00
if ( hfsplus_get_last_session ( sb , & part_start , & part_size ) )
2010-11-23 16:37:47 +03:00
goto out ;
2005-04-17 02:20:36 +04:00
2010-11-23 16:37:47 +03:00
error = - ENOMEM ;
2011-07-18 19:06:23 +04:00
sbi - > s_vhdr_buf = kmalloc ( hfsplus_min_io_size ( sb ) , GFP_KERNEL ) ;
if ( ! sbi - > s_vhdr_buf )
2010-11-23 16:37:47 +03:00
goto out ;
2011-07-18 19:06:23 +04:00
sbi - > s_backup_vhdr_buf = kmalloc ( hfsplus_min_io_size ( sb ) , GFP_KERNEL ) ;
if ( ! sbi - > s_backup_vhdr_buf )
2010-11-23 16:37:47 +03:00
goto out_free_vhdr ;
reread :
2011-07-18 19:06:23 +04:00
error = hfsplus_submit_bio ( sb , part_start + HFSPLUS_VOLHEAD_SECTOR ,
sbi - > s_vhdr_buf , ( void * * ) & sbi - > s_vhdr ,
READ ) ;
2010-11-23 16:37:47 +03:00
if ( error )
goto out_free_backup_vhdr ;
error = - EINVAL ;
switch ( sbi - > s_vhdr - > signature ) {
case cpu_to_be16 ( HFSPLUS_VOLHEAD_SIGX ) :
set_bit ( HFSPLUS_SB_HFSX , & sbi - > flags ) ;
/*FALLTHRU*/
case cpu_to_be16 ( HFSPLUS_VOLHEAD_SIG ) :
break ;
case cpu_to_be16 ( HFSP_WRAP_MAGIC ) :
if ( ! hfsplus_read_mdb ( sbi - > s_vhdr , & wd ) )
2011-02-02 18:55:06 +03:00
goto out_free_backup_vhdr ;
2010-11-23 16:37:47 +03:00
wd . ablk_size > > = HFSPLUS_SECTOR_SHIFT ;
2011-02-16 11:34:22 +03:00
part_start + = ( sector_t ) wd . ablk_start +
( sector_t ) wd . embed_start * wd . ablk_size ;
part_size = ( sector_t ) wd . embed_count * wd . ablk_size ;
2010-11-23 16:37:47 +03:00
goto reread ;
default :
/*
* Check for a partition block .
*
2005-04-17 02:20:36 +04:00
* ( should do this only for cdrom / loop though )
*/
if ( hfs_part_find ( sb , & part_start , & part_size ) )
2011-02-02 18:55:06 +03:00
goto out_free_backup_vhdr ;
2010-11-23 16:37:47 +03:00
goto reread ;
}
2011-07-18 19:06:23 +04:00
error = hfsplus_submit_bio ( sb , part_start + part_size - 2 ,
sbi - > s_backup_vhdr_buf ,
( void * * ) & sbi - > s_backup_vhdr , READ ) ;
2010-11-23 16:37:47 +03:00
if ( error )
goto out_free_backup_vhdr ;
error = - EINVAL ;
if ( sbi - > s_backup_vhdr - > signature ! = sbi - > s_vhdr - > signature ) {
2013-05-01 02:27:55 +04:00
pr_warn ( " invalid secondary volume header \n " ) ;
2010-11-23 16:37:47 +03:00
goto out_free_backup_vhdr ;
2005-04-17 02:20:36 +04:00
}
2010-11-23 16:37:47 +03:00
blocksize = be32_to_cpu ( sbi - > s_vhdr - > blocksize ) ;
2005-04-17 02:20:36 +04:00
2010-11-23 16:37:47 +03:00
/*
* Block size must be at least as large as a sector and a multiple of 2.
2005-04-17 02:20:36 +04:00
*/
2010-11-23 16:37:47 +03:00
if ( blocksize < HFSPLUS_SECTOR_SIZE | | ( ( blocksize - 1 ) & blocksize ) )
goto out_free_backup_vhdr ;
2010-10-01 07:42:59 +04:00
sbi - > alloc_blksz = blocksize ;
2014-06-07 01:36:29 +04:00
sbi - > alloc_blksz_shift = ilog2 ( blocksize ) ;
2014-06-07 01:36:27 +04:00
blocksize = min_t ( u32 , sbi - > alloc_blksz , PAGE_SIZE ) ;
2005-04-17 02:20:36 +04:00
2010-11-23 16:37:47 +03:00
/*
* Align block size to block offset .
*/
2005-04-17 02:20:36 +04:00
while ( part_start & ( ( blocksize > > HFSPLUS_SECTOR_SHIFT ) - 1 ) )
blocksize > > = 1 ;
if ( sb_set_blocksize ( sb , blocksize ) ! = blocksize ) {
2013-05-01 02:27:55 +04:00
pr_err ( " unable to set blocksize to %u! \n " , blocksize ) ;
2010-11-23 16:37:47 +03:00
goto out_free_backup_vhdr ;
2005-04-17 02:20:36 +04:00
}
2010-10-01 07:42:59 +04:00
sbi - > blockoffset =
part_start > > ( sb - > s_blocksize_bits - HFSPLUS_SECTOR_SHIFT ) ;
2010-11-23 16:37:47 +03:00
sbi - > part_start = part_start ;
2010-10-01 07:42:59 +04:00
sbi - > sect_count = part_size ;
sbi - > fs_shift = sbi - > alloc_blksz_shift - sb - > s_blocksize_bits ;
2005-04-17 02:20:36 +04:00
return 0 ;
2010-11-23 16:37:47 +03:00
out_free_backup_vhdr :
2011-09-15 18:48:27 +04:00
kfree ( sbi - > s_backup_vhdr_buf ) ;
2010-11-23 16:37:47 +03:00
out_free_vhdr :
2011-09-15 18:48:27 +04:00
kfree ( sbi - > s_vhdr_buf ) ;
2010-11-23 16:37:47 +03:00
out :
return error ;
2005-04-17 02:20:36 +04:00
}