linux/fs/adfs/dir_f.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 *  linux/fs/adfs/dir_f.c
   4 *
   5 * Copyright (C) 1997-1999 Russell King
   6 *
   7 *  E and F format directory handling
   8 */
   9#include "adfs.h"
  10#include "dir_f.h"
  11
  12static void adfs_f_free(struct adfs_dir *dir);
  13
  14/*
  15 * Read an (unaligned) value of length 1..4 bytes
  16 */
  17static inline unsigned int adfs_readval(unsigned char *p, int len)
  18{
  19        unsigned int val = 0;
  20
  21        switch (len) {
  22        case 4:         val |= p[3] << 24;
  23                        /* fall through */
  24        case 3:         val |= p[2] << 16;
  25                        /* fall through */
  26        case 2:         val |= p[1] << 8;
  27                        /* fall through */
  28        default:        val |= p[0];
  29        }
  30        return val;
  31}
  32
  33static inline void adfs_writeval(unsigned char *p, int len, unsigned int val)
  34{
  35        switch (len) {
  36        case 4:         p[3] = val >> 24;
  37                        /* fall through */
  38        case 3:         p[2] = val >> 16;
  39                        /* fall through */
  40        case 2:         p[1] = val >> 8;
  41                        /* fall through */
  42        default:        p[0] = val;
  43        }
  44}
  45
  46#define ror13(v) ((v >> 13) | (v << 19))
  47
  48#define dir_u8(idx)                             \
  49        ({ int _buf = idx >> blocksize_bits;    \
  50           int _off = idx - (_buf << blocksize_bits);\
  51          *(u8 *)(bh[_buf]->b_data + _off);     \
  52        })
  53
  54#define dir_u32(idx)                            \
  55        ({ int _buf = idx >> blocksize_bits;    \
  56           int _off = idx - (_buf << blocksize_bits);\
  57          *(__le32 *)(bh[_buf]->b_data + _off); \
  58        })
  59
  60#define bufoff(_bh,_idx)                        \
  61        ({ int _buf = _idx >> blocksize_bits;   \
  62           int _off = _idx - (_buf << blocksize_bits);\
  63          (u8 *)(_bh[_buf]->b_data + _off);     \
  64        })
  65
  66/*
  67 * There are some algorithms that are nice in
  68 * assembler, but a bitch in C...  This is one
  69 * of them.
  70 */
  71static u8
  72adfs_dir_checkbyte(const struct adfs_dir *dir)
  73{
  74        struct buffer_head * const *bh = dir->bh;
  75        const int blocksize_bits = dir->sb->s_blocksize_bits;
  76        union { __le32 *ptr32; u8 *ptr8; } ptr, end;
  77        u32 dircheck = 0;
  78        int last = 5 - 26;
  79        int i = 0;
  80
  81        /*
  82         * Accumulate each word up to the last whole
  83         * word of the last directory entry.  This
  84         * can spread across several buffer heads.
  85         */
  86        do {
  87                last += 26;
  88                do {
  89                        dircheck = le32_to_cpu(dir_u32(i)) ^ ror13(dircheck);
  90
  91                        i += sizeof(u32);
  92                } while (i < (last & ~3));
  93        } while (dir_u8(last) != 0);
  94
  95        /*
  96         * Accumulate the last few bytes.  These
  97         * bytes will be within the same bh.
  98         */
  99        if (i != last) {
 100                ptr.ptr8 = bufoff(bh, i);
 101                end.ptr8 = ptr.ptr8 + last - i;
 102
 103                do {
 104                        dircheck = *ptr.ptr8++ ^ ror13(dircheck);
 105                } while (ptr.ptr8 < end.ptr8);
 106        }
 107
 108        /*
 109         * The directory tail is in the final bh
 110         * Note that contary to the RISC OS PRMs,
 111         * the first few bytes are NOT included
 112         * in the check.  All bytes are in the
 113         * same bh.
 114         */
 115        ptr.ptr8 = bufoff(bh, 2008);
 116        end.ptr8 = ptr.ptr8 + 36;
 117
 118        do {
 119                __le32 v = *ptr.ptr32++;
 120                dircheck = le32_to_cpu(v) ^ ror13(dircheck);
 121        } while (ptr.ptr32 < end.ptr32);
 122
 123        return (dircheck ^ (dircheck >> 8) ^ (dircheck >> 16) ^ (dircheck >> 24)) & 0xff;
 124}
 125
 126/* Read and check that a directory is valid */
 127static int adfs_dir_read(struct super_block *sb, u32 indaddr,
 128                         unsigned int size, struct adfs_dir *dir)
 129{
 130        const unsigned int blocksize_bits = sb->s_blocksize_bits;
 131        int blk = 0;
 132
 133        /*
 134         * Directories which are not a multiple of 2048 bytes
 135         * are considered bad v2 [3.6]
 136         */
 137        if (size & 2047)
 138                goto bad_dir;
 139
 140        size >>= blocksize_bits;
 141
 142        dir->nr_buffers = 0;
 143        dir->sb = sb;
 144
 145        for (blk = 0; blk < size; blk++) {
 146                int phys;
 147
 148                phys = __adfs_block_map(sb, indaddr, blk);
 149                if (!phys) {
 150                        adfs_error(sb, "dir %06x has a hole at offset %d",
 151                                   indaddr, blk);
 152                        goto release_buffers;
 153                }
 154
 155                dir->bh[blk] = sb_bread(sb, phys);
 156                if (!dir->bh[blk])
 157                        goto release_buffers;
 158        }
 159
 160        memcpy(&dir->dirhead, bufoff(dir->bh, 0), sizeof(dir->dirhead));
 161        memcpy(&dir->dirtail, bufoff(dir->bh, 2007), sizeof(dir->dirtail));
 162
 163        if (dir->dirhead.startmasseq != dir->dirtail.new.endmasseq ||
 164            memcmp(&dir->dirhead.startname, &dir->dirtail.new.endname, 4))
 165                goto bad_dir;
 166
 167        if (memcmp(&dir->dirhead.startname, "Nick", 4) &&
 168            memcmp(&dir->dirhead.startname, "Hugo", 4))
 169                goto bad_dir;
 170
 171        if (adfs_dir_checkbyte(dir) != dir->dirtail.new.dircheckbyte)
 172                goto bad_dir;
 173
 174        dir->nr_buffers = blk;
 175
 176        return 0;
 177
 178bad_dir:
 179        adfs_error(sb, "dir %06x is corrupted", indaddr);
 180release_buffers:
 181        for (blk -= 1; blk >= 0; blk -= 1)
 182                brelse(dir->bh[blk]);
 183
 184        dir->sb = NULL;
 185
 186        return -EIO;
 187}
 188
 189/*
 190 * convert a disk-based directory entry to a Linux ADFS directory entry
 191 */
 192static inline void
 193adfs_dir2obj(struct adfs_dir *dir, struct object_info *obj,
 194        struct adfs_direntry *de)
 195{
 196        unsigned int name_len;
 197
 198        for (name_len = 0; name_len < ADFS_F_NAME_LEN; name_len++) {
 199                if (de->dirobname[name_len] < ' ')
 200                        break;
 201
 202                obj->name[name_len] = de->dirobname[name_len];
 203        }
 204
 205        obj->name_len = name_len;
 206        obj->indaddr  = adfs_readval(de->dirinddiscadd, 3);
 207        obj->loadaddr = adfs_readval(de->dirload, 4);
 208        obj->execaddr = adfs_readval(de->direxec, 4);
 209        obj->size     = adfs_readval(de->dirlen,  4);
 210        obj->attr     = de->newdiratts;
 211
 212        adfs_object_fixup(dir, obj);
 213}
 214
 215/*
 216 * convert a Linux ADFS directory entry to a disk-based directory entry
 217 */
 218static inline void
 219adfs_obj2dir(struct adfs_direntry *de, struct object_info *obj)
 220{
 221        adfs_writeval(de->dirinddiscadd, 3, obj->indaddr);
 222        adfs_writeval(de->dirload, 4, obj->loadaddr);
 223        adfs_writeval(de->direxec, 4, obj->execaddr);
 224        adfs_writeval(de->dirlen,  4, obj->size);
 225        de->newdiratts = obj->attr;
 226}
 227
 228/*
 229 * get a directory entry.  Note that the caller is responsible
 230 * for holding the relevant locks.
 231 */
 232static int
 233__adfs_dir_get(struct adfs_dir *dir, int pos, struct object_info *obj)
 234{
 235        struct super_block *sb = dir->sb;
 236        struct adfs_direntry de;
 237        int thissize, buffer, offset;
 238
 239        buffer = pos >> sb->s_blocksize_bits;
 240
 241        if (buffer > dir->nr_buffers)
 242                return -EINVAL;
 243
 244        offset = pos & (sb->s_blocksize - 1);
 245        thissize = sb->s_blocksize - offset;
 246        if (thissize > 26)
 247                thissize = 26;
 248
 249        memcpy(&de, dir->bh[buffer]->b_data + offset, thissize);
 250        if (thissize != 26)
 251                memcpy(((char *)&de) + thissize, dir->bh[buffer + 1]->b_data,
 252                       26 - thissize);
 253
 254        if (!de.dirobname[0])
 255                return -ENOENT;
 256
 257        adfs_dir2obj(dir, obj, &de);
 258
 259        return 0;
 260}
 261
 262static int
 263__adfs_dir_put(struct adfs_dir *dir, int pos, struct object_info *obj)
 264{
 265        struct super_block *sb = dir->sb;
 266        struct adfs_direntry de;
 267        int thissize, buffer, offset;
 268
 269        buffer = pos >> sb->s_blocksize_bits;
 270
 271        if (buffer > dir->nr_buffers)
 272                return -EINVAL;
 273
 274        offset = pos & (sb->s_blocksize - 1);
 275        thissize = sb->s_blocksize - offset;
 276        if (thissize > 26)
 277                thissize = 26;
 278
 279        /*
 280         * Get the entry in total
 281         */
 282        memcpy(&de, dir->bh[buffer]->b_data + offset, thissize);
 283        if (thissize != 26)
 284                memcpy(((char *)&de) + thissize, dir->bh[buffer + 1]->b_data,
 285                       26 - thissize);
 286
 287        /*
 288         * update it
 289         */
 290        adfs_obj2dir(&de, obj);
 291
 292        /*
 293         * Put the new entry back
 294         */
 295        memcpy(dir->bh[buffer]->b_data + offset, &de, thissize);
 296        if (thissize != 26)
 297                memcpy(dir->bh[buffer + 1]->b_data, ((char *)&de) + thissize,
 298                       26 - thissize);
 299
 300        return 0;
 301}
 302
 303/*
 304 * the caller is responsible for holding the necessary
 305 * locks.
 306 */
 307static int adfs_dir_find_entry(struct adfs_dir *dir, u32 indaddr)
 308{
 309        int pos, ret;
 310
 311        ret = -ENOENT;
 312
 313        for (pos = 5; pos < ADFS_NUM_DIR_ENTRIES * 26 + 5; pos += 26) {
 314                struct object_info obj;
 315
 316                if (!__adfs_dir_get(dir, pos, &obj))
 317                        break;
 318
 319                if (obj.indaddr == indaddr) {
 320                        ret = pos;
 321                        break;
 322                }
 323        }
 324
 325        return ret;
 326}
 327
 328static int adfs_f_read(struct super_block *sb, u32 indaddr, unsigned int size,
 329                       struct adfs_dir *dir)
 330{
 331        int ret;
 332
 333        if (size != ADFS_NEWDIR_SIZE)
 334                return -EIO;
 335
 336        ret = adfs_dir_read(sb, indaddr, size, dir);
 337        if (ret)
 338                adfs_error(sb, "unable to read directory");
 339        else
 340                dir->parent_id = adfs_readval(dir->dirtail.new.dirparent, 3);
 341
 342        return ret;
 343}
 344
 345static int
 346adfs_f_setpos(struct adfs_dir *dir, unsigned int fpos)
 347{
 348        if (fpos >= ADFS_NUM_DIR_ENTRIES)
 349                return -ENOENT;
 350
 351        dir->pos = 5 + fpos * 26;
 352        return 0;
 353}
 354
 355static int
 356adfs_f_getnext(struct adfs_dir *dir, struct object_info *obj)
 357{
 358        unsigned int ret;
 359
 360        ret = __adfs_dir_get(dir, dir->pos, obj);
 361        if (ret == 0)
 362                dir->pos += 26;
 363
 364        return ret;
 365}
 366
 367static int
 368adfs_f_update(struct adfs_dir *dir, struct object_info *obj)
 369{
 370        struct super_block *sb = dir->sb;
 371        int ret, i;
 372
 373        ret = adfs_dir_find_entry(dir, obj->indaddr);
 374        if (ret < 0) {
 375                adfs_error(dir->sb, "unable to locate entry to update");
 376                goto out;
 377        }
 378
 379        __adfs_dir_put(dir, ret, obj);
 380 
 381        /*
 382         * Increment directory sequence number
 383         */
 384        dir->bh[0]->b_data[0] += 1;
 385        dir->bh[dir->nr_buffers - 1]->b_data[sb->s_blocksize - 6] += 1;
 386
 387        ret = adfs_dir_checkbyte(dir);
 388        /*
 389         * Update directory check byte
 390         */
 391        dir->bh[dir->nr_buffers - 1]->b_data[sb->s_blocksize - 1] = ret;
 392
 393#if 1
 394        {
 395        const unsigned int blocksize_bits = sb->s_blocksize_bits;
 396
 397        memcpy(&dir->dirhead, bufoff(dir->bh, 0), sizeof(dir->dirhead));
 398        memcpy(&dir->dirtail, bufoff(dir->bh, 2007), sizeof(dir->dirtail));
 399
 400        if (dir->dirhead.startmasseq != dir->dirtail.new.endmasseq ||
 401            memcmp(&dir->dirhead.startname, &dir->dirtail.new.endname, 4))
 402                goto bad_dir;
 403
 404        if (memcmp(&dir->dirhead.startname, "Nick", 4) &&
 405            memcmp(&dir->dirhead.startname, "Hugo", 4))
 406                goto bad_dir;
 407
 408        if (adfs_dir_checkbyte(dir) != dir->dirtail.new.dircheckbyte)
 409                goto bad_dir;
 410        }
 411#endif
 412        for (i = dir->nr_buffers - 1; i >= 0; i--)
 413                mark_buffer_dirty(dir->bh[i]);
 414
 415        ret = 0;
 416out:
 417        return ret;
 418#if 1
 419bad_dir:
 420        adfs_error(dir->sb, "whoops!  I broke a directory!");
 421        return -EIO;
 422#endif
 423}
 424
 425static int
 426adfs_f_sync(struct adfs_dir *dir)
 427{
 428        int err = 0;
 429        int i;
 430
 431        for (i = dir->nr_buffers - 1; i >= 0; i--) {
 432                struct buffer_head *bh = dir->bh[i];
 433                sync_dirty_buffer(bh);
 434                if (buffer_req(bh) && !buffer_uptodate(bh))
 435                        err = -EIO;
 436        }
 437
 438        return err;
 439}
 440
 441static void
 442adfs_f_free(struct adfs_dir *dir)
 443{
 444        int i;
 445
 446        for (i = dir->nr_buffers - 1; i >= 0; i--) {
 447                brelse(dir->bh[i]);
 448                dir->bh[i] = NULL;
 449        }
 450
 451        dir->nr_buffers = 0;
 452        dir->sb = NULL;
 453}
 454
 455const struct adfs_dir_ops adfs_f_dir_ops = {
 456        .read           = adfs_f_read,
 457        .setpos         = adfs_f_setpos,
 458        .getnext        = adfs_f_getnext,
 459        .update         = adfs_f_update,
 460        .sync           = adfs_f_sync,
 461        .free           = adfs_f_free
 462};
 463