走马观花: Linux 系统调用 open 七日游(六)

简介:     还记得在上一个场景中,build_open_flags 里面有一个对标志位 O_PATH 的判断么?现在我们就来看看这个标志位是干啥的: 【场景二】open(pathname, O_PATH)     这个 O_PATH 似乎是不常用的,咱们先看看它的使用说明:【open(2)】http://man7.
    还记得在上一个场景中,build_open_flags 里面有一个对标志位 O_PATH 的判断么?现在我们就来看看这个标志位是干啥的:
【场景二】open(pathname, O_PATH)
    这个 O_PATH 似乎是不常用的,咱们先看看它的使用说明:
【open(2)】 http://man7.org/linux/man-pages/man2/open.2.html
  1. O_PATH (since Linux 2.6.39)
  2. 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.

  3. The following operations can be performed on the resulting file descriptor:
  4.  
  5. * close(2); fchdir(2) (since Linux 3.5); fstat(2) (since Linux 3.6).

  6. * Duplicating the file descriptor (dup(2), fcntl(2) F_DUPFD, etc.).

  7. * Getting and setting file descriptor flags (fcntl(2) F_GETFD and F_SETFD).

  8. * Retrieving open file status flags using the fcntl(2) F_GETFL operation: the returned flags will include the bit O_PATH.

  9. * 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.

  10. * Passing the file descriptor to another process via a UNIX domain socket (see SCM_RIGHTS in unix(7)).

  11. When O_PATH is specified in flags, flag bits other than O_CLOEXEC, O_DIRECTORY, and O_NOFOLLOW are ignored.

  12. 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.
    大家可以重点看看字体加粗的部分,大致意思就是使用 O_PATH 将不会真正打开一个文件,而只是准备好该文件的文件描述符,而且如果使用该标志位的话系统会忽略大部分其他的标志位。特别是如果配合使用 O_NOFOLLOW,那么遇到符号链接的时候将会返回这个符号链接本身的文件描述符,而非符号链接所指的对象。
    咱们还是先看看 build_open_flags 针对 O_PATH 做了什么手脚:
【fs/open.c】 sys_open > do_sys_open > build_open_flags

点击(此处)折叠或打开

  1. static inline int build_open_flags(int flags, umode_t mode, struct open_flags *op)
  2. {

  ...

  1.     } else if (flags & O_PATH) {
  2.         /*
  3.          * If we have O_PATH in the open flag. Then we
  4.          * cannot have anything other than the below set of flags
  5.          */
  6.         flags &= O_DIRECTORY | O_NOFOLLOW | O_PATH;
  7.         acc_mode = 0;
  8.     } else {

  ...

  1.     op->intent = flags & O_PATH ? 0 : LOOKUP_OPEN;

  ...

  1.     if (flags & O_DIRECTORY)
  2.         lookup_flags |= LOOKUP_DIRECTORY;
  3.     if (!(flags & O_NOFOLLOW))
  4.         lookup_flags |= LOOKUP_FOLLOW;
  5.     op->lookup_flags = lookup_flags;
  6.     return 0;
  7. }
    首先是 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

点击(此处)折叠或打开

  1. static int do_last(struct nameidata *nd, struct path *path,
  2.          struct file *file, const struct open_flags *op,
  3.          int *opened, struct filename *name)
  4. {

  ...

  1.     if (!(open_flag & O_CREAT)) {
  2.         if (nd->last.name[nd->last.len])
  3.             nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
  4.         if (open_flag & O_PATH && !(nd->flags & LOOKUP_FOLLOW))
  5.             symlink_ok = true;

  ...

  1.     }

  ...

  1.     error = lookup_open(nd, path, file, op, got_write, opened);

  ...

  1.     if (should_follow_link(path->dentry, !symlink_ok)) {

  ...

  1.         return 1;
  2.     }

  ...

  1.     error = finish_open(file, nd->path.dentry, NULL, opened);

  ...

  1. }
    看,两百多行的函数让我们连消带打就剩这么点了,所以说小的函数才是好函数嘛。先看 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

点击(此处)折叠或打开

  1. static int do_dentry_open(struct file *f,
  2.              int (*open)(struct inode *, struct file *),
  3.              const struct cred *cred)
  4. {

  ...

  1.     if (unlikely(f->f_flags & O_PATH)) {
  2.         f->f_mode = FMODE_PATH;
  3.         f->f_op = &empty_fops;
  4.         return 0;
  5.     }

  ...

  1. }
    为啥 O_PATH 不会真正打开一个文件,看到这里大家就明白了吧,这里的代码很简单,一切尽在不言中了。当从 finish_open 返回时,file 结构体几乎就是空的,只有 file.f_path 成员指向了这个文件,就连 f_op 都是空的。这或许就是 O_PATH 使用说明中一开始阐述的那两个目的具体表现吧。
    好像这个 O_PATH 情景比上一个 O_RDONLY 还要简单,那我们就再假设一个情景。
目录
相关文章
|
17天前
|
网络协议 Linux 调度
深入探索Linux操作系统的心脏:内核与系统调用####
本文旨在揭开Linux操作系统中最为核心的部分——内核与系统调用的神秘面纱,通过生动形象的语言和比喻,让读者仿佛踏上了一段奇妙的旅程,从宏观到微观,逐步深入了解这两个关键组件如何协同工作,支撑起整个操作系统的运行。不同于传统的技术解析,本文将以故事化的方式,带领读者领略Linux内核的精妙设计与系统调用的魅力所在,即便是对技术细节不甚了解的读者也能轻松享受这次知识之旅。 ####
|
13天前
|
缓存 算法 安全
深入理解Linux操作系统的心脏:内核与系统调用####
【10月更文挑战第20天】 本文将带你探索Linux操作系统的核心——其强大的内核和高效的系统调用机制。通过深入浅出的解释,我们将揭示这些技术是如何协同工作以支撑起整个系统的运行,同时也会触及一些常见的误解和背后的哲学思想。无论你是开发者、系统管理员还是普通用户,了解这些基础知识都将有助于你更好地利用Linux的强大功能。 ####
24 1
|
2月前
|
Docker 容器
14 response from daemon: open \\.\pipe\docker_engine_linux: The system cannot find the file speci
14 response from daemon: open \\.\pipe\docker_engine_linux: The system cannot find the file speci
32 1
|
3月前
|
项目管理 敏捷开发 开发框架
敏捷与瀑布的对决:解析Xamarin项目管理中如何运用敏捷方法提升开发效率并应对市场变化
【8月更文挑战第31天】在数字化时代,项目管理对软件开发至关重要,尤其是在跨平台框架 Xamarin 中。本文《Xamarin 项目管理:敏捷方法的应用》通过对比传统瀑布方法与敏捷方法,揭示敏捷在 Xamarin 项目中的优势。瀑布方法按线性顺序推进,适用于需求固定的小型项目;而敏捷方法如 Scrum 则强调迭代和增量开发,更适合需求多变、竞争激烈的环境。通过详细分析两种方法在 Xamarin 项目中的实际应用,本文展示了敏捷方法如何提高灵活性、适应性和开发效率,使其成为 Xamarin 项目成功的利器。
49 1
|
3月前
|
Linux
揭秘Linux心脏:那些让你的编程事半功倍的主要系统调用
【8月更文挑战第31天】Linux中的系统调用是操作系统提供给应用程序的接口,用于请求内核服务,如文件操作、进程控制等。本文列举了22种主要系统调用,包括fork()、exec()、exit()、wait()、open()、close()、read()、write()等,并通过示例代码展示了如何使用fork()创建新进程及使用open()、write()、close()操作文件。这些系统调用是Linux中最基本的接口,帮助应用程序与内核交互。
43 1
|
3月前
|
C语言
Linux0.11 系统调用进程创建与执行(九)(下)
Linux0.11 系统调用进程创建与执行(九)
33 1
|
3月前
|
存储 Linux 索引
Linux0.11 系统调用进程创建与执行(九)(上)
Linux0.11 系统调用进程创建与执行(九)
75 1
|
3月前
|
安全 Linux 程序员
在Linux中,系统调用是什么?
在Linux中,系统调用是什么?
|
3月前
|
安全 Linux 程序员
在Linux中,什么是系统调用?举例说明其作用是什么?
在Linux中,什么是系统调用?举例说明其作用是什么?
|
3月前
|
存储 Linux API
Linux源码阅读笔记08-进程调度API系统调用案例分析
Linux源码阅读笔记08-进程调度API系统调用案例分析