The Directory Was Never Synced

This routine is meant to replace a config file atomically and durably.

int save_config(const char *dir, const char *name, const char *buf, size_t n) {
    char path[512], tmp[512];
    snprintf(path, sizeof path, "%s/%s",     dir, name);
    snprintf(tmp,  sizeof tmp,  "%s/%s.tmp", dir, name);

    int fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) return -1;
    if (write(fd, buf, n) != (ssize_t)n) { close(fd); return -1; }
    if (fsync(fd) != 0)                  { close(fd); return -1; }
    close(fd);

    if (rename(tmp, path) != 0) return -1;
    return 0;
}

save_config returns 0 and the machine loses power one millisecond later. On reboot the config file still holds the old contents, and name.tmp is back in the directory holding the new ones. What did the routine fail to do?

A

fsync() flushes data but not the file's new size, so the inode still records length zero. fdatasync() should have been used, since it forces the metadata needed to read the data back.

B

It never made the rename itself durable. fsync(fd) covers the temporary file, not the directory entry that the rename moves, so the directory update can be lost. The fix is to open(dir, O_RDONLY) and fsync() that descriptor after the rename.

C

The caller is wrong: rename() is not atomic across a power loss, so path could have been missing altogether. A crash-safe replace needs link() followed by unlink() instead.

D

fsync() on a descriptor opened without O_SYNC only pushes the page cache into the drive's volatile write cache. The temporary file must be opened with O_SYNC for the bytes to reach the platter.