[PATCH v2] hfsplus: readonly support for directory hardlinks

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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


[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [Samba]     [Device Mapper]     [CEPH Development]
  Powered by Linux