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 . c
*
* Copyright ( C ) 1999 - 2000 Russell King
*
* Common directory handling for ADFS
*/
2019-12-09 14:09:20 +03:00
# include <linux/slab.h>
2005-04-17 02:20:36 +04:00
# include "adfs.h"
/*
* For future . This should probably be per - directory .
*/
2019-12-09 14:09:51 +03:00
static DECLARE_RWSEM ( adfs_dir_rwsem ) ;
2005-04-17 02:20:36 +04:00
2019-12-09 14:09:30 +03:00
int adfs_dir_copyfrom ( void * dst , struct adfs_dir * dir , unsigned int offset ,
size_t len )
{
struct super_block * sb = dir - > sb ;
unsigned int index , remain ;
index = offset > > sb - > s_blocksize_bits ;
offset & = sb - > s_blocksize - 1 ;
remain = sb - > s_blocksize - offset ;
if ( index + ( remain < len ) > = dir - > nr_buffers )
return - EINVAL ;
if ( remain < len ) {
memcpy ( dst , dir - > bhs [ index ] - > b_data + offset , remain ) ;
dst + = remain ;
len - = remain ;
index + = 1 ;
offset = 0 ;
}
memcpy ( dst , dir - > bhs [ index ] - > b_data + offset , len ) ;
return 0 ;
}
int adfs_dir_copyto ( struct adfs_dir * dir , unsigned int offset , const void * src ,
size_t len )
{
struct super_block * sb = dir - > sb ;
unsigned int index , remain ;
index = offset > > sb - > s_blocksize_bits ;
offset & = sb - > s_blocksize - 1 ;
remain = sb - > s_blocksize - offset ;
if ( index + ( remain < len ) > = dir - > nr_buffers )
return - EINVAL ;
if ( remain < len ) {
memcpy ( dir - > bhs [ index ] - > b_data + offset , src , remain ) ;
src + = remain ;
len - = remain ;
index + = 1 ;
offset = 0 ;
}
memcpy ( dir - > bhs [ index ] - > b_data + offset , src , len ) ;
return 0 ;
}
2019-12-09 14:10:01 +03:00
static void __adfs_dir_cleanup ( struct adfs_dir * dir )
2019-12-09 14:09:20 +03:00
{
dir - > nr_buffers = 0 ;
if ( dir - > bhs ! = dir - > bh )
kfree ( dir - > bhs ) ;
dir - > bhs = NULL ;
dir - > sb = NULL ;
}
2019-12-09 14:10:01 +03:00
void adfs_dir_relse ( struct adfs_dir * dir )
{
unsigned int i ;
for ( i = 0 ; i < dir - > nr_buffers ; i + + )
brelse ( dir - > bhs [ i ] ) ;
__adfs_dir_cleanup ( dir ) ;
}
static void adfs_dir_forget ( struct adfs_dir * dir )
{
unsigned int i ;
for ( i = 0 ; i < dir - > nr_buffers ; i + + )
bforget ( dir - > bhs [ i ] ) ;
__adfs_dir_cleanup ( dir ) ;
}
2019-12-09 14:09:35 +03:00
int adfs_dir_read_buffers ( struct super_block * sb , u32 indaddr ,
unsigned int size , struct adfs_dir * dir )
{
struct buffer_head * * bhs ;
unsigned int i , num ;
int block ;
num = ALIGN ( size , sb - > s_blocksize ) > > sb - > s_blocksize_bits ;
if ( num > ARRAY_SIZE ( dir - > bh ) ) {
/* We only allow one extension */
if ( dir - > bhs ! = dir - > bh )
return - EINVAL ;
bhs = kcalloc ( num , sizeof ( * bhs ) , GFP_KERNEL ) ;
if ( ! bhs )
return - ENOMEM ;
if ( dir - > nr_buffers )
memcpy ( bhs , dir - > bhs , dir - > nr_buffers * sizeof ( * bhs ) ) ;
dir - > bhs = bhs ;
}
for ( i = dir - > nr_buffers ; i < num ; i + + ) {
block = __adfs_block_map ( sb , indaddr , i ) ;
if ( ! block ) {
adfs_error ( sb , " dir %06x has a hole at offset %u " ,
indaddr , i ) ;
goto error ;
}
dir - > bhs [ i ] = sb_bread ( sb , block ) ;
if ( ! dir - > bhs [ i ] ) {
adfs_error ( sb ,
" dir %06x failed read at offset %u, mapped block 0x%08x " ,
indaddr , i , block ) ;
goto error ;
}
dir - > nr_buffers + + ;
}
return 0 ;
error :
adfs_dir_relse ( dir ) ;
return - EIO ;
}
2019-12-09 14:09:15 +03:00
static int adfs_dir_read ( struct super_block * sb , u32 indaddr ,
unsigned int size , struct adfs_dir * dir )
{
dir - > sb = sb ;
dir - > bhs = dir - > bh ;
dir - > nr_buffers = 0 ;
return ADFS_SB ( sb ) - > s_dir - > read ( sb , indaddr , size , dir ) ;
}
2019-12-09 14:09:40 +03:00
static int adfs_dir_read_inode ( struct super_block * sb , struct inode * inode ,
struct adfs_dir * dir )
{
int ret ;
2019-12-09 14:11:38 +03:00
ret = adfs_dir_read ( sb , ADFS_I ( inode ) - > indaddr , inode - > i_size , dir ) ;
2019-12-09 14:09:40 +03:00
if ( ret )
return ret ;
if ( ADFS_I ( inode ) - > parent_id ! = dir - > parent_id ) {
adfs_error ( sb ,
" parent directory id changed under me! (%06x but got %06x) \n " ,
ADFS_I ( inode ) - > parent_id , dir - > parent_id ) ;
adfs_dir_relse ( dir ) ;
ret = - EIO ;
}
return ret ;
}
2019-12-09 14:09:45 +03:00
static void adfs_dir_mark_dirty ( struct adfs_dir * dir )
{
unsigned int i ;
/* Mark the buffers dirty */
for ( i = 0 ; i < dir - > nr_buffers ; i + + )
mark_buffer_dirty ( dir - > bhs [ i ] ) ;
}
2019-12-09 14:09:25 +03:00
static int adfs_dir_sync ( struct adfs_dir * dir )
{
int err = 0 ;
int i ;
for ( i = dir - > nr_buffers - 1 ; i > = 0 ; i - - ) {
struct buffer_head * bh = dir - > bhs [ i ] ;
sync_dirty_buffer ( bh ) ;
if ( buffer_req ( bh ) & & ! buffer_uptodate ( bh ) )
err = - EIO ;
}
return err ;
}
2019-03-24 15:57:32 +03:00
void adfs_object_fixup ( struct adfs_dir * dir , struct object_info * obj )
{
2019-03-24 17:00:35 +03:00
unsigned int dots , i ;
2019-03-24 16:22:28 +03:00
/*
* RISC OS allows the use of ' / ' in directory entry names , so we need
* to fix these up . ' / ' is typically used for FAT compatibility to
* represent ' . ' , so do the same conversion here . In any case , ' . '
* will never be in a RISC OS name since it is used as the pathname
2019-03-24 17:00:35 +03:00
* separator . Handle the case where we may generate a ' . ' or ' . . '
* name , replacing the first character with ' ^ ' ( the RISC OS " parent
* directory " character.)
2019-03-24 16:22:28 +03:00
*/
2019-03-24 17:00:35 +03:00
for ( i = dots = 0 ; i < obj - > name_len ; i + + )
if ( obj - > name [ i ] = = ' / ' ) {
2019-03-24 16:22:28 +03:00
obj - > name [ i ] = ' . ' ;
2019-03-24 17:00:35 +03:00
dots + + ;
}
if ( obj - > name_len < = 2 & & dots = = obj - > name_len )
obj - > name [ 0 ] = ' ^ ' ;
2019-03-24 16:22:28 +03:00
2019-03-24 15:57:32 +03:00
/*
2019-06-04 16:50:24 +03:00
* If the object is a file , and the user requested the , xyz hex
* filetype suffix to the name , check the filetype and append .
2019-03-24 15:57:32 +03:00
*/
2019-06-04 16:50:24 +03:00
if ( ! ( obj - > attr & ADFS_NDA_DIRECTORY ) & & ADFS_SB ( dir - > sb ) - > s_ftsuffix ) {
u16 filetype = adfs_filetype ( obj - > loadaddr ) ;
2019-03-24 16:08:41 +03:00
2019-06-04 16:50:24 +03:00
if ( filetype ! = ADFS_FILETYPE_NONE ) {
2019-03-24 16:08:41 +03:00
obj - > name [ obj - > name_len + + ] = ' , ' ;
obj - > name [ obj - > name_len + + ] = hex_asc_lo ( filetype > > 8 ) ;
obj - > name [ obj - > name_len + + ] = hex_asc_lo ( filetype > > 4 ) ;
obj - > name [ obj - > name_len + + ] = hex_asc_lo ( filetype > > 0 ) ;
}
2019-03-24 15:57:32 +03:00
}
}
2019-12-09 14:10:11 +03:00
static int adfs_iterate ( struct file * file , struct dir_context * ctx )
2005-04-17 02:20:36 +04:00
{
2013-05-18 01:30:10 +04:00
struct inode * inode = file_inode ( file ) ;
2005-04-17 02:20:36 +04:00
struct super_block * sb = inode - > i_sb ;
2015-11-21 18:15:37 +03:00
const struct adfs_dir_ops * ops = ADFS_SB ( sb ) - > s_dir ;
2005-04-17 02:20:36 +04:00
struct adfs_dir dir ;
2019-12-09 14:10:16 +03:00
int ret ;
2005-04-17 02:20:36 +04:00
2019-12-09 14:09:51 +03:00
down_read ( & adfs_dir_rwsem ) ;
2019-12-09 14:09:40 +03:00
ret = adfs_dir_read_inode ( sb , inode , & dir ) ;
2005-04-17 02:20:36 +04:00
if ( ret )
2019-12-09 14:09:51 +03:00
goto unlock ;
2005-04-17 02:20:36 +04:00
2013-05-18 01:30:10 +04:00
if ( ctx - > pos = = 0 ) {
if ( ! dir_emit_dot ( file , ctx ) )
2019-12-09 14:09:51 +03:00
goto unlock_relse ;
2013-05-18 01:30:10 +04:00
ctx - > pos = 1 ;
}
if ( ctx - > pos = = 1 ) {
if ( ! dir_emit ( ctx , " .. " , 2 , dir . parent_id , DT_DIR ) )
2019-12-09 14:09:51 +03:00
goto unlock_relse ;
2013-05-18 01:30:10 +04:00
ctx - > pos = 2 ;
2005-04-17 02:20:36 +04:00
}
2019-12-09 14:10:16 +03:00
ret = ops - > iterate ( & dir , ctx ) ;
2005-04-17 02:20:36 +04:00
2019-12-09 14:09:51 +03:00
unlock_relse :
up_read ( & adfs_dir_rwsem ) ;
2019-12-09 14:09:20 +03:00
adfs_dir_relse ( & dir ) ;
2005-04-17 02:20:36 +04:00
return ret ;
2019-12-09 14:09:51 +03:00
unlock :
up_read ( & adfs_dir_rwsem ) ;
return ret ;
2005-04-17 02:20:36 +04:00
}
int
2009-06-08 08:44:42 +04:00
adfs_dir_update ( struct super_block * sb , struct object_info * obj , int wait )
2005-04-17 02:20:36 +04:00
{
2015-11-21 18:15:37 +03:00
const struct adfs_dir_ops * ops = ADFS_SB ( sb ) - > s_dir ;
2005-04-17 02:20:36 +04:00
struct adfs_dir dir ;
2019-12-09 14:10:06 +03:00
int ret ;
2005-04-17 02:20:36 +04:00
2019-12-09 14:10:06 +03:00
if ( ! IS_ENABLED ( CONFIG_ADFS_FS_RW ) )
return - EINVAL ;
2019-12-09 14:09:25 +03:00
if ( ! ops - > update )
return - EINVAL ;
2005-04-17 02:20:36 +04:00
2019-12-09 14:09:51 +03:00
down_write ( & adfs_dir_rwsem ) ;
2019-12-09 14:09:15 +03:00
ret = adfs_dir_read ( sb , obj - > parent_id , 0 , & dir ) ;
2005-04-17 02:20:36 +04:00
if ( ret )
2019-12-09 14:09:51 +03:00
goto unlock ;
2005-04-17 02:20:36 +04:00
ret = ops - > update ( & dir , obj ) ;
2019-12-09 14:10:47 +03:00
if ( ret )
goto forget ;
ret = ops - > commit ( & dir ) ;
2019-12-09 14:10:01 +03:00
if ( ret )
goto forget ;
2019-12-09 14:09:51 +03:00
up_write ( & adfs_dir_rwsem ) ;
2005-04-17 02:20:36 +04:00
2019-12-09 14:10:01 +03:00
adfs_dir_mark_dirty ( & dir ) ;
2019-12-09 14:09:45 +03:00
2019-12-09 14:10:01 +03:00
if ( wait )
ret = adfs_dir_sync ( & dir ) ;
2009-06-08 08:44:42 +04:00
2019-12-09 14:09:20 +03:00
adfs_dir_relse ( & dir ) ;
2019-12-09 14:09:51 +03:00
return ret ;
2019-12-09 14:10:01 +03:00
/*
* If the updated failed because the entry wasn ' t found , we can
* just release the buffers . If it was any other error , forget
* the dirtied buffers so they aren ' t written back to the media .
*/
forget :
if ( ret = = - ENOENT )
adfs_dir_relse ( & dir ) ;
else
adfs_dir_forget ( & dir ) ;
2019-12-09 14:09:51 +03:00
unlock :
up_write ( & adfs_dir_rwsem ) ;
2019-12-09 14:10:06 +03:00
2005-04-17 02:20:36 +04:00
return ret ;
}
2019-03-24 15:22:04 +03:00
static unsigned char adfs_tolower ( unsigned char c )
{
if ( c > = ' A ' & & c < = ' Z ' )
c + = ' a ' - ' A ' ;
return c ;
}
2019-03-24 13:54:03 +03:00
static int __adfs_compare ( const unsigned char * qstr , u32 qlen ,
const char * str , u32 len )
2005-04-17 02:20:36 +04:00
{
2019-03-24 13:54:03 +03:00
u32 i ;
2005-04-17 02:20:36 +04:00
2019-03-24 13:54:03 +03:00
if ( qlen ! = len )
return 1 ;
2005-04-17 02:20:36 +04:00
2019-03-24 15:22:04 +03:00
for ( i = 0 ; i < qlen ; i + + )
if ( adfs_tolower ( qstr [ i ] ) ! = adfs_tolower ( str [ i ] ) )
2019-03-24 13:54:03 +03:00
return 1 ;
2019-03-24 15:22:04 +03:00
2019-03-24 13:54:03 +03:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2019-03-24 13:54:03 +03:00
static int adfs_dir_lookup_byname ( struct inode * inode , const struct qstr * qstr ,
struct object_info * obj )
2005-04-17 02:20:36 +04:00
{
struct super_block * sb = inode - > i_sb ;
2015-11-21 18:15:37 +03:00
const struct adfs_dir_ops * ops = ADFS_SB ( sb ) - > s_dir ;
2019-03-24 13:54:03 +03:00
const unsigned char * name ;
2005-04-17 02:20:36 +04:00
struct adfs_dir dir ;
2019-03-24 13:54:03 +03:00
u32 name_len ;
2005-04-17 02:20:36 +04:00
int ret ;
2019-12-09 14:09:51 +03:00
down_read ( & adfs_dir_rwsem ) ;
2019-12-09 14:09:40 +03:00
ret = adfs_dir_read_inode ( sb , inode , & dir ) ;
2005-04-17 02:20:36 +04:00
if ( ret )
2019-12-09 14:09:51 +03:00
goto unlock ;
2005-04-17 02:20:36 +04:00
ret = ops - > setpos ( & dir , 0 ) ;
if ( ret )
2019-12-09 14:09:51 +03:00
goto unlock_relse ;
2005-04-17 02:20:36 +04:00
ret = - ENOENT ;
2019-03-24 13:54:03 +03:00
name = qstr - > name ;
name_len = qstr - > len ;
2005-04-17 02:20:36 +04:00
while ( ops - > getnext ( & dir , obj ) = = 0 ) {
2019-03-24 13:54:03 +03:00
if ( ! __adfs_compare ( name , name_len , obj - > name , obj - > name_len ) ) {
2005-04-17 02:20:36 +04:00
ret = 0 ;
break ;
}
}
2019-12-09 14:11:38 +03:00
obj - > parent_id = ADFS_I ( inode ) - > indaddr ;
2005-04-17 02:20:36 +04:00
2019-12-09 14:09:51 +03:00
unlock_relse :
up_read ( & adfs_dir_rwsem ) ;
2019-12-09 14:09:20 +03:00
adfs_dir_relse ( & dir ) ;
2019-12-09 14:09:51 +03:00
return ret ;
unlock :
up_read ( & adfs_dir_rwsem ) ;
2005-04-17 02:20:36 +04:00
return ret ;
}
2006-03-28 13:56:42 +04:00
const struct file_operations adfs_dir_operations = {
2005-04-17 02:20:36 +04:00
. read = generic_read_dir ,
2008-08-24 15:24:41 +04:00
. llseek = generic_file_llseek ,
2019-12-09 14:10:11 +03:00
. iterate_shared = adfs_iterate ,
2010-05-26 19:53:41 +04:00
. fsync = generic_file_fsync ,
2005-04-17 02:20:36 +04:00
} ;
static int
2013-05-22 02:22:44 +04:00
adfs_hash ( const struct dentry * parent , struct qstr * qstr )
2005-04-17 02:20:36 +04:00
{
const unsigned char * name ;
unsigned long hash ;
2019-03-24 14:02:02 +03:00
u32 len ;
2005-04-17 02:20:36 +04:00
2019-03-24 14:02:02 +03:00
if ( qstr - > len > ADFS_SB ( parent - > d_sb ) - > s_namelen )
return - ENAMETOOLONG ;
2005-04-17 02:20:36 +04:00
2019-03-24 14:02:02 +03:00
len = qstr - > len ;
2005-04-17 02:20:36 +04:00
name = qstr - > name ;
2016-06-10 17:51:30 +03:00
hash = init_name_hash ( parent ) ;
2019-03-24 14:02:02 +03:00
while ( len - - )
2019-03-24 15:22:04 +03:00
hash = partial_name_hash ( adfs_tolower ( * name + + ) , hash ) ;
2005-04-17 02:20:36 +04:00
qstr - > hash = end_name_hash ( hash ) ;
return 0 ;
}
/*
* Compare two names , taking note of the name length
* requirements of the underlying filesystem .
*/
2019-03-24 13:54:03 +03:00
static int adfs_compare ( const struct dentry * dentry , unsigned int len ,
const char * str , const struct qstr * qstr )
2005-04-17 02:20:36 +04:00
{
2019-03-24 13:54:03 +03:00
return __adfs_compare ( qstr - > name , qstr - > len , str , len ) ;
2005-04-17 02:20:36 +04:00
}
2009-02-20 08:55:13 +03:00
const struct dentry_operations adfs_dentry_operations = {
2005-04-17 02:20:36 +04:00
. d_hash = adfs_hash ,
. d_compare = adfs_compare ,
} ;
static struct dentry *
2012-06-11 01:13:09 +04:00
adfs_lookup ( struct inode * dir , struct dentry * dentry , unsigned int flags )
2005-04-17 02:20:36 +04:00
{
struct inode * inode = NULL ;
struct object_info obj ;
int error ;
error = adfs_dir_lookup_byname ( dir , & dentry - > d_name , & obj ) ;
if ( error = = 0 ) {
/*
* This only returns NULL if get_empty_inode
* fails .
*/
inode = adfs_iget ( dir - > i_sb , & obj ) ;
2018-05-01 05:57:42 +03:00
if ( ! inode )
inode = ERR_PTR ( - EACCES ) ;
} else if ( error ! = - ENOENT ) {
inode = ERR_PTR ( error ) ;
2005-04-17 02:20:36 +04:00
}
2018-05-01 05:57:42 +03:00
return d_splice_alias ( inode , dentry ) ;
2005-04-17 02:20:36 +04:00
}
/*
* directories can handle most operations . . .
*/
2007-02-12 11:55:38 +03:00
const struct inode_operations adfs_dir_inode_operations = {
2005-04-17 02:20:36 +04:00
. lookup = adfs_lookup ,
. setattr = adfs_notify_change ,
} ;