【问题标题】:Why is data written to a file opened with O_APPEND flag, always written at the end, even with `lseek`?为什么将数据写入使用 O_APPEND 标志打开的文件,始终写入最后,即使使用 `lseek` 也是如此?
【发布时间】:2014-08-05 02:17:59
【问题描述】:

我被分配了一个编程任务:

编写一个程序,使用 O_APPEND 标志打开现有文件进行写入,然后在写入一些数据之前查找文件的开头。数据出现在文件中的什么位置?为什么?

我想出的是:

main() {
    int fd = open("test.txt", O_WRONLY | O_APPEND);
    lseek(fd, 0, SEEK_SET);
    write(fd, "abc", 3);
    close(fd);
}

通过上面的尝试,我发现数据总是写在文件的末尾。这是为什么?是不是因为我注明了O_APPEND

【问题讨论】:

    标签: c linux unix kernel system-calls


    【解决方案1】:

    当您使用O_APPEND 打开文件时,所有数据都会写入到末尾,无论当前文件指针是来自最近对lseek(2) 的调用还是最近的读/写操作。来自open(2) documentation

    O_APPEND
    该文件以附加模式打开。在每个write(2)之前,文件偏移量位于文件末尾,就像lseek(2)一样。

    如果你想在文件的末尾写入数据,然后在文件的开头,打开它没有O_APPEND,使用fstat(2)获取文件大小(st_size成员在struct stat中),然后寻找该偏移量以写入结尾。

    【讨论】:

    • lseek 的调用不会被忽略。该职位因阅读而受到尊重,即使您不阅读,也可以保留并可以回读。但是,每次写入时,位置都会移回文件末尾。
    • 您也可以使用lseek(fd, 0, SEEK_END) 查找文件末尾(即,而不是获取文件大小)。
    • 当您使用O_APPEND 打开文件时,所有数据都会写入末尾 并非所有数据。根据 POSIX,the pwrite function (ssize_t pwrite(int fildes, const void *buf, size_t nbyte, off_t offset);) 可以写入任何偏移量:“pwrite() 函数应等效于 write()除了它写入给定位置并且不更改文件偏移量(无论 @987654341 是否@ 已设置)。”但是 Linux 上的 pwrite() 已损坏,无论在 offset 中传递什么值,都会将数据附加到文件的末尾。
    • 我对 Adam 的解决方案的原子性有疑问——因为文件不是用 O_APPEND 打开的,在第一个程序的查找和写入之间是否有另一个程序可以写入文件?如果是这样,如果我需要能够定期追加和写入文件,还有什么解决方案?
    • @DmitryM 正是创建 O_APPEND 的原因,因为没有它你不能保证写入文件末尾(因为你必须寻找和写入)
    【解决方案2】:

    实际上,O_APPEND 只影响write 的行为,而不影响read 的行为。无论lseek如何改变文件的当前位置,write将始终append-only

    当您open 使用O_RDWR | O_APPEND 的文件时,read 仍将从文件的开头开始。

    open(man 2 open)的手册中,

    O_APPEND 该文件以附加模式打开。在每次write(2)之前,文件偏移量位于文件末尾。

    write(man 2 write)的手册中,

    如果设置了文件状态标志的 O_APPEND 标志,则文件偏移量 应在每次写入之前设置到文件末尾。

    在Linux内核fs/ext4 syscall write -> vfs_write -> ext4_file_write_iter中, ext4_file_write_iter 将调用 ext4_write_checks

    然后拨打generic_write_checks

    你会找到设置pos = file.size的地方

    /* FIXME: this is for backwards compatibility with 2.4 */
    if (iocb->ki_flags & IOCB_APPEND)
        iocb->ki_pos = i_size_read(inode);
    pos = iocb->ki_pos;
    

    下面的demo可以验证一下。

    cat open_append.cc
    #include <fcntl.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    
    #include <string>
    #include <iostream>
    
    int main(int argc, char *argv[]) {
      std::string path = "./test.txt";
      std::string content = "hello_world";
      std::string read_buf(content.size(), 0x0);
      struct stat st_buf;
      ssize_t bytes_read = -1;
      ssize_t bytes_write = -1;
      int ret = -1;
      off_t cur_off = -1;
      int fd = ::open(path.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0644);
      if (fd < 0) {
        std::cerr << "open err path " << path
                  << " errno " << errno << std::endl;
        return -1;
      }
      std::cout << "open ok path " << path
                << " fd " << fd << std::endl;
    
      // Step 1 write some data into an empty file
      bytes_write = ::write(fd, content.data(), content.size());
      if (bytes_write < 0) {
        std::cerr << "write err fd " << fd
                  << " errno " << errno << std::endl;
        goto out;
      }
      std::cout << "write ok fd " << fd
                << " data " << content
                << " nbytes " << bytes_write << std::endl;
      ::close(fd);
    
      // Step 2 open the file again with O_APPEND
      fd = -1;
      fd = ::open(path.c_str(), O_CREAT | O_RDWR | O_APPEND, 0644);
      if (fd < 0) {
        std::cerr << "open again err path " << path
                  << " errno " << errno << std::endl;
        return -1;
      }
      std::cout << "open again ok path " << path
                << " fd " << fd << std::endl;
    
      // Step 3 the current position of the file NOT affected by O_APPEND
      cur_off = ::lseek(fd, 0, SEEK_CUR);
      if (cur_off < 0) {
        std::cerr << "lseek err SEEK_CUR fd " << fd
                  << " errno " << errno << std::endl;
        goto out;
      }
      // cur_off expected to be 0
      std::cout << "lseek ok SEEK_CUR fd " << fd
                << " cur_off " << cur_off << std::endl;
    
      // Step 4  the read will start from the beginning of the file
      bytes_read = read(fd, (char*)read_buf.data(), content.size());
      if (bytes_read < 0) {
        std::cerr << "read err fd " << fd
                  << " errno " << errno << std::endl;
        goto out;
      }
      std::cout << "read ok fd " << fd
                << " data " << read_buf
                << " nbytes " << bytes_read << std::endl;
    
      // Step 5 change the position to the half of the file size
      cur_off = ::lseek(fd, content.size() / 2, SEEK_SET);
      if (cur_off < 0) {
        std::cerr << "lseek err SEEK_SET fd " << fd
                  << " errno " << errno << std::endl;
        goto out;
      }
      // cur_off expected to be content.size() / 2
      std::cout << "lseek ok SEEK_SET fd " << fd
                << " cur_off " << cur_off << std::endl;
    
      // Step 6 write will append data from the end of the file
      // the current position is ignored
      bytes_write = ::write(fd, content.data(), content.size());
      if (bytes_write < 0) {
        std::cerr << "append write err fd " << fd
                  << " errno " << errno << std::endl;
        goto out;
      }
      std::cout << "append write ok fd " << fd
                << " append data " << content
                << " append nbytes " << bytes_write << std::endl;
    
      // Step 7 the file size is double content.size()
      memset((void*)&st_buf, 0x0, sizeof(struct stat));
      ret = lstat(path.c_str(), &st_buf);
      if (ret < 0) {
        std::cerr << "lstat err path " << path
                  << " errno " << errno << std::endl;
        goto out;
      }
      std::cout << "lstat ok path " << path
                << " st_size " << st_buf.st_size << std::endl;
      ret = 0;
    
    out:
      if (fd >= 0) {
        close(fd);
      }
      return ret;
    }
    

    输出结果

    open ok path ./test.txt fd 3
    write ok fd 3 data hello_world nbytes 11
    open again ok path ./test.txt fd 3
    lseek ok SEEK_CUR fd 3 cur_off 0
    read ok fd 3 data hello_world nbytes 11
    lseek ok SEEK_SET fd 3 cur_off 5
    append write ok fd 3 append data hello_world append nbytes 11
    lstat ok path ./test.txt st_size 22
    

    【讨论】:

      【解决方案3】:

      O_APPEND 标志强制文件指针仅指向文件末尾。因此,如果您从文件开头执行 lseek,它会将更新后的文件指针位置作为文件的开头,即旧文件的结束位置。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-01-04
        • 1970-01-01
        相关资源
        最近更新 更多