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