123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * linux/fs/adfs/dir.c
- *
- * Copyright (C) 1999-2000 Russell King
- *
- * Common directory handling for ADFS
- */
- #include <linux/slab.h>
- #include "adfs.h"
- /*
- * For future. This should probably be per-directory.
- */
- static DECLARE_RWSEM(adfs_dir_rwsem);
- 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;
- }
- static void __adfs_dir_cleanup(struct adfs_dir *dir)
- {
- dir->nr_buffers = 0;
- if (dir->bhs != dir->bh)
- kfree(dir->bhs);
- dir->bhs = NULL;
- dir->sb = NULL;
- }
- 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);
- }
- 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;
- }
- 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);
- }
- static int adfs_dir_read_inode(struct super_block *sb, struct inode *inode,
- struct adfs_dir *dir)
- {
- int ret;
- ret = adfs_dir_read(sb, ADFS_I(inode)->indaddr, inode->i_size, dir);
- 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;
- }
- 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]);
- }
- 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;
- }
- void adfs_object_fixup(struct adfs_dir *dir, struct object_info *obj)
- {
- unsigned int dots, i;
- /*
- * 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
- * separator. Handle the case where we may generate a '.' or '..'
- * name, replacing the first character with '^' (the RISC OS "parent
- * directory" character.)
- */
- for (i = dots = 0; i < obj->name_len; i++)
- if (obj->name[i] == '/') {
- obj->name[i] = '.';
- dots++;
- }
- if (obj->name_len <= 2 && dots == obj->name_len)
- obj->name[0] = '^';
- /*
- * If the object is a file, and the user requested the ,xyz hex
- * filetype suffix to the name, check the filetype and append.
- */
- if (!(obj->attr & ADFS_NDA_DIRECTORY) && ADFS_SB(dir->sb)->s_ftsuffix) {
- u16 filetype = adfs_filetype(obj->loadaddr);
- if (filetype != ADFS_FILETYPE_NONE) {
- 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);
- }
- }
- }
- static int adfs_iterate(struct file *file, struct dir_context *ctx)
- {
- struct inode *inode = file_inode(file);
- struct super_block *sb = inode->i_sb;
- const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
- struct adfs_dir dir;
- int ret;
- down_read(&adfs_dir_rwsem);
- ret = adfs_dir_read_inode(sb, inode, &dir);
- if (ret)
- goto unlock;
- if (ctx->pos == 0) {
- if (!dir_emit_dot(file, ctx))
- goto unlock_relse;
- ctx->pos = 1;
- }
- if (ctx->pos == 1) {
- if (!dir_emit(ctx, "..", 2, dir.parent_id, DT_DIR))
- goto unlock_relse;
- ctx->pos = 2;
- }
- ret = ops->iterate(&dir, ctx);
- unlock_relse:
- up_read(&adfs_dir_rwsem);
- adfs_dir_relse(&dir);
- return ret;
- unlock:
- up_read(&adfs_dir_rwsem);
- return ret;
- }
- int
- adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait)
- {
- const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
- struct adfs_dir dir;
- int ret;
- if (!IS_ENABLED(CONFIG_ADFS_FS_RW))
- return -EINVAL;
- if (!ops->update)
- return -EINVAL;
- down_write(&adfs_dir_rwsem);
- ret = adfs_dir_read(sb, obj->parent_id, 0, &dir);
- if (ret)
- goto unlock;
- ret = ops->update(&dir, obj);
- if (ret)
- goto forget;
- ret = ops->commit(&dir);
- if (ret)
- goto forget;
- up_write(&adfs_dir_rwsem);
- adfs_dir_mark_dirty(&dir);
- if (wait)
- ret = adfs_dir_sync(&dir);
- adfs_dir_relse(&dir);
- return ret;
- /*
- * 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);
- unlock:
- up_write(&adfs_dir_rwsem);
- return ret;
- }
- static unsigned char adfs_tolower(unsigned char c)
- {
- if (c >= 'A' && c <= 'Z')
- c += 'a' - 'A';
- return c;
- }
- static int __adfs_compare(const unsigned char *qstr, u32 qlen,
- const char *str, u32 len)
- {
- u32 i;
- if (qlen != len)
- return 1;
- for (i = 0; i < qlen; i++)
- if (adfs_tolower(qstr[i]) != adfs_tolower(str[i]))
- return 1;
- return 0;
- }
- static int adfs_dir_lookup_byname(struct inode *inode, const struct qstr *qstr,
- struct object_info *obj)
- {
- struct super_block *sb = inode->i_sb;
- const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
- const unsigned char *name;
- struct adfs_dir dir;
- u32 name_len;
- int ret;
- down_read(&adfs_dir_rwsem);
- ret = adfs_dir_read_inode(sb, inode, &dir);
- if (ret)
- goto unlock;
- ret = ops->setpos(&dir, 0);
- if (ret)
- goto unlock_relse;
- ret = -ENOENT;
- name = qstr->name;
- name_len = qstr->len;
- while (ops->getnext(&dir, obj) == 0) {
- if (!__adfs_compare(name, name_len, obj->name, obj->name_len)) {
- ret = 0;
- break;
- }
- }
- obj->parent_id = ADFS_I(inode)->indaddr;
- unlock_relse:
- up_read(&adfs_dir_rwsem);
- adfs_dir_relse(&dir);
- return ret;
- unlock:
- up_read(&adfs_dir_rwsem);
- return ret;
- }
- const struct file_operations adfs_dir_operations = {
- .read = generic_read_dir,
- .llseek = generic_file_llseek,
- .iterate_shared = adfs_iterate,
- .fsync = generic_file_fsync,
- };
- static int
- adfs_hash(const struct dentry *parent, struct qstr *qstr)
- {
- const unsigned char *name;
- unsigned long hash;
- u32 len;
- if (qstr->len > ADFS_SB(parent->d_sb)->s_namelen)
- return -ENAMETOOLONG;
- len = qstr->len;
- name = qstr->name;
- hash = init_name_hash(parent);
- while (len--)
- hash = partial_name_hash(adfs_tolower(*name++), hash);
- qstr->hash = end_name_hash(hash);
- return 0;
- }
- /*
- * Compare two names, taking note of the name length
- * requirements of the underlying filesystem.
- */
- static int adfs_compare(const struct dentry *dentry, unsigned int len,
- const char *str, const struct qstr *qstr)
- {
- return __adfs_compare(qstr->name, qstr->len, str, len);
- }
- const struct dentry_operations adfs_dentry_operations = {
- .d_hash = adfs_hash,
- .d_compare = adfs_compare,
- };
- static struct dentry *
- adfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
- {
- 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);
- if (!inode)
- inode = ERR_PTR(-EACCES);
- } else if (error != -ENOENT) {
- inode = ERR_PTR(error);
- }
- return d_splice_alias(inode, dentry);
- }
- /*
- * directories can handle most operations...
- */
- const struct inode_operations adfs_dir_inode_operations = {
- .lookup = adfs_lookup,
- .setattr = adfs_notify_change,
- };
|