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