[PATCH v4 06/23] nls: Add support for multiple versions of an encoding

Gabriel Krisman Bertazi
2018-12-06T23:09:31Z

This allows a user to request a specific version of an encoding, like
the version 10.0.0 of Unicode encoded in utf8.

Supporting specific versions of encodings is important to ensure
stability of names in filesystems, specially when doing transformations
like casefold and normalization. Even for unicode, where defined
code-points are stable, there is instability for code points that
weren't defined on a previous version, so the user might want to use an
older version of the encoding to ensure the encoding is exact.

Not every NLS charset supports this feature. It doesn't make sense for
many of them, like ASCII. Others just don't implement it yet, and never
will. In those cases, the interface allows the caller to get the
un-versioned charset, which is the same original behavior as if this
patch weren't applied. A user that is not interested in a specific
version can also ask for a versioned charset without specifying the
version, and in this case, NLS will return the latest version available
of that charset.

Signed-off-by: Gabriel Krisman Bertazi <krisman@collabora.co.uk>
---
fs/nls/nls_core.c | 45 ++++++++++++++++++++++++++++++++++++++-------
include/linux/nls.h | 8 ++++++++
2 files changed, 46 insertions(+), 7 deletions(-)

diff --git a/fs/nls/nls_core.c b/fs/nls/nls_core.c
index 200a7f8165e6..20e00a8b968c 100644
--- a/fs/nls/nls_core.c
+++ b/fs/nls/nls_core.c
@@ -19,10 +19,26 @@
extern struct nls_charset default_charset;
static struct nls_charset *charsets = &default_charset;
static DEFINE_SPINLOCK(nls_lock);
-static struct nls_table *nls_load_table(struct nls_charset *charset)
+
+static struct nls_table *nls_load_table(struct nls_charset *charset,
+ const char *version,
+ unsigned int flags)
{
- /* For now, return the default table, which is the first one found. */
- return charset->tables;
+ struct nls_table *tbl;
+
+ /* If there is no load_table hook, only 1 table is supported and
+ * it must have been loaded statically.
+ */
+ if (charset->load_table)
+ tbl = charset->load_table(version, flags);
+ else
+ tbl = charset->tables;
+
+ if (IS_ERR(tbl))
+ return tbl;
+
+ tbl->flags = flags;
+ return tbl;
}

int __register_nls(struct nls_charset *nls, struct module *owner)
@@ -85,21 +101,36 @@ static struct nls_charset *find_nls(const char *charset)
return nls;
}

-struct nls_table *load_nls(char *charset)
+struct nls_table *load_nls_version(const char *charset, const char *version,
+ unsigned int flags)
{
struct nls_charset *nls_charset;

nls_charset = try_then_request_module(find_nls(charset),
"nls_%s", charset);
- if (!IS_ERR(nls_charset))
+ if (IS_ERR(nls_charset))
+ return ERR_PTR(-EINVAL);
+
+ return nls_load_table(nls_charset, version, flags);
+}
+EXPORT_SYMBOL(load_nls_version);
+
+struct nls_table *load_nls(char *charset)
+{
+ struct nls_table *table = load_nls_version(charset, NULL, 0);
+
+ /* Pre-versioned load_nls() didn't return error pointers. Let's
+ * keep the abi for now to prevent breakage.
+ */
+ if (IS_ERR(table))
return NULL;

- return nls_load_table(nls_charset);
+ return table;
}

void unload_nls(struct nls_table *nls)
{
- if (nls)
+ if (!IS_ERR_OR_NULL(nls))
module_put(nls->charset->owner);
}

diff --git a/include/linux/nls.h b/include/linux/nls.h
index cdc95cd9e5d4..91524bb4477b 100644
--- a/include/linux/nls.h
+++ b/include/linux/nls.h
@@ -30,6 +30,9 @@ struct nls_ops {

struct nls_table {
const struct nls_charset *charset;
+ unsigned int version;
+ unsigned int flags;
+
const struct nls_ops *ops;
const unsigned char *charset2lower;
const unsigned char *charset2upper;
@@ -42,6 +45,8 @@ struct nls_charset {
struct module *owner;
struct nls_table *tables;
struct nls_charset *next;
+ struct nls_table *(*load_table)(const char *version,
+ unsigned int flags);
};

/* this value hold the maximum octet of charset */
@@ -58,6 +63,9 @@ enum utf16_endian {
extern int __register_nls(struct nls_charset *, struct module *);
extern int unregister_nls(struct nls_charset *);
extern struct nls_table *load_nls(char *);
+extern struct nls_table *load_nls_version(const char *charset,
+ const char *version,
+ unsigned int flags);
extern void unload_nls(struct nls_table *);
extern struct nls_table *load_nls_default(void);
#define register_nls(nls) __register_nls((nls), THIS_MODULE)
--
2.20.0.rc2