Slightly More Advanced File Handling in Linux

Two functions, read() and write(), are provided in <unistd.h>. The write() function writes X number of bytes from a buffer, and the read() function tries to read up to X number of bytes, and stores the data in the specified buffer. Both functions require a file descriptor to work and return -1 if the action has failed.

Partial reads are quite common. The read() function should be checked for two conditions: a negative return value, if it has failed, and 0, if it has reached EOF.

#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <cstring>
#include <stdio.h>
#include <stddef.h>

using namespace std;

const int read_buff_length = 16;

int main(void) {

    int lineCount = 0;
    
    int writeFd, readFd, returnVal;
    
    char *path = "test.txt";
    
    char writeBuffer[] = "You, the people have the power - the power to create machines. The power to create happiness! You, the people, have the power to make this life free and beautiful, to make this life a wonderful adventure.";
    
    char readBuffer[read_buff_length+1];
    
    
    //----write to the file------------------------------------
    if((writeFd = open(path, O_CREAT | O_TRUNC | O_WRONLY)) < 0){
        printf("Error opening %s.\n", path);
        exit(EXIT_FAILURE);
    } else {
        printf("File opened!\n");
    }
    
    returnVal = write(writeFd, writeBuffer, sizeof(writeBuffer) / sizeof(char));
    if(returnVal < 0){
        printf("Error writing to file.\n");
        exit(EXIT_FAILURE);
    } else {
        printf("File written to!\n");
    }
    
    if((close(writeFd))<0){
        printf("Error closing %s.\n", path);
        exit(EXIT_FAILURE);
    } else {
        printf("%s closed after writing.\n", path);
    }
    
    //---------read from the file----------------------------------
    if((readFd = open(path, O_RDONLY))<0){
        printf("Error opening %s.\n", path);
        exit(EXIT_FAILURE);
    } else {
        printf("%s opened!\n", path);
    }
    
    while(true){
        returnVal = read(readFd, readBuffer, read_buff_length);
        if(returnVal>0){
            readBuffer[read_buff_length+1]='';
            printf("Line %d: %s\n", ++lineCount, readBuffer);
        } else {
            if(returnVal==0){
                printf("Read completed.\n");
                break;
            } else {
                printf("Problem reading file.\n");
                exit(EXIT_FAILURE);
            }
        }
    }
    
    if((close(writeFd))<0){
        printf("Error closing %s.\n", path);
        exit(EXIT_FAILURE);
    } else {
        printf("%s closed after reading.\n", path);
    }
    
    return 0;
}

The third argument sent to read() and write() is size_t, which defines the number of bytes to be read in or written to the supplied buffer. The size_t type is required by POSIX and is used to store values used in measuring sizes in bytes. The ssize_t type is a signed version of size_t, and is used as the return value type of the read() and write() functions.  The maximum value for size_t is SIZE_MAX and the maximum value of ssize_t is  SSIZE_MAX. The value of SSIZE_MAX is quite large for a single read or write.

#include <stdio.h>
#include <limits.h>


int main(void){
     
    printf("SSIZE_MAX = %ld\n", SSIZE_MAX);
 
    return 0;
    
}

Note that SSIZE_MAX is found in the limits.h header file.

The open() system call can take a third argument; this argument is the file’s mode, of type mode_t, which is defined as the types.h header file.  We can define the mode_t value by OR-ing one or more of the symbolic constants from <sys/stat.h>.

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

using namespace std;

int main(void) {

    printf("User permissions: \n");
    printf("S_IRWXU = %d\n", S_IRWXU);//448
    printf("S_IRUSR = %d\n", S_IRUSR);//256
    printf("S_IWUSR = %d\n", S_IWUSR);//128
    printf("S_IXUSR = %d\n\n", S_IXUSR);//64
    
    printf("Group permissions: \n");
    printf("S_IRWXG = %d\n", S_IRWXG);//56
    printf("S_IRGRP = %d\n", S_IRGRP);//32
    printf("S_IWGRP = %d\n", S_IWGRP);//16
    printf("S_IXGRP = %d\n\n", S_IXGRP);//08
    
    printf("Other permissions: \n");
    printf("S_IRWXO = %d\n", S_IRWXO);//07
    printf("S_IROTH = %d\n", S_IROTH);//04
    printf("S_IWOTH = %d\n", S_IWOTH); //02
    printf("S_IXOTH = %d\n\n", S_IXOTH); //01
    
    return 0;
}

The S_IRWXU permission allows users to read, write and execute a file; the S_IRWXG permission allows group members to read, write and execute a file, and the S_IRWXO permission allows others to read, write and execute a file.

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

int main(void){
    
    char buffer[256];
    
    char *noWrite = "fileOne";
    char *noRead = "fileTwo";
    
    //can write, not read
    mode_t modeOne = S_IWUSR;
    //can read, not write
    mode_t modeTwo = S_IRUSR;
    
    int fileDescriptor;
    
    //-r--------
    if((fileDescriptor = open(noWrite, O_CREAT | O_RDONLY,modeTwo))<0){
        printf("Could not open file %s.\n", noWrite);
        exit(EXIT_FAILURE);
    }
    
    if(close(fileDescriptor)<0){
        printf("Could not close file %s.\n", noWrite);
        exit(EXIT_FAILURE);
    }
    
    //--w-------
    if((fileDescriptor = open(noRead, O_CREAT | O_WRONLY, modeOne))<0){
        printf("Could not open file %s.\n", noRead);
        exit(EXIT_FAILURE);
    }
    
    if(close(fileDescriptor)<0){
        printf("Could not close file %s.\n", noRead);
        exit(EXIT_FAILURE);
    }
    
    //try to open the read file for writing
    if((fileDescriptor = open(noWrite, O_WRONLY))<0){
        printf("Could not open %s for writing.\n", noWrite);
    }
    
    //try to open the write file for reading
    if((fileDescriptor = open(noRead, O_RDONLY))<0){
        printf("Could not open %s for reading.\n", noRead);
    }
   
    return 0;
    
}

Note that the umask defines what permissions cannot be allowed when new files are created. To allow for more permissions than a umask will allow, we must change the permissions after the file has been created.

When a file descriptor is opened in append mode, via the O_APPEND access flag, writes do not occur at the file descriptor’s current file position. Instead, writes occur at the end of the file.

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

int main(void){
    
    int i = 0;
    pid_t pid;
    ssize_t retVal;
    int fd;
    char buffer[256];
    char *path = "xyz";
    
    printf("Starting in process %lld\n", getpid());
    if((fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, S_IRWXU))<0){
        printf("Error creating file %s\n", path);
        exit(EXIT_FAILURE);
    } else {
        if(!(close(fd)<0)){
            printf("File %s created.\n", path);
        }
    }
    
    
    if((pid=fork())<0){
       printf("Could not fork process.\n");
       exit(EXIT_FAILURE);
    }
    
    //in child process
    if(pid==0){
       if((fd = open(path, O_WRONLY | O_APPEND))<0){
           printf("Error appending to file %s in %lld\n", path, getpid());
           exit(EXIT_FAILURE);
       }
       while(i++ < 5){
        sprintf(buffer, "Hello from process %d \t (child %d).\n", getpid(), i);
        if((retVal = write(fd, buffer, strlen(buffer)))<0){
            printf("Error writing to file %s in %lld\n", path, getpid());
            exit(EXIT_FAILURE);
        }
        printf("Writing to file from process %lld.\n", getpid());
        sleep(i);
       }
       if(close(fd)<0){
           printf("Error closing file %s in %lld\n", path, getpid());
           exit(EXIT_FAILURE);
       }
    }
    
    //in parent process
    if(pid>0){
        if((fd = open(path, O_WRONLY | O_APPEND))<0){
            printf("Error appending to file %s in %lld\n", path, getpid());
            exit(EXIT_FAILURE);
        }
        while(i++ < 5){
            sprintf(buffer, "Hello from process %d \t (parent %d).\n", getpid(), i);
            if((retVal = write(fd, buffer, strlen(buffer)))<0){
                printf("Error writing to file %s in %lld\n", path, getpid());
                exit(EXIT_FAILURE);
            }
            printf("Writing to file from process %lld\n", getpid());
            sleep(i + 2);
        }
        if(close(fd)<0){
            printf("Error closing file %s in %lld\n", path, getpid());
            exit(EXIT_FAILURE);
        }
    }
    
    return 0;
}

Alright, that’s enough mischief for today. When we next look at C in the *nix environment, we will look at lseek() as well inspecting and modifying file and directory metadata.