I/O with Linux File Descriptors

In this post let’s work with the low level open(), close(), read(), and write() calls. We will use these system calls to manipulate what are known as regular files, which is to say named bytes of data, organized into a linear array called a byte stream. Each of the four system calls we will look at rely on file descriptors.

File descriptors are small, positive integers that serve the function of index values for the array of open files that the Linux kernel maintains for each process. By default, each process start with three open files: standard input, standard output, and standard error. These correspond to file descriptors 0, 1, and 2, respectively.

A file is opened and a file descriptor is obtained by using the open() system call. The most basic version of open takes two arguments, a pointer to const char and an int. The pointer is the pathname, and the int specifies the access flag. The access flags are: O_CREAT, which creates the file if it does not already exist; O_EXCL, which is used only in conjunction with O_CREAT, it makes open() fail if the file already exists; O_TRUNC, that sets the file’s size to 0; O_APPEND, which causes writes to occur at the end of the file; O_NONBLOCK, which opens the file in non-blocking mode, and O_SYNC, which causes all writes to the file to be written to the disk before the call returns.

To use the open() system call, we need to include the <sys/types.h>, <sys/stat.h>, and <fcntl.h> header files.

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main(void){
    
    printf("O_CREAT = %d\n", O_CREAT);
    printf("O_EXCL = %d\n", O_EXCL);
    printf("O_TRUNC = %d\n", O_TRUNC);
    printf("O_APPEND = %d\n", O_APPEND);
    printf("O_NONBLOCK = %d\n", O_NONBLOCK);
    printf("O_SYNC = %d\n", O_SYNC);
    
    return 0;
    
}

The open() system call associates the file indicated by the pathname argument to a file descriptor, which it returns. Unless it fails, in which case it returns a negative number.

To close a file after we have opened it, use the close() system call. The close() system call takes a single argument, which is a file descriptor. To use close(), we need to include <unistd.h>.

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

int  main(void){
    
    int iFileDesc;
    char path[] = "ciel.txt";
    
    //create file if it doesn't exist
    //open for read / write
    iFileDesc = open(path, O_CREAT | O_RDWR);
    
    if(iFileDesc < 0){
        printf("Something didn't work.\n");
        exit(EXIT_FAILURE);
    } else {
        printf("%s opened.\n", path);
    }
    
    //close the file
    if(close(iFileDesc) < 0){
        printf("Problem closing file.\n");
        exit(EXIT_FAILURE);
    } else {
        printf("%s closed.\n", path);
    }
    
    exit(EXIT_SUCCESS);    
}

Note that the close() system call returns a negative value if it fails. As we have seen, the return value from open() is either the new file descriptor or -1 to indicate an error. When open() returns a file descriptor, it returns the lowest unused integer value.

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>

int main(void){
    
    int fd, fdTwo;
    char pathname[] = "testing.txt";
    
    if((fd = open(pathname, O_CREAT | O_WRONLY))<0){
        printf("could not create %s\n", pathname);
        exit(EXIT_FAILURE);
    } else {
        //file descriptor should be 3
        printf("%s opened with fd = %d\n", pathname, fd);
    }
    
    if((fdTwo = open("other", O_CREAT | O_RDWR))<0){
        printf("could not create second file\n");
        exit(EXIT_FAILURE);
    } else {
        //file descriptor should be 4
        printf("second file opened with fd = %d\n", fdTwo);
    }
    
    if(close(fd)<0){
        printf("Could not close %s\n", pathname);
        exit(EXIT_FAILURE);
    } else {
        printf("%s closed\n", pathname);
    }
    
    if(close(fdTwo)<0){
        printf("Could not close second file.\n");
        exit(EXIT_FAILURE);
    } else {
        printf("second file closed\n");
    }
    
    return 0;
    
}

The write() system call is used to write data from the file corresponding to the file descriptor and is defined in <unistd.h>. On success, write() returns the number of bytes written. On error, -1 is returned. The write() call takes three arguments, the first is the file descriptor, the second is the buffer, and the third is the number of bytes to write.

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(void){
    
    const char *pathname = "test4";
    int fd;
    char *buffer = "The President has been kidnapped by ninjas. Are you a bad enough dude to rescue the President?";
    ssize_t retVal;
    

    fd = open(pathname, O_CREAT | O_TRUNC | O_RDWR);
    if(fd < 0){
        printf("Error opening %s\n", pathname);
        exit(EXIT_FAILURE);
    } 
    
    retVal = write(fd, buffer, strlen(buffer));
    
    if(retVal < 0){
        printf("Error writing to %s\n", pathname);
        exit(EXIT_FAILURE);
    } else {
        printf("Wrote to %d bytes to %s\n", retVal, pathname);
    }
    
    if(close(fd)<0){
        printf("Error closing %s\n", pathname);
        exit(EXIT_FAILURE);
    } else {
        printf("Closed %s\n", pathname);
    }
    
    exit(EXIT_SUCCESS);
    
}

Like write(), the arguments to read() are the file descriptor for the open file, a pointer to a buffer to read data from (instead of write), and the number of bytes to read (instead of write. The return value is -1 if an error occurred.

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

const int buffer_size = 256;

int main(void){
    
    int fd;
    ssize_t bytesRead;
    char pathname[] = "test4";
    char buffer[buffer_size];
    
    if((fd = open(pathname, O_RDONLY))<0){
        printf("Error opening %s\n", pathname);
        exit(EXIT_FAILURE);
    } else {
        printf("%s opened\n", pathname);
    }
    
    bytesRead = read(fd, buffer, sizeof(buffer));
    
    if(bytesRead < 0){
        printf("Could not read %s\n", buffer);
        exit(EXIT_FAILURE);
    } else {
        printf("read: %s\n", buffer);
    }
    
    if(close(fd)<0){
        printf("Could not close %s\n", pathname);
        exit(EXIT_FAILURE);
    } else {
        printf("%s closed\n", pathname);
    }
    
    exit(EXIT_SUCCESS);
    
}

Note that read() can return 0 – in this case, the file position is at EOF. We will look at read() in more detail, as well as review file modes and ownership, in a later post.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s