open(2)
手册页中的描述提供了一些以以下内容开头的线索:
O_PATH (since Linux 2.6.39)
Obtain a file descriptor that can be used for two purposes:
to indicate a location in the filesystem tree and to per‐
form operations that act purely at the file descriptor
level. The file itself is not opened, and other file oper‐
ations (e.g., read(2), write(2), fchmod(2), fchown(2),
fgetxattr(2), ioctl(2), mmap(2)) fail with the error EBADF.
有时,我们不想打开文件或目录。相反,我们只希望引用该文件系统对象以执行某些操作(例如,fchdir()
引用到使用打开的文件描述符所引用的目录O_PATH
)。因此,有一点很重要:如果这是我们的目的,那么使用with打开O_PATH
文件应该便宜一些,因为实际上并未打开文件本身。
还有一个不那么琐碎的要点:在存在之前O_PATH
,获得对文件系统对象的此类引用的方法是使用来打开该对象O_RDONLY
。但是,使用O_RDONLY
要求我们具有对该对象的读取权限。但是,在许多用例中,我们不需要实际读取对象:例如,执行二进制文件或访问目录(fchdir()
)或通过目录访问以触摸目录中的对象。
在“ * at()”系统调用中的用法
常见的,但不是唯一的,使用的O_PATH
是打开一个目录,才能有“在*”系统调用,如参考该目录与使用openat()
,fstatat()
,fchownat()
,等等。这家系统调用,我们可以大致认为的现代接班人具有类似名称的旧的系统调用(open()
,fstat()
,fchown()
,等),服务于几个目的,当你问:“请问哪触及第一为什么我要使用文件描述符而不是目录路径?”。如果我们在open(2)
手册页中往下看,会发现以下文本(在带有“ * at”系统调用原理的子标题下):
First, openat() allows an application to avoid race conditions
that could occur when using open() to open files in directories
other than the current working directory. These race conditions
result from the fact that some component of the directory prefix
given to open() could be changed in parallel with the call to
open(). Suppose, for example, that we wish to create the file
path/to/xxx.dep if the file path/to/xxx exists. The problem is
that between the existence check and the file creation step, path
or to (which might be symbolic links) could be modified to point
to a different location. Such races can be avoided by opening a
file descriptor for the target directory, and then specifying that
file descriptor as the dirfd argument of (say) fstatat(2) and ope‐
nat().
为了使它更具体...假设我们有一个程序想要在除当前工作目录之外的目录中执行多个操作,这意味着我们必须指定一些目录前缀作为我们使用的文件名的一部分。例如,假设路径名是/dir1/dir2/file
并且我们要执行两个操作:
- 进行一些检查
/dir1/dir2/file
(例如,谁拥有文件,或文件的最后修改时间)。
- 如果对检查的结果感到满意,那么也许我们想在同一目录中执行其他文件系统操作,例如,创建一个名为的文件
/dir1/dir2/file.new
。
现在,首先假设我们使用传统的基于路径名的系统调用完成了所有操作:
struct stat stabuf;
stat("/dir1/dir2/file", &statbuf);
if ( /* Info returned in statbuf is to our liking */ ) {
fd = open("/dir1/dir2/file.new", O_CREAT | O_RDWR, 0600);
/* And then populate file referred to by fd */
}
现在,进一步假设在目录前缀/dir1/dir2
中,组件之一(例如dir2
)实际上是符号链接(指目录),并且在对stat()
open()
恶意人员的呼叫与呼叫之间可以更改目标的目标。dir2
指向不同目录的符号链接。这是经典的使用时间检查竞赛条件。我们的程序检查了一个目录中的文件,但随后被诱骗在另一个目录(可能是安全敏感目录)中创建文件。这里的关键是路径名/dir/dir2
看起来相同,但是它所指的却完全改变了。
我们可以使用“ * at”调用来避免这类问题。首先,我们获得一个指向将要执行工作的目录的句柄:
dirfd = open("/dir/dir2", O_PATH);
这里的关键点是对调用时路径所引用的目录dirfd
的稳定引用。如果随后更改符号链接的目标,则不会影响所引用的内容。现在,我们可以使用与上面的and 调用等效的“ * at”调用来执行check +操作:/dir1/dir2
open()
dir2
dirfd
stat()
open()
fstatat(dirfd, ""file", &statbuf)
struct stat stabuf;
fstatat(dirfd, "file", &statbuf);
if ( /* Info returned in statbuf is to our liking */ ) {
fd = openat(dirfd, "file.new", O_CREAT | O_RDWR, 0600);
/* And then populate file referred to by fd */
}
在这些步骤中,对路径名中的符号链接的任何操作/dir/dir2
都不会产生影响:检查(fstatat()
)和操作(openat()
)保证在同一目录中进行。
使用“ * at()”调用还有另一个目的,它涉及多线程程序中“每线程当前工作目录”的概念(同样,我们可以使用来打开目录O_PATH
),但是我认为这种用法可能是与您的问题不太相关,open(2)
如果您想了解更多信息,我让您阅读手册页。
与常规文件的文件描述符一起使用
O_PATH
常规文件的一种用法是打开一个我们具有执行权限的二进制文件(但不一定具有读取权限,因此我们无法使用打开文件O_RDONLY
)。然后可以fexecve(3)
将该文件描述符传递给执行程序。所有这一切fexecve(fd, argv, envp)
与它做的fd
说法基本上是:
snprintf(buf, "/proc/self/fd/%d", fd);
execve(buf, argv, envp);
(尽管从glibc 2.27开始,该实现将改为execveat(2)
在提供该系统调用的内核上使用系统调用。)