From: Gustav Munkby <grddev@gmail.com>
To: Christoph Hellwig <hch@lst.de>
Cc: Al Viro <viro@ZenIV.linux.org.uk>,
linux-fsdevel@vger.kernel.org, Gustav Munkby <grddev@gmail.com>
Subject: [PATCH v2] hfsplus: readonly support for directory hardlinks
Date: Fri, 20 May 2011 12:30:19 +0200 [thread overview]
Message-ID: <1305887419-28459-1-git-send-email-grddev@gmail.com> (raw)
In-Reply-To: <4DD53DDE.6020605@gmail.com>
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@gmail.com>
---
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
prev parent reply other threads:[~2011-05-20 10:31 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2011-04-29 10:20 [PATCH] hfsplus: read support for directory hardlinks Gustav Munkby
2011-05-02 8:46 ` Christoph Hellwig
2011-05-02 12:40 ` Al Viro
2011-05-03 14:11 ` Gustav Munkby
2011-05-03 14:26 ` [PATCH] hfsplus: disable rename of " Gustav Munkby
2011-05-03 17:10 ` Andreas Dilger
2011-05-03 21:29 ` Gustav Munkby
2011-05-04 9:30 ` [PATCH] hfsplus: read support for " Christoph Hellwig
2011-05-04 15:04 ` Gustav Munkby
2011-05-19 11:09 ` Christoph Hellwig
2011-05-19 15:57 ` Gustav Munkby
2011-05-20 10:30 ` Gustav Munkby [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1305887419-28459-1-git-send-email-grddev@gmail.com \
--to=grddev@gmail.com \
--cc=hch@lst.de \
--cc=linux-fsdevel@vger.kernel.org \
--cc=viro@ZenIV.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).