这篇博客讲述了
openat
系统调用和相关的TOCTTOU问题
openat v.s open
通常打开一个文件使用的系统调用是:
#include <fcntl.h>
int open(const char *path, int oflag);
open
系统调用和大多数语言提供的打开文件的api一致,包含两个参数:文件路径和打开
模式(只读、只写或者读写)
然而除了open
之外,类Unix系统还提供了另一个打开文件的系统调用openat
:
int openat(int dirfd, const char *path, int oflag);
openat
和open
功能相同,需要的参数本质上一样:需要打开的文件和打开模式。不同之处在于open
函
数直接通过文件路径指定文件,而openat
通过一个目录文件句柄dirfd
和相对路径指定文件
有三种可能:
-
path
参数是绝对路径,fd
参数被忽略,这时和open
函数一样了 -
path
参数是相对路径,fd
参数是相对路径的起始位置的文件描述符 -
path
参数是相对路径,fd
是AT_FDCWD
表示相对路径的起始位置是当前工作目录
openat的用途和TOCTTOU
- 支持使用相对路径在其他目录打开文件,这样能够方便实现多线程使用各自不同的工作目录
- 避免部分TOCTTOU问题
所谓TOCTTOU是一个简称,全称是:Time of Check To Time of Use
翻译成中文是:检查的时刻到使用的时刻
举个例子
char *filename = "/tmp/dir1/dir2/a.txt";
struct stat;
/**
* 先检查文件是否存在(Time of Check)
*/
if (stat(filename, &stat) == -1){
perror();
} else {
/**
* 打开文件(Time of Use)
*/
open(filename, O_WRONLY);
}
先检查文件,然后打开文件,写入或者读取文件,是典型的TOCTTOU问题,因为检查和写入
文件不是原子操作,检查和写入之间可能文件的状态可能已经发生了变化,比如dir1
是
一个链接目录,在检查文件后,其他程序修改了dir1
的指向,在打开文件时,文件已经
不是原本我们检查的那个文件了,也不是我们一开始意图打开的文件。
使用openat
可以一定程度上避免这个问题,因为dirfd
是一个文件描述符,直接指向了
真实的文件,在检查文件的操作后,即使dir1
指向其他目录或者dir1
改名,dirfd
指
向的目录也不会改变了。