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