/* * linux/fs/adfs/dir_fplus.c * * Copyright (C) 1997-1999 Russell King * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include <linux/buffer_head.h> #include <linux/slab.h> #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) { printk(KERN_WARNING "adfs: adfs_fplus_read:" " directory header size %X\n" " does not match directory size %X\n", size, sz); } if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 || h->bigdirversion[2] != 0 || size & 2047 || h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME)) { printk(KERN_WARNING "adfs: dir object %X has" " malformed dir header\n", 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) { ret = -ENOMEM; adfs_error(sb, "not enough memory for" " dir object %X (%d blocks)", id, size); 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) { printk(KERN_WARNING "adfs: dir object %X has " "malformed dir end\n", 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 i, 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->file_id = 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); for (i = 0; i < obj->name_len; i++) if (obj->name[i] == '/') obj->name[i] = '.'; obj->filetype = -1; /* * object is a file and is filetyped and timestamped? * RISC OS 12-bit filetype is stored in load_address[19:8] */ if ((0 == (obj->attr & ADFS_NDA_DIRECTORY)) && (0xfff00000 == (0xfff00000 & obj->loadaddr))) { obj->filetype = (__u16) ((0x000fff00 & obj->loadaddr) >> 8); /* optionally append the ,xyz hex filetype suffix */ if (ADFS_SB(dir->sb)->s_ftsuffix) obj->name_len += append_filetype_suffix( &obj->name[obj->name_len], obj->filetype); } 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; } 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 };