创建一个目录并使用“open”返回一个dirfd

2023-12-26

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

int dirfd = open(path, O_DIRECTORY | O_CREAT | O_RDONLY, mode);

这样做的通常方法是替换第一个open打电话给mkdir(3),但这不会打开目录,因此很活泼。

mkdir(path, mode);
DIR *dirp = opendir(path);

这可行吗?我所有的测试要么返回EISDIR or ENOTDIR。另外,手册页open(2) states:

当两个O_CREAT and O_DIRECTORY在标志中指定并且路径名指定的文件不存在,open()将创建一个常规文件(即O_DIRECTORY被忽略)。

从 Linux 5.09 开始,情况似乎仍然如此。我想知道这个问题是否可以解决,或者它是否永远是界面的一部分。

这是一个尝试创建和打开目录的示例程序open:

#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

int main(void) {
    /* const char *path = "directory"; */
    /* int dirfd = openat(AT_FDCWD, path, O_DIRECTORY | O_CREAT | O_RDONLY, 0755); */
    const char *path = "/tmp/test";
    int dirfd = open(path, O_DIRECTORY | O_CREAT | O_RDONLY, 0755);
    if(dirfd < 0) {
        fprintf(stderr, "openat(%s): %s\n", topdir, strerror(errno));
        return EXIT_FAILURE;
    }
    close(dirfd);
    return EXIT_SUCCESS;
}

此外,手册页中的这些行似乎是矛盾的:

  • open(3):

    If O_CREAT and O_DIRECTORY已设置且请求的访问模式都不是O_WRONLY nor O_RDWR,结果未指定。

  • open(2):

    EISDIR路径名指的是一个目录,请求的访问涉及写入(即O_WRONLY or O_RDWR已设置)。


The 人2开 https://www.man7.org/linux/man-pages/man2/open.2.html手册页(链接到 man7.org 上最新的 Linux 手册页)在Bugs使用的部分O_CREAT | O_DIRECTORY将创建一个常规文件。还有这个讨论 https://austin-group-l.opengroup.narkive.com/9z11WMS5/atomic-mkdir-open#post2.

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

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

这并不会使其他进程无法访问该目录,但这种模式被认为足够安全。

这是执行此操作的示例程序:

#define  _POSIX_C_SOURCE  200809L
#define  _ATFILE_SOURCE
#define  _GNU_SOURCE
#include <stdlib.h>
#include <inttypes.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/random.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

#ifndef  RENAME_NOREPLACE
#define  RENAME_NOREPLACE  (1 << 0)
static inline int renameat2(int olddirfd, const char *oldpath,
                            int newdirfd, const char *newpath, unsigned int flags)
{
    int  retval = syscall(SYS_renameat2, olddirfd, oldpath, newdirfd, newpath, flags);
    if (!retval)
        return 0;
    errno = -retval;
    return -1;
}
#endif

/* Xorshift64* pseudo-random number generator.
*/
static uint64_t  prng_state = 0; /* unseeded */

static uint64_t  prng_u64(void)
{
    uint64_t  state = prng_state;
    state ^= state >> 12;
    state ^= state << 25;
    state ^= state >> 27;
    prng_state = state;
    return state * UINT64_C(2685821657736338717);
}

static uint64_t  prng_randomize(void)
{
    uint64_t  state;

    /* Use Linux-specific getrandom() call. */
    {
        ssize_t   n;
        do {
            n = getrandom(&state, sizeof state, 0);
        } while (n == -1 && errno == EINTR);
        if (n == (ssize_t)sizeof state && state != 0) {
            prng_state = state;
            return state;
        }
    }

    /* Fall back to using time as a seed. */
    {
        struct timespec  now;
        size_t           rounds = 250;

        clock_gettime(CLOCK_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_gettime(CLOCK_THREAD_CPUTIME_ID, &now);
        state ^= (uint64_t)now.tv_sec * UINT64_C(3266177)
               ^ (uint64_t)now.tv_nsec * UINT64_C(900904331);

        clock_gettime(CLOCK_MONOTONIC, &now);
        state ^= (uint64_t)now.tv_sec * UINT64_C(24400169)
               ^ (uint64_t)now.tv_nsec * UINT64_C(1926466307);

        /* Make sure state is nonzero */
        state += (!state);

        /* Mix it a bit, to make it less predictable. */
        while (rounds-->0) {
            state ^= state >> 12;
            state ^= state << 25;
            state ^= state >> 27;
        }

        prng_state = state;
        return state;
    }
}

static const 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', '-', '_'
};

/* Create a new directory atomically, returning an open descriptor to it.
   name must be non-empty, and not contain a slash.
*/
int mkdiratfd(const int atfd, const char *dirpath, const char *name, const mode_t mode)
{
    char    buf[32];
    mode_t  curr_umask;
    int     atdirfd, fd;

    /* New directory name cannot be NULL, empty, or contain a slash. */
    if (!name || !*name || strchr(name, '/')) {
        errno = EINVAL;
        return -1;
    }

    /* If dirpath is NULL or empty, we use "." for it. */
    if (!dirpath || !*dirpath)
        dirpath = ".";

    /* Open a handle to the target directory. */
    do {
        atdirfd = openat(atfd, dirpath, O_PATH | O_DIRECTORY | O_CLOEXEC);
    } while (atdirfd == -1 && errno == EINTR);
    if (atdirfd == -1) {
        return -1;
    }

    /* Obtain current umask. */
    curr_umask = umask(0); umask(curr_umask);

    /* Make sure our PRNG has been seeded. */
    if (!prng_state)
        prng_randomize();

    /* Create a temporary random name for the directory. */
    while (1) {
        char *ptr = buf;

        /* Start with a dot, making it "hidden". */
        *(ptr++) = '.';

        /* Use 2*10 = 20 random characters (120 bits) */
        for (int k = 2; k > 0; k--) {
            uint64_t  u = prng_u64();
            int       n = 10;
            while (n-->0) {
                *(ptr++) = base64[u & 63];
                u >>= 6;
            }
        }

        /* Terminate name */
        *ptr = '\0';

        /* Create the temporary directory with access only to current user. */
        if (mkdirat(atdirfd, buf, 0700) == -1) {
            const int  saved_errno = errno;
            if (errno == EINTR || errno == EEXIST)
                continue;
            /* Actual error. */
            close(atdirfd);
            errno = saved_errno;
            return -1;
        }

        /* Successfully created. */
        break;
    }

    /* Open the temporary directory. */
    do {
        fd = openat(atdirfd, buf, O_PATH | O_DIRECTORY | O_CLOEXEC);
    } while (fd == -1 && errno == EINTR);
    if (fd == -1) {
        const int  saved_errno = errno;
        unlinkat(atdirfd, buf, AT_REMOVEDIR);
        close(atdirfd);
        errno = saved_errno;
        return -1;
    }

    /*
     * Note: Other actions, like file creation, etc.
     *       should be done at this stage.
    */

    /* Update directory owner group here, if necessary. */

    /* Update proper access mode. */
    if (fchmodat(atdirfd, buf, mode & (~curr_umask), 0) == -1) {
        const int  saved_errno = errno;
        close(fd);
        unlinkat(atdirfd, buf, AT_REMOVEDIR);
        close(atdirfd);
        errno = saved_errno;
        return -1;
    }

    /* Rename directory. */
    if (renameat2(atdirfd, buf, atdirfd, name, RENAME_NOREPLACE) == -1) {
        const int  saved_errno = errno;
        close(fd);
        unlinkat(atdirfd, buf, AT_REMOVEDIR);
        close(atdirfd);
        if (saved_errno == EPERM)
            errno = EEXIST;
        else
            errno = saved_errno;
        return -1;
    }

    /* Success. */
    close(atdirfd);
    return fd;
}

int main(int argc, char *argv[])
{
    int fd;

    if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        const char *argv0 = (argc > 0 && argv && argv[0] && argv[0][0]) ? argv[0] : "(this)";
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv0);
        fprintf(stderr, "       %s NAME\n", argv0);
        fprintf(stderr, "\n");
        fprintf(stderr, "This program creates directory NAME in the current directory.\n");
        fprintf(stderr, "\n");
        return EXIT_FAILURE;
    }

    fd = mkdiratfd(AT_FDCWD, NULL, argv[1], 0755);
    if (fd == -1) {
        fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

请注意,这使用renameat2()如果 C 库未公开它,则通过原始系统调用。 (它在 2.28 中被添加到 glibc,但从 3.15 开始受到 Linux 内核的支持)。

如果您仍然担心,偏执模式是创建一个临时目录来保存临时目录。打开将成为最终目录的内部目录后,将外部临时目录的模式更改为零,以停止遍历内部树。创建者仍然可以通过开放目录描述符访问内部树。该目录仍然可以重命名,因为它们驻留在同一文件系统上。

我个人不会打扰,因为使用临时名称,并且仅在完成后重命名目录(Linux 中的许多应用程序就是这样做的)就足够安全了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

创建一个目录并使用“open”返回一个dirfd 的相关文章

随机推荐

  • 在 Angular 5 环境中使用 process.env

    我尝试使用标准构建 Angular 5 应用程序ng build prod命令 我想在中设置基本的API Urlenvironment prod ts取决于我的价值process env变量 这是我的文件 export const envi
  • React + Material-UI - 警告:Prop className 不匹配

    由于分配的类名不同 我很难理解 Material UI 组件中客户端和服务器端样式渲染之间的差异 首次加载页面时 类名被正确分配 但刷新页面后 类名不再匹配 因此组件失去其样式 这是我在控制台上收到的错误消息 警告 道具className不
  • POST 请求适用于 Postman,但不适用于 Guzzle

    在我的 Laravel 应用程序中 我定期需要使用 Guzzle 将数据 POST 到 API API 使用不记名令牌进行身份验证 并请求和接受原始 json 为了进行测试 我使用 Postman 访问了 API 一切都运行良好 邮递员标题
  • 使用 mod_rewrite 代理 url

    例如我有一个域名 http example com 和另一个域 http reallylargerdomain name com 我想要有人访问http example com projects http example com proje
  • 我可以强制 NSURLConnection 从缓存加载吗?

    我曾考虑过编写自定义 url 缓存 但似乎 NSURLCache 应该支持磁盘缓存 我想让我的 iPhone 应用程序使用缓存的响应 而无需访问服务器 直到资源变得陈旧 由我从服务器发回的标头确定 我是否必须使用 NSURLConnecti
  • npm install 发出警告,npm 审计修复不起作用

    我正在开发一个带有 net core Web api 的 Angular 应用程序 当我克隆这个存储库时 我尝试在 Angular 应用程序上运行 npm install 但出现了一个奇怪的错误 npm install npm WARN o
  • 在python中获取活动的gtk窗口

    我如何获得活动的句柄gtk Window在Python中 不是我创建的窗口 而是当前聚焦的窗口 答案实际上与操作系统无关 您可以在 GTK 中完成 您可以使用以下命令从应用程序中获取所有顶级窗口的列表gtk window list topl
  • Rails 3,回形针 - 自定义插值

    我在制作自定义插值时遇到了一些麻烦 浏览了我在网上可以找到的每个示例 但无论我做什么 都没有成功 目前我有这个 Model has attached file photo path gt rails root public images i
  • 在 Netbeans 中包含本机库

    我正在尝试从 java 签名的小程序读取便携式设备 我发现了一个 jmtp 库http code google com p jmtp w list http code google com p jmtp w list访问便携式设备 但是当我
  • 如何正确使用 cv::triangulatePoints()

    我正在尝试用 OpenCV 对一些点进行三角测量 我发现了这个cv triangulatePoints 功能 问题是几乎没有相关的文档或示例 我对此有些疑问 它使用什么方法 我对三角测量做了一些研究 有几种方法 线性 线性 LS 特征值 迭
  • 使用审查表创建 ggplot2 生存曲线

    I am trying to create a Kaplan Meier plot with 95 confidence bands plus having the censored data in a table beneath it I
  • 如何在 pyinstaller 中设置隐藏导入

    我有一个包含多个包的大项目 这些包使用公共包中的一组模块 我尝试使用 pyinstaller 在 Windows 上创建 exe 但找不到通用包 这个被削减的项目也存在同样的问题 我的包的组织方式如下树所示 当我使用 python m my
  • 谷歌云存储控制台内容编码为gzip

    我正在使用 Google Cloud Storage 控制台上传文件 我没有使用任何命令行工具 我想在元数据中将内容编码设置为 gzip z 选项 请看下面的截图 z 值是否正确 我为所有 css 和 js 文件设置了值 z 并在 Page
  • 有没有办法从另一个 Android 应用程序获取 Android 应用程序视图层次结构?

    我正在研究在设备上运行 Android 自动化的方法 无需任何工作站连接 我的方法是这样的 我修改了猴子的源代码 并且能够启动第3方应用程序 我没有源访问权限 我也可以将击键发送到应用程序 但现在我面临另一个问题 我需要访问一些 ui 元素
  • 在 Scala 中使用 Spring @Transactional

    我们有一个混合 Java 和 Scala 的项目 它使用 Spring 事务管理 我们使用 Spring 方面将文件与 Transactional 带注释的方法编织在一起 问题是 Scala 类没有与 Spring 事务方面交织在一起 如何
  • HttpClient:禁用分块编码

    我正在使用 Apache Commons HttpClient 和 Restlet 来调用 Restful Web 服务 不幸的是 我的服务器 基于 Ruby on Rails 不喜欢Transfer Encoding chunkedHtt
  • 从 [[class alloc] init] 返回 nil 被认为是好的做法吗?

    这是 Objective C 中的常见习惯用法吗 我只在 NSImage alloc initWithContentsOfFile str 上看到过它 它总是让我认为存在内存泄漏 因为我调用了 alloc 口头禅是 调用 alloc 并且您
  • 在await调用之后,它不会执行下一行[关闭]

    Closed 这个问题是无法重现或由拼写错误引起 help closed questions 目前不接受答案 const blabla async gt const foobar async gt return new Promise re
  • 无法导入 com.google。 .... 为什么?

    我只是尝试编译并运行示例项目 http developer android com training location geofencing html http developer android com training location
  • 创建一个目录并使用“open”返回一个dirfd

    我想用 C 创建一个文件树并避免可能的竞争条件 我的意图是使用open 3 创建根目录并open会返回一个目录文件描述符 dirfd 我将给后续openat 3 mkdirat 3 调用创建树 int dirfd open path O D