内容简介: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 文件名》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 获取python文件扩展名和文件名方法
- IIS短文件名泄露漏洞
- Linux查找处理文件名后包含空格的文件(两种方法)
- Go日志,打印源码文件名和行号造成的性能开销
- Linux 5.10.3 发布,修复加密文件名重复的问题
- 如何在Linux/Unix上使用awk打印文件名
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Flow-Based Programming (2nd Edition)
CreateSpace / 2010-5-14 / $69.95
Written by a pioneer in the field, this is a thorough guide to the cost- and time-saving advantages of Flow-Based Programming. It explains the theoretical underpinnings and application of this program......一起来看看 《Flow-Based Programming (2nd Edition)》 这本书的介绍吧!