2019-06-04 11:11:33 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2005-04-17 02:20:36 +04:00
/*
* linux / fs / adfs / dir_fplus . c
*
* Copyright ( C ) 1997 - 1999 Russell King
*/
# include "adfs.h"
# include "dir_fplus.h"
2019-12-09 14:10:52 +03:00
/* Return the byte offset to directory entry pos */
static unsigned int adfs_fplus_offset ( const struct adfs_bigdirheader * h ,
unsigned int pos )
{
return offsetof ( struct adfs_bigdirheader , bigdirname ) +
ALIGN ( le32_to_cpu ( h - > bigdirnamelen ) , 4 ) +
pos * sizeof ( struct adfs_bigdirentry ) ;
}
2019-12-09 14:10:57 +03:00
static int adfs_fplus_validate_header ( const struct adfs_bigdirheader * h )
{
unsigned int size = le32_to_cpu ( h - > bigdirsize ) ;
2019-12-09 14:11:02 +03:00
unsigned int len ;
2019-12-09 14:10:57 +03:00
if ( h - > bigdirversion [ 0 ] ! = 0 | | h - > bigdirversion [ 1 ] ! = 0 | |
h - > bigdirversion [ 2 ] ! = 0 | |
h - > bigdirstartname ! = cpu_to_le32 ( BIGDIRSTARTNAME ) | |
2019-12-09 14:11:02 +03:00
! size | | size & 2047 | | size > SZ_4M )
return - EIO ;
size - = sizeof ( struct adfs_bigdirtail ) +
offsetof ( struct adfs_bigdirheader , bigdirname ) ;
/* Check that bigdirnamelen fits within the directory */
len = ALIGN ( le32_to_cpu ( h - > bigdirnamelen ) , 4 ) ;
if ( len > size )
return - EIO ;
size - = len ;
/* Check that bigdirnamesize fits within the directory */
len = le32_to_cpu ( h - > bigdirnamesize ) ;
if ( len > size )
return - EIO ;
size - = len ;
/*
* Avoid division , we know that absolute maximum number of entries
* can not be so large to cause overflow of the multiplication below .
*/
len = le32_to_cpu ( h - > bigdirentries ) ;
if ( len > SZ_4M / sizeof ( struct adfs_bigdirentry ) | |
len * sizeof ( struct adfs_bigdirentry ) > size )
2019-12-09 14:10:57 +03:00
return - EIO ;
return 0 ;
}
static int adfs_fplus_validate_tail ( const struct adfs_bigdirheader * h ,
const struct adfs_bigdirtail * t )
{
if ( t - > bigdirendname ! = cpu_to_le32 ( BIGDIRENDNAME ) | |
t - > bigdirendmasseq ! = h - > startmasseq | |
t - > reserved [ 0 ] ! = 0 | | t - > reserved [ 1 ] ! = 0 )
return - EIO ;
return 0 ;
}
2019-12-09 14:11:08 +03:00
static u8 adfs_fplus_checkbyte ( struct adfs_dir * dir )
{
struct adfs_bigdirheader * h = dir - > bighead ;
struct adfs_bigdirtail * t = dir - > bigtail ;
unsigned int end , bs , bi , i ;
__le32 * bp ;
u32 dircheck ;
end = adfs_fplus_offset ( h , le32_to_cpu ( h - > bigdirentries ) ) +
le32_to_cpu ( h - > bigdirnamesize ) ;
/* Accumulate the contents of the header, entries and names */
for ( dircheck = 0 , bi = 0 ; end ; bi + + ) {
bp = ( void * ) dir - > bhs [ bi ] - > b_data ;
bs = dir - > bhs [ bi ] - > b_size ;
if ( bs > end )
bs = end ;
for ( i = 0 ; i < bs ; i + = sizeof ( u32 ) )
dircheck = ror32 ( dircheck , 13 ) ^ le32_to_cpup ( bp + + ) ;
end - = bs ;
}
/* Accumulate the contents of the tail except for the check byte */
dircheck = ror32 ( dircheck , 13 ) ^ le32_to_cpu ( t - > bigdirendname ) ;
dircheck = ror32 ( dircheck , 13 ) ^ t - > bigdirendmasseq ;
dircheck = ror32 ( dircheck , 13 ) ^ t - > reserved [ 0 ] ;
dircheck = ror32 ( dircheck , 13 ) ^ t - > reserved [ 1 ] ;
return dircheck ^ dircheck > > 8 ^ dircheck > > 16 ^ dircheck > > 24 ;
}
2019-12-09 14:09:35 +03:00
static int adfs_fplus_read ( struct super_block * sb , u32 indaddr ,
unsigned int size , struct adfs_dir * dir )
2005-04-17 02:20:36 +04:00
{
struct adfs_bigdirheader * h ;
struct adfs_bigdirtail * t ;
2019-12-09 14:09:35 +03:00
unsigned int dirsize ;
int ret ;
2005-04-17 02:20:36 +04:00
2019-12-09 14:09:35 +03:00
/* Read first buffer */
ret = adfs_dir_read_buffers ( sb , indaddr , sb - > s_blocksize , dir ) ;
if ( ret )
return ret ;
2005-04-17 02:20:36 +04:00
2019-12-09 14:10:21 +03:00
dir - > bighead = h = ( void * ) dir - > bhs [ 0 ] - > b_data ;
2020-01-24 13:15:37 +03:00
ret = adfs_fplus_validate_header ( h ) ;
if ( ret ) {
2019-12-09 14:10:57 +03:00
adfs_error ( sb , " dir %06x has malformed header " , indaddr ) ;
goto out ;
}
2019-12-09 14:09:35 +03:00
dirsize = le32_to_cpu ( h - > bigdirsize ) ;
2019-12-09 14:11:13 +03:00
if ( size & & dirsize ! = size ) {
2019-06-04 16:49:52 +03:00
adfs_msg ( sb , KERN_WARNING ,
2019-12-09 14:09:35 +03:00
" dir %06x header size %X does not match directory size %X " ,
indaddr , dirsize , size ) ;
2005-04-17 02:20:36 +04:00
}
2019-12-09 14:09:35 +03:00
/* Read remaining buffers */
ret = adfs_dir_read_buffers ( sb , indaddr , dirsize , dir ) ;
if ( ret )
return ret ;
2005-04-17 02:20:36 +04:00
2019-12-09 14:10:21 +03:00
dir - > bigtail = t = ( struct adfs_bigdirtail * )
2019-12-09 14:09:35 +03:00
( dir - > bhs [ dir - > nr_buffers - 1 ] - > b_data + ( sb - > s_blocksize - 8 ) ) ;
2005-04-17 02:20:36 +04:00
2019-12-09 14:10:57 +03:00
ret = adfs_fplus_validate_tail ( h , t ) ;
if ( ret ) {
2019-12-09 14:09:35 +03:00
adfs_error ( sb , " dir %06x has malformed tail " , indaddr ) ;
2005-04-17 02:20:36 +04:00
goto out ;
2011-03-23 02:35:04 +03:00
}
2005-04-17 02:20:36 +04:00
2019-12-09 14:11:08 +03:00
if ( adfs_fplus_checkbyte ( dir ) ! = t - > bigdircheckbyte ) {
adfs_error ( sb , " dir %06x checkbyte mismatch \n " , indaddr ) ;
goto out ;
}
2005-04-17 02:20:36 +04:00
dir - > parent_id = le32_to_cpu ( h - > bigdirparent ) ;
return 0 ;
2011-03-23 02:35:04 +03:00
2005-04-17 02:20:36 +04:00
out :
2019-12-09 14:09:20 +03:00
adfs_dir_relse ( dir ) ;
2011-03-23 02:35:04 +03:00
2005-04-17 02:20:36 +04:00
return ret ;
}
static int
adfs_fplus_setpos ( struct adfs_dir * dir , unsigned int fpos )
{
int ret = - ENOENT ;
2019-12-09 14:10:21 +03:00
if ( fpos < = le32_to_cpu ( dir - > bighead - > bigdirentries ) ) {
2005-04-17 02:20:36 +04:00
dir - > pos = fpos ;
ret = 0 ;
}
return ret ;
}
static int
adfs_fplus_getnext ( struct adfs_dir * dir , struct object_info * obj )
{
2019-12-09 14:10:21 +03:00
struct adfs_bigdirheader * h = dir - > bighead ;
2005-04-17 02:20:36 +04:00
struct adfs_bigdirentry bde ;
unsigned int offset ;
2019-12-09 14:09:30 +03:00
int ret ;
2005-04-17 02:20:36 +04:00
if ( dir - > pos > = le32_to_cpu ( h - > bigdirentries ) )
2019-12-09 14:09:30 +03:00
return - ENOENT ;
2005-04-17 02:20:36 +04:00
2019-12-09 14:10:52 +03:00
offset = adfs_fplus_offset ( h , dir - > pos ) ;
2005-04-17 02:20:36 +04:00
2019-12-09 14:09:30 +03:00
ret = adfs_dir_copyfrom ( & bde , dir , offset ,
sizeof ( struct adfs_bigdirentry ) ) ;
if ( ret )
return ret ;
2005-04-17 02:20:36 +04:00
obj - > loadaddr = le32_to_cpu ( bde . bigdirload ) ;
obj - > execaddr = le32_to_cpu ( bde . bigdirexec ) ;
obj - > size = le32_to_cpu ( bde . bigdirlen ) ;
2019-06-04 16:49:57 +03:00
obj - > indaddr = le32_to_cpu ( bde . bigdirindaddr ) ;
2005-04-17 02:20:36 +04:00
obj - > attr = le32_to_cpu ( bde . bigdirattr ) ;
obj - > name_len = le32_to_cpu ( bde . bigdirobnamelen ) ;
2019-12-09 14:10:52 +03:00
offset = adfs_fplus_offset ( h , le32_to_cpu ( h - > bigdirentries ) ) ;
2005-04-17 02:20:36 +04:00
offset + = le32_to_cpu ( bde . bigdirobnameptr ) ;
2019-12-09 14:09:30 +03:00
ret = adfs_dir_copyfrom ( obj - > name , dir , offset , obj - > name_len ) ;
if ( ret )
return ret ;
2019-03-24 15:57:32 +03:00
adfs_object_fixup ( dir , obj ) ;
2011-03-23 02:35:06 +03:00
2005-04-17 02:20:36 +04:00
dir - > pos + = 1 ;
2019-12-09 14:09:30 +03:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2019-12-09 14:10:16 +03:00
static int adfs_fplus_iterate ( struct adfs_dir * dir , struct dir_context * ctx )
{
struct object_info obj ;
if ( ( ctx - > pos - 2 ) > > 32 )
return 0 ;
if ( adfs_fplus_setpos ( dir , ctx - > pos - 2 ) )
return 0 ;
while ( ! adfs_fplus_getnext ( dir , & obj ) ) {
if ( ! dir_emit ( ctx , obj . name , obj . name_len ,
obj . indaddr , DT_UNKNOWN ) )
break ;
ctx - > pos + + ;
}
return 0 ;
}
2019-12-09 14:11:13 +03:00
static int adfs_fplus_update ( struct adfs_dir * dir , struct object_info * obj )
{
struct adfs_bigdirheader * h = dir - > bighead ;
struct adfs_bigdirentry bde ;
int offset , end , ret ;
offset = adfs_fplus_offset ( h , 0 ) - sizeof ( bde ) ;
end = adfs_fplus_offset ( h , le32_to_cpu ( h - > bigdirentries ) ) ;
do {
offset + = sizeof ( bde ) ;
if ( offset > = end ) {
adfs_error ( dir - > sb , " unable to locate entry to update " ) ;
return - ENOENT ;
}
ret = adfs_dir_copyfrom ( & bde , dir , offset , sizeof ( bde ) ) ;
if ( ret ) {
adfs_error ( dir - > sb , " error reading directory entry " ) ;
return - ENOENT ;
}
} while ( le32_to_cpu ( bde . bigdirindaddr ) ! = obj - > indaddr ) ;
bde . bigdirload = cpu_to_le32 ( obj - > loadaddr ) ;
bde . bigdirexec = cpu_to_le32 ( obj - > execaddr ) ;
bde . bigdirlen = cpu_to_le32 ( obj - > size ) ;
bde . bigdirindaddr = cpu_to_le32 ( obj - > indaddr ) ;
bde . bigdirattr = cpu_to_le32 ( obj - > attr ) ;
return adfs_dir_copyto ( dir , offset , & bde , sizeof ( bde ) ) ;
}
static int adfs_fplus_commit ( struct adfs_dir * dir )
{
int ret ;
/* Increment directory sequence number */
dir - > bighead - > startmasseq + = 1 ;
dir - > bigtail - > bigdirendmasseq + = 1 ;
/* Update directory check byte */
dir - > bigtail - > bigdircheckbyte = adfs_fplus_checkbyte ( dir ) ;
/* Make sure the directory still validates correctly */
ret = adfs_fplus_validate_header ( dir - > bighead ) ;
if ( ret = = 0 )
ret = adfs_fplus_validate_tail ( dir - > bighead , dir - > bigtail ) ;
return ret ;
}
2015-11-21 18:15:37 +03:00
const struct adfs_dir_ops adfs_fplus_dir_ops = {
2005-04-17 02:20:36 +04:00
. read = adfs_fplus_read ,
2019-12-09 14:10:16 +03:00
. iterate = adfs_fplus_iterate ,
2005-04-17 02:20:36 +04:00
. setpos = adfs_fplus_setpos ,
. getnext = adfs_fplus_getnext ,
2019-12-09 14:11:13 +03:00
. update = adfs_fplus_update ,
. commit = adfs_fplus_commit ,
2005-04-17 02:20:36 +04:00
} ;