[PATCH] Add AFFS NLS support

From: Vladimir 'Ï-coder/phcoder' Serbinenko
Date: Sun May 13 2012 - 09:47:30 EST


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