I/O Multiplexing

Multiplexing essentially means reading from or writing to multiple file descriptors simultaneously. We can use the select() Linux system call to implement multiplexing. The select() system call can be used to monitor file descriptors for regular files, terminals, pipes, sockets, etc. A call to select() blocks until the given file descriptors are read to perform I/O, or until an optional timeout has passed.

The simplest call to select() contains four arguments plus NULL for the fifth argument. The first argument is an int value that contains the highest numbered file descriptor in any of the sets being monitored, plus one. The second, third, and fourth arguments are pointers to file descriptor sets, which have the data type fd_set.

These file descriptor sets are not manipulated directly, but are instead manipulated via four helper macros, FD_ZERO(), FD_SET(), FD_CLR(), and FD_ISSET(). The FD_ZERO() macro clears the specified set, and should be used every time before calling select(). The FD_SET() macro adds a file descriptor to a given set.

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

int main(void){
    
    fd_set readfds;
    fd_set writefds;
  
    int fdOne, fdTwo, fdThree;
    
    char *files[3] = {"FileOne", "FileTwo", "FileThree"};

    if((fdOne = open(files[0], O_CREAT | O_TRUNC | O_NONBLOCK, S_IRWXU))<0){
        printf("Problem opening the first file descriptor.\n");
        exit(EXIT_FAILURE);
    }
    
    if((fdTwo = open(files[1], O_CREAT | O_TRUNC | O_NONBLOCK, S_IRWXU))<0){
        printf("Problem opening the second file descriptor.\n");
        exit(EXIT_FAILURE);
    }
    
    if((fdThree = open(files[2], O_CREAT | O_TRUNC | O_NONBLOCK, S_IRWXU))<0){
        printf("Problem opening the third file descriptor.\n");
        exit(EXIT_FAILURE);
    }
    
    
    //clear the read and write sets
    FD_ZERO(&readfds);
    FD_ZERO(&writefds);
    
    //add file descriptors to the sets using FD_SET() 
    //macro
    FD_SET(fdOne, &readfds);
    FD_SET(fdTwo, &writefds);
    FD_SET(fdThree, &writefds);
    
    return 0;
    
}

The select() system call lets us do I/O multiplexing. We pass arguments to select() to tell the kernel what file descriptors we are interested in, whether we want to read from or write to the descriptor, and how long we want to wait.

On success, the select() system call returns the number of file descriptors ready for I/O, among the three sets. If there was an error, it returns -1. We can then use the FD_ISSET() macro to determine whether a file descriptor is in the set, meaning that it is ready to be read or to be written to.

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

int main(void){
    
    fd_set readfds;
    int retVal;
    int fd;
    
    fd = open("testing", O_CREAT | O_TRUNC | O_NONBLOCK, S_IRWXU);
    
    if(fd < 0){
        exit(EXIT_FAILURE);
    }
    
    //clear readfds
    FD_ZERO(&readfds);
    //add the file descriptor
    FD_SET(fd, &readfds);
    //add the descriptor for stdin
    FD_SET(STDIN_FILENO, &readfds);
    
    //ok let us see what happens
    //we're just looking to read, so pass in NULL
    //for other three parameters
    retVal = select(fd + 1, &readfds, NULL, NULL, NULL);
    
    if(retVal < 0){
        printf("select() failed.\n");
        exit(EXIT_FAILURE);
    }
    
    if(FD_ISSET(STDIN_FILENO, &readfds)){
        printf("Ready to read from stdin...\n");
    }
    
    if(FD_ISSET(fd, &readfds)){
        printf("File Descriptor %d is ready to read!", fd);
    }
      
    return 0;
    
}

Note what we mean by “ready”: a descriptor in the read set is ready if a read from that descriptor won’t block; likewise, a descriptor in the write set is ready if a write to that descriptor won’t block.

The last argument passed to select() specifies how long we want to wait.  If the argument is NULL, then we will wait forever, or until one of the specified descriptors is ready. If we pass the argument a timeval structure, we will wait the specified number of seconds and microseconds.

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

int main(void){
    
    int retVal;
    timeval t;
    //wait for five seconds
    t.tv_sec = 5;
    t.tv_usec = 0;
    
    fd_set writefds;
    
    FD_ZERO(&writefds);
    
    //1
    FD_SET(STDOUT_FILENO, &writefds);
    //0
    FD_SET(STDIN_FILENO, &writefds);
    
    retVal = select(STDOUT_FILENO + 1, NULL, &writefds, NULL, &t);
    
    if(retVal==0){
        printf("select() timed out.\n");
    }
    if(retVal>0){
        if(FD_ISSET(STDOUT_FILENO, &writefds)){
            printf("stdout is ready.\n");
        }
        if(FD_ISSET(STDIN_FILENO, &writefds)){
            printf("stdin is ready.\n");
        }
    }
    return 0;
    
}

Note that a return value of 0 means that no descriptors are ready and that the timeout has thus passed.

Advertisements

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.

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.

Named Pipes in Linux C

The mkfifo() function is used to create a named piped in the filesystem. The pipe() system call creates an anonymous pipe that can only be used by related processes. A named pipe, in contrast, can be used by any process, since the pipes are visible in the filesystem.

The mkfifo() call takes two arguments, the first being the pathname of the named pipe that we wish to create, and the second represents the read/write permissions for this pipe. The mkfifo() call returns 0 on success or -1 on error.

#include <stdio.h>
#include <sys/stat.h>

int main(void){

    //read, write, execute permissions for owner.
    printf("S_IRWXU = %d\n", S_IRWXU);

    int returnValue;

    //should return 0, if we are running this program
    //for the first time.
    returnValue = mkfifo("FIFOpipe", S_IRWXU);

    printf("The mkfifo() call returned %d\n", returnValue);

    return 0;


}

If we run this program twice (or more) the return value will be -1, as the named pipe will have already been created. We can see the error mkfifo() has encountered via errno, which is defined in the errno.h header file.

#include <stdio.h>
#include <sys/stat.h>
#include <errno.h>

int main(void){

    mode_t theMode = S_IRWXU;

    int returnValue = mkfifo("FIFOpipe", theMode);

    if(returnValue < 0){
        printf("mkfifo() failed.\n");
        printf("errno = %d\n", errno);
        if(errno==EEXIST){
            printf("That file already exists.\n");
            printf("(or we passed in a symbolic link, which we did not.)\n");
        }
    }

    return 0;

}

The mode_t permissions are delineated in the sys/stat.h header file.

To remove a FIFO, we can use the unlink() function which is included in the unistd.h header file.

#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
//note: unistd.h included
//so we can call unlink() to delete the FIFO

int main(void){

    //S_IRWXU is the same value as 
    //'OR'-ing S_IRUSR, S_IWUSR, S_IXUSR
    //(i.e. all permissions for file creator)
    mode_t theMode = S_IRWXU;
    int returnValue;

    printf("S_IRUSR = %d\n", S_IRUSR);
    printf("S_IWUSR = %d\n", S_IWUSR);
    printf("S_IXUSR = %d\n", S_IXUSR);

    printf("S_IRUSR | S_IWUSR | S_IXUSR = %d\n", S_IRUSR | S_IWUSR | S_IXUSR);
    printf("S_IRWXU = %d\n", S_IRWXU);

    returnValue = mkfifo("AnotherPipe", S_IRWXU);
    if(returnValue==0){
        printf("FIFO created successfully.\n");
        printf("Now let's delete it!\n");
        returnValue = unlink("AnotherPipe");
        if(returnValue==0){
            printf("FIFO deleted.\n");
        }
    }


    return 0;
}

Once a named pipe has been created, we can read and write to it just as with any other file.

Review of Pipes in Linux C, with dup2()

A pipe is created by calling the pipe() function. We must pass the pipe() function a two-element integer array as an argument.

Two file descriptors are returned via the integer array argument – the first element, 0, is open for reading, and the second element, 1, is open for writing.

To use the pipe, we first call the pipe() function, then call the fork() function. Since the child process inherits copies of its parent’s file descriptors, we can use them to send data from one process to another.

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

#define MAX_LINE 256

int main(void){

    int i, fileDesc[2];

    pid_t pid;
    char line[MAX_LINE];

    if(pipe(fileDesc)<0){
        printf("pipe error.\n");
        exit(EXIT_FAILURE);
    }

    if((pid=fork())<0){
        printf("fork error.\n");
        exit(EXIT_FAILURE);
    }

    if(pid > 0){
        close(fileDesc[0]);
        char * szPtr = "You are likely to be eaten by a grue.\n";
        write(fileDesc[1], szPtr, strlen(szPtr));
    } else {
        //child process fork() == 0
        close(fileDesc[1]);
        read(fileDesc[0], line, MAX_LINE);
        printf("In child process: %s\n", line);
    
    }

    return 0;
}

Note that the pipe() function returns 0 upon success and -1 upon failure. Since index 1 is used for writing, and index 0 is used for reading, we could help ourselves out by defining token constants or else constant int values to stand in for 0 and 1.

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

#define MAX_LINE 256

#define READ 0
#define WRITE 1

int main(void){

    int thePipe[2];
    pid_t pid;

    char buffer[MAX_LINE+1];

    if(pipe(thePipe)!=0){
        exit(EXIT_FAILURE);
    }

    if((pid=fork())<0){
        exit(EXIT_FAILURE);
    }

    if(pid==0){
        read(thePipe[READ], buffer, MAX_LINE);
        printf("Child read: %s\n", buffer);
        printf("Exiting from child process.\n");
    } else {
        char *str = "I survived the Kobayashi Maru!";
        write(thePipe[WRITE], str, strlen(str));
        wait(NULL);
        printf("Exiting from parent process.\n");
    }


    return 0;
}

For an extra bit of fun, we can place the fork() call in a switch() statement, with the default block containing the logic for executing in the parent process.

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

#define MAX_LINE 256

#define READ_PIPE 0
#define WRITE_PIPE 1

int main(void){

    int thePipe[2];
    
    if(pipe(thePipe)<0){
        exit(EXIT_FAILURE);
    }

    switch(fork()){
        case -1:
            printf("error forking.\n");
            exit(EXIT_FAILURE);
        case 0:
            close(thePipe[READ_PIPE]);
            char *str = "For relaxing times, make it Suntory time.";
            write(thePipe[WRITE_PIPE], str, strlen(str));
            break;
        default:
            close(thePipe[WRITE_PIPE]);
            char buffer[256];
            read(thePiper[READ_PIPE], buffer, MAX_LINE);
            break;
    }


}

We should make a note here on the importance of closing unused pipe descriptors in the reading and writing processes. In the reading process, we should close the write end of the pipe to ensure that when reading it will see EOF. The writing process should close its read descriptor to ensure correct functioning of the SIGPIPE signal. The SIGPIPE signal is sent whenever a writing process attempts to write to a pipe that doesn’t have a read descriptor – i.e., no one receiving. If the writing process still has its read file descriptor open, the signal will not be sent, and the process won’t be informed if it is writing with no one receiving on the other end.

Finally, let’s take a look at dup2(). The dup2() function enables us to duplicate a file descriptor. It’s primarily used to redirect stdin, stdout, and stderr. The dup2() function has the advantage over its predecessor of closing and duplicating the file descriptor as a single atomic action.

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

#define BUFFER_MAX 256

#define READ_PIPE 0
#define WRITE_PIPE 1

int main(void){
    
    pid_t returnPID;

    int thePipe[2];

    if(pipe(thePipe)<0){
        exit(EXIT_FAILURE);
    }

    if((returnPID=fork())<0){
        exit(EXIT_FAILURE);
    }

    if(returnPID==0){
        close(thePipe[READ_PIPE]);
        dup2(thePipe[WRITE_PIPE], 1);    
        printf("Never attribute to malice that which can be adequately explained by stupidity.\n");
    } else {
        close(thePipe[WRITE_PIPE]);
        wait(NULL);
        printf("In parent process:");
        char buffer[BUFFER_MAX];
        read(thePipe[READ_PIPE], buffer, BUFFER_MAX);
        printf("%s\n", buffer);    
    }

    return 0;

}

Note that file descriptor 0 is stdin and file descriptor 1 is stdout.

 

 

 

Files and File Modes in Linux C

Most resources on a *nix system can be accessed as a file, per the everything-is-a-file philosophy. Files come in a number of types, such as regular storage files, named and unnamed pipes, directories, devices, symbolic links and sockets.

Regular files are regular files, pipes are a data channel, directories contain a list of files stored in the directory, device files provide an interface to devices, symbolic links store a path to another file, and sockets are like pipes, but pipes that allow processes on separate machines to communicate.

Most files on a Linux system are either files or directories.

The stat() function can be used to access a file’s metadata. The stat() function accepts a pathname and stores the file’s metadata in a stat structure, which is supplied to it as the second argument. The stat() function’s prototype is stored in sys/stat.h, which also includes the definition for the stat structure (via bits/stat.h).  

Basically, we need to include sys/stat.h. Then, we need to declare a struct stat. Finally, we call stat(), passing as the first parameter the file we want to get the metadata of, and as the second argument we will pass the address of the struct stat variable we declared.

#include <stdio.h>
#include <sys/stat.h>


int main(void){

    char *szFileName = "test.txt";
    struct stat SMetaData;

    stat(szFileName, &SMetaData);

    //inode num is st_ino
    //of type ino_t (unsigned long)
    printf("%s \tinode: %lu\n", szFileName, SMetaData.st_ino);

    return 0;
}

The file’s type and it’s permissions are encoded together in one field in the stat structure, the st_mode field. The file’s mode is 16 bits in length, with the four high-order bits representing the file’s type, and the remaining lower 12 bits representing access permissions and their modifiers.

#include <stdio.h>
#include <sys/stat.h>

int main(void){

    char *szPath = "test.txt";
    mode_t sMode;

    struct stat SMeta;

    stat(szPath, &SMeta);

    sMode = SMeta.st_mode;

    printf("The mode for %s is %d\n", szPath, sMode);

    for(int i = 15; i >= 0; i--){
        if(sMode & (1 << i)){
            putchar('1');
        } else {
            putchar('0');
        }
        if(i==12){
            putchar(' ');
        }
    }

    putchar('\n');    

    return 0;
}

The bitmask for all file type fields is S_IFMT. By anding this value with the st_mode value, we can extract the file type information from the file’s mode field. We recall here that anding is used to apply a mask to a binary value. The binary AND operation returns a 1 when both bits are on, and a 0 where either bit is off, and uses the & operator.

#include <stdio.h>
#include <sys/stat.h>

int main(void){

    char *szPath = "test.txt";
    struct stat SBuff;

    stat(szPath, &SBuff);

    printf("inode: %lu \t", SBuff.st_ino);
    printf("name: %s \t", szPath);
    printf("type: ");

    switch(SBuff.st_mode & S_IFMT){
        case S_IFREG:
            printf("Regular File\n");
            break;
        case S_IFDIR:
            printf("Directory\n");
            break;
        case S_IFBLK:
            printf("Block Device\n");
            break;
        case S_IFCHR:
            printf("Character Device\n");
            break;
        case S_IFSOCK:
            printf("Socket\n");
            break;
        case S_IFLNK:
            printf("Symbolic Link\n");
            break;
    }

    return 0;

}

Again, the file type is encoded in the st_mode field of the stat structure. There are a set of macros that can help us to decipher the file type from the st_mode field. Each macro returns true if the file type is found in the mode, false if otherwise.

#include <stdio.h>
#include <sys/stat.h>

void printType(mode_t iMode);

int main(void){

    char *caArray[] = {"dir", "/run/avahi-daemon/socket", "test.txt", "/dev/sda", "/dev/tty0"};
    struct stat SBuffer;

    for(int i = 0; i < 5; i++){
        stat(caArray[i], &SBuffer);
        printf("File Name: %s\t", caArray[i]);
        printf("Inode: %lu\t", SBuffer.st_ino);
        printf("Type: ");
        printType(SBuffer.st_mode);
        printf("\n");
    }

    return 0;
}


void printType(mode_t iMode){
    if(S_ISREG(iMode)){
        printf("regular file");
        return;
    }
    if(S_ISDIR(iMode)){
        printf("directory");
        return;
    }
    if(S_ISBLK(iMode)){
        printf("block device");
        return;
    }
    if(S_ISCHR(iMode)){
        printf("character device");
        return;
    }
    if(S_ISSOCK(iMode)){
        printf("socket");
        return;
    }
}

The one thing to be aware of is that the stat() follows symbolic links; if we want to get metadata regarding the symbolic link itself, we should use the lstat() function. We will be returning to this at a later time.

 

 

 

Accesing Basic User and Group Information in Linux Using C

There are a set of environment variables consisting of name-value pairs for each program. These variables are often used to control program behavior. We can access the current environment variables using the getenv() function.

In our first program we will use the getenv() function to find out who the current user is.

#include <stdio.h>
#include <stdlib.h>

int main(void){

char *username = getenv(“USER”);

if(username==NULL){
printf(“Could not access USER environment variable.”);
exit(EXIT_FAILURE);
} else {
printf(“User: %s\n”, username);
}

return 0;
}

We can list all users by accessing the /etc/passwd file. The /etc/passwd file is an ASCII file consisting of seven fields separated by semicolons, with one line per user.

The <pwd.h> standard header file defines the functions that read the user database. The setpwnt() and endpwent() functions open and close the password database. Neither of these two functions return a value or accept an argument.

#include <stdio.h>
#include <pwd.h>

int main(void){

//open the password database
setpwent();

//close the password database
endpwent();

return 0;

}

Remember, access to the user database is through the routines declared in pwd.h.

The getpwnam() function retrieves a record from the password file. The function returns a pointer to a passwd structure that is filled in by the function.

#include <stdio.h>
#include <pwd.h>

int main(void){

struct passwd *pwd;
char *name = “aljensen”;

pwd = getpwnam(name);

if(pwd==NULL){
printf(“Could not find user %s.\n”, name);
} else {
printf(“Found user %s.\n”, name);
}

return 0;

}

The passwd structure contains seven fields. This structure is statically allocated and overwritten on each call to the function. The pw_name field contains the username, the pw_uid contains the user ID, and the pw_gid field contains the group ID.

#include <stdio.h>
#include <pwd.h>

int main(void){

struct passwd *pw;

if((pw = getpwnam(“root”))==NULL){
printf(“Problem accessing user information for root.\n”);
}

printf(“User Name = %s\n”, pw->pw_name);
printf(“User ID = %u\n”, pw->pw_uid);
printf(“Group ID = %u\n”, pw->pw_gid);
printf(“Shell = %s\n”, pw->pw_shell);

return 0;

}

The getpwent() function can be used to read through the entire password database, one user at a time. Like the getpwnam() function,it returns a pointer to a passwd structure.

#include <stdio.h>
#include <pwd.h>

int main(void){

struct passwd *ptr;

//rewinds the file
setpwent();

while(1){
//returns NULL when it reaches the end
ptr = getpwent();

if(ptr==NULL){
break;
}

//print out user ID and username
//-5 included to left align 5 column field
printf(“%-5u %s\n”, ptr->pw_uid, ptr->pw_name);

}

//closes the file
endpwent();

return 0;

}

The format of the group database is similar to that of the individual user database, but with four fields. The last field, gr_mem, contains a list of the group’s members. We can iterate through this list using pointer arithmetic.

The group database is typically located at /etc/group. The group database APIs are declared in <grp.h>.

The getgrent() function can be used to list all of the groups in our group database.

#include <stdio.h>
#include <grp.h>

int main(void){

struct group *grp;

int i = 0;

setgrent();

while((grp=getgrent())!=NULL){
printf(“%-5u %s\t”, grp->gr_gid, grp->gr_name);
i=0;
//gr_mem is basically an array of strings
while(*(grp->gr_mem+i)){
printf(“%s “, *(grp->gr_mem+i));
i++;
}
putchar(‘\n’);
}

endgrent();

return 0;

}

Well, that’s enough for today, I think. Please take a look at my Amazon author page http://www.amazon.com/Al-Jensen/e/B008MN382O/