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 ;
2020-08-23 17:36:59 -05:00
fallthrough ;
2005-04-16 15:20:36 -07:00
case 3 : val | = p [ 2 ] < < 16 ;
2020-08-23 17:36:59 -05:00
fallthrough ;
2005-04-16 15:20:36 -07:00
case 2 : val | = p [ 1 ] < < 8 ;
2020-08-23 17:36:59 -05:00
fallthrough ;
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 ;
2020-08-23 17:36:59 -05:00
fallthrough ;
2005-04-16 15:20:36 -07:00
case 3 : p [ 2 ] = val > > 16 ;
2020-08-23 17:36:59 -05:00
fallthrough ;
2005-04-16 15:20:36 -07:00
case 2 : p [ 1 ] = val > > 8 ;
2020-08-23 17:36:59 -05:00
fallthrough ;
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 */
2019-12-09 11:10:37 +00: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
{
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
2019-12-09 11:10:37 +00:00
if ( size & & size ! = ADFS_NEWDIR_SIZE )
return - EIO ;
2005-04-16 15:20:36 -07:00
2019-12-09 11:10:37 +00:00
ret = adfs_dir_read_buffers ( sb , indaddr , ADFS_NEWDIR_SIZE , dir ) ;
2019-12-09 11:09:35 +00:00
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 ;
2019-12-09 11:10:37 +00:00
dir - > parent_id = adfs_readval ( dir - > newtail - > dirparent , 3 ) ;
2005-04-16 15:20:36 -07:00
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_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 ;
}
2019-12-09 11:10:42 +00:00
static int adfs_f_update ( struct adfs_dir * dir , struct object_info * obj )
2005-04-16 15:20:36 -07:00
{
2019-12-09 11:10:42 +00:00
struct adfs_direntry de ;
int offset , ret ;
2005-04-16 15:20:36 -07:00
2019-12-09 11:10:42 +00:00
offset = 5 - ( int ) sizeof ( de ) ;
do {
offset + = sizeof ( de ) ;
ret = adfs_dir_copyfrom ( & de , dir , offset , sizeof ( de ) ) ;
if ( ret ) {
adfs_error ( dir - > sb , " error reading directory entry " ) ;
return - ENOENT ;
}
if ( ! de . dirobname [ 0 ] ) {
adfs_error ( dir - > sb , " unable to locate entry to update " ) ;
return - ENOENT ;
}
} while ( adfs_readval ( de . dirinddiscadd , 3 ) ! = obj - > indaddr ) ;
/* Update the directory entry with the new object state */
adfs_obj2dir ( & de , obj ) ;
2005-04-16 15:20:36 -07:00
2019-12-09 11:10:42 +00:00
/* Write the directory entry back to the directory */
2019-12-09 11:10:47 +00:00
return adfs_dir_copyto ( dir , offset , & de , 26 ) ;
}
static int adfs_f_commit ( struct adfs_dir * dir )
{
int ret ;
/* 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
2019-12-09 11:10:47 +00:00
/* Update directory check byte */
dir - > newtail - > dircheckbyte = adfs_dir_checkbyte ( dir ) ;
2005-04-16 15:20:36 -07:00
2019-12-09 11:10:47 +00:00
/* Make sure the directory still validates correctly */
2019-12-09 11:10:27 +00:00
ret = adfs_f_validate ( dir ) ;
if ( ret )
2019-12-09 11:10:47 +00:00
adfs_msg ( dir - > sb , KERN_ERR , " error: update broke 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 ,
2019-12-09 11:10:47 +00:00
. commit = adfs_f_commit ,
2005-04-16 15:20:36 -07:00
} ;