Chapter 5 Question 4 Linux Programming Interface - Michael Kerrisk

Implement dup() and dup2() using fcntl() and, where necessary, close(). (You may ignore the fact that dup2() and fcntl() return different errno values for some error cases.) For dup2(), remember to handle the special case where oldfd equals newfd. In this case, you should check whether oldfd is valid, which can be done by, for example, checking if fcntl(oldfd, F_GETFL) succeeds. If oldfd is not valid, then the function should return -1 with errno set to EBADF.

Implementation


/**
 * The Linux Programming Interface by Michael Kerrisk
 *
 * Chapter 5 Question 4
 *
 * Implement dup() and dup2() using fcntl() and, where necessary, close(). (You may ignore the fact that dup2()
 * and fcntl() return different errno values for some error cases.) For dup2(), remember to handle the special
 * case where oldfd equals newfd. In this case, you should check whether oldfd is valid, which can be done by,
 * for example, checking if fcntl(oldfd, F_GETFD) succeeds. If oldfd is not valid, then the function
 * should return -1 with errno set to EBADF.
 *
 * Answer: See code below.
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

// buffer size is 1 as defined by the problem
#define BUF_SIZE 64

/**
 * Rudimentary error handler used throughout these example problems
 *
 * @param errnoSaved errno value
 * @return int -1 for error handling
 */
int rudimentaryErrorHandler(int errnoSaved) {
    fprintf(stderr, "%s\n", strerror(errnoSaved));
    return -1;
}

/**
 * The dup call takes an open file descriptor and returns a new descriptor that refers to the same open file
 * description.  The new descriptor is guaranteed to the lowest unused file descriptor
 *
 * @param oldFileDescriptor
 * @return new file descriptor on success or -1 on error
 */
int dup(int oldFileDescriptor) {
    /*
     * verify the old file descriptor is valid
     * F_GETFD = marco used as the command argument to fcntl, to specify that it should return the file descriptor flags
     * associated with the file descriptor argument, the normal return value is an on negative, EBADF is the only
     * return error defined for this macro
     *
     * EBADF = the file descriptor is invalid
     */
    if (0 > fcntl(oldFileDescriptor, F_GETFD)) {
        errno = EBADF;
        return -1;
    }

    /*
     * Use file control to duplicate the existing file descriptor
     * F_DUPFD = duplicate the fd using the lowest-numbered available file descriptor greater than or equal to arg
     * on success the file descriptor is returned
     */
    return fcntl(oldFileDescriptor, F_DUPFD, 0);
}

/**
 * The dup2 system call makes a duplicate of the file descriptor given an old file descriptor using the
 * descriptor number supplied in new file descriptor.  If the file descriptor specified in newfd is already open,
 * dup2 closes it first. (Any error that occurs during this close is silently ignored; safer programming practice
 * is to explicitly close new file descriptor if it is open before the call to dup2.
 *
 * Note the file descriptors table for a process has various Fds that have fd flags and file ptrs.  These
 * file ptrs point to the open file descriptions table, this open file descriptions table has a file offset,
 * status flags and an inode ptr.  The inode ptr points to the i-node table which has a file type, file locks
 * and other various metadata.
 *
 * @param oldFileDescriptor
 * @param newFileDescriptor
 * @return returns the number of the duplicate descriptor, if old file descriptor is not valid throw error EBADF
 */
int dup2(int oldFileDescriptor, int newFileDescriptor) {
    // verify the old file descriptor is valid
    if (0 > fcntl(oldFileDescriptor, F_GETFD)) {
        errno = EBADF;
        return -1;
    }

    // Check the special case in which the old and new file descriptors match
    if (oldFileDescriptor == newFileDescriptor) {
        // If the new and old are the same and old is valid then return the new descriptor
        return newFileDescriptor;
    }

    // Check to see if new file descriptor is open
    int newFileDescriptorOpenCheck = fcntl(newFileDescriptor, F_GETFD);

    /*
     * If the new file descriptor isn't valid, make sure errno isn't EBADF which means file descriptor
     * didn't have an error it just wasn't open
     */
    if (0 > newFileDescriptorOpenCheck && errno != EBADF) {
        return -1;
    } else if ( 0 < newFileDescriptorOpenCheck) {
        // Ignore the closing error, per the question
        close(newFileDescriptor);
    }

    // Use file control operations to duplicate the file descriptor but pass in the file descriptor we want
    return fcntl(oldFileDescriptor, F_DUPFD, newFileDescriptor);
}

int main(int argc, char *argv[]) {
    /*
     * Open a file with the supplied command line parameter, create it if necessary.  Then use the implemented
     * dup and dup2 to create various file descriptors pointing to the same file descriptions.
     *
     * argv[0] = the program name, i.e. cp
     * argv[1] = the file to write to, create it if doesn't exist
     */
    if (argc <= 1) {
        fprintf(stderr, "usage: %s [fileName]\n", argv[0]);
        return 1;
    }

    // Store a reference pointer in memory to store the filename
    char *fileName = argv[1];
    int errnoSaved;

    fprintf(stdout, "Take the filename: %s and open the file\n", fileName);
    int openFd = open(fileName, O_CREAT | O_RDWR | O_NONBLOCK | O_TRUNC,
                      S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);

    fprintf(stdout, "Testing dup, passing in the open fd: %d\n", openFd);
    int testDup = dup(openFd);

    errnoSaved = errno;
    if (-1 == testDup) {
        fprintf(stderr, "There was an error calling dup with fd: %d\n", openFd);
        return rudimentaryErrorHandler(errnoSaved);
    }

    fprintf(stdout, "Successfully used dup! The newFd is now %d\n", testDup);
    fprintf(stdout, "Testing dup2(), passing in the open file descriptor: %d, with new file descriptor 8\n", openFd);
    int testDup2 = dup2(openFd, 8);

    errnoSaved = errno;
    if (-1 == testDup2) {
        fprintf(stderr, "There was an error calling dup2 with file descriptor\n");
        return rudimentaryErrorHandler(errnoSaved);
    }

    fprintf(stdout, "Successfully used dup2!\n");

    // Write to the file three buffers of size 64, for brevity I'm not validating they wrote successfully
    char buffer[BUF_SIZE];
    memset(buffer, 0, sizeof(buffer));
    write(openFd, buffer, BUF_SIZE);
    write(testDup, buffer, BUF_SIZE);
    write(testDup2, buffer, BUF_SIZE);

    // Close all the file descriptors, for brevity I'm not validating they closed successfully
    close(openFd);
    close(testDup);
    close(testDup2);

    // validate the file you passed in is 3*64 bytes long = 192
    return 0;
}