OPEN_BY_HANDLE_AT - Linux手册页
Linux程序员手册 第2部分
更新日期: 2020-04-11
名称
name_to_handle_at,open_by_handle_at-获取路径名的句柄并通过句柄打开文件
语法
#define _GNU_SOURCE /* See feature_test_macros(7) */ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int name_to_handle_at(int dirfd, const char *pathname, struct file_handle *handle, int *mount_id, int flags); int open_by_handle_at(int mount_fd, struct file_handle *handle, int flags);
说明
name_to_handle_at()和open_by_handle_at()系统调用将openat(2)的功能分为两部分:name_to_handle_at()返回对应于指定文件的不透明句柄; open_by_handle_at()打开与先前调用name_to_handle_at()返回的句柄相对应的文件,并返回打开的文件描述符。
name_to_handle_at()
系统调用name_to_handle_at()返回文件句柄和与dirfd和pathname参数指定的文件相对应的安装ID。通过参数句柄返回文件句柄,该参数句柄指向以下形式的结构:
struct file_handle { unsigned int handle_bytes; /* Size of f_handle [in, out] */ int handle_type; /* Handle type [out] */ unsigned char f_handle[0]; /* File identifier (sized by caller) [out] */ };
调用程序有责任分配足够大的结构以容纳f_handle中返回的句柄。在调用之前,应该初始化handle_bytes字段以包含为f_handle分配的大小。 (在中定义的常量MAX_HANDLE_SZ指定文件句柄的最大预期大小。这不是保证的上限,因为将来的文件系统可能需要更多空间。)成功返回后,handle_bytes字段将更新为包含实际的字节数写入f_handle。
调用者可以通过在handle-> handle_bytes为零的情况下进行调用来发现file_handle结构所需的大小。在这种情况下,调用失败,错误为EOVERFLOW,并且将handle-> handle_bytes设置为指示所需的大小;然后,调用者可以使用此信息来分配正确大小的结构(请参见下面的示例)。这里需要格外小心,因为EOVERFLOW还可以指示在通常不支持文件句柄查找的文件系统中,该特定名称没有可用的文件句柄。当返回EOVERFLOW错误而未增加handle_bytes时,可以检测到这种情况。
除了使用handle_bytes字段外,调用者还应将file_handle结构视为不透明的数据类型:handle_type和f_handle字段仅在后续调用open_by_handle_at()时才需要。
flags参数是通过将零个或多个AT_EMPTY_PATH和AT_SYMLINK_FOLLOW或运算在一起而构成的位掩码,如下所述。
路径名和dirfd参数一起标识了要为其获取句柄的文件。有四种不同的情况:
- *
- 如果pathname是包含绝对路径名的非空字符串,则将返回该路径名引用的文件的句柄。在这种情况下,dirfd被忽略。
- *
- 如果pathname是包含相对路径名的非空字符串,并且dirfd具有特殊值AT_FDCWD,则将路径名相对于调用者的当前工作目录进行解释,并为其引用的文件返回一个句柄。
- *
- 如果pathname是包含相对路径名的非空字符串,并且dirfd是引用目录的文件描述符,则路径名将相对于dirfd引用的目录进行解释,并为其引用的文件返回一个句柄。 (有关为什么"目录文件描述符"有用的说明,请参见openat(2)。)
- *
- 如果路径名是一个空字符串,并且标志指定值AT_EMPTY_PATH,则dirfd可以是一个打开的文件描述符,它引用任何类型的文件或AT_FDCWD,这表示当前的工作目录,并为其引用的文件返回一个句柄。
mount_id参数返回对应于路径名的文件系统装载的标识符。这对应于/ proc / self / mountinfo中的一条记录中的第一个字段。在该记录的第五个字段中打开路径名,将产生安装点的文件描述符。该文件描述符可用于后续对open_by_handle_at()的调用。对于成功的调用和导致错误EOVERFLOW的调用,都返回mount_id。
默认情况下,如果name_to_handle_at()是符号链接,则它不会取消引用路径名,因此会返回链接本身的句柄。如果在标志中指定了AT_SYMLINK_FOLLOW,则如果路径名是符号链接,则将取消引用该路径名(以便调用返回该链接引用的文件的句柄)。
当路径名的最后一部分是自动安装点时,name_to_handle_at()不会触发安装。当文件系统同时支持文件句柄和自动挂载点时,对自动挂载点的name_to_handle_at()调用将返回错误EOVERFLOW,而不会增加handle_bytes。从带有NFS的Linux 4.13开始,访问服务器上单独文件系统上的目录时,可能会发生这种情况。在这种情况下,可以通过在路径名的末尾添加" /"来触发自动挂载。
open_by_handle_at()
系统调用open_by_handle_at()将打开handle所引用的文件,该文件是先前对name_to_handle_at()的调用所返回的文件句柄。
mount_fd参数是已装入文件系统中应解释其句柄的任何对象(文件,目录等)的文件描述符。可以指定特殊值AT_FDCWD,即调用者的当前工作目录。
flags参数与open(2)相同。如果handle引用符号链接,则调用者必须指定O_PATH标志,并且不取消引用符号链接; O_NOFOLLOW标志(如果指定)将被忽略。
调用方必须具有CAP_DAC_READ_SEARCH功能才能调用open_by_handle_at()。
返回值
成功时,name_to_handle_at()返回0,而open_by_handle_at()返回文件描述符(非负整数)。
在发生错误的情况下,两个系统调用均返回-1并设置errno以指示错误原因。
错误说明
name_to_handle_at()和open_by_handle_at()可能会因与openat(2)相同的错误而失败。此外,它们可能会因以下错误而失败。
name_to_handle_at()可能因以下错误而失败:
- EFAULT
- 路径名,mount_id或可访问地址空间之外的句柄点。
- EINVAL
- 标志包含无效的位值。
- EINVAL
- handle->handle_bytes大于MAX_HANDLE_SZ。
- ENOENT
- 路径名是一个空字符串,但标志中未指定AT_EMPTY_PATH。
- ENOTDIR
- dirfd中提供的文件描述符不引用目录,并且两个标志都不包含AT_EMPTY_PATH且路径名不是空字符串也不是这种情况。
- EOPNOTSUPP
- 文件系统不支持解码文件句柄的路径名。
- EOVERFLOW
- 传递给调用的handle->handle_bytes值太小。发生此错误时,将更新handle->handle_bytes以指示所需的句柄大小。
open_by_handle_at()可能因以下错误而失败:
- EBADF
- mount_fd不是打开的文件描述符。
- EFAULT
- 处理您可访问的地址空间之外的点。
- EINVAL
- handle->handle_bytes大于MAX_HANDLE_SZ或等于零。
- ELOOP
- handle指向符号链接,但未在标志中指定O_PATH。
- EPERM
- 调用者不具有CAP_DAC_READ_SEARCH功能。
- ESTALE
- 指定的句柄无效。例如,如果文件已删除,则会发生此错误。
版本
这些系统调用首次出现在Linux 2.6.39中。自版本2.14开始,glibc中提供了库支持。
遵循规范
这些系统调用是非标准的Linux扩展。
FreeBSD有一对大致相似的系统调用,形式为getfh()和openfh()。
备注
可以在一个进程中使用name_to_handle_at()生成文件句柄,然后在另一个调用open_by_handle_at()的进程中使用它。
某些文件系统不支持将路径名转换为文件句柄,例如,/ proc,/ sys和各种网络文件系统。
如果删除了文件或其他特定于文件系统的原因,文件句柄可能变得无效("陈旧")。无效的句柄由open_by_handle_at()中的ESTALE错误通知。
这些系统调用旨在供用户空间文件服务器使用。例如,用户空间NFS服务器可能会生成文件句柄,并将其传递给NFS客户端。稍后,当客户端想要打开文件时,它可以将句柄传递回服务器。这种功能允许用户空间文件服务器相对于其服务的文件以无状态方式进行操作。
如果路径名引用符号链接,并且标志未指定AT_SYMLINK_FOLLOW,则name_to_handle_at()返回链接的句柄(而不是其引用的文件)。接收句柄的进程可以稍后在符号链接上执行操作,方法是使用带有O_PATH标志的open_by_handle_at()将句柄转换为文件描述符,然后将文件描述符作为dirfd参数传递给系统调用(如readlinkat(2)和fchownat(2)。
Obtaining a persistent filesystem ID
当卸载和挂载文件系统时,/ proc / self / mountinfo中的挂载ID可以重复使用。因此,由name_to_handle_at()返回的安装ID(在* mount_id中)不应被视为相应已安装文件系统的持久标识符。但是,应用程序可以使用mountinfo记录中与安装ID相对应的信息来导出持久标识符。
例如,可以使用mountinfo记录的第五个字段中的设备名称,通过/ dev / disks / by-uuid中的符号链接来搜索相应的设备UUID。 (获得UUID的一种更舒适的方法是使用libblkid(3)库。)然后可以通过使用UUID查找设备名称,然后获取相应的挂载点来反转该过程,以生成UUID。 open_by_handle_at()使用的mount_fd参数。
示例
下面的两个程序演示了name_to_handle_at()和open_by_handle_at()的用法。第一个程序(t_name_to_handle_at.c)使用name_to_handle_at()获取在其命令行参数中指定的文件的文件句柄和安装ID。句柄和安装ID被写入标准输出。
第二个程序(t_open_by_handle_at.c)从标准输入中读取安装ID和文件句柄。然后,程序使用open_by_handle_at()使用该句柄打开文件。如果提供了可选的命令行参数,则open_by_handle_at()的mount_fd参数是通过打开该参数中命名的目录获得的。否则,通过扫描/ proc / self / mountinfo来找到一条mount_fd与从标准输入读取的安装ID匹配的记录,并打开该记录中指定的安装目录,从而获得mount_fd。 (这些程序不处理安装标识不是持久性的事实。)
以下shell会话演示了这两个程序的用法:
$ echo 'Can you please think about it?' > cecilia.txt $ ./t_name_to_handle_at cecilia.txt > fh $ ./t_open_by_handle_at < fh open_by_handle_at: Operation not permitted $ sudo ./t_open_by_handle_at < fh # Need CAP_SYS_ADMIN Read 31 bytes $ rm cecilia.txt
现在,我们删除并(快速)重新创建文件,使其具有相同的内容和(偶然的)相同的inode。但是,open_by_handle_at()识别出文件句柄引用的原始文件已不存在。
$ stat --printf="%i\n" cecilia.txt # Display inode number 4072121 $ rm cecilia.txt $ echo 'Can you please think about it?' > cecilia.txt $ stat --printf="%i\n" cecilia.txt # Check inode number 4072121 $ sudo ./t_open_by_handle_at < fh open_by_handle_at: Stale NFS file handle
Program source: t_name_to_handle_at.c
#define _GNU_SOURCE #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ } while (0) int main(int argc, char *argv[]) { struct file_handle *fhp; int mount_id, fhsize, flags, dirfd, j; char *pathname; if (argc != 2) { fprintf(stderr, "Usage: %s pathname\n", argv[0]); exit(EXIT_FAILURE); } pathname = argv[1]; /* Allocate file_handle structure */ fhsize = sizeof(*fhp); fhp = malloc(fhsize); if (fhp == NULL) errExit("malloc"); /* Make an initial call to name_to_handle_at() to discover the size required for file handle */ dirfd = AT_FDCWD; /* For name_to_handle_at() calls */ flags = 0; /* For name_to_handle_at() calls */ fhp->handle_bytes = 0; if (name_to_handle_at(dirfd, pathname, fhp, &mount_id, flags) != -1 || errno != EOVERFLOW) { fprintf(stderr, "Unexpected result from name_to_handle_at()\n"); exit(EXIT_FAILURE); } /* Reallocate file_handle structure with correct size */ fhsize = sizeof(struct file_handle) + fhp->handle_bytes; fhp = realloc(fhp, fhsize); /* Copies fhp->handle_bytes */ if (fhp == NULL) errExit("realloc"); /* Get file handle from pathname supplied on command line */ if (name_to_handle_at(dirfd, pathname, fhp, &mount_id, flags) == -1) errExit("name_to_handle_at"); /* Write mount ID, file handle size, and file handle to stdout, for later reuse by t_open_by_handle_at.c */ printf("%d\n", mount_id); printf("%d %d ", fhp->handle_bytes, fhp->handle_type); for (j = 0; j < fhp->handle_bytes; j++) printf(" %02x", fhp->f_handle[j]); printf("\n"); exit(EXIT_SUCCESS); }
Program source: t_open_by_handle_at.c
#define _GNU_SOURCE #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ } while (0) /* Scan /proc/self/mountinfo to find the line whose mount ID matches aqmount_idaq. (An easier way to do this is to install and use the aqlibmountaq library provided by the aqutil-linuxaq project.) Open the corresponding mount path and return the resulting file descriptor. */ static int open_mount_path_by_id(int mount_id) { char *linep; size_t lsize; char mount_path[PATH_MAX]; int mi_mount_id, found; ssize_t nread; FILE *fp; fp = fopen("/proc/self/mountinfo", "r"); if (fp == NULL) errExit("fopen"); found = 0; linep = NULL; while (!found) { nread = getline(&linep, &lsize, fp); if (nread == -1) break; nread = sscanf(linep, "%d %*d %*s %*s %s", &mi_mount_id, mount_path); if (nread != 2) { fprintf(stderr, "Bad sscanf()\n"); exit(EXIT_FAILURE); } if (mi_mount_id == mount_id) found = 1; } free(linep); fclose(fp); if (!found) { fprintf(stderr, "Could not find mount point\n"); exit(EXIT_FAILURE); } return open(mount_path, O_RDONLY); } int main(int argc, char *argv[]) { struct file_handle *fhp; int mount_id, fd, mount_fd, handle_bytes, j; ssize_t nread; char buf[1000]; #define LINE_SIZE 100 char line1[LINE_SIZE], line2[LINE_SIZE]; char *nextp; if ((argc > 1 && strcmp(argv[1], "--help") == 0) || argc > 2) { fprintf(stderr, "Usage: %s [mount-path]\n", argv[0]); exit(EXIT_FAILURE); } /* Standard input contains mount ID and file handle information: Line 1: <mount_id> Line 2: <handle_bytes> <handle_type> <bytes of handle in hex> */ if ((fgets(line1, sizeof(line1), stdin) == NULL) || (fgets(line2, sizeof(line2), stdin) == NULL)) { fprintf(stderr, "Missing mount_id / file handle\n"); exit(EXIT_FAILURE); } mount_id = atoi(line1); handle_bytes = strtoul(line2, &nextp, 0); /* Given handle_bytes, we can now allocate file_handle structure */ fhp = malloc(sizeof(struct file_handle) + handle_bytes); if (fhp == NULL) errExit("malloc"); fhp->handle_bytes = handle_bytes; fhp->handle_type = strtoul(nextp, &nextp, 0); for (j = 0; j < fhp->handle_bytes; j++) fhp->f_handle[j] = strtoul(nextp, &nextp, 16); /* Obtain file descriptor for mount point, either by opening the pathname specified on the command line, or by scanning /proc/self/mounts to find a mount that matches the aqmount_idaq that we received from stdin. */ if (argc > 1) mount_fd = open(argv[1], O_RDONLY); else mount_fd = open_mount_path_by_id(mount_id); if (mount_fd == -1) errExit("opening mount fd"); /* Open file using handle and mount point */ fd = open_by_handle_at(mount_fd, fhp, O_RDONLY); if (fd == -1) errExit("open_by_handle_at"); /* Try reading a few bytes from the file */ nread = read(fd, buf, sizeof(buf)); if (nread == -1) errExit("read"); printf("Read %zd bytes\n", nread); exit(EXIT_SUCCESS); }
出版信息
这个页面是Linux手册页项目5.08版的一部分。有关项目的说明、有关报告错误的信息以及此页面的最新版本,请访问https://www.kernel.org/doc/man-pages/。