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 "adfs.h" 8 #include "dir_fplus.h" 9 10 /* Return the byte offset to directory entry pos */ 11 static unsigned int adfs_fplus_offset(const struct adfs_bigdirheader *h, 12 unsigned int pos) 13 { 14 return offsetof(struct adfs_bigdirheader, bigdirname) + 15 ALIGN(le32_to_cpu(h->bigdirnamelen), 4) + 16 pos * sizeof(struct adfs_bigdirentry); 17 } 18 19 static int adfs_fplus_validate_header(const struct adfs_bigdirheader *h) 20 { 21 unsigned int size = le32_to_cpu(h->bigdirsize); 22 unsigned int len; 23 24 if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 || 25 h->bigdirversion[2] != 0 || 26 h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME) || 27 !size || size & 2047 || size > SZ_4M) 28 return -EIO; 29 30 size -= sizeof(struct adfs_bigdirtail) + 31 offsetof(struct adfs_bigdirheader, bigdirname); 32 33 /* Check that bigdirnamelen fits within the directory */ 34 len = ALIGN(le32_to_cpu(h->bigdirnamelen), 4); 35 if (len > size) 36 return -EIO; 37 38 size -= len; 39 40 /* Check that bigdirnamesize fits within the directory */ 41 len = le32_to_cpu(h->bigdirnamesize); 42 if (len > size) 43 return -EIO; 44 45 size -= len; 46 47 /* 48 * Avoid division, we know that absolute maximum number of entries 49 * can not be so large to cause overflow of the multiplication below. 50 */ 51 len = le32_to_cpu(h->bigdirentries); 52 if (len > SZ_4M / sizeof(struct adfs_bigdirentry) || 53 len * sizeof(struct adfs_bigdirentry) > size) 54 return -EIO; 55 56 return 0; 57 } 58 59 static int adfs_fplus_validate_tail(const struct adfs_bigdirheader *h, 60 const struct adfs_bigdirtail *t) 61 { 62 if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) || 63 t->bigdirendmasseq != h->startmasseq || 64 t->reserved[0] != 0 || t->reserved[1] != 0) 65 return -EIO; 66 67 return 0; 68 } 69 70 static u8 adfs_fplus_checkbyte(struct adfs_dir *dir) 71 { 72 struct adfs_bigdirheader *h = dir->bighead; 73 struct adfs_bigdirtail *t = dir->bigtail; 74 unsigned int end, bs, bi, i; 75 __le32 *bp; 76 u32 dircheck; 77 78 end = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries)) + 79 le32_to_cpu(h->bigdirnamesize); 80 81 /* Accumulate the contents of the header, entries and names */ 82 for (dircheck = 0, bi = 0; end; bi++) { 83 bp = (void *)dir->bhs[bi]->b_data; 84 bs = dir->bhs[bi]->b_size; 85 if (bs > end) 86 bs = end; 87 88 for (i = 0; i < bs; i += sizeof(u32)) 89 dircheck = ror32(dircheck, 13) ^ le32_to_cpup(bp++); 90 91 end -= bs; 92 } 93 94 /* Accumulate the contents of the tail except for the check byte */ 95 dircheck = ror32(dircheck, 13) ^ le32_to_cpu(t->bigdirendname); 96 dircheck = ror32(dircheck, 13) ^ t->bigdirendmasseq; 97 dircheck = ror32(dircheck, 13) ^ t->reserved[0]; 98 dircheck = ror32(dircheck, 13) ^ t->reserved[1]; 99 100 return dircheck ^ dircheck >> 8 ^ dircheck >> 16 ^ dircheck >> 24; 101 } 102 103 static int adfs_fplus_read(struct super_block *sb, u32 indaddr, 104 unsigned int size, struct adfs_dir *dir) 105 { 106 struct adfs_bigdirheader *h; 107 struct adfs_bigdirtail *t; 108 unsigned int dirsize; 109 int ret; 110 111 /* Read first buffer */ 112 ret = adfs_dir_read_buffers(sb, indaddr, sb->s_blocksize, dir); 113 if (ret) 114 return ret; 115 116 dir->bighead = h = (void *)dir->bhs[0]->b_data; 117 ret = adfs_fplus_validate_header(h); 118 if (ret) { 119 adfs_error(sb, "dir %06x has malformed header", indaddr); 120 goto out; 121 } 122 123 dirsize = le32_to_cpu(h->bigdirsize); 124 if (size && dirsize != size) { 125 adfs_msg(sb, KERN_WARNING, 126 "dir %06x header size %X does not match directory size %X", 127 indaddr, dirsize, size); 128 } 129 130 /* Read remaining buffers */ 131 ret = adfs_dir_read_buffers(sb, indaddr, dirsize, dir); 132 if (ret) 133 return ret; 134 135 dir->bigtail = t = (struct adfs_bigdirtail *) 136 (dir->bhs[dir->nr_buffers - 1]->b_data + (sb->s_blocksize - 8)); 137 138 ret = adfs_fplus_validate_tail(h, t); 139 if (ret) { 140 adfs_error(sb, "dir %06x has malformed tail", indaddr); 141 goto out; 142 } 143 144 if (adfs_fplus_checkbyte(dir) != t->bigdircheckbyte) { 145 adfs_error(sb, "dir %06x checkbyte mismatch\n", indaddr); 146 goto out; 147 } 148 149 dir->parent_id = le32_to_cpu(h->bigdirparent); 150 return 0; 151 152 out: 153 adfs_dir_relse(dir); 154 155 return ret; 156 } 157 158 static int 159 adfs_fplus_setpos(struct adfs_dir *dir, unsigned int fpos) 160 { 161 int ret = -ENOENT; 162 163 if (fpos <= le32_to_cpu(dir->bighead->bigdirentries)) { 164 dir->pos = fpos; 165 ret = 0; 166 } 167 168 return ret; 169 } 170 171 static int 172 adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj) 173 { 174 struct adfs_bigdirheader *h = dir->bighead; 175 struct adfs_bigdirentry bde; 176 unsigned int offset; 177 int ret; 178 179 if (dir->pos >= le32_to_cpu(h->bigdirentries)) 180 return -ENOENT; 181 182 offset = adfs_fplus_offset(h, dir->pos); 183 184 ret = adfs_dir_copyfrom(&bde, dir, offset, 185 sizeof(struct adfs_bigdirentry)); 186 if (ret) 187 return ret; 188 189 obj->loadaddr = le32_to_cpu(bde.bigdirload); 190 obj->execaddr = le32_to_cpu(bde.bigdirexec); 191 obj->size = le32_to_cpu(bde.bigdirlen); 192 obj->indaddr = le32_to_cpu(bde.bigdirindaddr); 193 obj->attr = le32_to_cpu(bde.bigdirattr); 194 obj->name_len = le32_to_cpu(bde.bigdirobnamelen); 195 196 offset = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries)); 197 offset += le32_to_cpu(bde.bigdirobnameptr); 198 199 ret = adfs_dir_copyfrom(obj->name, dir, offset, obj->name_len); 200 if (ret) 201 return ret; 202 203 adfs_object_fixup(dir, obj); 204 205 dir->pos += 1; 206 207 return 0; 208 } 209 210 static int adfs_fplus_iterate(struct adfs_dir *dir, struct dir_context *ctx) 211 { 212 struct object_info obj; 213 214 if ((ctx->pos - 2) >> 32) 215 return 0; 216 217 if (adfs_fplus_setpos(dir, ctx->pos - 2)) 218 return 0; 219 220 while (!adfs_fplus_getnext(dir, &obj)) { 221 if (!dir_emit(ctx, obj.name, obj.name_len, 222 obj.indaddr, DT_UNKNOWN)) 223 break; 224 ctx->pos++; 225 } 226 227 return 0; 228 } 229 230 static int adfs_fplus_update(struct adfs_dir *dir, struct object_info *obj) 231 { 232 struct adfs_bigdirheader *h = dir->bighead; 233 struct adfs_bigdirentry bde; 234 int offset, end, ret; 235 236 offset = adfs_fplus_offset(h, 0) - sizeof(bde); 237 end = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries)); 238 239 do { 240 offset += sizeof(bde); 241 if (offset >= end) { 242 adfs_error(dir->sb, "unable to locate entry to update"); 243 return -ENOENT; 244 } 245 ret = adfs_dir_copyfrom(&bde, dir, offset, sizeof(bde)); 246 if (ret) { 247 adfs_error(dir->sb, "error reading directory entry"); 248 return -ENOENT; 249 } 250 } while (le32_to_cpu(bde.bigdirindaddr) != obj->indaddr); 251 252 bde.bigdirload = cpu_to_le32(obj->loadaddr); 253 bde.bigdirexec = cpu_to_le32(obj->execaddr); 254 bde.bigdirlen = cpu_to_le32(obj->size); 255 bde.bigdirindaddr = cpu_to_le32(obj->indaddr); 256 bde.bigdirattr = cpu_to_le32(obj->attr); 257 258 return adfs_dir_copyto(dir, offset, &bde, sizeof(bde)); 259 } 260 261 static int adfs_fplus_commit(struct adfs_dir *dir) 262 { 263 int ret; 264 265 /* Increment directory sequence number */ 266 dir->bighead->startmasseq += 1; 267 dir->bigtail->bigdirendmasseq += 1; 268 269 /* Update directory check byte */ 270 dir->bigtail->bigdircheckbyte = adfs_fplus_checkbyte(dir); 271 272 /* Make sure the directory still validates correctly */ 273 ret = adfs_fplus_validate_header(dir->bighead); 274 if (ret == 0) 275 ret = adfs_fplus_validate_tail(dir->bighead, dir->bigtail); 276 277 return ret; 278 } 279 280 const struct adfs_dir_ops adfs_fplus_dir_ops = { 281 .read = adfs_fplus_read, 282 .iterate = adfs_fplus_iterate, 283 .setpos = adfs_fplus_setpos, 284 .getnext = adfs_fplus_getnext, 285 .update = adfs_fplus_update, 286 .commit = adfs_fplus_commit, 287 }; 288