Add support for resolving directory hardlinks as introduced with Apple OS X 10.5 in general, and Time Machine backups in particular. Prevent tampering of directory hardlink metadata when mounted writable. Limit lookup to readonly filesystems to prevent rename from creating filesystem loops. Signed-off-by: Gustav Munkby <grddev@xxxxxxxxx> --- fs/hfsplus/catalog.c | 2 +- fs/hfsplus/dir.c | 70 ++++++++++++++++++++++++++++++++++++--------- fs/hfsplus/hfsplus_fs.h | 1 + fs/hfsplus/hfsplus_raw.h | 5 +++ fs/hfsplus/super.c | 54 ++++++++++++++++++++++++----------- 5 files changed, 100 insertions(+), 32 deletions(-) diff --git a/fs/hfsplus/catalog.c b/fs/hfsplus/catalog.c index b4ba1b3..e12919a 100644 --- a/fs/hfsplus/catalog.c +++ b/fs/hfsplus/catalog.c @@ -109,7 +109,7 @@ static int hfsplus_cat_build_record(hfsplus_cat_entry *entry, folder->attribute_mod_date = folder->access_date = hfsp_now2mt(); hfsplus_cat_set_perms(inode, &folder->permissions); - if (inode == sbi->hidden_dir) + if (inode == sbi->hidden_dir || inode == sbi->alias_dir) /* invisible and namelocked */ folder->user_info.frFlags = cpu_to_be16(0x5000); return sizeof(*folder); diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c index 4df5059..290467f 100644 --- a/fs/hfsplus/dir.c +++ b/fs/hfsplus/dir.c @@ -23,6 +23,41 @@ static inline void hfsplus_instantiate(struct dentry *dentry, d_instantiate(dentry, inode); } +static inline u32 hfsplus_hardlink_type(struct super_block *sb, + struct hfsplus_cat_file *file) +{ + struct inode *hdir = HFSPLUS_SB(sb)->hidden_dir; + struct inode *adir = HFSPLUS_SB(sb)->alias_dir; + struct inode *root = sb->s_root->d_inode; + u32 fdtype = be32_to_cpu(file->user_info.fdType); + u32 creator = be32_to_cpu(file->user_info.fdCreator); + __be32 create_date = file->create_date; + + if (hdir && fdtype == HFSP_HARDLINK_TYPE && + creator == HFSP_HFSPLUS_CREATOR && + (create_date == HFSPLUS_I(root)->create_date || + create_date == HFSPLUS_I(hdir)->create_date)) + return fdtype; + + /* Apple's Time Machine creates folder hardlinks similarly to + * normal file hardlinks. + * + * Since write operations rmdir and rename are not yet supported, + * only enable lookup of folder hardlink destinations when hfsplus + * is mounted readonly. + * + * Supporting folder hardlink rmdir would require processing similar + * to unlink for file hardlinks. All folder renames would require + * additional protection to not introduce filesystem loops. */ + if (adir && (sb->s_flags & MS_RDONLY) && + fdtype == HFSP_FOLDER_ALIAS_TYPE && + creator == HFSP_MACS_CREATOR && + be32_to_cpu(file->permissions.dev) >= 127) + return fdtype; + + return 0; +} + /* Find the entry inside dir named dentry->d_name */ static struct dentry *hfsplus_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) @@ -34,6 +69,7 @@ static struct dentry *hfsplus_lookup(struct inode *dir, struct dentry *dentry, int err; u32 cnid, linkid = 0; u16 type; + u32 fdtype; sb = dir->i_sb; @@ -58,26 +94,28 @@ again: goto fail; } cnid = be32_to_cpu(entry.folder.id); - dentry->d_fsdata = (void *)(unsigned long)cnid; + if (!dentry->d_fsdata) + dentry->d_fsdata = (void *)(unsigned long)cnid; } else if (type == HFSPLUS_FILE) { if (fd.entrylength < sizeof(struct hfsplus_cat_file)) { err = -EIO; goto fail; } cnid = be32_to_cpu(entry.file.id); - if (entry.file.user_info.fdType == - cpu_to_be32(HFSP_HARDLINK_TYPE) && - entry.file.user_info.fdCreator == - cpu_to_be32(HFSP_HFSPLUS_CREATOR) && - (entry.file.create_date == - HFSPLUS_I(HFSPLUS_SB(sb)->hidden_dir)-> - create_date || - entry.file.create_date == - HFSPLUS_I(sb->s_root->d_inode)-> - create_date) && - HFSPLUS_SB(sb)->hidden_dir) { + fdtype = hfsplus_hardlink_type(sb, &entry.file); + if (fdtype) { struct qstr str; char name[32]; + struct inode *dir; + char *namefmt; + + if (fdtype == HFSP_FOLDER_ALIAS_TYPE) { + dir = HFSPLUS_SB(sb)->alias_dir; + namefmt = "dir_%d"; + } else { + dir = HFSPLUS_SB(sb)->hidden_dir; + namefmt = "iNode%d"; + } if (dentry->d_fsdata) { /* @@ -90,10 +128,10 @@ again: dentry->d_fsdata = (void *)(unsigned long)cnid; linkid = be32_to_cpu(entry.file.permissions.dev); - str.len = sprintf(name, "iNode%d", linkid); + str.len = sprintf(name, namefmt, linkid); str.name = name; hfsplus_cat_build_key(sb, fd.search_key, - HFSPLUS_SB(sb)->hidden_dir->i_ino, + dir->i_ino, &str); goto again; } @@ -195,6 +233,10 @@ static int hfsplus_readdir(struct file *filp, void *dirent, filldir_t filldir) HFSPLUS_SB(sb)->hidden_dir->i_ino == be32_to_cpu(entry.folder.id)) goto next; + if (HFSPLUS_SB(sb)->alias_dir && + HFSPLUS_SB(sb)->alias_dir->i_ino == + be32_to_cpu(entry.folder.id)) + goto next; if (filldir(dirent, strbuf, len, filp->f_pos, be32_to_cpu(entry.folder.id), DT_DIR)) break; diff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h index d685752..42171ae 100644 --- a/fs/hfsplus/hfsplus_fs.h +++ b/fs/hfsplus/hfsplus_fs.h @@ -117,6 +117,7 @@ struct hfsplus_sb_info { struct hfs_btree *attr_tree; struct inode *alloc_file; struct inode *hidden_dir; + struct inode *alias_dir; struct nls_table *nls; /* Runtime variables */ diff --git a/fs/hfsplus/hfsplus_raw.h b/fs/hfsplus/hfsplus_raw.h index 927cdd6..c6d8859 100644 --- a/fs/hfsplus/hfsplus_raw.h +++ b/fs/hfsplus/hfsplus_raw.h @@ -36,6 +36,11 @@ #define HFSP_WRAPOFF_EMBEDSIG 0x7C #define HFSP_WRAPOFF_EMBEDEXT 0x7E +#define HFSP_ALIASDIR_NAME ".HFS+ Private Directory Data\r" + +#define HFSP_FOLDER_ALIAS_TYPE 0x66647270 /* 'fdrp' */ +#define HFSP_MACS_CREATOR 0x4d414353 /* 'MACS' */ + #define HFSP_HIDDENDIR_NAME \ "\xe2\x90\x80\xe2\x90\x80\xe2\x90\x80\xe2\x90\x80HFS+ Private Data" diff --git a/fs/hfsplus/super.c b/fs/hfsplus/super.c index b49b555..cb20383 100644 --- a/fs/hfsplus/super.c +++ b/fs/hfsplus/super.c @@ -251,6 +251,7 @@ static void hfsplus_put_super(struct super_block *sb) hfs_btree_close(sbi->ext_tree); iput(sbi->alloc_file); iput(sbi->hidden_dir); + iput(sbi->alias_dir); kfree(sbi->s_vhdr); kfree(sbi->s_backup_vhdr); unload_nls(sbi->nls); @@ -329,12 +330,34 @@ static const struct super_operations hfsplus_sops = { .show_options = hfsplus_show_options, }; +static int hfsplus_find_hidden_folder(struct super_block *sb, + struct qstr *name, + struct inode **inode) +{ + hfsplus_cat_entry entry; + struct hfs_find_data fd; + + hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd); + hfsplus_cat_build_key(sb, fd.search_key, HFSPLUS_ROOT_CNID, name); + if (hfs_brec_read(&fd, &entry, sizeof(entry))) { + hfs_find_exit(&fd); + return 0; + } + + hfs_find_exit(&fd); + if (entry.type != cpu_to_be16(HFSPLUS_FOLDER)) + return -EINVAL; + + *inode = hfsplus_iget(sb, be32_to_cpu(entry.folder.id)); + if (IS_ERR(*inode)) + return PTR_ERR(*inode); + return 0; +} + static int hfsplus_fill_super(struct super_block *sb, void *data, int silent) { struct hfsplus_vh *vhdr; struct hfsplus_sb_info *sbi; - hfsplus_cat_entry entry; - struct hfs_find_data fd; struct inode *root, *inode; struct qstr str; struct nls_table *nls = NULL; @@ -447,20 +470,15 @@ static int hfsplus_fill_super(struct super_block *sb, void *data, int silent) str.len = sizeof(HFSP_HIDDENDIR_NAME) - 1; str.name = HFSP_HIDDENDIR_NAME; - hfs_find_init(sbi->cat_tree, &fd); - hfsplus_cat_build_key(sb, fd.search_key, HFSPLUS_ROOT_CNID, &str); - if (!hfs_brec_read(&fd, &entry, sizeof(entry))) { - hfs_find_exit(&fd); - if (entry.type != cpu_to_be16(HFSPLUS_FOLDER)) - goto out_put_root; - inode = hfsplus_iget(sb, be32_to_cpu(entry.folder.id)); - if (IS_ERR(inode)) { - err = PTR_ERR(inode); - goto out_put_root; - } - sbi->hidden_dir = inode; - } else - hfs_find_exit(&fd); + err = hfsplus_find_hidden_folder(sb, &str, &sbi->hidden_dir); + if (err) + goto out_put_root; + + str.len = sizeof(HFSP_ALIASDIR_NAME) - 1; + str.name = HFSP_ALIASDIR_NAME; + err = hfsplus_find_hidden_folder(sb, &str, &sbi->alias_dir); + if (err) + goto out_put_hidden_dir; if (!(sb->s_flags & MS_RDONLY)) { /* @@ -490,13 +508,15 @@ static int hfsplus_fill_super(struct super_block *sb, void *data, int silent) sb->s_root = d_alloc_root(root); if (!sb->s_root) { err = -ENOMEM; - goto out_put_hidden_dir; + goto out_put_alias_dir; } unload_nls(sbi->nls); sbi->nls = nls; return 0; +out_put_alias_dir: + iput(sbi->alias_dir); out_put_hidden_dir: iput(sbi->hidden_dir); out_put_root: -- 1.7.5.1 -- To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html