[PATCH] Add AFFS NLS support

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

 



AFFS almost always uses latin1. However usual iocharset by now is UTF-8. So
NLS support is required.

Signed-off-by: Vladimir Serbinenko <phcoder@xxxxxxxxx>

diff --git a/fs/affs/Kconfig b/fs/affs/Kconfig
index cfad9af..93ab9d4b 100644
--- a/fs/affs/Kconfig
+++ b/fs/affs/Kconfig
@@ -19,3 +19,11 @@ config AFFS_FS
 
 	  To compile this file system support as a module, choose M here: the
 	  module will be called affs.  If unsure, say N.
+
+config AFFS_DEFAULT_CODEPAGE
+	string "Default codepage for AFFS"
+	depends on AFFS_FS
+	default "iso8859-1"
+	help
+	  This option should be set to the codepage of your AFFS filesystems.
+	  It can be overridden with the "codepage" mount option.
diff --git a/fs/affs/affs.h b/fs/affs/affs.h
index fc1d4ca..7bf696d 100644
--- a/fs/affs/affs.h
+++ b/fs/affs/affs.h
@@ -12,6 +12,8 @@
  */
 /*#define AFFS_NO_TRUNCATE */
 
+#define AFFS_MAXNAME 30
+
 /* Ugly macros make the code more pretty. */
 
 #define GET_END_PTR(st,p,sz)		 ((st *)((char *)(p)+((sz)-sizeof(st))))
@@ -107,6 +109,10 @@ struct affs_sb_info {
 	u32 s_bmap_bits;		/* # of bits in one bitmap blocks */
 	u32 s_last_bmap;
 	struct buffer_head *s_bmap_bh;
+	char *iocharset;
+	char *codepage;
+	struct nls_table *nls_io;
+	struct nls_table *nls_disk;
 	char *s_prefix;			/* Prefix for volumes and assigns. */
 	char s_volume[32];		/* Volume prefix for absolute symlinks. */
 	spinlock_t symlink_lock;	/* protects the previous two */
@@ -142,8 +148,9 @@ extern umode_t	prot_to_mode(u32 prot);
 extern void	mode_to_prot(struct inode *inode);
 extern void	affs_error(struct super_block *sb, const char *function, const char *fmt, ...);
 extern void	affs_warning(struct super_block *sb, const char *function, const char *fmt, ...);
-extern int	affs_check_name(const unsigned char *name, int len);
-extern int	affs_copy_name(unsigned char *bstr, struct dentry *dentry);
+extern int	affs_check_name(const unsigned char *name, unsigned int len);
+extern int	affs_copy_name(struct super_block *sb, unsigned char *bstr,
+			       struct dentry *dentry);
 
 /* bitmap. c */
 
@@ -165,8 +172,13 @@ extern int	affs_link(struct dentry *olddentry, struct inode *dir,
 			  struct dentry *dentry);
 extern int	affs_symlink(struct inode *dir, struct dentry *dentry,
 			     const char *symname);
+extern int      affs_read_symlink(struct inode *inode,
+				  char *link);
 extern int	affs_rename(struct inode *old_dir, struct dentry *old_dentry,
 			    struct inode *new_dir, struct dentry *new_dentry);
+extern size_t   affs_translate(u8 *to, const u8 *from, struct nls_table *nls_to,
+			       struct nls_table *nls_from, size_t limit,
+			       size_t from_len);
 
 /* inode.c */
 
diff --git a/fs/affs/amigaffs.c b/fs/affs/amigaffs.c
index 52a6407..08f39ee 100644
--- a/fs/affs/amigaffs.c
+++ b/fs/affs/amigaffs.c
@@ -477,15 +477,15 @@ affs_warning(struct super_block *sb, const char *function, const char *fmt, ...)
 /* Check if the name is valid for a affs object. */
 
 int
-affs_check_name(const unsigned char *name, int len)
+affs_check_name(const unsigned char *name, unsigned int len)
 {
 	int	 i;
 
-	if (len > 30)
+	if (len > AFFS_MAXNAME)
 #ifdef AFFS_NO_TRUNCATE
 		return -ENAMETOOLONG;
 #else
-		len = 30;
+		len = AFFS_MAXNAME;
 #endif
 
 	for (i = 0; i < len; i++) {
@@ -497,19 +497,3 @@ affs_check_name(const unsigned char *name, int len)
 	return 0;
 }
 
-/* This function copies name to bstr, with at most 30
- * characters length. The bstr will be prepended by
- * a length byte.
- * NOTE: The name will must be already checked by
- *       affs_check_name()!
- */
-
-int
-affs_copy_name(unsigned char *bstr, struct dentry *dentry)
-{
-	int len = min(dentry->d_name.len, 30u);
-
-	*bstr++ = len;
-	memcpy(bstr, dentry->d_name.name, len);
-	return len;
-}
diff --git a/fs/affs/dir.c b/fs/affs/dir.c
index 8ca8f3a..81da2ec 100644
--- a/fs/affs/dir.c
+++ b/fs/affs/dir.c
@@ -55,6 +55,8 @@ affs_readdir(struct file *filp, void *dirent, filldir_t filldir)
 	u32			 ino;
 	int			 stored;
 	int			 res;
+	struct nls_table *nls_io = AFFS_SB(sb)->nls_io;
+	struct nls_table *nls_disk = AFFS_SB(sb)->nls_disk;
 
 	pr_debug("AFFS: readdir(ino=%lu,f_pos=%lx)\n",inode->i_ino,(unsigned long)filp->f_pos);
 
@@ -122,17 +124,31 @@ affs_readdir(struct file *filp, void *dirent, filldir_t filldir)
 		f_pos = (hash_pos << 16) + 2;
 inside:
 		do {
+			/* AFFS names are at most 30 characters and are all in
+			   BMP. So at most 90 characters resulting translation
+			   assuming UTF-8 as iocharset. Then comes paranoia.
+			   In worst-case scenario filenames get truncated but
+			   no overflow occurs.
+			*/
+			char buf[200];
+			size_t translatedlen;
 			fh_bh = affs_bread(sb, ino);
 			if (!fh_bh) {
 				affs_error(sb, "readdir","Cannot read block %d", ino);
 				goto readdir_done;
 			}
 
-			namelen = min(AFFS_TAIL(sb, fh_bh)->name[0], (u8)30);
+			namelen = min(((u8 *)AFFS_TAIL(sb, fh_bh)->name)[0],
+				      (u8)AFFS_MAXNAME);
 			name = AFFS_TAIL(sb, fh_bh)->name + 1;
+			translatedlen = affs_translate(buf, name,
+						       nls_io, nls_disk,
+						       sizeof(buf),
+						       namelen);
 			pr_debug("AFFS: readdir(): filldir(\"%.*s\", ino=%u), hash=%d, f_pos=%x\n",
 				 namelen, name, ino, hash_pos, f_pos);
-			if (filldir(dirent, name, namelen, f_pos, ino, DT_UNKNOWN) < 0)
+			if (filldir(dirent, buf, translatedlen, f_pos, ino,
+				    DT_UNKNOWN) < 0)
 				goto readdir_done;
 			stored++;
 			f_pos++;
diff --git a/fs/affs/inode.c b/fs/affs/inode.c
index 88a4b0b..2a88f7a 100644
--- a/fs/affs/inode.c
+++ b/fs/affs/inode.c
@@ -143,11 +143,17 @@ struct inode *affs_iget(struct super_block *sb, unsigned long ino)
 		inode->i_fop = &affs_file_operations;
 		break;
 	case ST_SOFTLINK:
+	{
+		int s;
 		inode->i_mode |= S_IFLNK;
 		inode->i_op = &affs_symlink_inode_operations;
 		inode->i_data.a_ops = &affs_symlink_aops;
+		s = affs_read_symlink(inode, 0);
+
+		inode->i_size = s >= 0 ? s : 0;
 		break;
 	}
+	}
 
 	inode->i_mtime.tv_sec = inode->i_atime.tv_sec = inode->i_ctime.tv_sec
 		       = (be32_to_cpu(tail->change.days) * (24 * 60 * 60) +
@@ -375,7 +381,7 @@ affs_add_entry(struct inode *dir, struct inode *inode, struct dentry *dentry, s3
 
 	AFFS_HEAD(bh)->ptype = cpu_to_be32(T_SHORT);
 	AFFS_HEAD(bh)->key = cpu_to_be32(bh->b_blocknr);
-	affs_copy_name(AFFS_TAIL(sb, bh)->name, dentry);
+	affs_copy_name(sb, AFFS_TAIL(sb, bh)->name, dentry);
 	AFFS_TAIL(sb, bh)->stype = cpu_to_be32(type);
 	AFFS_TAIL(sb, bh)->parent = cpu_to_be32(dir->i_ino);
 
diff --git a/fs/affs/namei.c b/fs/affs/namei.c
index 4780694..600fa3e 100644
--- a/fs/affs/namei.c
+++ b/fs/affs/namei.c
@@ -9,6 +9,7 @@
  */
 
 #include "affs.h"
+#include <linux/nls.h>
 
 typedef int (*toupper_t)(int);
 
@@ -66,18 +67,24 @@ affs_get_toupper(struct super_block *sb)
  * Note: the dentry argument is the parent dentry.
  */
 static inline int
-__affs_hash_dentry(struct qstr *qstr, toupper_t toupper)
+__affs_hash_dentry(struct super_block *sb, struct qstr *qstr, toupper_t toupper)
 {
-	const u8 *name = qstr->name;
+	const u8 *name;
 	unsigned long hash;
 	int i;
+	u8 tmp[AFFS_MAXNAME + 1];
+	size_t len;
 
-	i = affs_check_name(qstr->name, qstr->len);
+	len = affs_translate(tmp, qstr->name, AFFS_SB(sb)->nls_disk,
+			     AFFS_SB(sb)->nls_io, sizeof(tmp), qstr->len);
+
+	i = affs_check_name(tmp, len);
 	if (i)
 		return i;
 
 	hash = init_name_hash();
-	i = min(qstr->len, 30u);
+	i = len;
+	name = tmp;
 	for (; i > 0; name++, i--)
 		hash = partial_name_hash(toupper(*name), hash);
 	qstr->hash = end_name_hash(hash);
@@ -89,41 +96,49 @@ static int
 affs_hash_dentry(const struct dentry *dentry, const struct inode *inode,
 		struct qstr *qstr)
 {
-	return __affs_hash_dentry(qstr, affs_toupper);
+	struct super_block *sb = inode->i_sb;
+	return __affs_hash_dentry(sb, qstr, affs_toupper);
 }
 static int
 affs_intl_hash_dentry(const struct dentry *dentry, const struct inode *inode,
 		struct qstr *qstr)
 {
-	return __affs_hash_dentry(qstr, affs_intl_toupper);
+	struct super_block *sb = inode->i_sb;
+	return __affs_hash_dentry(sb, qstr, affs_intl_toupper);
 }
 
-static inline int __affs_compare_dentry(unsigned int len,
-		const char *str, const struct qstr *name, toupper_t toupper)
+static inline int __affs_compare_dentry(struct super_block *sb,
+					unsigned int len,
+					const char *str,
+					const struct qstr *name,
+					toupper_t toupper)
 {
-	const u8 *aname = str;
-	const u8 *bname = name->name;
+	u8 atmp[AFFS_MAXNAME + 1], btmp[AFFS_MAXNAME + 1];
+	size_t alen, blen;
+	const u8 *aname = atmp;
+	const u8 *bname = btmp;
+
+	alen = affs_translate(atmp, str, AFFS_SB(sb)->nls_disk,
+			      AFFS_SB(sb)->nls_io, sizeof(atmp), len);
+	blen = affs_translate(btmp, name->name, AFFS_SB(sb)->nls_disk,
+			      AFFS_SB(sb)->nls_io, sizeof(btmp), name->len);
 
 	/*
 	 * 'str' is the name of an already existing dentry, so the name
 	 * must be valid. 'name' must be validated first.
 	 */
 
-	if (affs_check_name(name->name, name->len))
+	if (affs_check_name(bname, blen))
 		return 1;
 
 	/*
 	 * If the names are longer than the allowed 30 chars,
 	 * the excess is ignored, so their length may differ.
 	 */
-	if (len >= 30) {
-		if (name->len < 30)
-			return 1;
-		len = 30;
-	} else if (len != name->len)
+	if (alen != blen)
 		return 1;
 
-	for (; len > 0; len--)
+	for (; alen > 0; alen--)
 		if (toupper(*aname++) != toupper(*bname++))
 			return 1;
 
@@ -135,14 +150,16 @@ affs_compare_dentry(const struct dentry *parent, const struct inode *pinode,
 		const struct dentry *dentry, const struct inode *inode,
 		unsigned int len, const char *str, const struct qstr *name)
 {
-	return __affs_compare_dentry(len, str, name, affs_toupper);
+	struct super_block *sb = pinode->i_sb;
+	return __affs_compare_dentry(sb, len, str, name, affs_toupper);
 }
 static int
 affs_intl_compare_dentry(const struct dentry *parent,const struct inode *pinode,
 		const struct dentry *dentry, const struct inode *inode,
 		unsigned int len, const char *str, const struct qstr *name)
 {
-	return __affs_compare_dentry(len, str, name, affs_intl_toupper);
+	struct super_block *sb = pinode->i_sb;
+	return __affs_compare_dentry(sb, len, str, name, affs_intl_toupper);
 }
 
 /*
@@ -150,15 +167,12 @@ affs_intl_compare_dentry(const struct dentry *parent,const struct inode *pinode,
  */
 
 static inline int
-affs_match(struct dentry *dentry, const u8 *name2, toupper_t toupper)
+affs_match(const u8 *name, size_t len, const u8 *name2, toupper_t toupper)
 {
-	const u8 *name = dentry->d_name.name;
-	int len = dentry->d_name.len;
-
-	if (len >= 30) {
-		if (*name2 < 30)
+	if (len >= AFFS_MAXNAME) {
+		if (*name2 < AFFS_MAXNAME)
 			return 0;
-		len = 30;
+		len = AFFS_MAXNAME;
 	} else if (len != *name2)
 		return 0;
 
@@ -172,9 +186,12 @@ int
 affs_hash_name(struct super_block *sb, const u8 *name, unsigned int len)
 {
 	toupper_t toupper = affs_get_toupper(sb);
-	int hash;
+	uint32_t hash;
+
+	if (len > AFFS_MAXNAME)
+		len = AFFS_MAXNAME;
 
-	hash = len = min(len, 30u);
+	hash = len;
 	for (; len > 0; len--)
 		hash = (hash * 13 + toupper(*name++)) & 0x7ff;
 
@@ -188,6 +205,12 @@ affs_find_entry(struct inode *dir, struct dentry *dentry)
 	struct buffer_head *bh;
 	toupper_t toupper = affs_get_toupper(sb);
 	u32 key;
+	u8 name[AFFS_MAXNAME + 1];
+	size_t namelen;
+
+	namelen = affs_translate(name, dentry->d_name.name,
+				 AFFS_SB(sb)->nls_disk, AFFS_SB(sb)->nls_io,
+				 sizeof(name), dentry->d_name.len);
 
 	pr_debug("AFFS: find_entry(\"%.*s\")\n", (int)dentry->d_name.len, dentry->d_name.name);
 
@@ -195,7 +218,8 @@ affs_find_entry(struct inode *dir, struct dentry *dentry)
 	if (!bh)
 		return ERR_PTR(-EIO);
 
-	key = be32_to_cpu(AFFS_HEAD(bh)->table[affs_hash_name(sb, dentry->d_name.name, dentry->d_name.len)]);
+	key = be32_to_cpu(AFFS_HEAD(bh)->table[affs_hash_name(sb,
+							      name, namelen)]);
 
 	for (;;) {
 		affs_brelse(bh);
@@ -204,7 +228,7 @@ affs_find_entry(struct inode *dir, struct dentry *dentry)
 		bh = affs_bread(sb, key);
 		if (!bh)
 			return ERR_PTR(-EIO);
-		if (affs_match(dentry, AFFS_TAIL(sb, bh)->name, toupper))
+		if (affs_match(name, namelen, AFFS_TAIL(sb, bh)->name, toupper))
 			return bh;
 		key = be32_to_cpu(AFFS_TAIL(sb, bh)->hash_chain);
 	}
@@ -332,9 +356,14 @@ affs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
 	char			*p;
 	int			 i, maxlen, error;
 	char			 c, lc;
+	char                     convbuf[10];
+	size_t                   convbufpos = 0, convbuflen = 0;
+	struct nls_table        *nls_io = AFFS_SB(sb)->nls_io;
+	struct nls_table        *nls_disk = AFFS_SB(sb)->nls_disk;
+	const char              *symnameend = symname + strlen(symname);
 
-	pr_debug("AFFS: symlink(%lu,\"%.*s\" -> \"%s\")\n",dir->i_ino,
-		 (int)dentry->d_name.len,dentry->d_name.name,symname);
+	pr_debug("AFFS: symlink(%lu,\"%.*s\" -> \"%s\")\n", dir->i_ino,
+		 (int)dentry->d_name.len, dentry->d_name.name, symname);
 
 	maxlen = AFFS_SB(sb)->s_hashsize * sizeof(u32) - 1;
 	inode  = affs_new_inode(dir);
@@ -362,7 +391,44 @@ affs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
 			*p++ = sbi->s_volume[i++];
 		spin_unlock(&sbi->symlink_lock);
 	}
-	while (i < maxlen && (c = *symname++)) {
+
+	while (i < maxlen) {
+		if (convbuflen == convbufpos) {
+			convbufpos = convbuflen = 0;
+			if (*symname == 0)
+				break;
+			if (nls_disk && nls_io) {
+				ssize_t len;
+				wchar_t uni;
+				len = nls_io->char2uni(symname,
+						       symnameend - symname,
+						       &uni);
+				if (len <= 0) {
+					convbufpos = 0;
+					convbuflen = 1;
+					convbuf[0] = '?';
+					symname++;
+				} else {
+					symname += len;
+					len = nls_disk->uni2char(uni, convbuf,
+								 sizeof(convbuf)
+								 );
+					if (len > 0) {
+						convbuflen = len;
+					} else {
+						convbufpos = 0;
+						convbuflen = 1;
+						convbuf[0] = '?';
+					}
+				}
+			} else {
+				convbufpos = 0;
+				convbuflen = 1;
+				convbuf[0] = *symname++;
+			}
+		}
+		c = convbuf[convbufpos++];
+
 		if (c == '.' && lc == '/' && *symname == '.' && symname[1] == '/') {
 			*p++ = '/';
 			i++;
@@ -381,6 +447,9 @@ affs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
 				symname++;
 	}
 	*p = 0;
+
+	inode->i_size = affs_read_symlink(inode, 0);
+
 	mark_buffer_dirty_inode(bh, inode);
 	affs_brelse(bh);
 	mark_inode_dirty(inode);
@@ -444,7 +513,7 @@ affs_rename(struct inode *old_dir, struct dentry *old_dentry,
 		goto done;
 
 	/* And insert it into the new directory with the new name. */
-	affs_copy_name(AFFS_TAIL(sb, bh)->name, new_dentry);
+	affs_copy_name(sb, AFFS_TAIL(sb, bh)->name, new_dentry);
 	affs_fix_checksum(sb, bh);
 	affs_lock_dir(new_dir);
 	retval = affs_insert_hash(new_dir, bh);
@@ -456,3 +525,71 @@ done:
 	affs_brelse(bh);
 	return retval;
 }
+
+static size_t affs_translate_real(u8 *to, const u8 *from,
+				  struct nls_table *nls_to,
+				  struct nls_table *nls_from,
+				  size_t limit, size_t from_len)
+{
+	wchar_t uni;
+	size_t i;
+	ssize_t len;
+	size_t to_len = limit;
+	u8 *to0 = to;
+
+	if (nls_to) {
+		for (i = 0; i < from_len && to_len > 0 && from[i]; ) {
+			len = nls_from->char2uni(&from[i], from_len-i, &uni);
+			if (len > 0) {
+				i += len;
+				len = nls_to->uni2char(uni, to, to_len);
+				if (len > 0) {
+					to += len;
+					to_len -= len;
+				}
+			} else
+				i++;
+			if (len <= 0) {
+				*to++ = '?';
+				to_len--;
+			}
+		}
+		return to - to0;
+	} else {
+		size_t len;
+		len = from_len;
+		if (len > limit)
+			len = limit;
+		memcpy(to, from, len);
+		return len;
+	}
+}
+
+size_t affs_translate(u8 *to, const u8 *from, struct nls_table *nls_to,
+		      struct nls_table *nls_from, size_t limit, size_t from_len)
+{
+	size_t r;
+	r = affs_translate_real(to, from, nls_to, nls_from,
+				limit - 1, from_len);
+	to[r] = 0;
+	return r;
+}
+/* This function copies name to bstr, with at most AFFS_MAXNAME
+ * characters length. The bstr will be prepended by
+ * a length byte.
+ * NOTE: The name will must be already checked by
+ *       affs_check_name()!
+ */
+
+int
+affs_copy_name(struct super_block *sb, unsigned char *bstr,
+	       struct dentry *dentry)
+{
+	size_t len;
+	len = affs_translate_real(bstr + 1, dentry->d_name.name,
+				  AFFS_SB(sb)->nls_disk,
+				  AFFS_SB(sb)->nls_io, AFFS_MAXNAME,
+				  dentry->d_name.len);
+	*bstr = len;
+	return len;
+}
diff --git a/fs/affs/super.c b/fs/affs/super.c
index 1df3c95..27f9e98 100644
--- a/fs/affs/super.c
+++ b/fs/affs/super.c
@@ -17,10 +17,18 @@
 #include <linux/magic.h>
 #include <linux/sched.h>
 #include <linux/slab.h>
+#include <linux/nls.h>
 #include "affs.h"
 
 extern struct timezone sys_tz;
 
+#ifdef CONFIG_AFFS_DEFAULT_CODEPAGE
+static char affs_default_codepage[] = CONFIG_AFFS_DEFAULT_CODEPAGE;
+#else
+static char affs_default_codepage[] = "iso8859-1";
+#endif
+static char affs_default_iocharset[] = CONFIG_NLS_DEFAULT;
+
 static int affs_statfs(struct dentry *dentry, struct kstatfs *buf);
 static int affs_remount (struct super_block *sb, int *flags, char *data);
 
@@ -48,6 +56,15 @@ affs_put_super(struct super_block *sb)
 	if (!(sb->s_flags & MS_RDONLY) && sb->s_dirt)
 		affs_commit_super(sb, 1, 1);
 
+	if (sbi->nls_disk)
+		unload_nls(sbi->nls_disk);
+	if (sbi->nls_io)
+		unload_nls(sbi->nls_io);
+	if (sbi->iocharset != affs_default_iocharset)
+		kfree(sbi->iocharset);
+	if (sbi->codepage != affs_default_codepage)
+		kfree(sbi->codepage);
+
 	kfree(sbi->s_prefix);
 	affs_free_bitmap(sb);
 	affs_brelse(sbi->s_root_bh);
@@ -149,7 +166,8 @@ static const struct super_operations affs_sops = {
 enum {
 	Opt_bs, Opt_mode, Opt_mufs, Opt_prefix, Opt_protect,
 	Opt_reserved, Opt_root, Opt_setgid, Opt_setuid,
-	Opt_verbose, Opt_volume, Opt_ignore, Opt_err,
+	Opt_verbose, Opt_volume, Opt_iocharset, Opt_codepage,
+	Opt_ignore, Opt_err,
 };
 
 static const match_table_t tokens = {
@@ -168,12 +186,16 @@ static const match_table_t tokens = {
 	{Opt_ignore, "noquota"},
 	{Opt_ignore, "quota"},
 	{Opt_ignore, "usrquota"},
+	{Opt_iocharset, "iocharset=%s"},
+	{Opt_codepage, "codepage=%s"},
 	{Opt_err, NULL},
 };
 
 static int
 parse_options(char *options, uid_t *uid, gid_t *gid, int *mode, int *reserved, s32 *root,
-		int *blocksize, char **prefix, char *volume, unsigned long *mount_opts)
+	      int *blocksize, char **prefix, char *volume,
+	      unsigned long *mount_opts,
+	      char **iocharset, char **codepage)
 {
 	char *p;
 	substring_t args[MAX_OPT_ARGS];
@@ -246,6 +268,24 @@ parse_options(char *options, uid_t *uid, gid_t *gid, int *mode, int *reserved, s
 			*uid = option;
 			*mount_opts |= SF_SETUID;
 			break;
+		case Opt_iocharset:
+			if (*iocharset != affs_default_iocharset) {
+				kfree(*iocharset);
+				*iocharset = NULL;
+			}
+			*iocharset = match_strdup(&args[0]);
+			if (!*iocharset)
+				return 0;
+			break;
+		case Opt_codepage:
+			if (*codepage != affs_default_codepage) {
+				kfree(*codepage);
+				*codepage = NULL;
+			}
+			*codepage = match_strdup(&args[0]);
+			if (!*codepage)
+				return 0;
+			break;
 		case Opt_verbose:
 			*mount_opts |= SF_VERBOSE;
 			break;
@@ -309,14 +349,53 @@ static int affs_fill_super(struct super_block *sb, void *data, int silent)
 	mutex_init(&sbi->s_bmlock);
 	spin_lock_init(&sbi->symlink_lock);
 
+	sbi->iocharset = affs_default_iocharset;
+	sbi->codepage = affs_default_codepage;
+
 	if (!parse_options(data,&uid,&gid,&i,&reserved,&root_block,
-				&blocksize,&sbi->s_prefix,
-				sbi->s_volume, &mount_flags)) {
+			   &blocksize, &sbi->s_prefix,
+			   sbi->s_volume, &mount_flags,
+			   &sbi->iocharset, &sbi->codepage
+		    )) {
 		printk(KERN_ERR "AFFS: Error parsing options\n");
 		kfree(sbi->s_prefix);
+		if (sbi->iocharset != affs_default_iocharset)
+			kfree(sbi->iocharset);
+		if (sbi->codepage != affs_default_codepage)
+			kfree(sbi->codepage);
 		kfree(sbi);
 		return -EINVAL;
 	}
+
+	if (sbi->codepage[0] != '\0' && strcmp(sbi->codepage, "none") != 0) {
+		sbi->nls_disk = load_nls(sbi->codepage);
+		if (!sbi->nls_disk) {
+			printk(KERN_ERR "AFFS: codepage %s not found\n",
+			       sbi->codepage);
+			if (sbi->iocharset != affs_default_iocharset)
+				kfree(sbi->iocharset);
+			if (sbi->codepage != affs_default_codepage)
+				kfree(sbi->codepage);
+			kfree(sbi);
+			return -EINVAL;
+		}
+		sbi->nls_io = load_nls(sbi->iocharset);
+		if (!sbi->nls_io) {
+			printk(KERN_ERR "AFFS: IO charset %s not found\n",
+			       sbi->iocharset);
+			unload_nls(sbi->nls_disk);
+			if (sbi->iocharset != affs_default_iocharset)
+				kfree(sbi->iocharset);
+			if (sbi->codepage != affs_default_codepage)
+				kfree(sbi->codepage);
+			kfree(sbi);
+			return -EINVAL;
+		}
+	} else {
+		sbi->nls_io = NULL;
+		sbi->nls_disk = NULL;
+	}
+
 	/* N.B. after this point s_prefix must be released */
 
 	sbi->s_flags   = mount_flags;
@@ -449,7 +528,7 @@ got_root:
 	if (mount_flags & SF_VERBOSE) {
 		u8 len = AFFS_ROOT_TAIL(sb, root_bh)->disk_name[0];
 		printk(KERN_NOTICE "AFFS: Mounting volume \"%.*s\": Type=%.3s\\%c, Blocksize=%d\n",
-			len > 31 ? 31 : len,
+			len > (AFFS_MAXNAME + 1) ? (AFFS_MAXNAME + 1) : len,
 			AFFS_ROOT_TAIL(sb, root_bh)->disk_name + 1,
 			sig, sig[3] + '0', blocksize);
 	}
@@ -474,7 +553,7 @@ got_root:
 	root_inode = affs_iget(sb, root_block);
 	if (IS_ERR(root_inode)) {
 		ret = PTR_ERR(root_inode);
-		goto out_error;
+		goto out_error_noinode;
 	}
 
 	if (AFFS_SB(sb)->s_flags & SF_INTL)
@@ -495,6 +574,18 @@ got_root:
 	 * Begin the cascaded cleanup ...
 	 */
 out_error:
+	if (root_inode)
+		iput(root_inode);
+out_error_noinode:
+	if (sbi->nls_disk)
+		unload_nls(sbi->nls_disk);
+	if (sbi->nls_io)
+		unload_nls(sbi->nls_io);
+	if (sbi->iocharset != affs_default_iocharset)
+		kfree(sbi->iocharset);
+	if (sbi->codepage != affs_default_codepage)
+		kfree(sbi->codepage);
+
 	kfree(sbi->s_bitmap);
 	affs_brelse(root_bh);
 	kfree(sbi->s_prefix);
@@ -526,7 +617,8 @@ affs_remount(struct super_block *sb, int *flags, char *data)
 	memcpy(volume, sbi->s_volume, 32);
 	if (!parse_options(data, &uid, &gid, &mode, &reserved, &root_block,
 			   &blocksize, &prefix, volume,
-			   &mount_flags)) {
+			   &mount_flags,
+			   &sbi->iocharset, &sbi->codepage)) {
 		kfree(prefix);
 		kfree(new_opts);
 		return -EINVAL;
@@ -577,7 +669,7 @@ affs_statfs(struct dentry *dentry, struct kstatfs *buf)
 	buf->f_bavail  = free;
 	buf->f_fsid.val[0] = (u32)id;
 	buf->f_fsid.val[1] = (u32)(id >> 32);
-	buf->f_namelen = 30;
+	buf->f_namelen = AFFS_MAXNAME;
 	return 0;
 }
 
diff --git a/fs/affs/symlink.c b/fs/affs/symlink.c
index ee00f08..be94d01 100644
--- a/fs/affs/symlink.c
+++ b/fs/affs/symlink.c
@@ -9,17 +9,21 @@
  */
 
 #include "affs.h"
+#include <linux/nls.h>
 
-static int affs_symlink_readpage(struct file *file, struct page *page)
+int affs_read_symlink(struct inode *inode, char *link)
 {
 	struct buffer_head *bh;
-	struct inode *inode = page->mapping->host;
-	char *link = kmap(page);
 	struct slink_front *lf;
 	int err;
 	int			 i, j;
 	char			 c;
 	char			 lc;
+	const char              *symname, *symnameend;
+	char                     convbuf[10];
+	size_t                   convbufpos = 0, convbuflen = 0;
+	struct nls_table        *nls_io = AFFS_SB(inode->i_sb)->nls_io;
+	struct nls_table        *nls_disk = AFFS_SB(inode->i_sb)->nls_disk;
 
 	pr_debug("AFFS: follow_link(ino=%lu)\n",inode->i_ino);
 
@@ -37,36 +41,97 @@ static int affs_symlink_readpage(struct file *file, struct page *page)
 		char *pf;
 		spin_lock(&sbi->symlink_lock);
 		pf = sbi->s_prefix ? sbi->s_prefix : "/";
-		while (i < 1023 && (c = pf[i]))
-			link[i++] = c;
+		while (i < 1023 && (c = pf[i])) {
+			if (link)
+				link[i] = c;
+			i++;
+		}
 		spin_unlock(&sbi->symlink_lock);
 		while (i < 1023 && lf->symname[j] != ':')
 			link[i++] = lf->symname[j++];
+		if (i < 1023 && link)
+			link[i] = '/';
 		if (i < 1023)
-			link[i++] = '/';
+			i++;
 		j++;
 		lc = '/';
 	}
-	while (i < 1023 && (c = lf->symname[j])) {
+	symname = lf->symname + j;
+	symnameend = symname + strlen(symname);
+	while (i < 1023) {
+		pr_debug("Remaining <%s>\n", symname);
+		if (convbuflen == convbufpos) {
+			convbufpos = convbuflen = 0;
+			if (*symname == 0)
+				break;
+			if (nls_disk && nls_io) {
+				ssize_t len;
+				wchar_t uni;
+				len = nls_disk->char2uni(symname,
+							 symnameend - symname,
+							 &uni);
+				if (len <= 0) {
+					convbufpos = 0;
+					convbuflen = 1;
+					convbuf[0] = '?';
+					symname++;
+				} else {
+					symname += len;
+					len = nls_io->uni2char(uni, convbuf,
+							       sizeof(convbuf));
+					if (len > 0) {
+						convbuflen = len;
+					} else {
+						convbufpos = 0;
+						convbuflen = 1;
+						convbuf[0] = '?';
+					}
+				}
+			} else {
+				convbufpos = 0;
+				convbuflen = 1;
+				convbuf[0] = *symname++;
+			}
+		}
+		c = convbuf[convbufpos++];
+		pr_debug("Fetching char <%c>\n", c);
+
 		if (c == '/' && lc == '/' && i < 1020) {	/* parent dir */
-			link[i++] = '.';
-			link[i++] = '.';
+			if (link) {
+				link[i] = '.';
+				link[i + 1] = '.';
+			}
+			i += 2;
 		}
-		link[i++] = c;
+		if (link)
+			link[i] = c;
+		i++;
 		lc = c;
-		j++;
 	}
-	link[i] = '\0';
+	if (link)
+		link[i] = '\0';
 	affs_brelse(bh);
+	return i;
+fail:
+	return err;
+}
+
+static int affs_symlink_readpage(struct file *file, struct page *page)
+{
+	struct inode *inode = page->mapping->host;
+	char *link = kmap(page);
+	int ret;
+	ret = affs_read_symlink(inode, link);
+	if (ret < 0) {
+		SetPageError(page);
+		kunmap(page);
+		unlock_page(page);
+		return ret;
+	}
 	SetPageUptodate(page);
 	kunmap(page);
 	unlock_page(page);
 	return 0;
-fail:
-	SetPageError(page);
-	kunmap(page);
-	unlock_page(page);
-	return err;
 }
 
 const struct address_space_operations affs_symlink_aops = {


Attachment: signature.asc
Description: OpenPGP digital signature


[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