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
File | File Descriptor | POSIX Symbolic Constant in "unistd.h" |
---|---|---|
Standard Input | 0 | STDIN_FILENO |
Standard Output | 1 | STDOUT_FILENO |
Standard Error | 2 | STDERR |
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:
Constant | Description |
---|---|
EACCES | the requested access to the file or directory is not allowed |
EISDIR | file refers to a directory and the access requested involved writing |
EMFILE | the per-process limit on the number of open file descriptors has been reached |
ENFILE | the system-wide limit on the total number of open files has been reached. |
ENOENT | file not found and O_CREAT not speciefed |
EROFS | pathname refers to a file on a read-only filesystem andwrite access was requested |
ETXTBSY | is busy |
Flags
The second argument consists of access, creation & status flags and is created by bitwise OR'ing ('|') the constants you want together.
Access:
Constant | Description |
---|---|
O_RDONLY | open for reading only |
O_WRONLY | open for writing only |
O_RDWR | open for reading and writing |
O_APPEND | append on each write |
O_CREAT | create file if it does not exist: REQUIRES mode |
O_TRUNC | truncate size to 0 |
O_EXCL | is 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.
Constant | Description |
---|---|
S_IRUSR | User-read |
S_IWUSR | User-write |
S_IRGRP | Group-read |
S_IWGRP | Group-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;
}
wrote 11 bytes to 3
read from file 3: "Hello World"