1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * linux/fs/adfs/dir_fplus.c 4 * 5 * Copyright (C) 1997-1999 Russell King 6 */ 7 #include <linux/buffer_head.h> 8 #include <linux/slab.h> 9 #include "adfs.h" 10 #include "dir_fplus.h" 11 12 static int 13 adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir) 14 { 15 struct adfs_bigdirheader *h; 16 struct adfs_bigdirtail *t; 17 unsigned long block; 18 unsigned int blk, size; 19 int i, ret = -EIO; 20 21 dir->nr_buffers = 0; 22 23 /* start off using fixed bh set - only alloc for big dirs */ 24 dir->bh_fplus = &dir->bh[0]; 25 26 block = __adfs_block_map(sb, id, 0); 27 if (!block) { 28 adfs_error(sb, "dir object %X has a hole at offset 0", id); 29 goto out; 30 } 31 32 dir->bh_fplus[0] = sb_bread(sb, block); 33 if (!dir->bh_fplus[0]) 34 goto out; 35 dir->nr_buffers += 1; 36 37 h = (struct adfs_bigdirheader *)dir->bh_fplus[0]->b_data; 38 size = le32_to_cpu(h->bigdirsize); 39 if (size != sz) { 40 printk(KERN_WARNING "adfs: adfs_fplus_read:" 41 " directory header size %X\n" 42 " does not match directory size %X\n", 43 size, sz); 44 } 45 46 if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 || 47 h->bigdirversion[2] != 0 || size & 2047 || 48 h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME)) { 49 printk(KERN_WARNING "adfs: dir object %X has" 50 " malformed dir header\n", id); 51 goto out; 52 } 53 54 size >>= sb->s_blocksize_bits; 55 if (size > ARRAY_SIZE(dir->bh)) { 56 /* this directory is too big for fixed bh set, must allocate */ 57 struct buffer_head **bh_fplus = 58 kcalloc(size, sizeof(struct buffer_head *), 59 GFP_KERNEL); 60 if (!bh_fplus) { 61 ret = -ENOMEM; 62 adfs_error(sb, "not enough memory for" 63 " dir object %X (%d blocks)", id, size); 64 goto out; 65 } 66 dir->bh_fplus = bh_fplus; 67 /* copy over the pointer to the block that we've already read */ 68 dir->bh_fplus[0] = dir->bh[0]; 69 } 70 71 for (blk = 1; blk < size; blk++) { 72 block = __adfs_block_map(sb, id, blk); 73 if (!block) { 74 adfs_error(sb, "dir object %X has a hole at offset %d", id, blk); 75 goto out; 76 } 77 78 dir->bh_fplus[blk] = sb_bread(sb, block); 79 if (!dir->bh_fplus[blk]) { 80 adfs_error(sb, "dir object %x failed read for offset %d, mapped block %lX", 81 id, blk, block); 82 goto out; 83 } 84 85 dir->nr_buffers += 1; 86 } 87 88 t = (struct adfs_bigdirtail *) 89 (dir->bh_fplus[size - 1]->b_data + (sb->s_blocksize - 8)); 90 91 if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) || 92 t->bigdirendmasseq != h->startmasseq || 93 t->reserved[0] != 0 || t->reserved[1] != 0) { 94 printk(KERN_WARNING "adfs: dir object %X has " 95 "malformed dir end\n", id); 96 goto out; 97 } 98 99 dir->parent_id = le32_to_cpu(h->bigdirparent); 100 dir->sb = sb; 101 return 0; 102 103 out: 104 if (dir->bh_fplus) { 105 for (i = 0; i < dir->nr_buffers; i++) 106 brelse(dir->bh_fplus[i]); 107 108 if (&dir->bh[0] != dir->bh_fplus) 109 kfree(dir->bh_fplus); 110 111 dir->bh_fplus = NULL; 112 } 113 114 dir->nr_buffers = 0; 115 dir->sb = NULL; 116 return ret; 117 } 118 119 static int 120 adfs_fplus_setpos(struct adfs_dir *dir, unsigned int fpos) 121 { 122 struct adfs_bigdirheader *h = 123 (struct adfs_bigdirheader *) dir->bh_fplus[0]->b_data; 124 int ret = -ENOENT; 125 126 if (fpos <= le32_to_cpu(h->bigdirentries)) { 127 dir->pos = fpos; 128 ret = 0; 129 } 130 131 return ret; 132 } 133 134 static void 135 dir_memcpy(struct adfs_dir *dir, unsigned int offset, void *to, int len) 136 { 137 struct super_block *sb = dir->sb; 138 unsigned int buffer, partial, remainder; 139 140 buffer = offset >> sb->s_blocksize_bits; 141 offset &= sb->s_blocksize - 1; 142 143 partial = sb->s_blocksize - offset; 144 145 if (partial >= len) 146 memcpy(to, dir->bh_fplus[buffer]->b_data + offset, len); 147 else { 148 char *c = (char *)to; 149 150 remainder = len - partial; 151 152 memcpy(c, 153 dir->bh_fplus[buffer]->b_data + offset, 154 partial); 155 156 memcpy(c + partial, 157 dir->bh_fplus[buffer + 1]->b_data, 158 remainder); 159 } 160 } 161 162 static int 163 adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj) 164 { 165 struct adfs_bigdirheader *h = 166 (struct adfs_bigdirheader *) dir->bh_fplus[0]->b_data; 167 struct adfs_bigdirentry bde; 168 unsigned int offset; 169 int ret = -ENOENT; 170 171 if (dir->pos >= le32_to_cpu(h->bigdirentries)) 172 goto out; 173 174 offset = offsetof(struct adfs_bigdirheader, bigdirname); 175 offset += ((le32_to_cpu(h->bigdirnamelen) + 4) & ~3); 176 offset += dir->pos * sizeof(struct adfs_bigdirentry); 177 178 dir_memcpy(dir, offset, &bde, sizeof(struct adfs_bigdirentry)); 179 180 obj->loadaddr = le32_to_cpu(bde.bigdirload); 181 obj->execaddr = le32_to_cpu(bde.bigdirexec); 182 obj->size = le32_to_cpu(bde.bigdirlen); 183 obj->file_id = le32_to_cpu(bde.bigdirindaddr); 184 obj->attr = le32_to_cpu(bde.bigdirattr); 185 obj->name_len = le32_to_cpu(bde.bigdirobnamelen); 186 187 offset = offsetof(struct adfs_bigdirheader, bigdirname); 188 offset += ((le32_to_cpu(h->bigdirnamelen) + 4) & ~3); 189 offset += le32_to_cpu(h->bigdirentries) * sizeof(struct adfs_bigdirentry); 190 offset += le32_to_cpu(bde.bigdirobnameptr); 191 192 dir_memcpy(dir, offset, obj->name, obj->name_len); 193 adfs_object_fixup(dir, obj); 194 195 dir->pos += 1; 196 ret = 0; 197 out: 198 return ret; 199 } 200 201 static int 202 adfs_fplus_sync(struct adfs_dir *dir) 203 { 204 int err = 0; 205 int i; 206 207 for (i = dir->nr_buffers - 1; i >= 0; i--) { 208 struct buffer_head *bh = dir->bh_fplus[i]; 209 sync_dirty_buffer(bh); 210 if (buffer_req(bh) && !buffer_uptodate(bh)) 211 err = -EIO; 212 } 213 214 return err; 215 } 216 217 static void 218 adfs_fplus_free(struct adfs_dir *dir) 219 { 220 int i; 221 222 if (dir->bh_fplus) { 223 for (i = 0; i < dir->nr_buffers; i++) 224 brelse(dir->bh_fplus[i]); 225 226 if (&dir->bh[0] != dir->bh_fplus) 227 kfree(dir->bh_fplus); 228 229 dir->bh_fplus = NULL; 230 } 231 232 dir->nr_buffers = 0; 233 dir->sb = NULL; 234 } 235 236 const struct adfs_dir_ops adfs_fplus_dir_ops = { 237 .read = adfs_fplus_read, 238 .setpos = adfs_fplus_setpos, 239 .getnext = adfs_fplus_getnext, 240 .sync = adfs_fplus_sync, 241 .free = adfs_fplus_free 242 }; 243