linux/fs/hfsplus/dir.c
<<
>>
Prefs
   1/*
   2 *  linux/fs/hfsplus/dir.c
   3 *
   4 * Copyright (C) 2001
   5 * Brad Boyer (flar@allandria.com)
   6 * (C) 2003 Ardis Technologies <roman@ardistech.com>
   7 *
   8 * Handling of directories
   9 */
  10
  11#include <linux/errno.h>
  12#include <linux/fs.h>
  13#include <linux/slab.h>
  14#include <linux/random.h>
  15
  16#include "hfsplus_fs.h"
  17#include "hfsplus_raw.h"
  18#include "xattr.h"
  19
  20static inline void hfsplus_instantiate(struct dentry *dentry,
  21                                       struct inode *inode, u32 cnid)
  22{
  23        dentry->d_fsdata = (void *)(unsigned long)cnid;
  24        d_instantiate(dentry, inode);
  25}
  26
  27/* Find the entry inside dir named dentry->d_name */
  28static struct dentry *hfsplus_lookup(struct inode *dir, struct dentry *dentry,
  29                                     unsigned int flags)
  30{
  31        struct inode *inode = NULL;
  32        struct hfs_find_data fd;
  33        struct super_block *sb;
  34        hfsplus_cat_entry entry;
  35        int err;
  36        u32 cnid, linkid = 0;
  37        u16 type;
  38
  39        sb = dir->i_sb;
  40
  41        dentry->d_fsdata = NULL;
  42        err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd);
  43        if (err)
  44                return ERR_PTR(err);
  45        hfsplus_cat_build_key(sb, fd.search_key, dir->i_ino, &dentry->d_name);
  46again:
  47        err = hfs_brec_read(&fd, &entry, sizeof(entry));
  48        if (err) {
  49                if (err == -ENOENT) {
  50                        hfs_find_exit(&fd);
  51                        /* No such entry */
  52                        inode = NULL;
  53                        goto out;
  54                }
  55                goto fail;
  56        }
  57        type = be16_to_cpu(entry.type);
  58        if (type == HFSPLUS_FOLDER) {
  59                if (fd.entrylength < sizeof(struct hfsplus_cat_folder)) {
  60                        err = -EIO;
  61                        goto fail;
  62                }
  63                cnid = be32_to_cpu(entry.folder.id);
  64                dentry->d_fsdata = (void *)(unsigned long)cnid;
  65        } else if (type == HFSPLUS_FILE) {
  66                if (fd.entrylength < sizeof(struct hfsplus_cat_file)) {
  67                        err = -EIO;
  68                        goto fail;
  69                }
  70                cnid = be32_to_cpu(entry.file.id);
  71                if (entry.file.user_info.fdType ==
  72                                cpu_to_be32(HFSP_HARDLINK_TYPE) &&
  73                                entry.file.user_info.fdCreator ==
  74                                cpu_to_be32(HFSP_HFSPLUS_CREATOR) &&
  75                                (entry.file.create_date ==
  76                                        HFSPLUS_I(HFSPLUS_SB(sb)->hidden_dir)->
  77                                                create_date ||
  78                                entry.file.create_date ==
  79                                        HFSPLUS_I(sb->s_root->d_inode)->
  80                                                create_date) &&
  81                                HFSPLUS_SB(sb)->hidden_dir) {
  82                        struct qstr str;
  83                        char name[32];
  84
  85                        if (dentry->d_fsdata) {
  86                                /*
  87                                 * We found a link pointing to another link,
  88                                 * so ignore it and treat it as regular file.
  89                                 */
  90                                cnid = (unsigned long)dentry->d_fsdata;
  91                                linkid = 0;
  92                        } else {
  93                                dentry->d_fsdata = (void *)(unsigned long)cnid;
  94                                linkid =
  95                                        be32_to_cpu(entry.file.permissions.dev);
  96                                str.len = sprintf(name, "iNode%d", linkid);
  97                                str.name = name;
  98                                hfsplus_cat_build_key(sb, fd.search_key,
  99                                        HFSPLUS_SB(sb)->hidden_dir->i_ino,
 100                                        &str);
 101                                goto again;
 102                        }
 103                } else if (!dentry->d_fsdata)
 104                        dentry->d_fsdata = (void *)(unsigned long)cnid;
 105        } else {
 106                pr_err("invalid catalog entry type in lookup\n");
 107                err = -EIO;
 108                goto fail;
 109        }
 110        hfs_find_exit(&fd);
 111        inode = hfsplus_iget(dir->i_sb, cnid);
 112        if (IS_ERR(inode))
 113                return ERR_CAST(inode);
 114        if (S_ISREG(inode->i_mode))
 115                HFSPLUS_I(inode)->linkid = linkid;
 116out:
 117        d_add(dentry, inode);
 118        return NULL;
 119fail:
 120        hfs_find_exit(&fd);
 121        return ERR_PTR(err);
 122}
 123
 124static int hfsplus_readdir(struct file *file, struct dir_context *ctx)
 125{
 126        struct inode *inode = file_inode(file);
 127        struct super_block *sb = inode->i_sb;
 128        int len, err;
 129        char strbuf[HFSPLUS_MAX_STRLEN + 1];
 130        hfsplus_cat_entry entry;
 131        struct hfs_find_data fd;
 132        struct hfsplus_readdir_data *rd;
 133        u16 type;
 134
 135        if (file->f_pos >= inode->i_size)
 136                return 0;
 137
 138        err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd);
 139        if (err)
 140                return err;
 141        hfsplus_cat_build_key(sb, fd.search_key, inode->i_ino, NULL);
 142        err = hfs_brec_find(&fd, hfs_find_rec_by_key);
 143        if (err)
 144                goto out;
 145
 146        if (ctx->pos == 0) {
 147                /* This is completely artificial... */
 148                if (!dir_emit_dot(file, ctx))
 149                        goto out;
 150                ctx->pos = 1;
 151        }
 152        if (ctx->pos == 1) {
 153                if (fd.entrylength > sizeof(entry) || fd.entrylength < 0) {
 154                        err = -EIO;
 155                        goto out;
 156                }
 157
 158                hfs_bnode_read(fd.bnode, &entry, fd.entryoffset,
 159                        fd.entrylength);
 160                if (be16_to_cpu(entry.type) != HFSPLUS_FOLDER_THREAD) {
 161                        pr_err("bad catalog folder thread\n");
 162                        err = -EIO;
 163                        goto out;
 164                }
 165                if (fd.entrylength < HFSPLUS_MIN_THREAD_SZ) {
 166                        pr_err("truncated catalog thread\n");
 167                        err = -EIO;
 168                        goto out;
 169                }
 170                if (!dir_emit(ctx, "..", 2,
 171                            be32_to_cpu(entry.thread.parentID), DT_DIR))
 172                        goto out;
 173                ctx->pos = 2;
 174        }
 175        if (ctx->pos >= inode->i_size)
 176                goto out;
 177        err = hfs_brec_goto(&fd, ctx->pos - 1);
 178        if (err)
 179                goto out;
 180        for (;;) {
 181                if (be32_to_cpu(fd.key->cat.parent) != inode->i_ino) {
 182                        pr_err("walked past end of dir\n");
 183                        err = -EIO;
 184                        goto out;
 185                }
 186
 187                if (fd.entrylength > sizeof(entry) || fd.entrylength < 0) {
 188                        err = -EIO;
 189                        goto out;
 190                }
 191
 192                hfs_bnode_read(fd.bnode, &entry, fd.entryoffset,
 193                        fd.entrylength);
 194                type = be16_to_cpu(entry.type);
 195                len = HFSPLUS_MAX_STRLEN;
 196                err = hfsplus_uni2asc(sb, &fd.key->cat.name, strbuf, &len);
 197                if (err)
 198                        goto out;
 199                if (type == HFSPLUS_FOLDER) {
 200                        if (fd.entrylength <
 201                                        sizeof(struct hfsplus_cat_folder)) {
 202                                pr_err("small dir entry\n");
 203                                err = -EIO;
 204                                goto out;
 205                        }
 206                        if (HFSPLUS_SB(sb)->hidden_dir &&
 207                            HFSPLUS_SB(sb)->hidden_dir->i_ino ==
 208                                        be32_to_cpu(entry.folder.id))
 209                                goto next;
 210                        if (!dir_emit(ctx, strbuf, len,
 211                                    be32_to_cpu(entry.folder.id), DT_DIR))
 212                                break;
 213                } else if (type == HFSPLUS_FILE) {
 214                        if (fd.entrylength < sizeof(struct hfsplus_cat_file)) {
 215                                pr_err("small file entry\n");
 216                                err = -EIO;
 217                                goto out;
 218                        }
 219                        if (!dir_emit(ctx, strbuf, len,
 220                                    be32_to_cpu(entry.file.id), DT_REG))
 221                                break;
 222                } else {
 223                        pr_err("bad catalog entry type\n");
 224                        err = -EIO;
 225                        goto out;
 226                }
 227next:
 228                ctx->pos++;
 229                if (ctx->pos >= inode->i_size)
 230                        goto out;
 231                err = hfs_brec_goto(&fd, 1);
 232                if (err)
 233                        goto out;
 234        }
 235        rd = file->private_data;
 236        if (!rd) {
 237                rd = kmalloc(sizeof(struct hfsplus_readdir_data), GFP_KERNEL);
 238                if (!rd) {
 239                        err = -ENOMEM;
 240                        goto out;
 241                }
 242                file->private_data = rd;
 243                rd->file = file;
 244                list_add(&rd->list, &HFSPLUS_I(inode)->open_dir_list);
 245        }
 246        memcpy(&rd->key, fd.key, sizeof(struct hfsplus_cat_key));
 247out:
 248        hfs_find_exit(&fd);
 249        return err;
 250}
 251
 252static int hfsplus_dir_release(struct inode *inode, struct file *file)
 253{
 254        struct hfsplus_readdir_data *rd = file->private_data;
 255        if (rd) {
 256                mutex_lock(&inode->i_mutex);
 257                list_del(&rd->list);
 258                mutex_unlock(&inode->i_mutex);
 259                kfree(rd);
 260        }
 261        return 0;
 262}
 263
 264static int hfsplus_link(struct dentry *src_dentry, struct inode *dst_dir,
 265                        struct dentry *dst_dentry)
 266{
 267        struct hfsplus_sb_info *sbi = HFSPLUS_SB(dst_dir->i_sb);
 268        struct inode *inode = src_dentry->d_inode;
 269        struct inode *src_dir = src_dentry->d_parent->d_inode;
 270        struct qstr str;
 271        char name[32];
 272        u32 cnid, id;
 273        int res;
 274
 275        if (HFSPLUS_IS_RSRC(inode))
 276                return -EPERM;
 277        if (!S_ISREG(inode->i_mode))
 278                return -EPERM;
 279
 280        mutex_lock(&sbi->vh_mutex);
 281        if (inode->i_ino == (u32)(unsigned long)src_dentry->d_fsdata) {
 282                for (;;) {
 283                        get_random_bytes(&id, sizeof(cnid));
 284                        id &= 0x3fffffff;
 285                        str.name = name;
 286                        str.len = sprintf(name, "iNode%d", id);
 287                        res = hfsplus_rename_cat(inode->i_ino,
 288                                                 src_dir, &src_dentry->d_name,
 289                                                 sbi->hidden_dir, &str);
 290                        if (!res)
 291                                break;
 292                        if (res != -EEXIST)
 293                                goto out;
 294                }
 295                HFSPLUS_I(inode)->linkid = id;
 296                cnid = sbi->next_cnid++;
 297                src_dentry->d_fsdata = (void *)(unsigned long)cnid;
 298                res = hfsplus_create_cat(cnid, src_dir,
 299                        &src_dentry->d_name, inode);
 300                if (res)
 301                        /* panic? */
 302                        goto out;
 303                sbi->file_count++;
 304        }
 305        cnid = sbi->next_cnid++;
 306        res = hfsplus_create_cat(cnid, dst_dir, &dst_dentry->d_name, inode);
 307        if (res)
 308                goto out;
 309
 310        inc_nlink(inode);
 311        hfsplus_instantiate(dst_dentry, inode, cnid);
 312        ihold(inode);
 313        inode->i_ctime = CURRENT_TIME_SEC;
 314        mark_inode_dirty(inode);
 315        sbi->file_count++;
 316        hfsplus_mark_mdb_dirty(dst_dir->i_sb);
 317out:
 318        mutex_unlock(&sbi->vh_mutex);
 319        return res;
 320}
 321
 322static int hfsplus_unlink(struct inode *dir, struct dentry *dentry)
 323{
 324        struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb);
 325        struct inode *inode = dentry->d_inode;
 326        struct qstr str;
 327        char name[32];
 328        u32 cnid;
 329        int res;
 330
 331        if (HFSPLUS_IS_RSRC(inode))
 332                return -EPERM;
 333
 334        mutex_lock(&sbi->vh_mutex);
 335        cnid = (u32)(unsigned long)dentry->d_fsdata;
 336        if (inode->i_ino == cnid &&
 337            atomic_read(&HFSPLUS_I(inode)->opencnt)) {
 338                str.name = name;
 339                str.len = sprintf(name, "temp%lu", inode->i_ino);
 340                res = hfsplus_rename_cat(inode->i_ino,
 341                                         dir, &dentry->d_name,
 342                                         sbi->hidden_dir, &str);
 343                if (!res) {
 344                        inode->i_flags |= S_DEAD;
 345                        drop_nlink(inode);
 346                }
 347                goto out;
 348        }
 349        res = hfsplus_delete_cat(cnid, dir, &dentry->d_name);
 350        if (res)
 351                goto out;
 352
 353        if (inode->i_nlink > 0)
 354                drop_nlink(inode);
 355        if (inode->i_ino == cnid)
 356                clear_nlink(inode);
 357        if (!inode->i_nlink) {
 358                if (inode->i_ino != cnid) {
 359                        sbi->file_count--;
 360                        if (!atomic_read(&HFSPLUS_I(inode)->opencnt)) {
 361                                res = hfsplus_delete_cat(inode->i_ino,
 362                                                         sbi->hidden_dir,
 363                                                         NULL);
 364                                if (!res)
 365                                        hfsplus_delete_inode(inode);
 366                        } else
 367                                inode->i_flags |= S_DEAD;
 368                } else
 369                        hfsplus_delete_inode(inode);
 370        } else
 371                sbi->file_count--;
 372        inode->i_ctime = CURRENT_TIME_SEC;
 373        mark_inode_dirty(inode);
 374out:
 375        mutex_unlock(&sbi->vh_mutex);
 376        return res;
 377}
 378
 379static int hfsplus_rmdir(struct inode *dir, struct dentry *dentry)
 380{
 381        struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb);
 382        struct inode *inode = dentry->d_inode;
 383        int res;
 384
 385        if (inode->i_size != 2)
 386                return -ENOTEMPTY;
 387
 388        mutex_lock(&sbi->vh_mutex);
 389        res = hfsplus_delete_cat(inode->i_ino, dir, &dentry->d_name);
 390        if (res)
 391                goto out;
 392        clear_nlink(inode);
 393        inode->i_ctime = CURRENT_TIME_SEC;
 394        hfsplus_delete_inode(inode);
 395        mark_inode_dirty(inode);
 396out:
 397        mutex_unlock(&sbi->vh_mutex);
 398        return res;
 399}
 400
 401static int hfsplus_symlink(struct inode *dir, struct dentry *dentry,
 402                           const char *symname)
 403{
 404        struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb);
 405        struct inode *inode;
 406        int res = -ENOSPC;
 407
 408        mutex_lock(&sbi->vh_mutex);
 409        inode = hfsplus_new_inode(dir->i_sb, S_IFLNK | S_IRWXUGO);
 410        if (!inode)
 411                goto out;
 412
 413        res = page_symlink(inode, symname, strlen(symname) + 1);
 414        if (res)
 415                goto out_err;
 416
 417        res = hfsplus_create_cat(inode->i_ino, dir, &dentry->d_name, inode);
 418        if (res)
 419                goto out_err;
 420
 421        res = hfsplus_init_inode_security(inode, dir, &dentry->d_name);
 422        if (res == -EOPNOTSUPP)
 423                res = 0; /* Operation is not supported. */
 424        else if (res) {
 425                /* Try to delete anyway without error analysis. */
 426                hfsplus_delete_cat(inode->i_ino, dir, &dentry->d_name);
 427                goto out_err;
 428        }
 429
 430        hfsplus_instantiate(dentry, inode, inode->i_ino);
 431        mark_inode_dirty(inode);
 432        goto out;
 433
 434out_err:
 435        clear_nlink(inode);
 436        hfsplus_delete_inode(inode);
 437        iput(inode);
 438out:
 439        mutex_unlock(&sbi->vh_mutex);
 440        return res;
 441}
 442
 443static int hfsplus_mknod(struct inode *dir, struct dentry *dentry,
 444                         umode_t mode, dev_t rdev)
 445{
 446        struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb);
 447        struct inode *inode;
 448        int res = -ENOSPC;
 449
 450        mutex_lock(&sbi->vh_mutex);
 451        inode = hfsplus_new_inode(dir->i_sb, mode);
 452        if (!inode)
 453                goto out;
 454
 455        if (S_ISBLK(mode) || S_ISCHR(mode) || S_ISFIFO(mode) || S_ISSOCK(mode))
 456                init_special_inode(inode, mode, rdev);
 457
 458        res = hfsplus_create_cat(inode->i_ino, dir, &dentry->d_name, inode);
 459        if (res)
 460                goto failed_mknod;
 461
 462        res = hfsplus_init_inode_security(inode, dir, &dentry->d_name);
 463        if (res == -EOPNOTSUPP)
 464                res = 0; /* Operation is not supported. */
 465        else if (res) {
 466                /* Try to delete anyway without error analysis. */
 467                hfsplus_delete_cat(inode->i_ino, dir, &dentry->d_name);
 468                goto failed_mknod;
 469        }
 470
 471        hfsplus_instantiate(dentry, inode, inode->i_ino);
 472        mark_inode_dirty(inode);
 473        goto out;
 474
 475failed_mknod:
 476        clear_nlink(inode);
 477        hfsplus_delete_inode(inode);
 478        iput(inode);
 479out:
 480        mutex_unlock(&sbi->vh_mutex);
 481        return res;
 482}
 483
 484static int hfsplus_create(struct inode *dir, struct dentry *dentry, umode_t mode,
 485                          bool excl)
 486{
 487        return hfsplus_mknod(dir, dentry, mode, 0);
 488}
 489
 490static int hfsplus_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
 491{
 492        return hfsplus_mknod(dir, dentry, mode | S_IFDIR, 0);
 493}
 494
 495static int hfsplus_rename(struct inode *old_dir, struct dentry *old_dentry,
 496                          struct inode *new_dir, struct dentry *new_dentry)
 497{
 498        int res;
 499
 500        /* Unlink destination if it already exists */
 501        if (new_dentry->d_inode) {
 502                if (S_ISDIR(new_dentry->d_inode->i_mode))
 503                        res = hfsplus_rmdir(new_dir, new_dentry);
 504                else
 505                        res = hfsplus_unlink(new_dir, new_dentry);
 506                if (res)
 507                        return res;
 508        }
 509
 510        res = hfsplus_rename_cat((u32)(unsigned long)old_dentry->d_fsdata,
 511                                 old_dir, &old_dentry->d_name,
 512                                 new_dir, &new_dentry->d_name);
 513        if (!res)
 514                new_dentry->d_fsdata = old_dentry->d_fsdata;
 515        return res;
 516}
 517
 518const struct inode_operations hfsplus_dir_inode_operations = {
 519        .lookup                 = hfsplus_lookup,
 520        .create                 = hfsplus_create,
 521        .link                   = hfsplus_link,
 522        .unlink                 = hfsplus_unlink,
 523        .mkdir                  = hfsplus_mkdir,
 524        .rmdir                  = hfsplus_rmdir,
 525        .symlink                = hfsplus_symlink,
 526        .mknod                  = hfsplus_mknod,
 527        .rename                 = hfsplus_rename,
 528        .setxattr               = generic_setxattr,
 529        .getxattr               = generic_getxattr,
 530        .listxattr              = hfsplus_listxattr,
 531        .removexattr            = hfsplus_removexattr,
 532};
 533
 534const struct file_operations hfsplus_dir_operations = {
 535        .fsync          = hfsplus_file_fsync,
 536        .read           = generic_read_dir,
 537        .iterate        = hfsplus_readdir,
 538        .unlocked_ioctl = hfsplus_ioctl,
 539        .llseek         = generic_file_llseek,
 540        .release        = hfsplus_dir_release,
 541};
 542