linux utf8 文件名,[译] 深度:Linux kernel 支持 UTF-8 文件名

大家都知道生活中的文件都在用各种各样的语言,也就有各种各样的文字写法。这些字符集可以用很多不同的方式编码。kernel的视角中一直比较简单:文件名和其他字符串数据只是字节流(byte stream),对kernel来说透明,不需要特殊处理。很少数情况下,kernel要处理text,也只需要ASCII。但是,最近出现了一个建议, 希望 在ext4文件系统中支持 不区分大小写的文件名查找 。如此一来,kernel代码必须完整支持复杂的Unicode标准。本文里面我们来看看这些新增的处理编码格式的API,看看究竟有多复杂。

当然,Unicode标准定义了“code point”,简单来说,每个code point就是表示特定语言组中的一个特定字符。这些code point如何在字节流中表示,就是我们所说的“编码 ”(encoding) 。编码的处理,存在很多挑战,不过近年来 UTF-8 编码已成为在许多环境中表示code point的首选方式。UTF-8能够跟ASCII兼容,同时又能表示整个Unicode空间。也就是说任何一个有效的ASCII字符串同时也是有效的UTF-8。在kernel中开发case independence(忽略大小写)的开发者决定只支持UTF-8编码,这样既能解决问题,又不会引入太夸张的复杂工作。

最后出现的API分为两层:一组相对简单的high-level API,以及用于它们的底层实现中所用到的primitive(原语)。我们先从高级操作开始讨论。

The high-level UTF-8 API

high-level来讲,可以非常简单地描述所需的操作:验证(valid)字符串,规范化(normalize)字符串,并比较(compare)两个字符串(可能在不同的大小写格式下)。但是有一个问题:Unicode标准有多个版本( 版本12.0.0 在3月初发布),每个版本都有差异。normalization和case folding的规则在不通版本之间都不一样,每个版本所支持的code point也可能略有差异。因此在做具体的字符串操作之前,必须针对要用的Unicode版本先加载“map”:

struct unicode_map * utf8_load(const char * version);

参数里version 可以为 NULL ,在这种情况下,将使用最新支持的版本并给出一个warning message。在ext4实现中,Unicode的version信息存储在文件系统的superblock里面。可以利用u tf8version_latest()函数来 获取所能支持的最新utf8版本号,很方便。之后可以调用 utf8_load(),它 的返回值是指向map的指针,可以作为其他一些调用的参数。当不再需要时,应使用 utf8_unload() 释放map的指针。

UTF-8字符串使用 中 定义的 qstr 结构 来 表示,不再是简单的char *了。这里有个假设,即这个API仅限于文件系统代码使用。目前确实是这么个情况,但未来可能会有变化。

所提供的单字符串操作API是:

int utf8_validate(const struct unicode_map * um,const struct qstr * str);

int utf8_normalize(const struct unicode_map * um,const struct qstr * str,

unsigned char * dest,size_t dlen);

int utf8_casefold(const struct unicode_map * um,const struct qstr * str,

unsigned char * dest,size_t dlen);

所有函数都需要指向map的指针( um )和字符串( str )作为参数。 如果 str 是有效的UTF-8字符串,则 utf_validate() 返回零,否则返回非零。对 utf8_normalize()的 调用 将 在 dest中 存储 str 的normalized版本并返回结果的长度; utf8_casefold() 执行case fold以及normalization。如果输入字符串无效或结果长于 dlen, 则两个函数都将返回 -EINVAL 。

字符串比较的API是:

int utf8_strncmp(const struct unicode_map * um,

const struct qstr * s1,const struct qstr * s2);

int utf8_strncasecmp(const struct unicode_map * um,

const struct qstr * s1,const struct qstr * s2);

这两个函数都将比较 s1 和 s2 的normalized版本。而 utf8_strncasecmp() 在比较时会忽略大小写。如果字符串相同,则返回值为0,如果它们不同则返回1,出错的话返回 -EINVAL 。这些函数只测试字符串是否相等,不会进行大于或者小于这类比较。

底层API

normalize和case fold需要kernel了解整个Unicode的code point空间。有多种规则多种方式来组织那些code point。好消息是这些规则以机器可读的形式与Unicode标准本身 打包 在一起。坏消息是它们占用了几兆字节的空间。

Kernel的UTF-8 patch会把这些规则塞到C头文件里面去,用一个数据结构来表示。这样当我们需要计生空间的时候,就可以通过删除例如韩语支持即可。但是仍然有许多数据必须编译进kernel,并且对于每个版本的Unicode,这些数据还有一些区别。

想要使用较低级别API的代码,第一步是获取指向此 数据库 的指针,以用于正在使用的Unicode版本。这是通过以下方式之一完成的:

struct utf8data * utf8nfdi(unsigned int maxage);

struct utf8data * utf8nfdicf(unsigned int maxage);

这里, maxage 是通过 UNICODE_AGE() 宏来编码出来的一个版本号。如果只需要normalization, 则应调用 utf8nfdi() ; 如果还需要进行case fold,请使用 utf8nfdicf() 。返回值是个指针,如果不支持给定版本,则返回 NULL 。

接下来,应该设置一个cursor来表示字符串处理的进度:

int utf8cursor(struct utf8cursor * cursor,const struct utf8data * data,

const char * s);

int utf8ncursor(struct utf8cursor * cursor,const struct utf8data * data,

const char * s,size_t len);

这个 cursor 结构必须由函数调用者提供, data 是上面获得的数据库的指针。如果已知字符串的长度(以字节为单位),则应使用 utf8ncursor() ; 当长度未知但字符串最后有一个null终止符时,可以使用 utf8cursor() 。这些函数在成功时返回零,否则返回非零值。

然后通过重复调用以下函数来完成字符串的处理:

int utf8byte(struct utf8cursor * u8c);

此函数将返回normalize之后的(可能也经过了case fold)字符串中的下一个字节,以0结尾。当然,UTF-8编码的code point可能需要多个字节,因此单个字节本身不代表一个code point。这样处理之后,返回的字符串可能比传入的字符串长。

下面是一个例子,怎么把所有这些底层函数组合调用的。也就是 utf8_strncasecmp() 的完整实现:

int utf8_strncasecmp(const struct unicode_map * um,

const struct qstr * s1,const struct qstr * s2)

{

const struct utf8data * data = utf8nfdicf(um-> version);

struct utf8cursor cur1,cur2;

int c1,c2;

if(utf8ncursor(&cur1,data,s1-> name,s1-> len)<0)

return -EINVAL;

if(utf8ncursor(&cur2,data,s2-> name,s2-> len)<0)

return -EINVAL;

do {

c1 = utf8byte(&cur1);

c2 = utf8byte(&cur2);

if(c1 <0 || c2 <0)

返回-EINVAL;

if(c1!= c2)

返回1;

} while(c1);

返回0;

}

Low level API中还有其他函数用于测试有效性(valid),获取字符串的长度等等,但上面的内容已经介绍了它最核心的内容了。那些对细节感兴趣的人可以在https://lwn.net/ml/linux-fsdevel/20190318202745.5200-4-krisman@collabora.com/ 补丁中 查看细节。

一般人可能以为比较字符串是个超级简单的事情,但现在我们不再能用Kernighan&Ritchie提出的那些简单字符串操作函数了。但看起来,这就是我们现在生活的世界所需要的。从好处想,至少这拨复杂操作之后,我们能用那些emoji表情符号了,耶!:+1:。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值