288 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * linux/fs/adfs/dir_fplus.c4 *5 * Copyright (C) 1997-1999 Russell King6 */7#include "adfs.h"8#include "dir_fplus.h"9 10/* Return the byte offset to directory entry pos */11static 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 19static 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 entries49 * 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 59static 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 70static 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 103static 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 152out:153 adfs_dir_relse(dir);154 155 return ret;156}157 158static int159adfs_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 171static int172adfs_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 210static 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 230static 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 261static 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 280const 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