GNU/Linux >> Linux Esercitazione >  >> Linux

Perché i dati vengono scritti in un file aperto con il flag O_APPEND, sempre scritto alla fine, anche con `lseek`?

Quando apri un file con O_APPEND , tutti i dati vengono scritti fino alla fine, indipendentemente dal puntatore del file corrente dall'ultima chiamata a lseek(2) o l'ultima operazione di lettura/scrittura. Dal open(2) documentazione:

O_APPEND
Il file viene aperto in modalità accodamento. Prima di ogni write(2) , l'offset del file è posizionato alla fine del file, come con lseek(2) .

Se vuoi scrivere i dati alla fine del file e poi all'inizio, aprilo senza O_APPEND , usa fstat(2) per ottenere la dimensione del file (st_size membro all'interno di struct stat ), e poi cerca in quell'offset per scrivere la fine.


In effetti, O_APPEND influenza solo il comportamento di write , ma non quello di read . Qualunque sia il modo in cui la posizione corrente di un file viene modificata da lseek , write sarà sempre append-only .

Quando open un file con O_RDWR | O_APPEND , read inizierà comunque dall'inizio del file.

Nel manuale di open (man 2 open ),

O_APPENDIl file viene aperto in modalità append. Prima di ogni scrittura (2), l'offset del file è posizionato alla fine del file.

Nel manuale di write (man 2 write ),

Se il flag O_APPEND dei flag di stato del file è impostato, l'offset del file deve essere impostato alla fine del file prima di ogni scrittura .

Nel kernel Linux fs/ext4 syscall write -> vfs_write -> ext4_file_write_iter ,il ext4_file_write_iter chiamerà ext4_write_checks

quindi chiama generic_write_checks

troverai il posto dove impostare il 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;

La seguente demo può verificarlo.

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

Risultato dell'output

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. Perché l'arresto di net rpc fallisce con le giuste credenziali?

  2. Perché il terribile 'rm -rf /' è persino permesso?

  3. Comportamento di rsync con il file che è ancora in fase di scrittura?

  4. Perché la directory principale è indicata da un segno /?

  5. L'eseguibile Linux non riesce con File non trovato anche se il file è presente e in PATH

Costruisci un desktop Apple retrò con Linux MLVWM

Iniziare con il comando tac di Linux

Come aggiungere una nuova riga alla fine di un file?

Proteggi Linux con il file Sudoers

Voglio cambiare DPI con ImageMagick senza modificare la dimensione effettiva in byte dei dati dell'immagine

Come combinare il comando 'tar' con 'find'