还记得在上一个场景中,build_open_flags 里面有一个对标志位 O_PATH 的判断么?现在我们就来看看这个标志位是干啥的:
【场景二】open(pathname, O_PATH)
这个 O_PATH 似乎是不常用的,咱们先看看它的使用说明:
【open(2)】 http://man7.org/linux/man-pages/man2/open.2.html
大家可以重点看看字体加粗的部分,大致意思就是使用 O_PATH 将不会真正打开一个文件,而只是准备好该文件的文件描述符,而且如果使用该标志位的话系统会忽略大部分其他的标志位。特别是如果配合使用 O_NOFOLLOW,那么遇到符号链接的时候将会返回这个符号链接本身的文件描述符,而非符号链接所指的对象。
咱们还是先看看 build_open_flags 针对 O_PATH 做了什么手脚:
【fs/open.c】 sys_open > do_sys_open > build_open_flags
首先是 872 行,这里进行了一个“与”操作,这就将除了 O_DIRECTORY 和 O_NOFOLLOW 的其他标志位全部清零了,这就忽略了其他的标志位。在 open 的说明中还有一个标志位 O_CLOEXEC 也受到 O_PATH 的保护,但是这个标志位不允许在用户空间直接设置,所以 build_open_flags 一开始就把它干掉了。另外 O_PATH 本身连一个真正的打开操作都不是就跟别提创建了,所以 mode 当然要置零了(873)。既然不会打开文件那么也就和 LOOKUP_OPEN 无缘了(891)。接下来就是处理一下受 O_PATH 保护两个标志位。注意,如果没有设置 O_NOFOLLOW 的话遇到符号链接是需要跟踪到底的(902)。其实就算设置了 O_NOFOLLOW,我们还会看到在 do_last 里还有一次补救的机会,那就是路径名以“/”结尾的话也会跟踪符号链接到底的。
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > do_last
看,两百多行的函数让我们连消带打就剩这么点了,所以说小的函数才是好函数嘛。先看 2902 行的 symlink_ok,这个变量名很形象,它为 true 的意思就是“如果最终目标是一个符号链接也 OK 啦”,如果为 false 的话就需要跟随这个符号链接。我们来看看什么情况下符号链接是 OK 的?首先必须是 O_PATH,也就是我们假设的场景;同时还需要没有设置 LOOKUP_FOLLOW(2901)。在 build_open_flags 我们已经见过了一次设置 LOOKUP_FOLLOW 的地方,这里就是前面所说的补救的地方(2900)。也就是说只要路径名最后一个字符 + 1 不为零就一定是“/”(为什么?不明白的可以回头看看 link_path_walk 的代码),那就表示如果这个最终目标是符号链接的话就要跟随。
接下来 lookup_open 就不用说了吧,当它返回的时候 path 会站上最终目标,nd 则原地不动,它在等待在观望:如果 path 站上的不是符号链接或者即使是符号链接但是“即使是符号链接也 OK 啦”(3003)就会跟着 path 站上最终目标,然后在 finish_open 中完成打开。
finish_open 主要是调用 do_dentry_open,我们进去看看:
【fs/open.c】 sys_open > do_sys_open > do_filp_open > path_openat > do_last
为啥 O_PATH 不会真正打开一个文件,看到这里大家就明白了吧,这里的代码很简单,一切尽在不言中了。当从 finish_open 返回时,file 结构体几乎就是空的,只有 file.f_path 成员指向了这个文件,就连 f_op 都是空的。这或许就是 O_PATH 使用说明中一开始阐述的那两个目的具体表现吧。
好像这个 O_PATH 情景比上一个 O_RDONLY 还要简单,那我们就再假设一个情景。
【场景二】open(pathname, O_PATH)
这个 O_PATH 似乎是不常用的,咱们先看看它的使用说明:
【open(2)】 http://man7.org/linux/man-pages/man2/open.2.html
- 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 perform operations that act purely at the file descriptor level. The file itself is not opened, and other file operations (e.g., read(2), write(2), fchmod(2), fchown(2), fgetxattr(2), mmap(2)) fail with the error EBADF.
-
- The following operations can be performed on the resulting file descriptor:
-
- * close(2); fchdir(2) (since Linux 3.5); fstat(2) (since Linux 3.6).
-
- * Duplicating the file descriptor (dup(2), fcntl(2) F_DUPFD, etc.).
-
- * Getting and setting file descriptor flags (fcntl(2) F_GETFD and F_SETFD).
-
- * Retrieving open file status flags using the fcntl(2) F_GETFL operation: the returned flags will include the bit O_PATH.
-
- * Passing the file descriptor as the dirfd argument of openat(2) and the other "*at()" system calls. This includes linkat(2) with AT_EMPTY_PATH (or via procfs using AT_SYMLINK_FOLLOW) even if the file is not a directory.
-
- * Passing the file descriptor to another process via a UNIX domain socket (see SCM_RIGHTS in unix(7)).
-
- When O_PATH is specified in flags, flag bits other than O_CLOEXEC, O_DIRECTORY, and O_NOFOLLOW are ignored.
-
- If pathname is a symbolic link and the O_NOFOLLOW flag is also specified, then the call returns a file descriptor referring to the symbolic link. This file descriptor can be used as the dirfd argument in calls to fchownat(2), fstatat(2), linkat(2), and readlinkat(2) with an empty pathname to have the calls operate on the symbolic link.
咱们还是先看看 build_open_flags 针对 O_PATH 做了什么手脚:
【fs/open.c】 sys_open > do_sys_open > build_open_flags
点击(此处)折叠或打开
- static inline int build_open_flags(int flags, umode_t mode, struct open_flags *op)
- {
...
- } else if (flags & O_PATH) {
- /*
- * If we have O_PATH in the open flag. Then we
- * cannot have anything other than the below set of flags
- */
- flags &= O_DIRECTORY | O_NOFOLLOW | O_PATH;
- acc_mode = 0;
- } else {
...
- op->intent = flags & O_PATH ? 0 : LOOKUP_OPEN;
...
- if (flags & O_DIRECTORY)
- lookup_flags |= LOOKUP_DIRECTORY;
- if (!(flags & O_NOFOLLOW))
- lookup_flags |= LOOKUP_FOLLOW;
- op->lookup_flags = lookup_flags;
- return 0;
- }
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > do_last
点击(此处)折叠或打开
- static int do_last(struct nameidata *nd, struct path *path,
- struct file *file, const struct open_flags *op,
- int *opened, struct filename *name)
- {
...
- if (!(open_flag & O_CREAT)) {
- if (nd->last.name[nd->last.len])
- nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
- if (open_flag & O_PATH && !(nd->flags & LOOKUP_FOLLOW))
- symlink_ok = true;
...
- }
...
- error = lookup_open(nd, path, file, op, got_write, opened);
...
- if (should_follow_link(path->dentry, !symlink_ok)) {
...
- return 1;
- }
...
- error = finish_open(file, nd->path.dentry, NULL, opened);
...
- }
接下来 lookup_open 就不用说了吧,当它返回的时候 path 会站上最终目标,nd 则原地不动,它在等待在观望:如果 path 站上的不是符号链接或者即使是符号链接但是“即使是符号链接也 OK 啦”(3003)就会跟着 path 站上最终目标,然后在 finish_open 中完成打开。
finish_open 主要是调用 do_dentry_open,我们进去看看:
【fs/open.c】 sys_open > do_sys_open > do_filp_open > path_openat > do_last
点击(此处)折叠或打开
- static int do_dentry_open(struct file *f,
- int (*open)(struct inode *, struct file *),
- const struct cred *cred)
- {
...
- if (unlikely(f->f_flags & O_PATH)) {
- f->f_mode = FMODE_PATH;
- f->f_op = &empty_fops;
- return 0;
- }
...
- }
好像这个 O_PATH 情景比上一个 O_RDONLY 还要简单,那我们就再假设一个情景。