首页 > 其他分享 >open与openat

open与openat

时间:2023-05-11 13:57:46浏览次数:44  
标签:文件 int openat 描述符 flags open

原文:https://evian-zhang.github.io/introduction-to-linux-x86_64-syscall/src/filesystem/open-openat-name_to_handle_at-open_by_handle_at-open_tree.html

系统调用号

open为2,openat为257。

函数原型

内核接口

 
asmlinkage long sys_open(const char __user *filename, int flags, umode_t mode);
asmlinkage long sys_openat(int dfd, const char __user *filename, int flags, umode_t mode);

glibc封装

 
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int openat(int dirfd, const char *pathname, int flags);
int openat(int dirfd, const char *pathname, int flags, mode_t mode);

简介

我们知道,绝大多数文件相关的系统调用都是直接操作文件描述符(file descriptor),而openopenat这两个系统调用是一种创建文件描述符的方式。open系统调用将打开路径为filename的文件,而openat则将打开相对描述符为dirfd的目录,路径为filename的文件。

详细来说,openopenat的行为是

  • filename是绝对路径
    • open打开位于filename的文件
    • openat打开位于filename的文件,忽略dirfd
  • filename是相对路径
    • open打开相对于当前目录,路径为filename的文件
    • openat打开相对于dirfd对应的目录,路径为filename的文件;若dirfd是定义在fcntl.h中的宏AT_FDCWD,则打开相对于当前目录,路径为filename的文件。

接着,是“怎么打开”的问题。openopenat的参数flagsmode控制了打开文件的行为(mode详情请见creat系统调用。TODO:增加超链接)。

flags

用于打开文件的标志均定义在fcntl.h头文件中。

文件访问模式标志(file access mode flag)
  • O_RDONLY

    以只读方式打开。创建的文件描述符不可写。

  • O_WDONLY

    以只写方式打开。创建的文件描述符不可读。

  • O_RDWR

    以读写方式打开。创建的文件描述符既可读也可写。

  • O_EXEC

    以只执行方式打开。创建的文件描述符只可以被执行。只能用于非目录路径。

  • O_SEARCH

    以只搜索方式打开。创建的文件描述符值可以被用于搜索。只能用于目录路径。

POSIX标准要求在打开文件时,必须且只能使用上述标志位中的一个。而glibc的封装则要求在打开文件时,必须且只能使用前三个标志位(只读、只写、读写)中的一个。

文件创建标志(file creation flag)

文件创建标志控制的是openopenat在打开文件时的行为。部分比较常见的标志位有:

  • O_CLOEXEC
    • 文件描述符将在调用exec函数族时关闭。

    • 我们知道,当一个Linux进程使用fork创建子进程后,父进程原有的文件描述符也会复制给子进程。而常见的模式是在fork之后使用exec函数族替换当前进程空间。此时,由于替换前的所有变量都不会被继承,所以文件描述符将丢失,而丢失之后就无法关闭相应的文件描述符,造成泄露。如以下代码

       
      #include <fcntl.h>
      #include <unistd.h>
      
      int main() {
          int fd = open("./text.txt", O_RDONLY);
          if (fork() == 0) {
              // child process
              char *const argv[] = {"./child", NULL};
              execve("./child", argv, NULL); // fd left opened
          } else {
              // parent process
              sleep(30);
          }
      
          return 0;
      }

      其中./child在启动30秒后会自动退出。

      在启动这个程序之后,我们使用ps -aux | grep child找到child对应的进程ID,然后使用

       
      readlink /proc/xxx/fd/yyy

      查看,其中xxx为进程ID,yyyfd中的任意一个文件。我们调查fd中的所有文件,一定能发现一个文件描述符对应text.txt。也就是说,在执行execve之后,子进程始终保持着text.txt的描述符,且没有任何方法关闭它。

    • 解决这个问题的方法一般有两种:

      • fork之后,execve之前使用close关闭所有文件描述符。但是如果该进程在此之前创建了许多文件描述符,在这里就很容易漏掉,也不易于维护。
      • 在使用open创建文件描述符时,加入O_CLOEXEC标志位:  
        int fd = open("./text.txt", O_RDONLY | O_CLOEXEC);
        通过这种方法,在子进程使用execve时,文件描述符会自动关闭。
  • O_CREAT
    • filename路径不存在时,创建相应的文件。
    • 使用此标志时,mode参数将作为创建的文件的文件模式标志位。详情请见creat系统调用。TODO: 增加超链接
  • O_EXCL
    • 该标志位一般会与O_CREAT搭配使用。
    • 如果filename路径存在相应的文件(包括符号链接),则open会失败。
  • O_DIRECTORY
    • 如果filename路径不是一个目录,则失败。
    • 这个标志位是用来替代opendir函数的。TODO: 解释其受拒绝服务攻击的原理。
  • O_TRUNC
    • 如果filename路径存在相应的文件,且以写的方式打开(即O_WDONLYO_RDWR),那么将文件内容清空。
文件状态标志(file status flag)

文件状态标志控制的是打开文件后的后续IO操作。

  • O_APPEND

    • 使用此标志位时,在后续每一次write操作前,会将文件偏移移至文件末尾。(详情请见write)。
  • O_ASYNC

    • 使用信号驱动的IO。后续的IO操作将立即返回,同时在IO操作完成时发出相应的信号。
    • 这种方式在异步IO模式中较少使用,主要由于这种基于中断的信号处理机制比系统调用的耗费更大,并且无法处理多个文件描述符同时完成IO操作。参考What's the difference between async and nonblocking in unix socket?
    • 对正常文件的描述符无效,对套接字等文件描述符有效。
  • O_NONBLOCK

    • 后续的IO操作立即返回,而不是等IO操作完成后返回。

    • 对正常文件的描述符无效,对套接字等文件描述符有效。

    • 对于以此种方式打开的文件,后续的readwrite操作可能会产生特殊的错误——EAGAIN(对于套接字文件还可能产生EWOULDBLOCK)。

      这种错误的含义是接下来的读取或写入会阻塞,常见的原因可能是已经读取完毕了,或者写满了。比如说,当客户端发送的数据被服务器端全部读取之后,再次对以非阻塞形式打开的套接字文件进行read操作,就会返回EAGAINEWOULDBLOCK错误。

  • O_SYNCO_DSYNC

    • 使用O_SYNC时,每一次write操作结束前,都会将文件内容和元信息写入相应的硬件。
    • 使用O_DSYNC时,每一次write操作结束前,都会将文件内容写入相应的硬件(不保证元信息)。
    • 这两种方法可以看作是在每一次write操作后使用fsync
  • O_PATH

    • 仅以文件描述符层次打开相应的文件。
    • 我们使用openopenat打开文件通常有两个目的:一是在文件系统中找到相应的文件,二是打开文件对其内容进行查看或修改。如果传入O_PATH标志位,则只执行第一个目的,不仅耗费更低,同时所需要的权限也更少。
    • 通过O_PATH打开的文件描述符可以传递给closefchdirfstat等只在文件层面进行的操作,而不能传递给readwrite等需要对文件内容进行查看或修改的操作。

其他注意点

此外,还有一些需要注意的。

在新的Linux内核(版本不低于2.26)中,glibc的封装open底层调用的是openat系统调用而不是open系统调用(dirfdAT_FDCWD)。我们可以在glibc源码的sysdeps/unix/sysv/linux/open.c中看到:

 
int
__libc_open (const char *file, int oflag, ...)
{
  int mode = 0;

  if (__OPEN_NEEDS_MODE (oflag))
    {
      va_list arg;
      va_start (arg, oflag);
      mode = va_arg (arg, int);
      va_end (arg);
    }

  return SYSCALL_CANCEL (openat, AT_FDCWD, file, oflag, mode);
}

open的glibc封装实际上就是系统调用openat(AT_FDCWD, file, oflag, mode)

openopenat返回的是文件描述符(file descriptor),在Linux内核中,还有一个概念为文件描述(file description)。操作系统会维护一张全局的表,记录所有打开的文件的信息,如文件偏移、打开文件的状态标志等。这张全局的表的表项即为文件描述。而文件描述符则是对文件描述的引用。

每一次成功调用openopenat,都会在文件描述表中创建一个新的表项,返回的文件描述符即是对该表项的引用。而我们常见的dupfork等复制的文件描述符,则会指向同一个文件描述。

文件描述创建之后,不会随着文件路径的修改而修改。也就是说,当我们通过open打开了某个特定路径下的文件,然后我们将该文件移动到别的路径上,我们后续的readwrite等操作仍能成功。

用法

我们在使用openopenat时,可以有如下的思考顺序:

  1. 打开文件的路径是绝对路径还是相对路径
    • 绝对路径使用openopenat都可以
    • 对于相对路径而言,如果相对于当前目录,则可以使用open,但大部分情况而言还是openat适用性更广(相对于当前目录可以传递AT_FDCWDdirfd参数)
  2. 打开文件是否需要读、写
    • 只需要读,flags加入标志位O_RDONLY
    • 只需要写,flags加入标志位O_WDONLY
    • 既需要读,又需要写,flags加入标志位O_RDWR
  3. 对于可能会产生子进程并使用exec函数族的程序,flags加入标志位O_CLOEXEC
  4. 如果需要对文件进行写入:
    • 如果需要在写之前清空文件内容,flags加入标志位O_TRUNC
    • 如果需要在文件末尾追加,flags加入标志位O_APPEND
    • 如果文件不存在的时候需要创建文件,flags加入标志位O_CREAT,并且传递相应的文件模式标志位给mode

以下几种都是合理的使用方式:

 
int fd1 = open(filename, O_RDONLY);
int fd2 = open(filename, O_RDWR | O_APPEND);
int fd3 = open(filename, O_WDONLY | O_CLOEXEC | O_TRUNC);

实现

openopenat的实现均位于fs/open.c文件中,与其相关的函数是do_sys_open:

 
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
    struct open_flags op;
    int fd = build_open_flags(flags, mode, &op);
    struct filename *tmp;

    if (fd)
        return fd;

    tmp = getname(filename);
    if (IS_ERR(tmp))
        return PTR_ERR(tmp);

    fd = get_unused_fd_flags(flags);
    if (fd >= 0) {
        struct file *f = do_filp_open(dfd, tmp, &op);
        if (IS_ERR(f)) {
            put_unused_fd(fd);
            fd = PTR_ERR(f);
        } else {
            fsnotify_open(f);
            fd_install(fd, f);
        }
    }
    putname(tmp);
    return fd;
}

由其实现可知,无论路径是否一样,flag是否相同,open总会使用新的文件描述符。也就是说:

 
int a = open("./text.txt", O_RDONLY);
int b = open("./text.txt", O_RDONLY);

尽管调用参数一样,ab依然是不同的。

此外,这个函数调用了do_filp_open函数作为真正的操作,而其核心是实现在fs/namei.c的函数path_openat:

 
static struct file *path_openat(struct nameidata *nd, const struct open_flags *op, unsigned flags)
{
    struct file *file;
    int error;

    file = alloc_empty_file(op->open_flag, current_cred());
    if (IS_ERR(file))
        return file;

    if (unlikely(file->f_flags & __O_TMPFILE)) {
        error = do_tmpfile(nd, flags, op, file);
    } else if (unlikely(file->f_flags & O_PATH)) {
        error = do_o_path(nd, flags, file);
    } else {
        const char *s = path_init(nd, flags);
        while (!(error = link_path_walk(s, nd)) &&
            (error = do_last(nd, file, op)) > 0) {
            nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
            s = trailing_symlink(nd);
        }
        terminate_walk(nd);
    }
    if (likely(!error)) {
        if (likely(file->f_mode & FMODE_OPENED))
            return file;
        WARN_ON(1);
        error = -EINVAL;
    }
    fput(file);
    if (error == -EOPENSTALE) {
        if (flags & LOOKUP_RCU)
            error = -ECHILD;
        else
            error = -ESTALE;
    }
    return ERR_PTR(error);
}

可见对于大部分情况而言,就是按照符号链接的路径找到最终的文件,然后用do_last打开文件。

标签:文件,int,openat,描述符,flags,open
From: https://www.cnblogs.com/itfanr/p/17390826.html

相关文章

  • OpenSeadragon 实战系列dzi图像切割命名规则篇
    序言根据前边的两篇文章,我们已经可以实现图像的显示了。但是现在我们显示的还是由微软软件自动生成的图片,在实际运用中,需要由后端将图片切割,具体切割方式在微软的dzi图片格式说明中也有,地址:https://docs.microsoft.com/en-us/previous-versions/windows/silverlight/dotnet-wi......
  • OpenPCDet复现过程记录
    0、前言OpenPCDet项目之前我就复现过,一个很优秀的项目,这几天又需要用到这个项目,再次复现遇到了不少问题,特此记录复现的流程1、环境准备1.1、前置条件以下是我安装的版本CUDA11.3CUDNN8.2.1CUDA和CUDNN安装可以参考这篇文章https://blog.csdn.net/qq_50195602/articl......
  • Error: cannot open file: winim/lean(nim学习系列)
    某日尝试编译一个文件,报错如下。Error:cannotopenfile:winim/lean根据错误消息,需要安装“winim”,但是安装失败如下所示。cmdshell>nimbleinstallwinim--verboseReadingofficialpackagelistPrompt:winimnotfoundinanylocalpackages.json,checkin......
  • OpenResty学习笔记03:再探WAF
    一.再谈WAF 我们上一篇安装的WAF来自另一位技术大神赵舜东,花名赵班长,一直从事自动化运维方面的架构设计工作。阿里云MVP、华为云MVP、中国SaltStack用户组发起人、新运维社区发起人。虽然并非安全专业出身,但根据他的自述,边学边写,只用了几天的时间就将WAF写出来了,并于2016......
  • OpenCV实现多目标追踪(1)
    目录1.配置参数2.实例化追踪器3.处理视频的每一帧4.框选ROI并追踪本文利用OpenCV已有的七种目标追踪算法,通过对传入视频中感兴趣的目标对象进行框选,实现实时追踪框选对象。步骤:首先需要配置相应参数并创建MultiTracker对象,其次,读取并处理视频的每一帧,接着,框选ROI区域,然后,给M......
  • 调用openAI API出现429错误
    如果不是请求太频繁的话,请检查一下自己账号Usage的期限 openAI给每个账号18美刀、三个月的免费用量,所以过期了就会报429错误......
  • openai模型个性化训练Embedding和fine-tuning区别
    现在基于自然语言和文档进行对话的背后都是使用的基于嵌入的向量搜索。OpenAI在这方面做的很好,它的Cookbook(github.com/openai/openai-cookbook)上有很多案例,最近他们对文档做了一些更新。GPT擅长回答问题,但是只能回答它以前被训练过的问题,如果是没有训练过的数据,比如一些私有数据......
  • OpenSSL测试-大数-2
    任务详情0.在openEuler(推荐)或Ubuntu或Windows(不推荐)中完成下面任务1.基于OpenSSL的大数库计算1000内的素数的乘积(5‘)2.基于OpenSSL的大数库计算你以及后面15位同学的8位学号(数字)的乘积(5‘)3.用Python或bc命令验证计算的正确性(5’)4.**提交代码(或代码链接)和运行结果截......
  • OpenSSL测试-大数
    OpenSSL测试-大数20201331黄文刚0.在openEuler(推荐)或Ubuntu或Windows(不推荐)中完成下面任务基于OpenSSL的大数库计算1000内的素数的乘积(5‘)基于OpenSSL的大数库计算你以及后面15位同学的8位学号(数字)的乘积(5‘)用Python或bc命令验证计算的正确性(5’)提交代码(或代码链接)......
  • Failed to open connection to "session" message bus: Using X11 for dbus-daemon au
    Failedtoopenconnectionto"session"messagebus:UsingX11fordbus-daemonautolaunchwasdisabledatcompiletime,setyourDBUS_SESSION_BUS_ADDRESSinstead4Failedtoopenconnectionto"session"messagebus:UsingX11fordbus-da......