O_APPEND changes a write from two separable actions into one coupled operation: Linux positions the open file description at the current end of the file and performs the write as a single atomic step.
That property matters when multiple writers target one regular file. A sequence built from lseek(fd, 0, SEEK_END) followed by write(fd, ...) does not carry the same append semantics because another writer can change the file between those two system calls.
Append state belongs to the open file description
O_APPEND is a file status flag. File status flags are stored in the open file description rather than in the integer file descriptor itself.
A descriptor created by dup() refers to the same open file description as its source. A descriptor inherited across fork() also continues to refer to that shared object. Changing O_APPEND through fcntl() on one such descriptor therefore affects I/O through the others that share the same open file description.
Two independent calls to open() normally create separate open file descriptions, even when both target the same pathname. Each can still have O_APPEND set, and the filesystem can serialize their append operations at the file boundary.
The distinction is useful because append atomicity does not depend on the writers sharing a file offset object. It depends on each append write being positioned at end-of-file as part of the write operation itself.
Seek then write leaves an interleaving point
A manual append sequence can appear equivalent in a single process:
lseek(fd, 0, SEEK_END);
write(fd, record, record_len);With concurrent writers, the gap between the calls is observable. Two processes can both seek to the same current end, then issue writes after each has selected that position. The resulting behavior is not the append guarantee supplied by O_APPEND.
Opening the file with append status removes that specific gap:
int fd = open("events.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
write(fd, record, record_len);For each write(), the kernel positions the offset at the file’s end immediately before writing, with positioning and data transfer forming the atomic append step.
A prior lseek() does not override this behavior. If O_APPEND remains set, a later write() is placed at end-of-file regardless of the offset selected by the seek.
Atomic append does not make a record protocol atomic
The atomic unit is the individual write operation’s end positioning and write placement. An application-level record split across several write() calls is still several operations.
For example:
write(fd, header, header_len);
write(fd, payload, payload_len);
write(fd, trailer, trailer_len);Another append writer can run between those calls. Each individual call is appended without the seek/write race, but the three pieces are not guaranteed to remain adjacent as one logical transaction.
Applications that require one record to occupy one contiguous append operation can assemble that record before issuing the write, subject to the normal semantics and limits of the target filesystem and I/O interface. writev() can also express several buffers as one vectored write operation when that interface fits the data layout.
A successful write can also be shorter than requested. Append positioning does not remove partial-write handling from robust I/O code.
File offset movement remains visible
After an append write, the open file description’s offset reflects the resulting position. Descriptors sharing that open file description observe the same offset state.
This can produce surprising interactions when code mixes append writes with reads or explicit seeks on a descriptor opened for both reading and writing. The append flag governs write placement; it does not force reads to come from the end.
The separation between read position and append write semantics is especially visible after a seek. A read can use the selected offset, while the next ordinary write() still goes to the current file end because O_APPEND takes precedence for that write.
Per-operation append can avoid persistent append state
Linux also provides RWF_APPEND for pwritev2(). It supplies append behavior for that write operation rather than requiring O_APPEND to remain set on the open file description.
This distinction is useful when a descriptor handles both positioned writes and occasional append writes. Persistent O_APPEND changes the semantics of ordinary writes made through every descriptor sharing the same open file description, while RWF_APPEND scopes the append request to a specific operation.
Recent Linux versions also provide RWF_NOAPPEND for pwritev2(), allowing an operation to bypass an O_APPEND setting and honor its explicit offset. This addresses the long-standing Linux behavior in which positioned writes could still honor append status.
These interfaces make the scope of append policy explicit: it can be state attached to an open file description or a property requested for one vectored write.
Network filesystems can weaken the local guarantee
The Linux open(2) interface documents an important boundary for NFS. NFS does not provide the same native append primitive expected by local Linux file handling, so the client must simulate append behavior. Concurrent appenders can therefore encounter races and file corruption despite using O_APPEND.
That boundary prevents a local system-call guarantee from being treated as a universal distributed-filesystem guarantee. The semantics exposed by a remote filesystem depend on its protocol and implementation, not only on the flags passed to open().
Other filesystems and storage layers can have their own constraints. Append-sensitive formats should treat filesystem semantics as part of the concurrency model rather than assuming every mounted target behaves like a local regular file.
Append placement is separate from durability
O_APPEND controls placement. It does not state that completed writes have reached persistent storage.
A process can append data successfully and still lose recently written bytes after a crash if the relevant data and metadata have not reached durable media according to the filesystem and storage stack’s persistence rules. Flags such as O_DSYNC or O_SYNC, and synchronization calls such as fsync(), address different guarantees.
This separation is central for logs and journals. Correct concurrent placement prevents writers from racing over the same logical end position, while durability policy determines when completed records can survive failures.
O_APPEND therefore solves a narrow but important concurrency problem: each append write selects the then-current end and writes there as one operation. It does not combine multiple writes into a transaction, guarantee persistence, or erase the semantic limits of remote filesystems.