TLPI读书笔记第16章-扩展属性

文件的扩展属性(EA),即以名称-值对形式将任意元数据与文件 i 节点关联起来的技术

16.1 概述

EA 可用于实现访问列表(第 17 章)和文件能力(第 39 章)。但就设计而论,其能力绝不仅限于此。例如,还可利用 EA 去记录文件的版本号、与文件的 MIME 类型/字符集有关的信息,或是指向图符的指针。 SUSv3 并未对 EA 加以规范。但少数其他 UNIX 实现却提供了类似的特性,其中知名的有现代 BSD(详见 extattr(2))系列和 Solaris 9 及其后续版本(详见 fsattr(5))。 EA 需要有底层文件系统来提供支撑, Btrfs、 ext2、 ext3、 ext4、 JFS、 Reiserfs 以及 XFS等文件系统都支持 EA。

EA 命名空间

EA 的命名格式为 namespace.name。其中 namespace 用来把 EA 从功能上划分为截然不同的几大类,而 name 则用来在既定命名空间内唯一标识某个 EA。 可供 namespace 使用的值有 4 个: user、 trusted、 system 以及 security。这 4 类 EA 的用途如下所示。 1.user EA 将在文件权限检查的制约下由非特权级进程操控。欲获取 user EA 值,需要有文件的读权限; 欲改变 user EA 值, 则需要写权限。(若无所需权限, 将会导致 EACCES错误。)在 ext2、 ext3、 ext4 或 Reiserfs 文件系统上,如欲将 user EA 与一文件关联,在装配底层文件系统时需带有 user_xattr 选项。

mount -o user_xattr device directory

2.trusted EA 也可由用户进程“驱使”, 这点与 user EA 相似。 而区别则在于, 要操纵 trustedEA,进程必须具有特权(CAP_SYS_ADMIN)。

3.system EA 供内核使用,将系统对象与一文件关联。目前仅支持访问控制列表(第17 章)。

4.security EA 的作用有二:其一,用来存储服务于操作系统安全模块的文件安全标签;其二,将可执行文件与能力关联起来(39.9.2 节)。而发明 security EA 的初衷是为了支持安全强化版的 Linux(SELinux)。 一个 i 节点可以拥有多个相关 EA,其所从属的命名空间可以相同,也可不同。在各命名空间内的 EA 名均自成一体。在 user 和 trusted 命名空间内, EA 名可以为任意字符串。而在system 命名空间内,只有经内核明确认可的(例如,用于访问控制列表的)命名方可使用

通过 shell 创建并查看 EA

在 shell 中,可执行 setfattr(1)和 getfattr(1)命令来设置和查看文件的 EA。

touch tfile
setfattr -n user.x -v "the past is not dead" tfile
setfattr -n user.y -v "In fact,it's not even past" tfile
getfattr -n user.x tfile
#获取所有user属性
getfattr -d tfile
#将属性置为空字符串
setfattr -n user.x  tfile
#删除一个属性
setfattr -x user.y  tfile

 

以上 shell 会话所展示的要点之一是, EA 值可以为空字符串,这不同于未定义的 EA 值。(由 shell 会话结尾处的例子可知, user.x 的值为空字符串, user.y 的值为未定义。) 默认情况下, getfattr 只会列出 user EA 值。还可利用-m 选项来指定一正则表达式,来筛选想要显示的 EA 名:

getfattr -m 'pattern' tfile

pattern 的默认值为“^user.”。可执行如下命令,列出一个文件的所有 EA 值

getfattr -m - tfile

16.2 扩展属性的实现细节

本节是对上一节内容的延伸,描述 EA 实现的部分细节。

对 user 扩展属性的限制

user EA 只能施之于文件或目录,之所以将其他文件类型排除在外,原因如下。 1.对于符号链接,会对所有用户开启所有权限,且不容更改。这意味着,无法利用权限来阻止任意用户将 user EA 置于符号链接之上。要想解决这个问题,就得防止所有用户针对符号链接创建 user EA。

2.对于设备文件、套接字以及 FIFO 而言,授予用户权限,意在对其针对底层对象所执行的 I/O 操作加以控制。如欲操控这些权限,转而求取对创建 user EA 的控制,则二者间会产生冲突。 此外,若某一目录启用了粘性位(sticky 位)(15.4.5 节),且为其他用户所拥有,则非特权进程不能将一 user EA 置于该目录之上。惟其如此,才能防止任一用户将 EA 附着于诸如/tmp之类的目录,由于其可写权限对所有用户开放(从而导致任意用户均可操纵此目录的 EA),而设置粘性位,意在防止用户删除该目录下为其他用户所拥有的文件。

EA 在实现方面的限制

Linux VFS 针对所有文件系统上的 EA 均施以如下限制。 1.EA 名称的长度不能超过 255 个字节。

2.EA 值的容量为 64KB。

此外,某些文件系统对可与文件挂钩的 EA 数量及其大小还有更为严格的限制。

1.在 ext2、 ext3 以及 ext4 文件系统上,与一文件关联的所有 EA 命名和 EA 值的总字节数不会超过单个逻辑磁盘块(14.3 节)的大小: 1024 字节、 2048 字节或 4096 字节。

2.在 JFS 上,为某一文件所使用的所有 EA 名和 EA 值的总字节数上限为 128KB。

16.3 操控扩展属性的系统调用

本节将会介绍用来更新、获取以及删除 EA 的系统调用。

创建和修改 EA

系统调用 setxattr()、 lsetxattr()以及 fsetxattr()用来设置文件的 EA 值之一。

#include<sys/xattr.h>
int setxattr(const char *pathname,const char *name,const void *value,size_t size,int flags)
int lsetxattr(const char *pathname,const char *name,const void *value,size_t size,int flags)
int fsetxattr(int fd,const char *name,const void *value,size_t size,int flags)

 

这 3 个系统调用之间的区别类似于 stat()、 lstat()以及 fstat()(15.1 节)三者间的差异。 1.setxattr()通过 pathname 来标识文件,若文件名为符号链接,则对其解引用。 2.lsetxattr()通过 pathname 来标识文件,但不会对符号链接解引用。 3.fsetxattr()则通过打开文件描述符 fd 来标识文件。 以上 3 者之间的差异同样适用于本节下面将要介绍的其他各组系统调用。 参数 name 是一个以空字符结尾的字符串,定义了 EA 的名称。参数 value 是一个指向缓冲区的指针,包含了为 EA 定义的新值。参数 size 则指明了缓冲区大小。 默认情况下,若具有给定名称(name)的 EA 不存在,上述系统调用会创建一个新 EA。若 EA 已经存在,则将替换 EA 值。可利用参数 flags 将这一行为控制得更为精准。将该参数指定为 0,以获得默认行为,或者可将其指定为如下常量之一。 XATTR_CREATE 若具有给定名称(name)的 EA 已经存在,则失败。 XATTR_REPLACE 若具有给定名称(name)的 EA 不存在,则失败。 下例使用 setxattr()创建了一个 user EA

char *value
value ="the past is not dead";
if(setxattr(pathname,'user.x',value,strlen(value),0)==-1)
    errExit("setxattr")

 

获取 EA 值

可利用系统调用 getxattr()、 lgetxattr()以及 fgetxattr()来获取 EA 值。

#include<sys/xattr.h>
size_t getxattr(const char *pathname,const char *name, void *value,size_t size)
size_t lgetxattr(const char *pathname,const char *name, void *value,size_t size)
size_t fgetxattr(int fd,const char *name,const void *value,size_t size)

 

参数 name 是一个以空字符结尾的字符串,用来标识欲取值的 EA。返回的 EA 值保存于参数 value 所指向的缓冲区中。该缓冲区必须由调用者分配,其大小应在 size 中指定。若调用成功,上述系统调用会返回复制到 value 所指缓冲区中的字节数 若文件不含名为“name”的属性 ,上述系统调用则会失败,并会返回错误 ENODATA。 若 size 值过小,上述系统调用也会失败,并返回错误 ERANGE。 可把 size 指定为 0,对于这种情况,将忽略 vlaue 值,但系统调用仍将返回 EA 值的大小。 可利用这一机制来确定后续系统调用在实际获取 EA 值时所需的 value 缓冲区大小。但是应当注意, 这并不能保证后续在通过系统调用获取 EA 值时, 上述返回值就足够大。 系统调用期间另一进程可能为文件的这一属性分配了较大的值,或是将其完全删除。

删除 EA

系统调用 removexattr()、 lremovexattr()以及 fremovexattr()用来删除文件的 EA

#include<sys/xattr.h>
int removexattr(const char *pathname,const char *name);
int lremovexattr(const char *pathname,const char *name);
int fremovexattr(int fd,const char *name);

 

name 所含以空字符结尾的字符串,用于标识打算删除的 EA。若试图删除不存在的 EA,调用将失败,并会返回错误 ENODATA。

获取与文件相关联的所有 EA 的名称

执行系统调用 listxattr()、 llistxattr()以及 flistxattr(),所返回的列表会包含与某文件关联的所有 EA 的名称。

#include<sys/xattr.h>
size_t listxattr(const char *pathname, char *list,size_t size);
size_t llistxattr(const char *pathname,char *list,size_t size);
size_t flistxattr(int fd,char *list,size_t size);

 

调用将 EA 的名称列表以一系列以空字符结尾的字符串形式置于 list 所指向的缓冲区中。缓冲区的大小由 size 指定。一旦成功,上述系统调用会返回复制到 list 中的字节数 与 getxattr()一样,也可将 size 指定为 0,系统调用将忽略 list,并返回后续调用实际获取EA 名称列表(假定该列表尚未改变)时所需的缓冲区大小。 想获取与某文件相关联的 EA 名列表,只需对文件拥有“访问”权限(亦即对 pathname下的所有路径均拥有执行权限),对文件本身则无需任何权限。 出于安全考虑, list 中返回的 EA 名称可能不包含调用进程无权访问的属性名。 比方说,在非特权进程中调用 listxattr()时,大多数文件系统都会略去 trusted 属性。请注意上一句中的“可能”二字,这表明文件系统实现并非一定要如此。因而,使用 list 中返回的 EA 名 去调用 getxattr(),是有可能失败的,因为进程并不具有获得该 EA 值所需的特权。(同样,当另一进程在 listxattr()和 getxattr()调用之间将该属性删除,也会发生类似错误。)

程序示例

程序清单 16-1 所示程序将获取并显示命令行所列文件的所有 EA 名和 EA 值。该程序使用 listxattr(),去获取与每个文件相关联的所有 EA 名称,随后循环调用 getxattr(),为每个名称获取相应的值。默认以纯文本方式显示属性值。若带有-x 选项,那么属性值将以十六进制字符串形式显示。以下 shell 会话记录展示了该程序的使用。

设置属性

 

#include<sys/xattr.h>
#include <stdio.h>
#include <string.h>

/**设置EA属性*/
int main() {
    char *value;
    value = "the past is not dead";
    if (setxattr("/opt/tlpi-dist/xattr/Makefile", "user.x", value, strlen(value), 0) == -1)
        printf("setxattr");
    value = "the past is coming";
    if (setxattr("/opt/tlpi-dist/xattr/Makefile", "user.y", value, strlen(value), 0) == -1)
        printf("setxattr");

}

获取属性

#include <sys/xattr.h>
#include "tlpi_hdr.h"

#define XATTR_SIZE 10000

static void
usageError(char *progName)
{
    fprintf(stderr, "Usage: %s [-x] file...\n", progName);
    exit(EXIT_FAILURE);
}

int
main(int argc, char *argv[])
{
    /**list是一个char数组,存放xattr的key,key字符串以\0结尾,所有才需要+1
     * value也是一个char数组,存放xattr的value,后面会详细将如何控制输出*/
    char list[XATTR_SIZE], value[XATTR_SIZE];
    ssize_t listLen, valueLen;
    int ns, j, k, opt;
    Boolean hexDisplay;

    hexDisplay = 0;
    while ((opt = getopt(argc, argv, "x")) != -1) {
        switch (opt) {
        case 'x': hexDisplay = 1;       break;
        case '?': usageError(argv[0]);
        }
    }

    if (optind >= argc)
        usageError(argv[0]);

    for (j = optind; j < argc; j++) {
        /**列出文件的所有EA属性的size,存入list缓冲区*/
        listLen = listxattr(argv[j], list, XATTR_SIZE);
        if (listLen == -1)
            errExit("listxattr");
        /**打印文件名*/
        printf("%s:\n", argv[j]);

        /** listLen实际读到的缓冲区大小,遍历打印出每个属性 */

        for (ns = 0; ns < listLen; ns += strlen(&list[ns]) + 1) {
            /**使用指针打印数组中的属性名,到\0为止*/
            printf("        name=%s; ", &list[ns]);

            valueLen = getxattr(argv[j], &list[ns], value, XATTR_SIZE);
            if (valueLen == -1) {
                printf("couldn't get value");
            } else if (!hexDisplay) {
            /** printf("格式",输出列表),格式为:[标志][输出最小宽度][.精度][长度]类型
             * 逆序来看:
             * 类型就是输出类型,比如d=十进制整数,o=八进制,x=十六进制,f=浮点数,c=单字符,s=字符串
             * 长度格式符为 h,l等,h 表示按短整型量输出,l 表示按长整型量输出
             * 精度格式符以.开头,后跟十进制整数。本项是:如果输出数字,则表示小数的位数;如果输出的是字符,则表示输出字符的个数;若实际位数大于所定义的精度数,则截去超过的部分
             * 最小宽度:用十进制整数来表示输出的最少位数。 若实际位数多于定义的宽度,则按实际位数输出,若实际位数少于定义的宽度则补以空格或 0
             * 标志:标志字符为-、+、#、空格四种,其意义下表所示:
               -  结果左对齐,右边填空格
               +  输出符号(正号或负号)
               空格  输出值为正时冠以空格,为负时冠以负号
               #  对 c,s,d,u类无影响;对 o 类,在输出时加前缀 o;对 x 类,在输出时加前缀 0x;对 e,g,f 类当结果有小数时才给出小数点
             * 本例中.*以星号代替数值,类似于width中的*,在输出参数列表中指定精度*/
                printf("value=%.*s", (int) valueLen, value);
            } else {
                printf("value=");
                for (k = 0; k < valueLen; k++)
                    printf("%02x ", (unsigned char) value[k]);
            }

            printf("\n");
        }

        printf("\n");
    }

    exit(EXIT_SUCCESS);
}

 

posted @ 2021-04-15 11:31  Mars.wang  阅读(100)  评论(0编辑  收藏  举报