繁体   English   中英

为什么将数据写入以 O_APPEND 标志打开的文件,总是写在末尾,即使使用 `lseek` 也是如此?

[英]Why is data written to a file opened with O_APPEND flag, always written at the end, even with `lseek`?

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

编写一个程序,使用 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吗?

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

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

如果您想在文件末尾写入数据,然后在文件开头写入数据,请在不使用O_APPEND情况下打开它,使用fstat(2)获取文件大小( struct stat st_size成员),然后寻找该偏移量以写到最后。

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

当你用O_RDWR | O_APPEND open文件时O_RDWR | O_APPEND O_RDWR | O_APPENDread仍将从文件的开头开始。

open ( man 2 open ) 的手册中,

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

write手册( man 2 write )中,

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

在 Linux 内核fs/ext4 syscall write -> vfs_write -> ext4_file_write_iterext4_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

一些运气可能会引导您在 UNIXⓇ 环境中的高级编程一书中找到一个短语,特别是在第 77 页(第 3 版)第 3.10 节中:

如果使用O_APPEND标志打开文件,则在文件表条目的文件状态标志中设置相应的标志。 每次对设置了此 append 标志的文件执行write操作时,文件表条目中的当前文件偏移量首先设置为 i 节点表条目中的当前文件大小。 这会强制每次写入都附加到文件的当前末尾。

这描述了为什么。

打开文件的内核数据结构

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM