458 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * linux/fs/adfs/dir.c4 *5 * Copyright (C) 1999-2000 Russell King6 *7 * Common directory handling for ADFS8 */9#include <linux/slab.h>10#include "adfs.h"11 12/*13 * For future. This should probably be per-directory.14 */15static DECLARE_RWSEM(adfs_dir_rwsem);16 17int adfs_dir_copyfrom(void *dst, struct adfs_dir *dir, unsigned int offset,18 size_t len)19{20 struct super_block *sb = dir->sb;21 unsigned int index, remain;22 23 index = offset >> sb->s_blocksize_bits;24 offset &= sb->s_blocksize - 1;25 remain = sb->s_blocksize - offset;26 if (index + (remain < len) >= dir->nr_buffers)27 return -EINVAL;28 29 if (remain < len) {30 memcpy(dst, dir->bhs[index]->b_data + offset, remain);31 dst += remain;32 len -= remain;33 index += 1;34 offset = 0;35 }36 37 memcpy(dst, dir->bhs[index]->b_data + offset, len);38 39 return 0;40}41 42int adfs_dir_copyto(struct adfs_dir *dir, unsigned int offset, const void *src,43 size_t len)44{45 struct super_block *sb = dir->sb;46 unsigned int index, remain;47 48 index = offset >> sb->s_blocksize_bits;49 offset &= sb->s_blocksize - 1;50 remain = sb->s_blocksize - offset;51 if (index + (remain < len) >= dir->nr_buffers)52 return -EINVAL;53 54 if (remain < len) {55 memcpy(dir->bhs[index]->b_data + offset, src, remain);56 src += remain;57 len -= remain;58 index += 1;59 offset = 0;60 }61 62 memcpy(dir->bhs[index]->b_data + offset, src, len);63 64 return 0;65}66 67static void __adfs_dir_cleanup(struct adfs_dir *dir)68{69 dir->nr_buffers = 0;70 71 if (dir->bhs != dir->bh)72 kfree(dir->bhs);73 dir->bhs = NULL;74 dir->sb = NULL;75}76 77void adfs_dir_relse(struct adfs_dir *dir)78{79 unsigned int i;80 81 for (i = 0; i < dir->nr_buffers; i++)82 brelse(dir->bhs[i]);83 84 __adfs_dir_cleanup(dir);85}86 87static void adfs_dir_forget(struct adfs_dir *dir)88{89 unsigned int i;90 91 for (i = 0; i < dir->nr_buffers; i++)92 bforget(dir->bhs[i]);93 94 __adfs_dir_cleanup(dir);95}96 97int adfs_dir_read_buffers(struct super_block *sb, u32 indaddr,98 unsigned int size, struct adfs_dir *dir)99{100 struct buffer_head **bhs;101 unsigned int i, num;102 int block;103 104 num = ALIGN(size, sb->s_blocksize) >> sb->s_blocksize_bits;105 if (num > ARRAY_SIZE(dir->bh)) {106 /* We only allow one extension */107 if (dir->bhs != dir->bh)108 return -EINVAL;109 110 bhs = kcalloc(num, sizeof(*bhs), GFP_KERNEL);111 if (!bhs)112 return -ENOMEM;113 114 if (dir->nr_buffers)115 memcpy(bhs, dir->bhs, dir->nr_buffers * sizeof(*bhs));116 117 dir->bhs = bhs;118 }119 120 for (i = dir->nr_buffers; i < num; i++) {121 block = __adfs_block_map(sb, indaddr, i);122 if (!block) {123 adfs_error(sb, "dir %06x has a hole at offset %u",124 indaddr, i);125 goto error;126 }127 128 dir->bhs[i] = sb_bread(sb, block);129 if (!dir->bhs[i]) {130 adfs_error(sb,131 "dir %06x failed read at offset %u, mapped block 0x%08x",132 indaddr, i, block);133 goto error;134 }135 136 dir->nr_buffers++;137 }138 return 0;139 140error:141 adfs_dir_relse(dir);142 143 return -EIO;144}145 146static int adfs_dir_read(struct super_block *sb, u32 indaddr,147 unsigned int size, struct adfs_dir *dir)148{149 dir->sb = sb;150 dir->bhs = dir->bh;151 dir->nr_buffers = 0;152 153 return ADFS_SB(sb)->s_dir->read(sb, indaddr, size, dir);154}155 156static int adfs_dir_read_inode(struct super_block *sb, struct inode *inode,157 struct adfs_dir *dir)158{159 int ret;160 161 ret = adfs_dir_read(sb, ADFS_I(inode)->indaddr, inode->i_size, dir);162 if (ret)163 return ret;164 165 if (ADFS_I(inode)->parent_id != dir->parent_id) {166 adfs_error(sb,167 "parent directory id changed under me! (%06x but got %06x)\n",168 ADFS_I(inode)->parent_id, dir->parent_id);169 adfs_dir_relse(dir);170 ret = -EIO;171 }172 173 return ret;174}175 176static void adfs_dir_mark_dirty(struct adfs_dir *dir)177{178 unsigned int i;179 180 /* Mark the buffers dirty */181 for (i = 0; i < dir->nr_buffers; i++)182 mark_buffer_dirty(dir->bhs[i]);183}184 185static int adfs_dir_sync(struct adfs_dir *dir)186{187 int err = 0;188 int i;189 190 for (i = dir->nr_buffers - 1; i >= 0; i--) {191 struct buffer_head *bh = dir->bhs[i];192 sync_dirty_buffer(bh);193 if (buffer_req(bh) && !buffer_uptodate(bh))194 err = -EIO;195 }196 197 return err;198}199 200void adfs_object_fixup(struct adfs_dir *dir, struct object_info *obj)201{202 unsigned int dots, i;203 204 /*205 * RISC OS allows the use of '/' in directory entry names, so we need206 * to fix these up. '/' is typically used for FAT compatibility to207 * represent '.', so do the same conversion here. In any case, '.'208 * will never be in a RISC OS name since it is used as the pathname209 * separator. Handle the case where we may generate a '.' or '..'210 * name, replacing the first character with '^' (the RISC OS "parent211 * directory" character.)212 */213 for (i = dots = 0; i < obj->name_len; i++)214 if (obj->name[i] == '/') {215 obj->name[i] = '.';216 dots++;217 }218 219 if (obj->name_len <= 2 && dots == obj->name_len)220 obj->name[0] = '^';221 222 /*223 * If the object is a file, and the user requested the ,xyz hex224 * filetype suffix to the name, check the filetype and append.225 */226 if (!(obj->attr & ADFS_NDA_DIRECTORY) && ADFS_SB(dir->sb)->s_ftsuffix) {227 u16 filetype = adfs_filetype(obj->loadaddr);228 229 if (filetype != ADFS_FILETYPE_NONE) {230 obj->name[obj->name_len++] = ',';231 obj->name[obj->name_len++] = hex_asc_lo(filetype >> 8);232 obj->name[obj->name_len++] = hex_asc_lo(filetype >> 4);233 obj->name[obj->name_len++] = hex_asc_lo(filetype >> 0);234 }235 }236}237 238static int adfs_iterate(struct file *file, struct dir_context *ctx)239{240 struct inode *inode = file_inode(file);241 struct super_block *sb = inode->i_sb;242 const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;243 struct adfs_dir dir;244 int ret;245 246 down_read(&adfs_dir_rwsem);247 ret = adfs_dir_read_inode(sb, inode, &dir);248 if (ret)249 goto unlock;250 251 if (ctx->pos == 0) {252 if (!dir_emit_dot(file, ctx))253 goto unlock_relse;254 ctx->pos = 1;255 }256 if (ctx->pos == 1) {257 if (!dir_emit(ctx, "..", 2, dir.parent_id, DT_DIR))258 goto unlock_relse;259 ctx->pos = 2;260 }261 262 ret = ops->iterate(&dir, ctx);263 264unlock_relse:265 up_read(&adfs_dir_rwsem);266 adfs_dir_relse(&dir);267 return ret;268 269unlock:270 up_read(&adfs_dir_rwsem);271 return ret;272}273 274int275adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait)276{277 const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;278 struct adfs_dir dir;279 int ret;280 281 if (!IS_ENABLED(CONFIG_ADFS_FS_RW))282 return -EINVAL;283 284 if (!ops->update)285 return -EINVAL;286 287 down_write(&adfs_dir_rwsem);288 ret = adfs_dir_read(sb, obj->parent_id, 0, &dir);289 if (ret)290 goto unlock;291 292 ret = ops->update(&dir, obj);293 if (ret)294 goto forget;295 296 ret = ops->commit(&dir);297 if (ret)298 goto forget;299 up_write(&adfs_dir_rwsem);300 301 adfs_dir_mark_dirty(&dir);302 303 if (wait)304 ret = adfs_dir_sync(&dir);305 306 adfs_dir_relse(&dir);307 return ret;308 309 /*310 * If the updated failed because the entry wasn't found, we can311 * just release the buffers. If it was any other error, forget312 * the dirtied buffers so they aren't written back to the media.313 */314forget:315 if (ret == -ENOENT)316 adfs_dir_relse(&dir);317 else318 adfs_dir_forget(&dir);319unlock:320 up_write(&adfs_dir_rwsem);321 322 return ret;323}324 325static unsigned char adfs_tolower(unsigned char c)326{327 if (c >= 'A' && c <= 'Z')328 c += 'a' - 'A';329 return c;330}331 332static int __adfs_compare(const unsigned char *qstr, u32 qlen,333 const char *str, u32 len)334{335 u32 i;336 337 if (qlen != len)338 return 1;339 340 for (i = 0; i < qlen; i++)341 if (adfs_tolower(qstr[i]) != adfs_tolower(str[i]))342 return 1;343 344 return 0;345}346 347static int adfs_dir_lookup_byname(struct inode *inode, const struct qstr *qstr,348 struct object_info *obj)349{350 struct super_block *sb = inode->i_sb;351 const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;352 const unsigned char *name;353 struct adfs_dir dir;354 u32 name_len;355 int ret;356 357 down_read(&adfs_dir_rwsem);358 ret = adfs_dir_read_inode(sb, inode, &dir);359 if (ret)360 goto unlock;361 362 ret = ops->setpos(&dir, 0);363 if (ret)364 goto unlock_relse;365 366 ret = -ENOENT;367 name = qstr->name;368 name_len = qstr->len;369 while (ops->getnext(&dir, obj) == 0) {370 if (!__adfs_compare(name, name_len, obj->name, obj->name_len)) {371 ret = 0;372 break;373 }374 }375 obj->parent_id = ADFS_I(inode)->indaddr;376 377unlock_relse:378 up_read(&adfs_dir_rwsem);379 adfs_dir_relse(&dir);380 return ret;381 382unlock:383 up_read(&adfs_dir_rwsem);384 return ret;385}386 387const struct file_operations adfs_dir_operations = {388 .read = generic_read_dir,389 .llseek = generic_file_llseek,390 .iterate_shared = adfs_iterate,391 .fsync = generic_file_fsync,392};393 394static int395adfs_hash(const struct dentry *parent, struct qstr *qstr)396{397 const unsigned char *name;398 unsigned long hash;399 u32 len;400 401 if (qstr->len > ADFS_SB(parent->d_sb)->s_namelen)402 return -ENAMETOOLONG;403 404 len = qstr->len;405 name = qstr->name;406 hash = init_name_hash(parent);407 while (len--)408 hash = partial_name_hash(adfs_tolower(*name++), hash);409 qstr->hash = end_name_hash(hash);410 411 return 0;412}413 414/*415 * Compare two names, taking note of the name length416 * requirements of the underlying filesystem.417 */418static int adfs_compare(const struct dentry *dentry, unsigned int len,419 const char *str, const struct qstr *qstr)420{421 return __adfs_compare(qstr->name, qstr->len, str, len);422}423 424const struct dentry_operations adfs_dentry_operations = {425 .d_hash = adfs_hash,426 .d_compare = adfs_compare,427};428 429static struct dentry *430adfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)431{432 struct inode *inode = NULL;433 struct object_info obj;434 int error;435 436 error = adfs_dir_lookup_byname(dir, &dentry->d_name, &obj);437 if (error == 0) {438 /*439 * This only returns NULL if get_empty_inode440 * fails.441 */442 inode = adfs_iget(dir->i_sb, &obj);443 if (!inode)444 inode = ERR_PTR(-EACCES);445 } else if (error != -ENOENT) {446 inode = ERR_PTR(error);447 }448 return d_splice_alias(inode, dentry);449}450 451/*452 * directories can handle most operations...453 */454const struct inode_operations adfs_dir_inode_operations = {455 .lookup = adfs_lookup,456 .setattr = adfs_notify_change,457};458