linux/fs/adfs/dir.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 *  linux/fs/adfs/dir.c
   4 *
   5 *  Copyright (C) 1999-2000 Russell King
   6 *
   7 *  Common directory handling for ADFS
   8 */
   9#include <linux/slab.h>
  10#include "adfs.h"
  11
  12/*
  13 * For future.  This should probably be per-directory.
  14 */
  15static DECLARE_RWSEM(adfs_dir_rwsem);
  16
  17int adfs_dir_copyfrom(void *dst, struct adfs_dir *dir, unsigned int offset,
  18                      size_t len)
  19{
  20        struct super_block *sb = dir->sb;
  21        unsigned int index, remain;
  22
  23        index = offset >> sb->s_blocksize_bits;
  24        offset &= sb->s_blocksize - 1;
  25        remain = sb->s_blocksize - offset;
  26        if (index + (remain < len) >= dir->nr_buffers)
  27                return -EINVAL;
  28
  29        if (remain < len) {
  30                memcpy(dst, dir->bhs[index]->b_data + offset, remain);
  31                dst += remain;
  32                len -= remain;
  33                index += 1;
  34                offset = 0;
  35        }
  36
  37        memcpy(dst, dir->bhs[index]->b_data + offset, len);
  38
  39        return 0;
  40}
  41
  42int adfs_dir_copyto(struct adfs_dir *dir, unsigned int offset, const void *src,
  43                    size_t len)
  44{
  45        struct super_block *sb = dir->sb;
  46        unsigned int index, remain;
  47
  48        index = offset >> sb->s_blocksize_bits;
  49        offset &= sb->s_blocksize - 1;
  50        remain = sb->s_blocksize - offset;
  51        if (index + (remain < len) >= dir->nr_buffers)
  52                return -EINVAL;
  53
  54        if (remain < len) {
  55                memcpy(dir->bhs[index]->b_data + offset, src, remain);
  56                src += remain;
  57                len -= remain;
  58                index += 1;
  59                offset = 0;
  60        }
  61
  62        memcpy(dir->bhs[index]->b_data + offset, src, len);
  63
  64        return 0;
  65}
  66
  67static void __adfs_dir_cleanup(struct adfs_dir *dir)
  68{
  69        dir->nr_buffers = 0;
  70
  71        if (dir->bhs != dir->bh)
  72                kfree(dir->bhs);
  73        dir->bhs = NULL;
  74        dir->sb = NULL;
  75}
  76
  77void adfs_dir_relse(struct adfs_dir *dir)
  78{
  79        unsigned int i;
  80
  81        for (i = 0; i < dir->nr_buffers; i++)
  82                brelse(dir->bhs[i]);
  83
  84        __adfs_dir_cleanup(dir);
  85}
  86
  87static void adfs_dir_forget(struct adfs_dir *dir)
  88{
  89        unsigned int i;
  90
  91        for (i = 0; i < dir->nr_buffers; i++)
  92                bforget(dir->bhs[i]);
  93
  94        __adfs_dir_cleanup(dir);
  95}
  96
  97int adfs_dir_read_buffers(struct super_block *sb, u32 indaddr,
  98                          unsigned int size, struct adfs_dir *dir)
  99{
 100        struct buffer_head **bhs;
 101        unsigned int i, num;
 102        int block;
 103
 104        num = ALIGN(size, sb->s_blocksize) >> sb->s_blocksize_bits;
 105        if (num > ARRAY_SIZE(dir->bh)) {
 106                /* We only allow one extension */
 107                if (dir->bhs != dir->bh)
 108                        return -EINVAL;
 109
 110                bhs = kcalloc(num, sizeof(*bhs), GFP_KERNEL);
 111                if (!bhs)
 112                        return -ENOMEM;
 113
 114                if (dir->nr_buffers)
 115                        memcpy(bhs, dir->bhs, dir->nr_buffers * sizeof(*bhs));
 116
 117                dir->bhs = bhs;
 118        }
 119
 120        for (i = dir->nr_buffers; i < num; i++) {
 121                block = __adfs_block_map(sb, indaddr, i);
 122                if (!block) {
 123                        adfs_error(sb, "dir %06x has a hole at offset %u",
 124                                   indaddr, i);
 125                        goto error;
 126                }
 127
 128                dir->bhs[i] = sb_bread(sb, block);
 129                if (!dir->bhs[i]) {
 130                        adfs_error(sb,
 131                                   "dir %06x failed read at offset %u, mapped block 0x%08x",
 132                                   indaddr, i, block);
 133                        goto error;
 134                }
 135
 136                dir->nr_buffers++;
 137        }
 138        return 0;
 139
 140error:
 141        adfs_dir_relse(dir);
 142
 143        return -EIO;
 144}
 145
 146static int adfs_dir_read(struct super_block *sb, u32 indaddr,
 147                         unsigned int size, struct adfs_dir *dir)
 148{
 149        dir->sb = sb;
 150        dir->bhs = dir->bh;
 151        dir->nr_buffers = 0;
 152
 153        return ADFS_SB(sb)->s_dir->read(sb, indaddr, size, dir);
 154}
 155
 156static int adfs_dir_read_inode(struct super_block *sb, struct inode *inode,
 157                               struct adfs_dir *dir)
 158{
 159        int ret;
 160
 161        ret = adfs_dir_read(sb, ADFS_I(inode)->indaddr, inode->i_size, dir);
 162        if (ret)
 163                return ret;
 164
 165        if (ADFS_I(inode)->parent_id != dir->parent_id) {
 166                adfs_error(sb,
 167                           "parent directory id changed under me! (%06x but got %06x)\n",
 168                           ADFS_I(inode)->parent_id, dir->parent_id);
 169                adfs_dir_relse(dir);
 170                ret = -EIO;
 171        }
 172
 173        return ret;
 174}
 175
 176static void adfs_dir_mark_dirty(struct adfs_dir *dir)
 177{
 178        unsigned int i;
 179
 180        /* Mark the buffers dirty */
 181        for (i = 0; i < dir->nr_buffers; i++)
 182                mark_buffer_dirty(dir->bhs[i]);
 183}
 184
 185static int adfs_dir_sync(struct adfs_dir *dir)
 186{
 187        int err = 0;
 188        int i;
 189
 190        for (i = dir->nr_buffers - 1; i >= 0; i--) {
 191                struct buffer_head *bh = dir->bhs[i];
 192                sync_dirty_buffer(bh);
 193                if (buffer_req(bh) && !buffer_uptodate(bh))
 194                        err = -EIO;
 195        }
 196
 197        return err;
 198}
 199
 200void adfs_object_fixup(struct adfs_dir *dir, struct object_info *obj)
 201{
 202        unsigned int dots, i;
 203
 204        /*
 205         * RISC OS allows the use of '/' in directory entry names, so we need
 206         * to fix these up.  '/' is typically used for FAT compatibility to
 207         * represent '.', so do the same conversion here.  In any case, '.'
 208         * will never be in a RISC OS name since it is used as the pathname
 209         * separator.  Handle the case where we may generate a '.' or '..'
 210         * name, replacing the first character with '^' (the RISC OS "parent
 211         * directory" character.)
 212         */
 213        for (i = dots = 0; i < obj->name_len; i++)
 214                if (obj->name[i] == '/') {
 215                        obj->name[i] = '.';
 216                        dots++;
 217                }
 218
 219        if (obj->name_len <= 2 && dots == obj->name_len)
 220                obj->name[0] = '^';
 221
 222        /*
 223         * If the object is a file, and the user requested the ,xyz hex
 224         * filetype suffix to the name, check the filetype and append.
 225         */
 226        if (!(obj->attr & ADFS_NDA_DIRECTORY) && ADFS_SB(dir->sb)->s_ftsuffix) {
 227                u16 filetype = adfs_filetype(obj->loadaddr);
 228
 229                if (filetype != ADFS_FILETYPE_NONE) {
 230                        obj->name[obj->name_len++] = ',';
 231                        obj->name[obj->name_len++] = hex_asc_lo(filetype >> 8);
 232                        obj->name[obj->name_len++] = hex_asc_lo(filetype >> 4);
 233                        obj->name[obj->name_len++] = hex_asc_lo(filetype >> 0);
 234                }
 235        }
 236}
 237
 238static int adfs_iterate(struct file *file, struct dir_context *ctx)
 239{
 240        struct inode *inode = file_inode(file);
 241        struct super_block *sb = inode->i_sb;
 242        const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
 243        struct adfs_dir dir;
 244        int ret;
 245
 246        down_read(&adfs_dir_rwsem);
 247        ret = adfs_dir_read_inode(sb, inode, &dir);
 248        if (ret)
 249                goto unlock;
 250
 251        if (ctx->pos == 0) {
 252                if (!dir_emit_dot(file, ctx))
 253                        goto unlock_relse;
 254                ctx->pos = 1;
 255        }
 256        if (ctx->pos == 1) {
 257                if (!dir_emit(ctx, "..", 2, dir.parent_id, DT_DIR))
 258                        goto unlock_relse;
 259                ctx->pos = 2;
 260        }
 261
 262        ret = ops->iterate(&dir, ctx);
 263
 264unlock_relse:
 265        up_read(&adfs_dir_rwsem);
 266        adfs_dir_relse(&dir);
 267        return ret;
 268
 269unlock:
 270        up_read(&adfs_dir_rwsem);
 271        return ret;
 272}
 273
 274int
 275adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait)
 276{
 277        const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
 278        struct adfs_dir dir;
 279        int ret;
 280
 281        if (!IS_ENABLED(CONFIG_ADFS_FS_RW))
 282                return -EINVAL;
 283
 284        if (!ops->update)
 285                return -EINVAL;
 286
 287        down_write(&adfs_dir_rwsem);
 288        ret = adfs_dir_read(sb, obj->parent_id, 0, &dir);
 289        if (ret)
 290                goto unlock;
 291
 292        ret = ops->update(&dir, obj);
 293        if (ret)
 294                goto forget;
 295
 296        ret = ops->commit(&dir);
 297        if (ret)
 298                goto forget;
 299        up_write(&adfs_dir_rwsem);
 300
 301        adfs_dir_mark_dirty(&dir);
 302
 303        if (wait)
 304                ret = adfs_dir_sync(&dir);
 305
 306        adfs_dir_relse(&dir);
 307        return ret;
 308
 309        /*
 310         * If the updated failed because the entry wasn't found, we can
 311         * just release the buffers. If it was any other error, forget
 312         * the dirtied buffers so they aren't written back to the media.
 313         */
 314forget:
 315        if (ret == -ENOENT)
 316                adfs_dir_relse(&dir);
 317        else
 318                adfs_dir_forget(&dir);
 319unlock:
 320        up_write(&adfs_dir_rwsem);
 321
 322        return ret;
 323}
 324
 325static unsigned char adfs_tolower(unsigned char c)
 326{
 327        if (c >= 'A' && c <= 'Z')
 328                c += 'a' - 'A';
 329        return c;
 330}
 331
 332static int __adfs_compare(const unsigned char *qstr, u32 qlen,
 333                          const char *str, u32 len)
 334{
 335        u32 i;
 336
 337        if (qlen != len)
 338                return 1;
 339
 340        for (i = 0; i < qlen; i++)
 341                if (adfs_tolower(qstr[i]) != adfs_tolower(str[i]))
 342                        return 1;
 343
 344        return 0;
 345}
 346
 347static int adfs_dir_lookup_byname(struct inode *inode, const struct qstr *qstr,
 348                                  struct object_info *obj)
 349{
 350        struct super_block *sb = inode->i_sb;
 351        const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
 352        const unsigned char *name;
 353        struct adfs_dir dir;
 354        u32 name_len;
 355        int ret;
 356
 357        down_read(&adfs_dir_rwsem);
 358        ret = adfs_dir_read_inode(sb, inode, &dir);
 359        if (ret)
 360                goto unlock;
 361
 362        ret = ops->setpos(&dir, 0);
 363        if (ret)
 364                goto unlock_relse;
 365
 366        ret = -ENOENT;
 367        name = qstr->name;
 368        name_len = qstr->len;
 369        while (ops->getnext(&dir, obj) == 0) {
 370                if (!__adfs_compare(name, name_len, obj->name, obj->name_len)) {
 371                        ret = 0;
 372                        break;
 373                }
 374        }
 375        obj->parent_id = ADFS_I(inode)->indaddr;
 376
 377unlock_relse:
 378        up_read(&adfs_dir_rwsem);
 379        adfs_dir_relse(&dir);
 380        return ret;
 381
 382unlock:
 383        up_read(&adfs_dir_rwsem);
 384        return ret;
 385}
 386
 387const struct file_operations adfs_dir_operations = {
 388        .read           = generic_read_dir,
 389        .llseek         = generic_file_llseek,
 390        .iterate_shared = adfs_iterate,
 391        .fsync          = generic_file_fsync,
 392};
 393
 394static int
 395adfs_hash(const struct dentry *parent, struct qstr *qstr)
 396{
 397        const unsigned char *name;
 398        unsigned long hash;
 399        u32 len;
 400
 401        if (qstr->len > ADFS_SB(parent->d_sb)->s_namelen)
 402                return -ENAMETOOLONG;
 403
 404        len = qstr->len;
 405        name = qstr->name;
 406        hash = init_name_hash(parent);
 407        while (len--)
 408                hash = partial_name_hash(adfs_tolower(*name++), hash);
 409        qstr->hash = end_name_hash(hash);
 410
 411        return 0;
 412}
 413
 414/*
 415 * Compare two names, taking note of the name length
 416 * requirements of the underlying filesystem.
 417 */
 418static int adfs_compare(const struct dentry *dentry, unsigned int len,
 419                        const char *str, const struct qstr *qstr)
 420{
 421        return __adfs_compare(qstr->name, qstr->len, str, len);
 422}
 423
 424const struct dentry_operations adfs_dentry_operations = {
 425        .d_hash         = adfs_hash,
 426        .d_compare      = adfs_compare,
 427};
 428
 429static struct dentry *
 430adfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
 431{
 432        struct inode *inode = NULL;
 433        struct object_info obj;
 434        int error;
 435
 436        error = adfs_dir_lookup_byname(dir, &dentry->d_name, &obj);
 437        if (error == 0) {
 438                /*
 439                 * This only returns NULL if get_empty_inode
 440                 * fails.
 441                 */
 442                inode = adfs_iget(dir->i_sb, &obj);
 443                if (!inode)
 444                        inode = ERR_PTR(-EACCES);
 445        } else if (error != -ENOENT) {
 446                inode = ERR_PTR(error);
 447        }
 448        return d_splice_alias(inode, dentry);
 449}
 450
 451/*
 452 * directories can handle most operations...
 453 */
 454const struct inode_operations adfs_dir_inode_operations = {
 455        .lookup         = adfs_lookup,
 456        .setattr        = adfs_notify_change,
 457};
 458