linux/fs/adfs/dir_fplus.c
<<
>>
Prefs
   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 */
  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 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
  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 int
 159adfs_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 int
 172adfs_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