Digital Garden
Computer Science
C & System Programming
System Programming
POSIX File I/O

POSIX File I/O

The POSIX standard also defines functions/system calls for file I/O which are commonly found and used on unix systems. You have to remember that in unix everything is a file so these system calls aren't just used for text files but also multiple other things like devices, sockets etc.

File descriptors

When a file is opened or created by a process the kernel assigns a position in an array unique to each process called the file descriptor. Each entry of this array contains a pointer to a file table which stores for each file, the file descriptor, file status flags, and offset. The file table does not itself contain the file, but instead has a pointer to another table, called the vnode table, which has vital information about the file, including its location in memory.

The file descriptors are unique to a process but the integers may by reused by another process without referring to the same file or location within a file. By convention the following are however always the same

FileFile DescriptorPOSIX Symbolic Constant in "unistd.h"
Standard Input0STDIN_FILENO
Standard Output1STDOUT_FILENO
Standard Error2STDERR
How files are represented and managed in Unix systems to ensure that multiple processes can access the same file without interfering with each other.

System Calls

Opening and closing

You can open a file with int open(const char *pathname, int flags, mode_t mode from fcntl.h which will return the smallest int that’s not used in by the current processor as the file descriptor. Once you are done with the file you can int close(int fd) it which will detach the use of the file descriptor for a process. When a process terminates any open file descriptors are automatically closed by the kernel. If something goes wrong with opening a file it will return -1 and you can find the error saved under errno, a global variable.

Possible errors:

ConstantDescription
EACCESthe requested access to the file or directory is not allowed
EISDIRfile refers to a directory and the access requested involved writing
EMFILEthe per-process limit on the number of open file descriptors has been reached
ENFILEthe system-wide limit on the total number of open files has been reached.
ENOENTfile not found and O_CREAT not speciefed
EROFSpathname refers to a file on a read-only filesystem andwrite access was requested
ETXTBSYis busy

Flags

The second argument consists of access, creation & status flags and is created by bitwise OR'ing ('|') the constants you want together.

Access:

ConstantDescription
O_RDONLYopen for reading only
O_WRONLYopen for writing only
O_RDWRopen for reading and writing
O_APPENDappend on each write
O_CREATcreate file if it does not exist: REQUIRES mode
O_TRUNCtruncate size to 0
O_EXCLis specified with O_CREAT, if already exists, then open() fails with the error EEXIST
O_SYNC

Modes

If you used the flag O_CREAT then you must specify with the mode the permissions of the created file by bitwise OR'ing ('|') the constants you want together.

ConstantDescription
S_IRUSRUser-read
S_IWUSRUser-write
S_IRGRPGroup-read
S_IWGRPGroup-write

Reading

The ssize_t read(int fd, void *buf, size_t count) function attempts to read up to count bytes from file descriptor fd into the buffer starting at buf. The read operation starts at the files offset, and increments it by the number of bytes read and returns the amount of bytes it read. If the file offset is at or past the end of file, no bytes are read, and read() returns zero.

Writing

The ssize_t write(int fd, const void *buf, size_t count) function writes up to count bytes from the buffer starting at buf to the file referred to by the file descriptor fd. The number of bytes written may be less than count if, for example, there is insufficient space.

Positioning

For each open file descriptor the kernel holds a file offset, the position where the next read or write will begin. With the off_t lseek(int fd, off_t offset, int whence) function you can change this offset. The value of whence must be one of the constants SEEK_SET, SEEK_CUR, or SEEK_END, to indicate whether the offset is relative to the beginning of the file, the current file position, or the end of the file.

Truncating

With the int ftruncate(int fd, off_t length) function you can truncate the file to a size of precisely length bytes.

Flushing

With the int fsync(int fd) function you can flush the buffers, or in other words synchronize the file states. This has the same effect as adding the O_SYNC flag when opening the file.

Example

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
 
int main(void)
{
    int fd = open("text.txt", O_RDWR | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR);
    char *msg = "Hello World";
    int written = write(fd, msg, strlen(msg));
    printf("wrote %d bytes to %d\n", written, fd);
 
    lseek(fd, -written, SEEK_CUR);
 
    char read_msg[100];
    read(fd, read_msg, 100);
 
    printf("read from file %d: \"%s\"", fd, read_msg);
 
    return 0;
}
output
wrote 11 bytes to 3
read from file 3: "Hello World"