// SPDX-License-Identifier: GPL-2.0-only /* * linux/fs/adfs/dir_fplus.c * * Copyright (C) 1997-1999 Russell King */ #include #include "adfs.h" #include "dir_fplus.h" static int adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir) { struct adfs_bigdirheader *h; struct adfs_bigdirtail *t; unsigned long block; unsigned int blk, size; int i, ret = -EIO; dir->nr_buffers = 0; /* start off using fixed bh set - only alloc for big dirs */ dir->bh_fplus = &dir->bh[0]; block = __adfs_block_map(sb, id, 0); if (!block) { adfs_error(sb, "dir object %X has a hole at offset 0", id); goto out; } dir->bh_fplus[0] = sb_bread(sb, block); if (!dir->bh_fplus[0]) goto out; dir->nr_buffers += 1; h = (struct adfs_bigdirheader *)dir->bh_fplus[0]->b_data; size = le32_to_cpu(h->bigdirsize); if (size != sz) { adfs_msg(sb, KERN_WARNING, "directory header size %X does not match directory size %X", size, sz); } if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 || h->bigdirversion[2] != 0 || size & 2047 || h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME)) { adfs_error(sb, "dir %06x has malformed header", id); goto out; } size >>= sb->s_blocksize_bits; if (size > ARRAY_SIZE(dir->bh)) { /* this directory is too big for fixed bh set, must allocate */ struct buffer_head **bh_fplus = kcalloc(size, sizeof(struct buffer_head *), GFP_KERNEL); if (!bh_fplus) { adfs_msg(sb, KERN_ERR, "not enough memory for dir object %X (%d blocks)", id, size); ret = -ENOMEM; goto out; } dir->bh_fplus = bh_fplus; /* copy over the pointer to the block that we've already read */ dir->bh_fplus[0] = dir->bh[0]; } for (blk = 1; blk < size; blk++) { block = __adfs_block_map(sb, id, blk); if (!block) { adfs_error(sb, "dir object %X has a hole at offset %d", id, blk); goto out; } dir->bh_fplus[blk] = sb_bread(sb, block); if (!dir->bh_fplus[blk]) { adfs_error(sb, "dir object %x failed read for offset %d, mapped block %lX", id, blk, block); goto out; } dir->nr_buffers += 1; } t = (struct adfs_bigdirtail *) (dir->bh_fplus[size - 1]->b_data + (sb->s_blocksize - 8)); if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) || t->bigdirendmasseq != h->startmasseq || t->reserved[0] != 0 || t->reserved[1] != 0) { adfs_error(sb, "dir %06x has malformed tail", id); goto out; } dir->parent_id = le32_to_cpu(h->bigdirparent); dir->sb = sb; return 0; out: if (dir->bh_fplus) { for (i = 0; i < dir->nr_buffers; i++) brelse(dir->bh_fplus[i]); if (&dir->bh[0] != dir->bh_fplus) kfree(dir->bh_fplus); dir->bh_fplus = NULL; } dir->nr_buffers = 0; dir->sb = NULL; return ret; } static int adfs_fplus_setpos(struct adfs_dir *dir, unsigned int fpos) { struct adfs_bigdirheader *h = (struct adfs_bigdirheader *) dir->bh_fplus[0]->b_data; int ret = -ENOENT; if (fpos <= le32_to_cpu(h->bigdirentries)) { dir->pos = fpos; ret = 0; } return ret; } static void dir_memcpy(struct adfs_dir *dir, unsigned int offset, void *to, int len) { struct super_block *sb = dir->sb; unsigned int buffer, partial, remainder; buffer = offset >> sb->s_blocksize_bits; offset &= sb->s_blocksize - 1; partial = sb->s_blocksize - offset; if (partial >= len) memcpy(to, dir->bh_fplus[buffer]->b_data + offset, len); else { char *c = (char *)to; remainder = len - partial; memcpy(c, dir->bh_fplus[buffer]->b_data + offset, partial); memcpy(c + partial, dir->bh_fplus[buffer + 1]->b_data, remainder); } } static int adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj) { struct adfs_bigdirheader *h = (struct adfs_bigdirheader *) dir->bh_fplus[0]->b_data; struct adfs_bigdirentry bde; unsigned int offset; int ret = -ENOENT; if (dir->pos >= le32_to_cpu(h->bigdirentries)) goto out; offset = offsetof(struct adfs_bigdirheader, bigdirname); offset += ((le32_to_cpu(h->bigdirnamelen) + 4) & ~3); offset += dir->pos * sizeof(struct adfs_bigdirentry); dir_memcpy(dir, offset, &bde, sizeof(struct adfs_bigdirentry)); obj->loadaddr = le32_to_cpu(bde.bigdirload); obj->execaddr = le32_to_cpu(bde.bigdirexec); obj->size = le32_to_cpu(bde.bigdirlen); obj->indaddr = le32_to_cpu(bde.bigdirindaddr); obj->attr = le32_to_cpu(bde.bigdirattr); obj->name_len = le32_to_cpu(bde.bigdirobnamelen); offset = offsetof(struct adfs_bigdirheader, bigdirname); offset += ((le32_to_cpu(h->bigdirnamelen) + 4) & ~3); offset += le32_to_cpu(h->bigdirentries) * sizeof(struct adfs_bigdirentry); offset += le32_to_cpu(bde.bigdirobnameptr); dir_memcpy(dir, offset, obj->name, obj->name_len); adfs_object_fixup(dir, obj); dir->pos += 1; ret = 0; out: return ret; } static int adfs_fplus_sync(struct adfs_dir *dir) { int err = 0; int i; for (i = dir->nr_buffers - 1; i >= 0; i--) { struct buffer_head *bh = dir->bh_fplus[i]; sync_dirty_buffer(bh); if (buffer_req(bh) && !buffer_uptodate(bh)) err = -EIO; } return err; } static void adfs_fplus_free(struct adfs_dir *dir) { int i; if (dir->bh_fplus) { for (i = 0; i < dir->nr_buffers; i++) brelse(dir->bh_fplus[i]); if (&dir->bh[0] != dir->bh_fplus) kfree(dir->bh_fplus); dir->bh_fplus = NULL; } dir->nr_buffers = 0; dir->sb = NULL; } const struct adfs_dir_ops adfs_fplus_dir_ops = { .read = adfs_fplus_read, .setpos = adfs_fplus_setpos, .getnext = adfs_fplus_getnext, .sync = adfs_fplus_sync, .free = adfs_fplus_free };