From: Roman Zippel <zippel@linux-m68k.org>

Add full nls support for HFS+.  The default is still utf8, but that can be
changed now via a mount option.

Signed-off-by: Roman Zippel <zippel@linux-m68k.org>
Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 25-akpm/fs/Kconfig              |    1 
 25-akpm/fs/hfsplus/catalog.c    |   39 ++++++++++++++--------------
 25-akpm/fs/hfsplus/dir.c        |    8 ++---
 25-akpm/fs/hfsplus/hfsplus_fs.h |    7 ++---
 25-akpm/fs/hfsplus/options.c    |   30 ++++++++++++++++++++-
 25-akpm/fs/hfsplus/super.c      |   31 ++++++++++++++++------
 25-akpm/fs/hfsplus/unicode.c    |   55 ++++++++++++++++------------------------
 7 files changed, 102 insertions(+), 69 deletions(-)

diff -puN fs/hfsplus/catalog.c~hfs-add-nls-support fs/hfsplus/catalog.c
--- 25/fs/hfsplus/catalog.c~hfs-add-nls-support	2005-03-18 23:43:06.000000000 -0800
+++ 25-akpm/fs/hfsplus/catalog.c	2005-03-18 23:43:06.000000000 -0800
@@ -25,14 +25,14 @@ int hfsplus_cat_cmp_key(hfsplus_btree_ke
 	return hfsplus_unistrcmp(&k1->cat.name, &k2->cat.name);
 }
 
-void hfsplus_cat_build_key(hfsplus_btree_key *key, u32 parent,
-			  struct qstr *str)
+void hfsplus_cat_build_key(struct super_block *sb, hfsplus_btree_key *key,
+			   u32 parent, struct qstr *str)
 {
 	int len;
 
 	key->cat.parent = cpu_to_be32(parent);
 	if (str) {
-		hfsplus_asc2uni(&key->cat.name, str->name, str->len);
+		hfsplus_asc2uni(sb, &key->cat.name, str->name, str->len);
 		len = be16_to_cpu(key->cat.name.length);
 	} else {
 		key->cat.name.length = 0;
@@ -113,13 +113,14 @@ static int hfsplus_cat_build_record(hfsp
 	}
 }
 
-static int hfsplus_fill_cat_thread(hfsplus_cat_entry *entry, int type,
+static int hfsplus_fill_cat_thread(struct super_block *sb,
+				   hfsplus_cat_entry *entry, int type,
 				   u32 parentid, struct qstr *str)
 {
 	entry->type = cpu_to_be16(type);
 	entry->thread.reserved = 0;
 	entry->thread.parentID = cpu_to_be32(parentid);
-	hfsplus_asc2uni(&entry->thread.nodeName, str->name, str->len);
+	hfsplus_asc2uni(sb, &entry->thread.nodeName, str->name, str->len);
 	return 10 + be16_to_cpu(entry->thread.nodeName.length) * 2;
 }
 
@@ -131,7 +132,7 @@ int hfsplus_find_cat(struct super_block 
 	int err;
 	u16 type;
 
-	hfsplus_cat_build_key(fd->search_key, cnid, NULL);
+	hfsplus_cat_build_key(sb, fd->search_key, cnid, NULL);
 	err = hfs_brec_read(fd, &tmp, sizeof(hfsplus_cat_entry));
 	if (err)
 		return err;
@@ -159,8 +160,8 @@ int hfsplus_create_cat(u32 cnid, struct 
 	sb = dir->i_sb;
 	hfs_find_init(HFSPLUS_SB(sb).cat_tree, &fd);
 
-	hfsplus_cat_build_key(fd.search_key, cnid, NULL);
-	entry_size = hfsplus_fill_cat_thread(&entry, S_ISDIR(inode->i_mode) ?
+	hfsplus_cat_build_key(sb, fd.search_key, cnid, NULL);
+	entry_size = hfsplus_fill_cat_thread(sb, &entry, S_ISDIR(inode->i_mode) ?
 			HFSPLUS_FOLDER_THREAD : HFSPLUS_FILE_THREAD,
 			dir->i_ino, str);
 	err = hfs_brec_find(&fd);
@@ -173,7 +174,7 @@ int hfsplus_create_cat(u32 cnid, struct 
 	if (err)
 		goto err2;
 
-	hfsplus_cat_build_key(fd.search_key, dir->i_ino, str);
+	hfsplus_cat_build_key(sb, fd.search_key, dir->i_ino, str);
 	entry_size = hfsplus_cat_build_record(&entry, cnid, inode);
 	err = hfs_brec_find(&fd);
 	if (err != -ENOENT) {
@@ -193,7 +194,7 @@ int hfsplus_create_cat(u32 cnid, struct 
 	return 0;
 
 err1:
-	hfsplus_cat_build_key(fd.search_key, cnid, NULL);
+	hfsplus_cat_build_key(sb, fd.search_key, cnid, NULL);
 	if (!hfs_brec_find(&fd))
 		hfs_brec_remove(&fd);
 err2:
@@ -217,7 +218,7 @@ int hfsplus_delete_cat(u32 cnid, struct 
 	if (!str) {
 		int len;
 
-		hfsplus_cat_build_key(fd.search_key, cnid, NULL);
+		hfsplus_cat_build_key(sb, fd.search_key, cnid, NULL);
 		err = hfs_brec_find(&fd);
 		if (err)
 			goto out;
@@ -229,7 +230,7 @@ int hfsplus_delete_cat(u32 cnid, struct 
 		hfs_bnode_read(fd.bnode, &fd.search_key->cat.name.unicode, off + 2, len);
 		fd.search_key->key_len = cpu_to_be16(6 + len);
 	} else
-		hfsplus_cat_build_key(fd.search_key, dir->i_ino, str);
+		hfsplus_cat_build_key(sb, fd.search_key, dir->i_ino, str);
 
 	err = hfs_brec_find(&fd);
 	if (err)
@@ -259,7 +260,7 @@ int hfsplus_delete_cat(u32 cnid, struct 
 	if (err)
 		goto out;
 
-	hfsplus_cat_build_key(fd.search_key, cnid, NULL);
+	hfsplus_cat_build_key(sb, fd.search_key, cnid, NULL);
 	err = hfs_brec_find(&fd);
 	if (err)
 		goto out;
@@ -294,7 +295,7 @@ int hfsplus_rename_cat(u32 cnid,
 	dst_fd = src_fd;
 
 	/* find the old dir entry and read the data */
-	hfsplus_cat_build_key(src_fd.search_key, src_dir->i_ino, src_name);
+	hfsplus_cat_build_key(sb, src_fd.search_key, src_dir->i_ino, src_name);
 	err = hfs_brec_find(&src_fd);
 	if (err)
 		goto out;
@@ -303,7 +304,7 @@ int hfsplus_rename_cat(u32 cnid,
 				src_fd.entrylength);
 
 	/* create new dir entry with the data from the old entry */
-	hfsplus_cat_build_key(dst_fd.search_key, dst_dir->i_ino, dst_name);
+	hfsplus_cat_build_key(sb, dst_fd.search_key, dst_dir->i_ino, dst_name);
 	err = hfs_brec_find(&dst_fd);
 	if (err != -ENOENT) {
 		if (!err)
@@ -319,7 +320,7 @@ int hfsplus_rename_cat(u32 cnid,
 	mark_inode_dirty(dst_dir);
 
 	/* finally remove the old entry */
-	hfsplus_cat_build_key(src_fd.search_key, src_dir->i_ino, src_name);
+	hfsplus_cat_build_key(sb, src_fd.search_key, src_dir->i_ino, src_name);
 	err = hfs_brec_find(&src_fd);
 	if (err)
 		goto out;
@@ -331,7 +332,7 @@ int hfsplus_rename_cat(u32 cnid,
 	mark_inode_dirty(src_dir);
 
 	/* remove old thread entry */
-	hfsplus_cat_build_key(src_fd.search_key, cnid, NULL);
+	hfsplus_cat_build_key(sb, src_fd.search_key, cnid, NULL);
 	err = hfs_brec_find(&src_fd);
 	if (err)
 		goto out;
@@ -341,8 +342,8 @@ int hfsplus_rename_cat(u32 cnid,
 		goto out;
 
 	/* create new thread entry */
-	hfsplus_cat_build_key(dst_fd.search_key, cnid, NULL);
-	entry_size = hfsplus_fill_cat_thread(&entry, type, dst_dir->i_ino, dst_name);
+	hfsplus_cat_build_key(sb, dst_fd.search_key, cnid, NULL);
+	entry_size = hfsplus_fill_cat_thread(sb, &entry, type, dst_dir->i_ino, dst_name);
 	err = hfs_brec_find(&dst_fd);
 	if (err != -ENOENT) {
 		if (!err)
diff -puN fs/hfsplus/dir.c~hfs-add-nls-support fs/hfsplus/dir.c
--- 25/fs/hfsplus/dir.c~hfs-add-nls-support	2005-03-18 23:43:06.000000000 -0800
+++ 25-akpm/fs/hfsplus/dir.c	2005-03-18 23:43:06.000000000 -0800
@@ -40,7 +40,7 @@ static struct dentry *hfsplus_lookup(str
 	sb = dir->i_sb;
 	dentry->d_fsdata = NULL;
 	hfs_find_init(HFSPLUS_SB(sb).cat_tree, &fd);
-	hfsplus_cat_build_key(fd.search_key, dir->i_ino, &dentry->d_name);
+	hfsplus_cat_build_key(sb, fd.search_key, dir->i_ino, &dentry->d_name);
 again:
 	err = hfs_brec_read(&fd, &entry, sizeof(entry));
 	if (err) {
@@ -80,7 +80,7 @@ again:
 			linkid = be32_to_cpu(entry.file.permissions.dev);
 			str.len = sprintf(name, "iNode%d", linkid);
 			str.name = name;
-			hfsplus_cat_build_key(fd.search_key, HFSPLUS_SB(sb).hidden_dir->i_ino, &str);
+			hfsplus_cat_build_key(sb, fd.search_key, HFSPLUS_SB(sb).hidden_dir->i_ino, &str);
 			goto again;
 		} else if (!dentry->d_fsdata)
 			dentry->d_fsdata = (void *)(unsigned long)cnid;
@@ -118,7 +118,7 @@ static int hfsplus_readdir(struct file *
 		return 0;
 
 	hfs_find_init(HFSPLUS_SB(sb).cat_tree, &fd);
-	hfsplus_cat_build_key(fd.search_key, inode->i_ino, NULL);
+	hfsplus_cat_build_key(sb, fd.search_key, inode->i_ino, NULL);
 	err = hfs_brec_find(&fd);
 	if (err)
 		goto out;
@@ -164,7 +164,7 @@ static int hfsplus_readdir(struct file *
 		hfs_bnode_read(fd.bnode, &entry, fd.entryoffset, fd.entrylength);
 		type = be16_to_cpu(entry.type);
 		len = HFSPLUS_MAX_STRLEN;
-		err = hfsplus_uni2asc(&fd.key->cat.name, strbuf, &len);
+		err = hfsplus_uni2asc(sb, &fd.key->cat.name, strbuf, &len);
 		if (err)
 			goto out;
 		if (type == HFSPLUS_FOLDER) {
diff -puN fs/hfsplus/hfsplus_fs.h~hfs-add-nls-support fs/hfsplus/hfsplus_fs.h
--- 25/fs/hfsplus/hfsplus_fs.h~hfs-add-nls-support	2005-03-18 23:43:06.000000000 -0800
+++ 25-akpm/fs/hfsplus/hfsplus_fs.h	2005-03-18 23:43:06.000000000 -0800
@@ -114,6 +114,7 @@ struct hfsplus_sb_info {
 	struct hfs_btree *attr_tree;
 	struct inode *alloc_file;
 	struct inode *hidden_dir;
+	struct nls_table *nls;
 
 	/* Runtime variables */
 	u32 blockoffset;
@@ -305,7 +306,7 @@ int hfs_brec_goto(struct hfs_find_data *
 
 /* catalog.c */
 int hfsplus_cat_cmp_key(hfsplus_btree_key *, hfsplus_btree_key *);
-void hfsplus_cat_build_key(hfsplus_btree_key *, u32, struct qstr *);
+void hfsplus_cat_build_key(struct super_block *sb, hfsplus_btree_key *, u32, struct qstr *);
 int hfsplus_find_cat(struct super_block *, u32, struct hfs_find_data *);
 int hfsplus_create_cat(u32, struct inode *, struct qstr *, struct inode *);
 int hfsplus_delete_cat(u32, struct inode *, struct qstr *);
@@ -349,8 +350,8 @@ extern u16 case_fold_table[];
 
 /* unicode.c */
 int hfsplus_unistrcmp(const struct hfsplus_unistr *, const struct hfsplus_unistr *);
-int hfsplus_uni2asc(const struct hfsplus_unistr *, char *, int *);
-int hfsplus_asc2uni(struct hfsplus_unistr *, const char *, int);
+int hfsplus_uni2asc(struct super_block *, const struct hfsplus_unistr *, char *, int *);
+int hfsplus_asc2uni(struct super_block *, struct hfsplus_unistr *, const char *, int);
 
 /* wrapper.c */
 int hfsplus_read_wrapper(struct super_block *);
diff -puN fs/hfsplus/options.c~hfs-add-nls-support fs/hfsplus/options.c
--- 25/fs/hfsplus/options.c~hfs-add-nls-support	2005-03-18 23:43:06.000000000 -0800
+++ 25-akpm/fs/hfsplus/options.c	2005-03-18 23:43:06.000000000 -0800
@@ -12,12 +12,13 @@
 #include <linux/kernel.h>
 #include <linux/sched.h>
 #include <linux/parser.h>
+#include <linux/nls.h>
 #include "hfsplus_fs.h"
 
 enum {
 	opt_creator, opt_type,
 	opt_umask, opt_uid, opt_gid,
-	opt_part, opt_session,
+	opt_part, opt_session, opt_nls,
 	opt_err
 };
 
@@ -29,6 +30,7 @@ static match_table_t tokens = {
 	{ opt_gid, "gid=%u" },
 	{ opt_part, "part=%u" },
 	{ opt_session, "session=%u" },
+	{ opt_nls, "nls=%s" },
 	{ opt_err, NULL }
 };
 
@@ -65,7 +67,7 @@ int parse_options(char *input, struct hf
 	int tmp, token;
 
 	if (!input)
-		return 1;
+		goto done;
 
 	while ((p = strsep(&input, ",")) != NULL) {
 		if (!*p)
@@ -118,10 +120,34 @@ int parse_options(char *input, struct hf
 				return 0;
 			}
 			break;
+		case opt_nls:
+			if (sbi->nls) {
+				printk("HFS+-fs: unable to change nls mapping\n");
+				return 0;
+			}
+			p = match_strdup(&args[0]);
+			sbi->nls = load_nls(p);
+			if (!sbi->nls) {
+				printk("HFS+-fs: unable to load nls mapping \"%s\"\n", p);
+				kfree(p);
+				return 0;
+			}
+			kfree(p);
+			break;
 		default:
 			return 0;
 		}
 	}
 
+done:
+	if (!sbi->nls) {
+		/* try utf8 first, as this is the old default behaviour */
+		sbi->nls = load_nls("utf8");
+		if (!sbi->nls)
+			sbi->nls = load_nls_default();
+		if (!sbi->nls)
+			return 0;
+	}
+
 	return 1;
 }
diff -puN fs/hfsplus/super.c~hfs-add-nls-support fs/hfsplus/super.c
--- 25/fs/hfsplus/super.c~hfs-add-nls-support	2005-03-18 23:43:06.000000000 -0800
+++ 25-akpm/fs/hfsplus/super.c	2005-03-18 23:43:06.000000000 -0800
@@ -16,6 +16,7 @@
 #include <linux/slab.h>
 #include <linux/version.h>
 #include <linux/vfs.h>
+#include <linux/nls.h>
 
 static struct inode *hfsplus_alloc_inode(struct super_block *sb);
 static void hfsplus_destroy_inode(struct inode *inode);
@@ -223,6 +224,8 @@ static void hfsplus_put_super(struct sup
 	iput(HFSPLUS_SB(sb).alloc_file);
 	iput(HFSPLUS_SB(sb).hidden_dir);
 	brelse(HFSPLUS_SB(sb).s_vhbh);
+	if (HFSPLUS_SB(sb).nls)
+		unload_nls(HFSPLUS_SB(sb).nls);
 }
 
 static int hfsplus_statfs(struct super_block *sb, struct kstatfs *buf)
@@ -280,13 +283,13 @@ static int hfsplus_fill_super(struct sup
 	struct hfs_find_data fd;
 	struct inode *root;
 	struct qstr str;
+	struct nls_table *nls = NULL;
 	int err = -EINVAL;
 
 	sbi = kmalloc(sizeof(struct hfsplus_sb_info), GFP_KERNEL);
-	if (!sbi) {
-		err = -ENOMEM;
-		goto out2;
-	}
+	if (!sbi)
+		return -ENOMEM;
+
 	memset(sbi, 0, sizeof(HFSPLUS_SB(sb)));
 	sb->s_fs_info = sbi;
 	INIT_HLIST_HEAD(&sbi->rsrc_inodes);
@@ -295,7 +298,16 @@ static int hfsplus_fill_super(struct sup
 		if (!silent)
 			printk("HFS+-fs: unable to parse mount options\n");
 		err = -EINVAL;
-		goto out2;
+		goto cleanup;
+	}
+
+	/* temporarily use utf8 to correctly find the hidden dir below */
+	nls = sbi->nls;
+	sbi->nls = load_nls("utf8");
+	if (!nls) {
+		printk("HFS+: unable to load nls for utf8\n");
+		err = -EINVAL;
+		goto cleanup;
 	}
 
 	/* Grab the volume header */
@@ -303,7 +315,7 @@ static int hfsplus_fill_super(struct sup
 		if (!silent)
 			printk("HFS+-fs: unable to find HFS+ superblock\n");
 		err = -EINVAL;
-		goto out2;
+		goto cleanup;
 	}
 	vhdr = HFSPLUS_SB(sb).s_vhdr;
 
@@ -376,7 +388,7 @@ static int hfsplus_fill_super(struct sup
 	str.len = sizeof(HFSP_HIDDENDIR_NAME) - 1;
 	str.name = HFSP_HIDDENDIR_NAME;
 	hfs_find_init(HFSPLUS_SB(sb).cat_tree, &fd);
-	hfsplus_cat_build_key(fd.search_key, HFSPLUS_ROOT_CNID, &str);
+	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))
@@ -410,11 +422,14 @@ static int hfsplus_fill_super(struct sup
 		mark_inode_dirty(HFSPLUS_SB(sb).hidden_dir);
 	}
 out:
+	unload_nls(sbi->nls);
+	sbi->nls = nls;
 	return 0;
 
 cleanup:
 	hfsplus_put_super(sb);
-out2:
+	if (nls)
+		unload_nls(nls);
 	return err;
 }
 
diff -puN fs/hfsplus/unicode.c~hfs-add-nls-support fs/hfsplus/unicode.c
--- 25/fs/hfsplus/unicode.c~hfs-add-nls-support	2005-03-18 23:43:06.000000000 -0800
+++ 25-akpm/fs/hfsplus/unicode.c	2005-03-18 23:43:06.000000000 -0800
@@ -59,19 +59,20 @@ int hfsplus_unistrcmp(const struct hfspl
 	}
 }
 
-int hfsplus_uni2asc(const struct hfsplus_unistr *ustr, char *astr, int *len)
+int hfsplus_uni2asc(struct super_block *sb, const struct hfsplus_unistr *ustr, char *astr, int *len_p)
 {
 	const hfsplus_unichr *ip;
+	struct nls_table *nls = HFSPLUS_SB(sb).nls;
 	u8 *op;
 	u16 ustrlen, cc;
-	int size, tmp;
+	int size, len;
 
 	op = astr;
 	ip = ustr->unicode;
 	ustrlen = be16_to_cpu(ustr->length);
-	tmp = *len;
-	while (ustrlen > 0 && tmp > 0) {
-		cc = be16_to_cpu(*ip);
+	len = *len_p;
+	while (ustrlen > 0 && len > 0) {
+		cc = be16_to_cpu(*ip++);
 		switch (cc) {
 		case 0:
 			cc = 0x2400;
@@ -80,48 +81,36 @@ int hfsplus_uni2asc(const struct hfsplus
 			cc = ':';
 			break;
 		}
-		if (cc > 0x7f) {
-			size = utf8_wctomb(op, cc, tmp);
-			if (size == -1) {
-				/* ignore */
-			} else {
-				op += size;
-				tmp -= size;
-			}
-		} else {
-			*op++ = (u8) cc;
-			tmp--;
+		size = nls->uni2char(cc, op, len);
+		if (size <= 0) {
+			*op = '?';
+			size = 1;
 		}
-		ip++;
+		op += size;
+		len -= size;
 		ustrlen--;
 	}
-	*len = (char *)op - astr;
+	*len_p = (char *)op - astr;
 	if (ustrlen)
 		return -ENAMETOOLONG;
 	return 0;
 }
 
-int hfsplus_asc2uni(struct hfsplus_unistr *ustr, const char *astr, int len)
+int hfsplus_asc2uni(struct super_block *sb, struct hfsplus_unistr *ustr, const char *astr, int len)
 {
-	int tmp;
+	struct nls_table *nls = HFSPLUS_SB(sb).nls;
+	int size;
 	wchar_t c;
 	u16 outlen = 0;
 
 	while (outlen <= HFSPLUS_MAX_STRLEN && len > 0) {
-		if (*astr & 0x80) {
-			tmp = utf8_mbtowc(&c, astr, len);
-			if (tmp < 0) {
-				astr++;
-				len--;
-				continue;
-			} else {
-				astr += tmp;
-				len -= tmp;
-			}
-		} else {
-			c = *astr++;
-			len--;
+		size = nls->char2uni(astr, len, &c);
+		if (size <= 0) {
+			c = '?';
+			size = 1;
 		}
+		astr += size;
+		len -= size;
 		switch (c) {
 		case 0x2400:
 			c = 0;
diff -puN fs/Kconfig~hfs-add-nls-support fs/Kconfig
--- 25/fs/Kconfig~hfs-add-nls-support	2005-03-18 23:43:06.000000000 -0800
+++ 25-akpm/fs/Kconfig	2005-03-18 23:43:06.000000000 -0800
@@ -948,6 +948,7 @@ config HFS_FS
 config HFSPLUS_FS
 	tristate "Apple Extended HFS file system support"
 	select NLS
+	select NLS_UTF8
 	help
 	  If you say Y here, you will be able to mount extended format
 	  Macintosh-formatted hard drive partitions with full read-write access.
_