From mboxrd@z Thu Jan 1 00:00:00 1970 From: Gustav Munkby Subject: [PATCH] hfsplus: read support for directory hardlinks Date: Fri, 29 Apr 2011 12:20:52 +0200 Message-ID: <1304072452-6590-1-git-send-email-grddev@gmail.com> Cc: linux-fsdevel@vger.kernel.org To: Christoph Hellwig Return-path: Received: from mail-ew0-f46.google.com ([209.85.215.46]:57146 "EHLO mail-ew0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751029Ab1D2KV7 (ORCPT ); Fri, 29 Apr 2011 06:21:59 -0400 Received: by ewy4 with SMTP id 4so1097901ewy.19 for ; Fri, 29 Apr 2011 03:21:57 -0700 (PDT) Sender: linux-fsdevel-owner@vger.kernel.org List-ID: With OS X 10.5 in general, and Time Machine backups in particular, Apple added support for directory hardlinks in HFS+. As for file hardlinks, directory hardlinks are represented by normal files with special creator and type attributes, storing an pseudo inode number in the link count, referencing a secret file in a special folder. This patch extends the existing lookup of file hardlinks to also support folder hardlinks. Signed-off-by: Gustav Munkby --- fs/hfsplus/catalog.c | 2 +- fs/hfsplus/dir.c | 59 +++++++++++++++++++++++++++++++++++---------- fs/hfsplus/hfsplus_fs.h | 1 + fs/hfsplus/hfsplus_raw.h | 5 ++++ fs/hfsplus/super.c | 55 +++++++++++++++++++++++++++++------------- 5 files changed, 91 insertions(+), 31 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..eadff10 100644 --- a/fs/hfsplus/dir.c +++ b/fs/hfsplus/dir.c @@ -23,6 +23,33 @@ 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 fdCreator = be32_to_cpu(file->user_info.fdCreator); + __be32 create_date = file->create_date; + + if (hdir && fdType == HFSP_HARDLINK_TYPE && + fdCreator == 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. + */ + if (adir && fdType == HFSP_FOLDER_ALIAS_TYPE && + fdCreator == 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 +61,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; @@ -65,19 +93,20 @@ again: 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 +119,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 +224,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..1ba6618 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,16 @@ 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 +509,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