C 创建一个目录并返回一个带有“open”的dirfd`

C 创建一个目录并返回一个带有“open”的dirfd`,c,linux,directory,posix,system-calls,C,Linux,Directory,Posix,System Calls,我想在C中创建一个文件树,避免可能的争用条件。我的意图是使用open(3)创建根目录,并且open将返回一个目录文件描述符(dirfd),我将给后续的openat(3)/mkdirat(3)调用创建树 int dirfd=open(路径,O|u目录| O|u创建| O|RDONLY,模式); 通常的做法是将第一个open调用替换为mkdir(3),但这样做不会打开目录,因此是快速的 mkdir(路径、模式); DIR*dirp=opendir(路径); 这可行吗?我的所有测试要么返回EISD

我想在C中创建一个文件树,避免可能的争用条件。我的意图是使用
open(3)
创建根目录,并且
open
将返回一个目录文件描述符(dirfd),我将给后续的
openat(3)
/
mkdirat(3)
调用创建树

int dirfd=open(路径,O|u目录| O|u创建| O|RDONLY,模式);
通常的做法是将第一个
open
调用替换为
mkdir(3)
,但这样做不会打开目录,因此是快速的

mkdir(路径、模式);
DIR*dirp=opendir(路径);
这可行吗?我的所有测试要么返回
EISDIR
要么返回
ENOTDIR
。另外,
open(2)
的手册页说明:

O_create
O_DIRECTORY
都在标志中指定且路径名指定的文件不存在时,
open()
将创建一个常规文件(即忽略
O_DIRECTORY

从Linux5.09开始,情况似乎仍然如此。我想知道这是否可以修复,或者它是否永远是界面的一部分

下面是一个示例程序,用于尝试使用
open
创建和打开目录:

#包括
#包括
#包括
#包括
#包括
#包括
#包括
内部主(空){
/*const char*path=“目录”*/
/*int dirfd=openat(AT_FDCWD,path,O_目录| O|u创建| O|RDONLY,0755)*/
const char*path=“/tmp/test”;
int dirfd=open(路径,O|u目录| O|u创建| O|RDONLY,0755);
if(dirfd<0){
fprintf(stderr,“openat(%s):%s\n”、topdir、strerror(errno));
返回退出失败;
}
关闭(dirfd);
返回退出成功;
}
此外,手册页中的这些行似乎相互矛盾:

  • 打开(3)

    如果设置了
    O_create
    O_DIRECTORY
    ,并且请求的访问模式既不是
    O_WRONLY
    也不是
    O_RDWR
    ,则结果未指定

  • 打开(2)

    EISDIR
    pathname指的是一个目录,请求的访问涉及到写入(即设置了
    O_WRONLY
    O_RDWR

手册页(链接到man7.org上最新的Linux手册页)在bug部分明确指出,使用
O|u create | O_目录
将创建一个常规文件

更重要的是,即使它成功了,在创建成功之后,甚至在调用返回到程序之前,其他进程仍然可以立即访问目录。因此,您担心的竞争窗口无论如何都会存在

常见的模式是在同一目录中创建一个临时目录,该目录具有足够的随机名称(以
开头,将其从典型的文件和目录列表中删除),仅可供当前用户访问;然后填充它;然后调整其接入方式;然后将其重命名为最终名称

这并不意味着其他进程无法访问该目录,但这种模式被认为是足够安全的

下面是一个执行此操作的示例程序:

#定义POSIX_C_SOURCE200809L
#定义文件源
#定义GNU源
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#ifndef重命名\u NOREPLACE
#定义重命名位置(1>12;
州^=州>27;
prng_状态=状态;
返回状态*UINT64_C(2685821657736338717);
}
静态uint64随机(无效)
{
uint64_t状态;
/*使用Linux特定的getrandom()调用*/
{
(三);
做{
n=getrandom(&state,sizeof state,0);
}而(n==1&&errno==EINTR);
如果(n==(ssize_t)大小为state&&state!=0){
prng_状态=状态;
返回状态;
}
}
/*回过头来,把时间当作种子*/
{
现在构造timespec;
轮数=250;
clock\u gettime(clock\u REALTIME和now);
state=(uint64_t)now.tv_sec*uint64_C(270547637)
^(uint64_t)now.tv_nsec*uint64_C(90640031)
^(uint64_t)getpid()*uint64_C(4758041);
clock\u gettime(clock\u线程\u CPUTIME\u ID,&now);
state^=(uint64_t)now.tv_sec*uint64_C(3266177)
^(uint64_t)now.tv_nsec*uint64_C(900904331);
时钟获得时间(时钟单调,&现在);
state^=(uint64_t)now.tv_sec*uint64_C(24400169)
^(uint64_t)now.tv_nsec*uint64_C(1926466307);
/*确保状态为非零*/
状态+=(!状态);
/*混合一点,让它更不可预测*/
while(舍入-->0){
州^=州>>12;
州^=州>27;
}
prng_状态=状态;
返回状态;
}
}
静态常量char base64[64]={
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
‘A’、‘B’、‘C’、‘D’、‘E’、‘F’、‘G’、‘H’、‘I’、‘J’,
‘K’、‘L’、‘M’、‘N’、‘O’、‘P’、‘Q’、‘R’、‘S’、‘T’,
‘U’、‘V’、‘W’、‘X’、‘Y’、‘Z’、‘a’、‘b’、‘c’、‘d’,
‘e’、‘f’、‘g’、‘h’、‘i’、‘j’、‘k’、‘l’、‘m’、‘n’,
‘o’、‘p’、‘q’、‘r’、‘s’、‘t’、‘u’、‘v’、‘w’、‘x’,
‘y’、‘z’、‘-’、‘’
};
/*以原子方式创建一个新目录,并向其返回一个打开的描述符。
名称必须为非空,且不包含斜杠。
*/
int mkdiratfd(常量int atfd,常量char*dirpath,常量char*name,常量mode\u t mode)
{
char-buf[32];
当前任务模式;
int-atdirfd,fd;
/*新目录名不能为NULL、空或包含斜杠*/
如果(!name | |!*name | | strchr(name,“/”)){
errno=EINVAL;
返回-1;
}
/*如果dirpath为NULL或空,则使用“.”*/
如果(!dirpath | |!*dirpath)
dirpath=“.”;
/*打开目标目录的句柄*/
做{
atdirfd=开放AT(atfd,dirpat