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

栏目: 服务器 · Linux · 发布时间: 5年前

内容简介:https://lwn.net/Articles/784124/全文完欢迎将文章分享到朋友圈

Working with UTF-8 in the kernel

By  Jonathan Corbet   March 28, 2019

https://lwn.net/Articles/784124/

大家都知道生活中的文件都在用各种各样的语言,也就有各种各样的文字写法。这些字符集可以用很多不同的方式编码。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字符串使用 <linux/dcache.h>中 定义的 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:。

全文完

欢迎将文章分享到朋友圈 

长按下面二维码关注:Linux News搬运工,希望每周的深度文章以及开源社区的各种新近言论,能够让大家满意~

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


以上所述就是小编给大家介绍的《[译] 深度:Linux kernel 支持 UTF-8 文件名》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Chinese Authoritarianism in the Information Age

Chinese Authoritarianism in the Information Age

Routledge / 2018-2-13 / GBP 115.00

This book examines information and public opinion control by the authoritarian state in response to popular access to information and upgraded political communication channels among the citizens in co......一起来看看 《Chinese Authoritarianism in the Information Age》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

随机密码生成器
随机密码生成器

多种字符组合密码

URL 编码/解码
URL 编码/解码

URL 编码/解码