web-dev-qa-db-ja.com

O_APPENDフラグと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フラグを示したので、それほど単純ではないと思います

14
karim

_O_APPEND_でファイルを開くと、現在のファイルポインタがlseek(2)の最新の呼び出しまたは最新の読み取り/書き込み操作からのものであるかどうかに関係なく、すべてのデータが最後まで書き込まれます。 open(2)ドキュメント から:

_O_APPEND_
ファイルは追加モードで開かれます。各write(2)の前に、lseek(2)の場合と同様に、ファイルオフセットがファイルの最後に配置されます。

ファイルの終わりと後でデータを書き込みたい場合は、_O_APPEND_なしで開き、 fstat(2) を使用してファイルサイズを取得します( _st_size_内の_struct stat_メンバー、次にそのオフセットを探して終わりを書き込みます。

19
Adam Rosenfield

実際、O_APPENDはwriteの動作にのみ影響し、readの動作には影響しません。ファイルの現在の位置がlseekによってどのように変更されても、writeは常に_append-only_になります。

_O_RDWR | O_APPEND_を含むファイルをopenする場合でも、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;
_

次のデモで確認できます。

_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
_
2
York Tsai

O_APPENDフラグは、ファイルポインタがファイルの終わりのみを指すように強制します。したがって、ファイルの先頭からlseekを実行すると、更新されたファイルポインタの位置がファイルの先頭、つまり古いファイルの終了位置になります。

0
Ajay Kumar