0%

首先保证 A.txt 文件是空的。然后编译运行:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void
perror_and_exit (const char *msg)
{
    perror (msg);
    exit (errno);
}

int
main (int argc, char **argv)
{
    int fd;
    char buf[64];

    /* 完成截断 */
    if ((fd = open ("A.txt", O_CREAT | O_WRONLY | O_TRUNC)) == -1)
        perror_and_exit ("open for truncation");
    if (close (fd) == -1)
        perror_and_exit ("close for truncation");

    if ((fd = open ("A.txt", O_APPEND | O_RDWR)) == -1)
        perror_and_exit ("open");

    /* 第一次写入 */
    if (write (fd, "nodoubtnodoubt", 14) == -1)
        perror_and_exit ("write nodoubtnodoubt");

    /* 第二次写入只能写入到末尾,是因为 O_APPEND 存在 */
    if (lseek (fd, 0, SEEK_SET) == -1)
        perror_and_exit ("lseek 64");
    if (write (fd, "lalala", 6) == -1)
        perror_and_exit ("write lalala");

    /*
     * 尽管之前调用过 lseek,但还是无法读出字符,说明 O_APPEND 模式下的 write
     * 也会更新文件偏移量
     */
    if (read (fd, buf, 16) == -1)
        perror_and_exit ("read");
    printf ("read: %s\n", buf);

    /* 再次 lseek 就能读出开头的字符 */
    if (lseek (fd, 0, SEEK_SET) == -1)
        perror_and_exit ("lseek 0");
    if (read (fd, buf, 16) == -1)
        perror_and_exit ("read");
    printf ("read: %s\n", buf);
}

最后 A.txt 文件的内容:

nodoubtnodoubtlalala

输出的内容:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/syscall.h>
#include <sys/times.h>
#include <unistd.h>

extern int errno;

void
err_sys (const char *msg)
{
  perror (msg);
  exit (errno);
}

int
main (int argc, char **argv)
{
  int fd = open ("A.txt", O_WRONLY | O_CREAT | O_TRUNC,
                 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
  if (fd == -1)
    err_sys ("open");
  
  int ret, fd2;
  if ((ret = fcntl (fd, F_SETFD, FD_CLOEXEC)) == -1)
    err_sys ("fcntl set fd failed");
  fd2 = dup (fd);
  if (fd2 == -1)
    err_sys ("dup");
  if ((ret = fcntl (fd, F_GETFD, FD_CLOEXEC)) == -1)
    err_sys ("fcntl get fd failed");
  printf ("ret=%d\n", ret);
  if ((ret = fcntl (fd2, F_GETFD, FD_CLOEXEC)) == -1)
    err_sys ("fcntl get fd2 failed");
  printf ("ret=%d\n", ret);
}

结果:

ret=1
ret=0

FD_CLOEXEC 被清理掉了。

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <sys/times.h>
#include <unistd.h>
#include <sys/param.h>
#include <fcntl.h>
#include <string.h>

extern int errno;

void err_sys(const char *msg) {
    perror(msg);
    exit(errno);
}

int
main (int argc, char **argv)
{
    int fd = open("A.txt", 
                  O_WRONLY | O_CREAT | O_TRUNC, 
                  S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
    if (fd == -1) {
        err_sys("open");
    }
    int fd2 = dup(fd);
    if (fd2 == -1) {
        err_sys("dup");
    }

    const char *msg;
    size_t n;
    
    msg = "first message";
    n = strlen(msg);
    if (write(fd, msg, n) != n) {
        err_sys("write fd");
    }
    msg = "second message";
    n = strlen(msg);
    if (write(fd2, msg, n) != n) {
        err_sys("write fd2");
    }
}

结果:两条消息按顺序写入了文件,后者并没有覆盖前者。

Caution

应当参考 13 文件输入输出缓冲,当前文件中有些内容可能是有错误的。

syncfs(int fd) 是 Linux 特有。sync / fsync / fdatasync 是 POSIX 标准。

函数功能返回时是否完成写入?
POSIXBSDLinux
fsync(int fd)写入磁盘。不保证含糊不清保证
fdatasync(int fd)写入数据到磁盘,不保证属性被写入。不保证含糊不清保证
sync(void)发起写入磁盘请求。不保证不保证保证?
syncfs(int fd)发起写入磁盘请求。\\不保证

fsyncfdatasync 的保证性比 sync 更强。受接口限制,在想要保证写入时只能选择它们。

按照 Hacker News 帖子 的说法,BSD 上不少厂商是没有做到 fsync 返回时数据已写入的保证的,比如 macOS。