還記得在上一個場景中,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 (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.
大家可以重點看看字型加粗的部分,大緻意思就是使用 O_PATH 将不會真正打開一個檔案,而隻是準備好該檔案的檔案描述符,而且如果使用該标志位的話系統會忽略大部分其他的标志位。特别是如果配合使用 O_NOFOLLOW,那麼遇到符号連結的時候将會傳回這個符号連結本身的檔案描述符,而非符号連結所指的對象。
咱們還是先看看 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;
}
首先是 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
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);
看,兩百多行的函數讓我們連消帶打就剩這麼點了,是以說小的函數才是好函數嘛。先看 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
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 不會真正打開一個檔案,看到這裡大家就明白了吧,這裡的代碼很簡單,一切盡在不言中了。當從 finish_open 傳回時,file 結構體幾乎就是空的,隻有 file.f_path 成員指向了這個檔案,就連 f_op 都是空的。這或許就是 O_PATH 使用說明中一開始闡述的那兩個目的具體表現吧。
好像這個 O_PATH 情景比上一個 O_RDONLY 還要簡單,那我們就再假設一個情景。