linux/fs/hfsplus/super.c
<<
>>
Prefs
   1/*
   2 *  linux/fs/hfsplus/super.c
   3 *
   4 * Copyright (C) 2001
   5 * Brad Boyer (flar@allandria.com)
   6 * (C) 2003 Ardis Technologies <roman@ardistech.com>
   7 *
   8 */
   9
  10#include <linux/module.h>
  11#include <linux/init.h>
  12#include <linux/pagemap.h>
  13#include <linux/fs.h>
  14#include <linux/slab.h>
  15#include <linux/vfs.h>
  16#include <linux/nls.h>
  17
  18static struct inode *hfsplus_alloc_inode(struct super_block *sb);
  19static void hfsplus_destroy_inode(struct inode *inode);
  20
  21#include "hfsplus_fs.h"
  22
  23static void hfsplus_read_inode(struct inode *inode)
  24{
  25        struct hfs_find_data fd;
  26        struct hfsplus_vh *vhdr;
  27        int err;
  28
  29        INIT_LIST_HEAD(&HFSPLUS_I(inode).open_dir_list);
  30        init_MUTEX(&HFSPLUS_I(inode).extents_lock);
  31        HFSPLUS_I(inode).flags = 0;
  32        HFSPLUS_I(inode).rsrc_inode = NULL;
  33        atomic_set(&HFSPLUS_I(inode).opencnt, 0);
  34
  35        if (inode->i_ino >= HFSPLUS_FIRSTUSER_CNID) {
  36        read_inode:
  37                hfs_find_init(HFSPLUS_SB(inode->i_sb).cat_tree, &fd);
  38                err = hfsplus_find_cat(inode->i_sb, inode->i_ino, &fd);
  39                if (!err)
  40                        err = hfsplus_cat_read_inode(inode, &fd);
  41                hfs_find_exit(&fd);
  42                if (err)
  43                        goto bad_inode;
  44                return;
  45        }
  46        vhdr = HFSPLUS_SB(inode->i_sb).s_vhdr;
  47        switch(inode->i_ino) {
  48        case HFSPLUS_ROOT_CNID:
  49                goto read_inode;
  50        case HFSPLUS_EXT_CNID:
  51                hfsplus_inode_read_fork(inode, &vhdr->ext_file);
  52                inode->i_mapping->a_ops = &hfsplus_btree_aops;
  53                break;
  54        case HFSPLUS_CAT_CNID:
  55                hfsplus_inode_read_fork(inode, &vhdr->cat_file);
  56                inode->i_mapping->a_ops = &hfsplus_btree_aops;
  57                break;
  58        case HFSPLUS_ALLOC_CNID:
  59                hfsplus_inode_read_fork(inode, &vhdr->alloc_file);
  60                inode->i_mapping->a_ops = &hfsplus_aops;
  61                break;
  62        case HFSPLUS_START_CNID:
  63                hfsplus_inode_read_fork(inode, &vhdr->start_file);
  64                break;
  65        case HFSPLUS_ATTR_CNID:
  66                hfsplus_inode_read_fork(inode, &vhdr->attr_file);
  67                inode->i_mapping->a_ops = &hfsplus_btree_aops;
  68                break;
  69        default:
  70                goto bad_inode;
  71        }
  72
  73        return;
  74
  75 bad_inode:
  76        make_bad_inode(inode);
  77}
  78
  79static int hfsplus_write_inode(struct inode *inode, int unused)
  80{
  81        struct hfsplus_vh *vhdr;
  82        int ret = 0;
  83
  84        dprint(DBG_INODE, "hfsplus_write_inode: %lu\n", inode->i_ino);
  85        hfsplus_ext_write_extent(inode);
  86        if (inode->i_ino >= HFSPLUS_FIRSTUSER_CNID) {
  87                return hfsplus_cat_write_inode(inode);
  88        }
  89        vhdr = HFSPLUS_SB(inode->i_sb).s_vhdr;
  90        switch (inode->i_ino) {
  91        case HFSPLUS_ROOT_CNID:
  92                ret = hfsplus_cat_write_inode(inode);
  93                break;
  94        case HFSPLUS_EXT_CNID:
  95                if (vhdr->ext_file.total_size != cpu_to_be64(inode->i_size)) {
  96                        HFSPLUS_SB(inode->i_sb).flags |= HFSPLUS_SB_WRITEBACKUP;
  97                        inode->i_sb->s_dirt = 1;
  98                }
  99                hfsplus_inode_write_fork(inode, &vhdr->ext_file);
 100                hfs_btree_write(HFSPLUS_SB(inode->i_sb).ext_tree);
 101                break;
 102        case HFSPLUS_CAT_CNID:
 103                if (vhdr->cat_file.total_size != cpu_to_be64(inode->i_size)) {
 104                        HFSPLUS_SB(inode->i_sb).flags |= HFSPLUS_SB_WRITEBACKUP;
 105                        inode->i_sb->s_dirt = 1;
 106                }
 107                hfsplus_inode_write_fork(inode, &vhdr->cat_file);
 108                hfs_btree_write(HFSPLUS_SB(inode->i_sb).cat_tree);
 109                break;
 110        case HFSPLUS_ALLOC_CNID:
 111                if (vhdr->alloc_file.total_size != cpu_to_be64(inode->i_size)) {
 112                        HFSPLUS_SB(inode->i_sb).flags |= HFSPLUS_SB_WRITEBACKUP;
 113                        inode->i_sb->s_dirt = 1;
 114                }
 115                hfsplus_inode_write_fork(inode, &vhdr->alloc_file);
 116                break;
 117        case HFSPLUS_START_CNID:
 118                if (vhdr->start_file.total_size != cpu_to_be64(inode->i_size)) {
 119                        HFSPLUS_SB(inode->i_sb).flags |= HFSPLUS_SB_WRITEBACKUP;
 120                        inode->i_sb->s_dirt = 1;
 121                }
 122                hfsplus_inode_write_fork(inode, &vhdr->start_file);
 123                break;
 124        case HFSPLUS_ATTR_CNID:
 125                if (vhdr->attr_file.total_size != cpu_to_be64(inode->i_size)) {
 126                        HFSPLUS_SB(inode->i_sb).flags |= HFSPLUS_SB_WRITEBACKUP;
 127                        inode->i_sb->s_dirt = 1;
 128                }
 129                hfsplus_inode_write_fork(inode, &vhdr->attr_file);
 130                hfs_btree_write(HFSPLUS_SB(inode->i_sb).attr_tree);
 131                break;
 132        }
 133        return ret;
 134}
 135
 136static void hfsplus_clear_inode(struct inode *inode)
 137{
 138        dprint(DBG_INODE, "hfsplus_clear_inode: %lu\n", inode->i_ino);
 139        if (HFSPLUS_IS_RSRC(inode)) {
 140                HFSPLUS_I(HFSPLUS_I(inode).rsrc_inode).rsrc_inode = NULL;
 141                iput(HFSPLUS_I(inode).rsrc_inode);
 142        }
 143}
 144
 145static void hfsplus_write_super(struct super_block *sb)
 146{
 147        struct hfsplus_vh *vhdr = HFSPLUS_SB(sb).s_vhdr;
 148
 149        dprint(DBG_SUPER, "hfsplus_write_super\n");
 150        sb->s_dirt = 0;
 151        if (sb->s_flags & MS_RDONLY)
 152                /* warn? */
 153                return;
 154
 155        vhdr->free_blocks = cpu_to_be32(HFSPLUS_SB(sb).free_blocks);
 156        vhdr->next_alloc = cpu_to_be32(HFSPLUS_SB(sb).next_alloc);
 157        vhdr->next_cnid = cpu_to_be32(HFSPLUS_SB(sb).next_cnid);
 158        vhdr->folder_count = cpu_to_be32(HFSPLUS_SB(sb).folder_count);
 159        vhdr->file_count = cpu_to_be32(HFSPLUS_SB(sb).file_count);
 160
 161        mark_buffer_dirty(HFSPLUS_SB(sb).s_vhbh);
 162        if (HFSPLUS_SB(sb).flags & HFSPLUS_SB_WRITEBACKUP) {
 163                if (HFSPLUS_SB(sb).sect_count) {
 164                        struct buffer_head *bh;
 165                        u32 block, offset;
 166
 167                        block = HFSPLUS_SB(sb).blockoffset;
 168                        block += (HFSPLUS_SB(sb).sect_count - 2) >> (sb->s_blocksize_bits - 9);
 169                        offset = ((HFSPLUS_SB(sb).sect_count - 2) << 9) & (sb->s_blocksize - 1);
 170                        printk(KERN_DEBUG "hfs: backup: %u,%u,%u,%u\n", HFSPLUS_SB(sb).blockoffset,
 171                                HFSPLUS_SB(sb).sect_count, block, offset);
 172                        bh = sb_bread(sb, block);
 173                        if (bh) {
 174                                vhdr = (struct hfsplus_vh *)(bh->b_data + offset);
 175                                if (be16_to_cpu(vhdr->signature) == HFSPLUS_VOLHEAD_SIG) {
 176                                        memcpy(vhdr, HFSPLUS_SB(sb).s_vhdr, sizeof(*vhdr));
 177                                        mark_buffer_dirty(bh);
 178                                        brelse(bh);
 179                                } else
 180                                        printk(KERN_WARNING "hfs: backup not found!\n");
 181                        }
 182                }
 183                HFSPLUS_SB(sb).flags &= ~HFSPLUS_SB_WRITEBACKUP;
 184        }
 185}
 186
 187static void hfsplus_put_super(struct super_block *sb)
 188{
 189        dprint(DBG_SUPER, "hfsplus_put_super\n");
 190        if (!sb->s_fs_info)
 191                return;
 192        if (!(sb->s_flags & MS_RDONLY) && HFSPLUS_SB(sb).s_vhdr) {
 193                struct hfsplus_vh *vhdr = HFSPLUS_SB(sb).s_vhdr;
 194
 195                vhdr->modify_date = hfsp_now2mt();
 196                vhdr->attributes |= cpu_to_be32(HFSPLUS_VOL_UNMNT);
 197                vhdr->attributes &= cpu_to_be32(~HFSPLUS_VOL_INCNSTNT);
 198                mark_buffer_dirty(HFSPLUS_SB(sb).s_vhbh);
 199                sync_dirty_buffer(HFSPLUS_SB(sb).s_vhbh);
 200        }
 201
 202        hfs_btree_close(HFSPLUS_SB(sb).cat_tree);
 203        hfs_btree_close(HFSPLUS_SB(sb).ext_tree);
 204        iput(HFSPLUS_SB(sb).alloc_file);
 205        iput(HFSPLUS_SB(sb).hidden_dir);
 206        brelse(HFSPLUS_SB(sb).s_vhbh);
 207        if (HFSPLUS_SB(sb).nls)
 208                unload_nls(HFSPLUS_SB(sb).nls);
 209        kfree(sb->s_fs_info);
 210        sb->s_fs_info = NULL;
 211}
 212
 213static int hfsplus_statfs(struct dentry *dentry, struct kstatfs *buf)
 214{
 215        struct super_block *sb = dentry->d_sb;
 216
 217        buf->f_type = HFSPLUS_SUPER_MAGIC;
 218        buf->f_bsize = sb->s_blocksize;
 219        buf->f_blocks = HFSPLUS_SB(sb).total_blocks << HFSPLUS_SB(sb).fs_shift;
 220        buf->f_bfree = HFSPLUS_SB(sb).free_blocks << HFSPLUS_SB(sb).fs_shift;
 221        buf->f_bavail = buf->f_bfree;
 222        buf->f_files = 0xFFFFFFFF;
 223        buf->f_ffree = 0xFFFFFFFF - HFSPLUS_SB(sb).next_cnid;
 224        buf->f_namelen = HFSPLUS_MAX_STRLEN;
 225
 226        return 0;
 227}
 228
 229static int hfsplus_remount(struct super_block *sb, int *flags, char *data)
 230{
 231        if ((*flags & MS_RDONLY) == (sb->s_flags & MS_RDONLY))
 232                return 0;
 233        if (!(*flags & MS_RDONLY)) {
 234                struct hfsplus_vh *vhdr = HFSPLUS_SB(sb).s_vhdr;
 235                struct hfsplus_sb_info sbi;
 236
 237                memset(&sbi, 0, sizeof(struct hfsplus_sb_info));
 238                sbi.nls = HFSPLUS_SB(sb).nls;
 239                if (!hfsplus_parse_options(data, &sbi))
 240                        return -EINVAL;
 241
 242                if (!(vhdr->attributes & cpu_to_be32(HFSPLUS_VOL_UNMNT))) {
 243                        printk(KERN_WARNING "hfs: filesystem was not cleanly unmounted, "
 244                               "running fsck.hfsplus is recommended.  leaving read-only.\n");
 245                        sb->s_flags |= MS_RDONLY;
 246                        *flags |= MS_RDONLY;
 247                } else if (sbi.flags & HFSPLUS_SB_FORCE) {
 248                        /* nothing */
 249                } else if (vhdr->attributes & cpu_to_be32(HFSPLUS_VOL_SOFTLOCK)) {
 250                        printk(KERN_WARNING "hfs: filesystem is marked locked, leaving read-only.\n");
 251                        sb->s_flags |= MS_RDONLY;
 252                        *flags |= MS_RDONLY;
 253                } else if (vhdr->attributes & cpu_to_be32(HFSPLUS_VOL_JOURNALED)) {
 254                        printk(KERN_WARNING "hfs: filesystem is marked journaled, leaving read-only.\n");
 255                        sb->s_flags |= MS_RDONLY;
 256                        *flags |= MS_RDONLY;
 257                }
 258        }
 259        return 0;
 260}
 261
 262static const struct super_operations hfsplus_sops = {
 263        .alloc_inode    = hfsplus_alloc_inode,
 264        .destroy_inode  = hfsplus_destroy_inode,
 265        .read_inode     = hfsplus_read_inode,
 266        .write_inode    = hfsplus_write_inode,
 267        .clear_inode    = hfsplus_clear_inode,
 268        .put_super      = hfsplus_put_super,
 269        .write_super    = hfsplus_write_super,
 270        .statfs         = hfsplus_statfs,
 271        .remount_fs     = hfsplus_remount,
 272        .show_options   = hfsplus_show_options,
 273};
 274
 275static int hfsplus_fill_super(struct super_block *sb, void *data, int silent)
 276{
 277        struct hfsplus_vh *vhdr;
 278        struct hfsplus_sb_info *sbi;
 279        hfsplus_cat_entry entry;
 280        struct hfs_find_data fd;
 281        struct inode *root;
 282        struct qstr str;
 283        struct nls_table *nls = NULL;
 284        int err = -EINVAL;
 285
 286        sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);
 287        if (!sbi)
 288                return -ENOMEM;
 289
 290        sb->s_fs_info = sbi;
 291        INIT_HLIST_HEAD(&sbi->rsrc_inodes);
 292        hfsplus_fill_defaults(sbi);
 293        if (!hfsplus_parse_options(data, sbi)) {
 294                printk(KERN_ERR "hfs: unable to parse mount options\n");
 295                err = -EINVAL;
 296                goto cleanup;
 297        }
 298
 299        /* temporarily use utf8 to correctly find the hidden dir below */
 300        nls = sbi->nls;
 301        sbi->nls = load_nls("utf8");
 302        if (!sbi->nls) {
 303                printk(KERN_ERR "hfs: unable to load nls for utf8\n");
 304                err = -EINVAL;
 305                goto cleanup;
 306        }
 307
 308        /* Grab the volume header */
 309        if (hfsplus_read_wrapper(sb)) {
 310                if (!silent)
 311                        printk(KERN_WARNING "hfs: unable to find HFS+ superblock\n");
 312                err = -EINVAL;
 313                goto cleanup;
 314        }
 315        vhdr = HFSPLUS_SB(sb).s_vhdr;
 316
 317        /* Copy parts of the volume header into the superblock */
 318        sb->s_magic = HFSPLUS_VOLHEAD_SIG;
 319        if (be16_to_cpu(vhdr->version) < HFSPLUS_MIN_VERSION ||
 320            be16_to_cpu(vhdr->version) > HFSPLUS_CURRENT_VERSION) {
 321                printk(KERN_ERR "hfs: wrong filesystem version\n");
 322                goto cleanup;
 323        }
 324        HFSPLUS_SB(sb).total_blocks = be32_to_cpu(vhdr->total_blocks);
 325        HFSPLUS_SB(sb).free_blocks = be32_to_cpu(vhdr->free_blocks);
 326        HFSPLUS_SB(sb).next_alloc = be32_to_cpu(vhdr->next_alloc);
 327        HFSPLUS_SB(sb).next_cnid = be32_to_cpu(vhdr->next_cnid);
 328        HFSPLUS_SB(sb).file_count = be32_to_cpu(vhdr->file_count);
 329        HFSPLUS_SB(sb).folder_count = be32_to_cpu(vhdr->folder_count);
 330        HFSPLUS_SB(sb).data_clump_blocks = be32_to_cpu(vhdr->data_clump_sz) >> HFSPLUS_SB(sb).alloc_blksz_shift;
 331        if (!HFSPLUS_SB(sb).data_clump_blocks)
 332                HFSPLUS_SB(sb).data_clump_blocks = 1;
 333        HFSPLUS_SB(sb).rsrc_clump_blocks = be32_to_cpu(vhdr->rsrc_clump_sz) >> HFSPLUS_SB(sb).alloc_blksz_shift;
 334        if (!HFSPLUS_SB(sb).rsrc_clump_blocks)
 335                HFSPLUS_SB(sb).rsrc_clump_blocks = 1;
 336
 337        /* Set up operations so we can load metadata */
 338        sb->s_op = &hfsplus_sops;
 339        sb->s_maxbytes = MAX_LFS_FILESIZE;
 340
 341        if (!(vhdr->attributes & cpu_to_be32(HFSPLUS_VOL_UNMNT))) {
 342                printk(KERN_WARNING "hfs: Filesystem was not cleanly unmounted, "
 343                       "running fsck.hfsplus is recommended.  mounting read-only.\n");
 344                sb->s_flags |= MS_RDONLY;
 345        } else if (sbi->flags & HFSPLUS_SB_FORCE) {
 346                /* nothing */
 347        } else if (vhdr->attributes & cpu_to_be32(HFSPLUS_VOL_SOFTLOCK)) {
 348                printk(KERN_WARNING "hfs: Filesystem is marked locked, mounting read-only.\n");
 349                sb->s_flags |= MS_RDONLY;
 350        } else if (vhdr->attributes & cpu_to_be32(HFSPLUS_VOL_JOURNALED)) {
 351                printk(KERN_WARNING "hfs: write access to a jounaled filesystem is not supported, "
 352                       "use the force option at your own risk, mounting read-only.\n");
 353                sb->s_flags |= MS_RDONLY;
 354        }
 355        sbi->flags &= ~HFSPLUS_SB_FORCE;
 356
 357        /* Load metadata objects (B*Trees) */
 358        HFSPLUS_SB(sb).ext_tree = hfs_btree_open(sb, HFSPLUS_EXT_CNID);
 359        if (!HFSPLUS_SB(sb).ext_tree) {
 360                printk(KERN_ERR "hfs: failed to load extents file\n");
 361                goto cleanup;
 362        }
 363        HFSPLUS_SB(sb).cat_tree = hfs_btree_open(sb, HFSPLUS_CAT_CNID);
 364        if (!HFSPLUS_SB(sb).cat_tree) {
 365                printk(KERN_ERR "hfs: failed to load catalog file\n");
 366                goto cleanup;
 367        }
 368
 369        HFSPLUS_SB(sb).alloc_file = iget(sb, HFSPLUS_ALLOC_CNID);
 370        if (!HFSPLUS_SB(sb).alloc_file) {
 371                printk(KERN_ERR "hfs: failed to load allocation file\n");
 372                goto cleanup;
 373        }
 374
 375        /* Load the root directory */
 376        root = iget(sb, HFSPLUS_ROOT_CNID);
 377        sb->s_root = d_alloc_root(root);
 378        if (!sb->s_root) {
 379                printk(KERN_ERR "hfs: failed to load root directory\n");
 380                iput(root);
 381                goto cleanup;
 382        }
 383        sb->s_root->d_op = &hfsplus_dentry_operations;
 384
 385        str.len = sizeof(HFSP_HIDDENDIR_NAME) - 1;
 386        str.name = HFSP_HIDDENDIR_NAME;
 387        hfs_find_init(HFSPLUS_SB(sb).cat_tree, &fd);
 388        hfsplus_cat_build_key(sb, fd.search_key, HFSPLUS_ROOT_CNID, &str);
 389        if (!hfs_brec_read(&fd, &entry, sizeof(entry))) {
 390                hfs_find_exit(&fd);
 391                if (entry.type != cpu_to_be16(HFSPLUS_FOLDER))
 392                        goto cleanup;
 393                HFSPLUS_SB(sb).hidden_dir = iget(sb, be32_to_cpu(entry.folder.id));
 394                if (!HFSPLUS_SB(sb).hidden_dir)
 395                        goto cleanup;
 396        } else
 397                hfs_find_exit(&fd);
 398
 399        if (sb->s_flags & MS_RDONLY)
 400                goto out;
 401
 402        /* H+LX == hfsplusutils, H+Lx == this driver, H+lx is unused
 403         * all three are registered with Apple for our use
 404         */
 405        vhdr->last_mount_vers = cpu_to_be32(HFSP_MOUNT_VERSION);
 406        vhdr->modify_date = hfsp_now2mt();
 407        vhdr->write_count = cpu_to_be32(be32_to_cpu(vhdr->write_count) + 1);
 408        vhdr->attributes &= cpu_to_be32(~HFSPLUS_VOL_UNMNT);
 409        vhdr->attributes |= cpu_to_be32(HFSPLUS_VOL_INCNSTNT);
 410        mark_buffer_dirty(HFSPLUS_SB(sb).s_vhbh);
 411        sync_dirty_buffer(HFSPLUS_SB(sb).s_vhbh);
 412
 413        if (!HFSPLUS_SB(sb).hidden_dir) {
 414                printk(KERN_DEBUG "hfs: create hidden dir...\n");
 415                HFSPLUS_SB(sb).hidden_dir = hfsplus_new_inode(sb, S_IFDIR);
 416                hfsplus_create_cat(HFSPLUS_SB(sb).hidden_dir->i_ino, sb->s_root->d_inode,
 417                                   &str, HFSPLUS_SB(sb).hidden_dir);
 418                mark_inode_dirty(HFSPLUS_SB(sb).hidden_dir);
 419        }
 420out:
 421        unload_nls(sbi->nls);
 422        sbi->nls = nls;
 423        return 0;
 424
 425cleanup:
 426        hfsplus_put_super(sb);
 427        if (nls)
 428                unload_nls(nls);
 429        return err;
 430}
 431
 432MODULE_AUTHOR("Brad Boyer");
 433MODULE_DESCRIPTION("Extended Macintosh Filesystem");
 434MODULE_LICENSE("GPL");
 435
 436static struct kmem_cache *hfsplus_inode_cachep;
 437
 438static struct inode *hfsplus_alloc_inode(struct super_block *sb)
 439{
 440        struct hfsplus_inode_info *i;
 441
 442        i = kmem_cache_alloc(hfsplus_inode_cachep, GFP_KERNEL);
 443        return i ? &i->vfs_inode : NULL;
 444}
 445
 446static void hfsplus_destroy_inode(struct inode *inode)
 447{
 448        kmem_cache_free(hfsplus_inode_cachep, &HFSPLUS_I(inode));
 449}
 450
 451#define HFSPLUS_INODE_SIZE      sizeof(struct hfsplus_inode_info)
 452
 453static int hfsplus_get_sb(struct file_system_type *fs_type,
 454                          int flags, const char *dev_name, void *data,
 455                          struct vfsmount *mnt)
 456{
 457        return get_sb_bdev(fs_type, flags, dev_name, data, hfsplus_fill_super,
 458                           mnt);
 459}
 460
 461static struct file_system_type hfsplus_fs_type = {
 462        .owner          = THIS_MODULE,
 463        .name           = "hfsplus",
 464        .get_sb         = hfsplus_get_sb,
 465        .kill_sb        = kill_block_super,
 466        .fs_flags       = FS_REQUIRES_DEV,
 467};
 468
 469static void hfsplus_init_once(struct kmem_cache *cachep, void *p)
 470{
 471        struct hfsplus_inode_info *i = p;
 472
 473        inode_init_once(&i->vfs_inode);
 474}
 475
 476static int __init init_hfsplus_fs(void)
 477{
 478        int err;
 479
 480        hfsplus_inode_cachep = kmem_cache_create("hfsplus_icache",
 481                HFSPLUS_INODE_SIZE, 0, SLAB_HWCACHE_ALIGN,
 482                hfsplus_init_once);
 483        if (!hfsplus_inode_cachep)
 484                return -ENOMEM;
 485        err = register_filesystem(&hfsplus_fs_type);
 486        if (err)
 487                kmem_cache_destroy(hfsplus_inode_cachep);
 488        return err;
 489}
 490
 491static void __exit exit_hfsplus_fs(void)
 492{
 493        unregister_filesystem(&hfsplus_fs_type);
 494        kmem_cache_destroy(hfsplus_inode_cachep);
 495}
 496
 497module_init(init_hfsplus_fs)
 498module_exit(exit_hfsplus_fs)
 499