GNU/Linux >> Belajar Linux >  >> Linux

Mengapa data ditulis ke file dibuka dengan flag O_APPEND, selalu ditulis di akhir, bahkan dengan `lseek`?

Saat Anda membuka file dengan O_APPEND , semua data ditulis sampai akhir, terlepas dari apa pun penunjuk file saat ini dari panggilan terakhir ke lseek(2) atau operasi baca/tulis terbaru. Dari open(2) dokumentasi:

O_APPEND
File dibuka dalam mode tambahkan. Sebelum setiap write(2) , offset file diposisikan di akhir file, seolah-olah dengan lseek(2) .

Jika Anda ingin menulis data di bagian akhir file dan di bagian awal nanti, buka tanpa O_APPEND , gunakan fstat(2) untuk mendapatkan ukuran file (st_size anggota dalam struct stat ), lalu cari offset itu untuk menulis bagian akhirnya.


Akibatnya, O_APPEND hanya memengaruhi perilaku write , tetapi bukan read . Apapun bagaimana posisi file saat ini diubah oleh lseek , write akan selalu append-only .

Saat Anda open file dengan O_RDWR | O_APPEND , read masih akan mulai dari awal file.

Dalam manual open (man 2 open ),

O_APPENDFile dibuka dalam mode penambahan. Sebelum setiap tulis (2), offset file diposisikan di akhir file.

Dalam manual write (man 2 write ),

Jika flag O_APPEND dari flag status file disetel, offset file harus disetel ke akhir file sebelum setiap penulisan .

Di kernel Linux fs/ext4 syscall write -> vfs_write -> ext4_file_write_iter , ext4_file_write_iter akan memanggil ext4_write_checks

lalu panggil generic_write_checks

Anda akan menemukan tempat pengaturan 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 berikut dapat memverifikasinya.

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;
}

Hasil keluaran

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

Linux
  1. Memulai dengan perintah tac Linux

  2. Bagaimana cara menambahkan baris baru ke akhir file?

  3. Mengapa net rpc shutdown gagal dengan kredensial yang tepat?

  1. Periksa status file di Linux dengan perintah stat

  2. Mengapa Skrip Bash Tidak Mengenal Alias?

  3. Saya ingin mengubah DPI dengan ImageMagick tanpa mengubah ukuran byte sebenarnya dari data gambar

  1. Mengapa File Descriptor Dibuka Dan Hanya Dibaca Sekali?

  2. Pergi ke Awal atau Akhir File di Vim [Kiat Singkat]

  3. Mengapa 'rm -rf /' yang mengerikan bahkan diizinkan?