The open , read , and write functions can also be used for file I/O. The API differs somewhat, but we ll also look here at how to switch between file and stream mode with fdopen .
Note | These functions are referred to as the base API because they are the platform from which the standard I/O library is built. |
The open function allows us to open or create a new file. Two variations are provided, with their APIs listed here:
int open ( const char *pathname, int flags ); int open ( const char *pathname, int flags, mode_t mode );
The pathname argument defines the file (with path ) to be opened or created (such as temp.txt or /tmp/myfile.txt ). The flags argument is one of O_RDONLY , O_WRONLY , or O_RDWR . One or more of the flags shown in Table 10.3 may also be OR d in, depending on the needs of the open call.
Flag | Description |
---|---|
O_CREAT | Create the file if it doesn t exist. |
O_EXCL | If used with O_CREAT , will return an error if the file already exists, otherwise the file is created. |
O_NOCTTY | If the file descriptor refers to a TTY device, this process will not become the controlling terminal. |
O_TRUNC | The file will be truncated (if it exists) and the length reset to zero if write privileges are permitted. |
O_APPEND | The file pointer is repositioned to the end of the file prior to each write. |
O_NONBLOCK | Opens the file in nonblocking mode. Operations on the file will not block (such as read , write , and so on). |
O_SYNC | write functions are blocked until the data is written to the physical device. |
O_NOFOLLOW | Fail following symbolic links. |
O_DIRECTORY | Fail the open if the file being opened is not a directory. |
O_DIRECT | Attempts to minimize cache effects by doing I/O directly to/from user space buffers (synchronously as with O_SYNC ). |
O_ASYNC | Requests a signal when data is available on input or output of this file descriptor. |
O_LARGEFILE | Request a large filesystem file to be opened on a 32-bit system whose size cannot be represented in 32 bits. |
The third argument for the second open instance is a mode. This mode defines the permissions to be used with the file is created (used only with the flag O_CREAT ). Table 10.4 lists the possible symbolic constants that can be OR d together.
Constant | Use |
---|---|
S_IRWXU | User has read/write/execute permissions. |
S_IREAD | User has read permission. |
S_IWRITE | User has write permission. |
S_IEXEC | User has execute permission. |
S_IRWXG | Group has read/write/execute permissions. |
S_IRGRP | Group has read permission. |
S_IWGRP | Group has write permission. |
S_IXGRP | Group has execute permission. |
S_IRWXO | Others have read/write/execute permissions. |
S_IROTH | Others have read permission. |
S_IWOTH | Others have write permission. |
S_IXOTH | Others have execute permission. |
To open a new file in the tmp directory, we could do this simply as:
int fd; fd = open ( "/tmp/newfile.txt", O_CREAT O_WRONLY );
To instead open an existing file for read, we could open as follows :
int fd; fd = open ( "/tmp/newfile.txt", O_RDONLY );
Reading and writing to these files is done very simply with the read and write API functions.
ssize_t read ( int fd, void *buf, size_t count ); ssize_t write ( int fd, const void *buf, size_t count );
These are used simply with a buffer and a size to represent the number of bytes to read or write, such as:
unsigned char buffer[MAX_BUF+1]; int fd, ret; ... ret = read ( fd, (void *)buffer, MAX_BUF ); ... ret = write ( fd, (void *)buffer, MAX_BUF );
We ll see more examples of these in Chapter 11. What s interesting here is that the same set of API functions to read and write data to a file can also be used for pipes and sockets. This represents a unique aspect of the UNIX-like operating systems, where many types of devices can be represented as files.
Finally, a file descriptor can be attached to a stream by using the fdopen system call. This call has the prototype:
FILE * fdopen ( int filedes, const char *mode );
Therefore, if we ve opened a device using the open function call, we can associate a stream with it using fdopen and then use stream system calls on the device (such as fscanf or fprintf). Consider the following example:
FILE *fp; int fd; fd = open ( "/tmp/myfile.txt", O_RDWR ); fp = fdopen ( fd, "rw" );
Once this is done, we can use read/write with the fd descriptor or fscanf/_fprintf with the fp descriptor.
One other useful API to consider is the pread/pwrite API. These functions require an offset into the file to read or write, but they do not affect the file pointer. These functions have the prototype:
ssize_t pread ( int filedes, void *buf, size_t nbyte, off_t offset ); ssize_t pwrite ( int filedes, void *buf, size_t nbyte, off_t offset );
These functions require that the target be seekable (in other words, regular files) and are used regularly for record I/O in databases.