2019-06-04 10:11:33 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2005-04-16 15:20:36 -07:00
/*
* linux / fs / adfs / dir_f . c
*
* Copyright ( C ) 1997 - 1999 Russell King
*
* E and F format directory handling
*/
# include "adfs.h"
# include "dir_f.h"
/*
* Read an ( unaligned ) value of length 1. .4 bytes
*/
static inline unsigned int adfs_readval ( unsigned char * p , int len )
{
unsigned int val = 0 ;
switch ( len ) {
case 4 : val | = p [ 3 ] < < 24 ;
2019-01-14 14:30:36 -06:00
/* fall through */
2005-04-16 15:20:36 -07:00
case 3 : val | = p [ 2 ] < < 16 ;
2019-01-14 14:30:36 -06:00
/* fall through */
2005-04-16 15:20:36 -07:00
case 2 : val | = p [ 1 ] < < 8 ;
2019-01-14 14:30:36 -06:00
/* fall through */
2005-04-16 15:20:36 -07:00
default : val | = p [ 0 ] ;
}
return val ;
}
static inline void adfs_writeval ( unsigned char * p , int len , unsigned int val )
{
switch ( len ) {
case 4 : p [ 3 ] = val > > 24 ;
2019-01-14 14:30:36 -06:00
/* fall through */
2005-04-16 15:20:36 -07:00
case 3 : p [ 2 ] = val > > 16 ;
2019-01-14 14:30:36 -06:00
/* fall through */
2005-04-16 15:20:36 -07:00
case 2 : p [ 1 ] = val > > 8 ;
2019-01-14 14:30:36 -06:00
/* fall through */
2005-04-16 15:20:36 -07:00
default : p [ 0 ] = val ;
}
}
# define ror13(v) ((v >> 13) | (v << 19))
# define dir_u8(idx) \
( { int _buf = idx > > blocksize_bits ; \
int _off = idx - ( _buf < < blocksize_bits ) ; \
* ( u8 * ) ( bh [ _buf ] - > b_data + _off ) ; \
} )
# define dir_u32(idx) \
( { int _buf = idx > > blocksize_bits ; \
int _off = idx - ( _buf < < blocksize_bits ) ; \
* ( __le32 * ) ( bh [ _buf ] - > b_data + _off ) ; \
} )
# define bufoff(_bh,_idx) \
( { int _buf = _idx > > blocksize_bits ; \
int _off = _idx - ( _buf < < blocksize_bits ) ; \
2019-12-09 11:10:21 +00:00
( void * ) ( _bh [ _buf ] - > b_data + _off ) ; \
2005-04-16 15:20:36 -07:00
} )
/*
* There are some algorithms that are nice in
* assembler , but a bitch in C . . . This is one
* of them .
*/
static u8
adfs_dir_checkbyte ( const struct adfs_dir * dir )
{
struct buffer_head * const * bh = dir - > bh ;
const int blocksize_bits = dir - > sb - > s_blocksize_bits ;
union { __le32 * ptr32 ; u8 * ptr8 ; } ptr , end ;
u32 dircheck = 0 ;
int last = 5 - 26 ;
int i = 0 ;
/*
* Accumulate each word up to the last whole
* word of the last directory entry . This
* can spread across several buffer heads .
*/
do {
last + = 26 ;
do {
dircheck = le32_to_cpu ( dir_u32 ( i ) ) ^ ror13 ( dircheck ) ;
i + = sizeof ( u32 ) ;
} while ( i < ( last & ~ 3 ) ) ;
} while ( dir_u8 ( last ) ! = 0 ) ;
/*
* Accumulate the last few bytes . These
* bytes will be within the same bh .
*/
if ( i ! = last ) {
ptr . ptr8 = bufoff ( bh , i ) ;
end . ptr8 = ptr . ptr8 + last - i ;
2008-04-29 00:58:41 -07:00
do {
2005-04-16 15:20:36 -07:00
dircheck = * ptr . ptr8 + + ^ ror13 ( dircheck ) ;
2008-04-29 00:58:41 -07:00
} while ( ptr . ptr8 < end . ptr8 ) ;
2005-04-16 15:20:36 -07:00
}
/*
* The directory tail is in the final bh
* Note that contary to the RISC OS PRMs ,
* the first few bytes are NOT included
* in the check . All bytes are in the
* same bh .
*/
ptr . ptr8 = bufoff ( bh , 2008 ) ;
end . ptr8 = ptr . ptr8 + 36 ;
do {
__le32 v = * ptr . ptr32 + + ;
dircheck = le32_to_cpu ( v ) ^ ror13 ( dircheck ) ;
} while ( ptr . ptr32 < end . ptr32 ) ;
return ( dircheck ^ ( dircheck > > 8 ) ^ ( dircheck > > 16 ) ^ ( dircheck > > 24 ) ) & 0xff ;
}
2019-12-09 11:10:27 +00:00
static int adfs_f_validate ( struct adfs_dir * dir )
{
struct adfs_dirheader * head = dir - > dirhead ;
struct adfs_newdirtail * tail = dir - > newtail ;
if ( head - > startmasseq ! = tail - > endmasseq | |
2019-12-09 11:10:32 +00:00
tail - > dirlastmask | | tail - > reserved [ 0 ] | | tail - > reserved [ 1 ] | |
2019-12-09 11:10:27 +00:00
( memcmp ( & head - > startname , " Nick " , 4 ) & &
memcmp ( & head - > startname , " Hugo " , 4 ) ) | |
memcmp ( & head - > startname , & tail - > endname , 4 ) | |
adfs_dir_checkbyte ( dir ) ! = tail - > dircheckbyte )
return - EIO ;
return 0 ;
}
2019-06-04 14:49:57 +01:00
/* Read and check that a directory is valid */
static int adfs_dir_read ( struct super_block * sb , u32 indaddr ,
unsigned int size , struct adfs_dir * dir )
2005-04-16 15:20:36 -07:00
{
const unsigned int blocksize_bits = sb - > s_blocksize_bits ;
2019-12-09 11:09:35 +00:00
int ret ;
2005-04-16 15:20:36 -07:00
/*
* Directories which are not a multiple of 2048 bytes
* are considered bad v2 [ 3.6 ]
*/
if ( size & 2047 )
goto bad_dir ;
2019-12-09 11:09:35 +00:00
ret = adfs_dir_read_buffers ( sb , indaddr , size , dir ) ;
if ( ret )
return ret ;
2005-04-16 15:20:36 -07:00
2019-12-09 11:10:21 +00:00
dir - > dirhead = bufoff ( dir - > bh , 0 ) ;
dir - > newtail = bufoff ( dir - > bh , 2007 ) ;
2005-04-16 15:20:36 -07:00
2019-12-09 11:10:27 +00:00
if ( adfs_f_validate ( dir ) )
2005-04-16 15:20:36 -07:00
goto bad_dir ;
return 0 ;
bad_dir :
2019-06-04 14:49:57 +01:00
adfs_error ( sb , " dir %06x is corrupted " , indaddr ) ;
2019-12-09 11:09:20 +00:00
adfs_dir_relse ( dir ) ;
2005-04-16 15:20:36 -07:00
return - EIO ;
}
/*
* convert a disk - based directory entry to a Linux ADFS directory entry
*/
static inline void
2011-03-22 16:35:06 -07:00
adfs_dir2obj ( struct adfs_dir * dir , struct object_info * obj ,
struct adfs_direntry * de )
2005-04-16 15:20:36 -07:00
{
2019-03-24 13:22:28 +00:00
unsigned int name_len ;
for ( name_len = 0 ; name_len < ADFS_F_NAME_LEN ; name_len + + ) {
if ( de - > dirobname [ name_len ] < ' ' )
break ;
obj - > name [ name_len ] = de - > dirobname [ name_len ] ;
}
obj - > name_len = name_len ;
2019-06-04 14:49:57 +01:00
obj - > indaddr = adfs_readval ( de - > dirinddiscadd , 3 ) ;
2005-04-16 15:20:36 -07:00
obj - > loadaddr = adfs_readval ( de - > dirload , 4 ) ;
obj - > execaddr = adfs_readval ( de - > direxec , 4 ) ;
obj - > size = adfs_readval ( de - > dirlen , 4 ) ;
obj - > attr = de - > newdiratts ;
2011-03-22 16:35:06 -07:00
2019-03-24 12:57:32 +00:00
adfs_object_fixup ( dir , obj ) ;
2005-04-16 15:20:36 -07:00
}
/*
* convert a Linux ADFS directory entry to a disk - based directory entry
*/
static inline void
adfs_obj2dir ( struct adfs_direntry * de , struct object_info * obj )
{
2019-06-04 14:49:57 +01:00
adfs_writeval ( de - > dirinddiscadd , 3 , obj - > indaddr ) ;
2005-04-16 15:20:36 -07:00
adfs_writeval ( de - > dirload , 4 , obj - > loadaddr ) ;
adfs_writeval ( de - > direxec , 4 , obj - > execaddr ) ;
adfs_writeval ( de - > dirlen , 4 , obj - > size ) ;
de - > newdiratts = obj - > attr ;
}
/*
* get a directory entry . Note that the caller is responsible
* for holding the relevant locks .
*/
static int
__adfs_dir_get ( struct adfs_dir * dir , int pos , struct object_info * obj )
{
struct adfs_direntry de ;
2019-12-09 11:09:30 +00:00
int ret ;
2005-04-16 15:20:36 -07:00
2019-12-09 11:09:30 +00:00
ret = adfs_dir_copyfrom ( & de , dir , pos , 26 ) ;
if ( ret )
return ret ;
2005-04-16 15:20:36 -07:00
if ( ! de . dirobname [ 0 ] )
return - ENOENT ;
2011-03-22 16:35:06 -07:00
adfs_dir2obj ( dir , obj , & de ) ;
2005-04-16 15:20:36 -07:00
return 0 ;
}
static int
__adfs_dir_put ( struct adfs_dir * dir , int pos , struct object_info * obj )
{
struct adfs_direntry de ;
2019-12-09 11:09:30 +00:00
int ret ;
2005-04-16 15:20:36 -07:00
2019-12-09 11:09:30 +00:00
ret = adfs_dir_copyfrom ( & de , dir , pos , 26 ) ;
if ( ret )
return ret ;
2005-04-16 15:20:36 -07:00
adfs_obj2dir ( & de , obj ) ;
2019-12-09 11:09:30 +00:00
return adfs_dir_copyto ( dir , pos , & de , 26 ) ;
2005-04-16 15:20:36 -07:00
}
/*
* the caller is responsible for holding the necessary
* locks .
*/
2019-06-04 14:49:57 +01:00
static int adfs_dir_find_entry ( struct adfs_dir * dir , u32 indaddr )
2005-04-16 15:20:36 -07:00
{
int pos , ret ;
ret = - ENOENT ;
for ( pos = 5 ; pos < ADFS_NUM_DIR_ENTRIES * 26 + 5 ; pos + = 26 ) {
struct object_info obj ;
if ( ! __adfs_dir_get ( dir , pos , & obj ) )
break ;
2019-06-04 14:49:57 +01:00
if ( obj . indaddr = = indaddr ) {
2005-04-16 15:20:36 -07:00
ret = pos ;
break ;
}
}
return ret ;
}
2019-06-04 14:49:57 +01:00
static int adfs_f_read ( struct super_block * sb , u32 indaddr , unsigned int size ,
struct adfs_dir * dir )
2005-04-16 15:20:36 -07:00
{
int ret ;
2019-06-04 14:49:57 +01:00
if ( size ! = ADFS_NEWDIR_SIZE )
2005-04-16 15:20:36 -07:00
return - EIO ;
2019-06-04 14:49:57 +01:00
ret = adfs_dir_read ( sb , indaddr , size , dir ) ;
2005-04-16 15:20:36 -07:00
if ( ret )
adfs_error ( sb , " unable to read directory " ) ;
else
2019-12-09 11:10:21 +00:00
dir - > parent_id = adfs_readval ( dir - > newtail - > dirparent , 3 ) ;
2005-04-16 15:20:36 -07:00
return ret ;
}
static int
adfs_f_setpos ( struct adfs_dir * dir , unsigned int fpos )
{
if ( fpos > = ADFS_NUM_DIR_ENTRIES )
return - ENOENT ;
dir - > pos = 5 + fpos * 26 ;
return 0 ;
}
static int
adfs_f_getnext ( struct adfs_dir * dir , struct object_info * obj )
{
unsigned int ret ;
ret = __adfs_dir_get ( dir , dir - > pos , obj ) ;
if ( ret = = 0 )
dir - > pos + = 26 ;
return ret ;
}
2019-12-09 11:10:16 +00:00
static int adfs_f_iterate ( struct adfs_dir * dir , struct dir_context * ctx )
{
struct object_info obj ;
int pos = 5 + ( ctx - > pos - 2 ) * 26 ;
while ( ctx - > pos < 2 + ADFS_NUM_DIR_ENTRIES ) {
if ( __adfs_dir_get ( dir , pos , & obj ) )
break ;
if ( ! dir_emit ( ctx , obj . name , obj . name_len ,
obj . indaddr , DT_UNKNOWN ) )
break ;
pos + = 26 ;
ctx - > pos + + ;
}
return 0 ;
}
2005-04-16 15:20:36 -07:00
static int
adfs_f_update ( struct adfs_dir * dir , struct object_info * obj )
{
2019-12-09 11:09:45 +00:00
int ret ;
2005-04-16 15:20:36 -07:00
2019-06-04 14:49:57 +01:00
ret = adfs_dir_find_entry ( dir , obj - > indaddr ) ;
2005-04-16 15:20:36 -07:00
if ( ret < 0 ) {
adfs_error ( dir - > sb , " unable to locate entry to update " ) ;
2019-12-09 11:10:27 +00:00
return ret ;
2005-04-16 15:20:36 -07:00
}
__adfs_dir_put ( dir , ret , obj ) ;
/*
* Increment directory sequence number
*/
2019-12-09 11:10:21 +00:00
dir - > dirhead - > startmasseq + = 1 ;
dir - > newtail - > endmasseq + = 1 ;
2005-04-16 15:20:36 -07:00
ret = adfs_dir_checkbyte ( dir ) ;
/*
* Update directory check byte
*/
2019-12-09 11:10:21 +00:00
dir - > newtail - > dircheckbyte = ret ;
2005-04-16 15:20:36 -07:00
2019-12-09 11:10:27 +00:00
ret = adfs_f_validate ( dir ) ;
if ( ret )
adfs_error ( dir - > sb , " whoops! I broke a directory! " ) ;
2005-04-16 15:20:36 -07:00
return ret ;
}
2015-11-21 16:15:37 +01:00
const struct adfs_dir_ops adfs_f_dir_ops = {
2005-04-16 15:20:36 -07:00
. read = adfs_f_read ,
2019-12-09 11:10:16 +00:00
. iterate = adfs_f_iterate ,
2005-04-16 15:20:36 -07:00
. setpos = adfs_f_setpos ,
. getnext = adfs_f_getnext ,
. update = adfs_f_update ,
} ;