Question 4.2 The Linux programming interface

Write a program like cp that, when used to copy a regular file that contains holes (sequences of null bytes), also creates corresponding holes in the target file.

C Implenentation


/**
 * The Linux Programming Interface by Michael Kerrisk
 *
 * Chapter 4 Question 2:
 *
 * Write a program like cp that, when used to copy a regular file that
 * contains holes (sequences of null bytes), also creates corresponding holes in the target file.
 *
 * @author Michael Trottier
 * @version 06/22/2018
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

#define BUF_SIZE 8096

int cp(char *sourceFile, char *destinationFile) {
    /*
     * ssize_t is used for functions whose return value could either be a valid size or a negative value to
     * indicate an error, it is guaranteed to have the reange [-1, SSIZE_MAX], SSIZE_MAX system dependent
     */
    ssize_t bytesRead, writeStatus;

    int inputFd, writeFd, errnoSaved;
    char buffer[BUF_SIZE];
    memset(buffer, 0, sizeof(buffer));

    /*
     * O_RDONLY open for read only
     * O_NONBLOCK open file in nonblocking mode
     */
    fprintf(stdout, "Attempting to open file %s\n", sourceFile);
    inputFd = open(sourceFile, O_RDONLY, O_NONBLOCK);
    errnoSaved = errno;

    /*
     * Will return negative one if open system call fails
     * errno man page: http://man7.org/linux/man-pages/man3/errno.3.html
     */
    if (0 > inputFd) {
        fprintf(stderr, strerror(errnoSaved));
        fprintf(stderr, "Couldn't open file: %s\n", sourceFile);
        return -1;
    }

    /*
     * O_RDWR open for reading / writing
     * O_CREAT create file if it doesn't already exist
     * O_TRUNC truncate existing file to zero length
     * S_IRUSR read permission bit for the owner of the file 0400
     * S_IWUSR write permissions bit for the owner 0200
     * S_IGRRP read permission bit for teh group owner of the file usually 040
     * S_IWGRP write permission bit for the group owner of the file usually 020
     * S_IROTH read permission bit for other users usually 04
     * S_IWOTH write permission bit for other users usually 02
     */
    fprintf(stdout, "Attempting to open file %s\n", destinationFile);
    writeFd = open(destinationFile, O_RDWR | O_CREAT | O_TRUNC,
            S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);

    /*
     * Will return negative one if open system call fails
     * errno man page: http://man7.org/linux/man-pages/man3/errno.3.html
     */
    errnoSaved = errno;
    if (0 > writeFd) {
        fprintf(stderr, strerror(errnoSaved));
        fprintf(stderr, "Couldn't open file: %s\n", destinationFile);
        return -1;
    }

    while ((bytesRead = read(inputFd, buffer, BUF_SIZE)) > 0) {
        /*
         * Only pass the number of bytes read to the write call so you are not writing junk, also, thils is safe because
         * the body of the while loop will on enter if the bytesRead is greater than zero
         */
        writeStatus = write(writeFd, buffer, (size_t) bytesRead);

        errnoSaved = errno;
        if (-1 == writeStatus) {
            fprintf(stderr, strerror(errnoSaved));
            fprintf(stderr, "Couldn't write to file: %s\n", destinationFile);
            return -1;
        }

        // Returns a void pointer to the buffer no need to check system call
        memset(buffer, 0, sizeof(buffer));
    }

    errnoSaved = errno;
    if (-1 == bytesRead) {
        fprintf(stderr, strerror(errnoSaved));
        fprintf(stderr, "Issue in reading bytes from pipe. %s\n", sourceFile);
        return -1;
    }

    // TODO verify sizes are identical
    int inputCloseStatus = close(inputFd);
    int outputCloseStatus = close(writeFd);

    if (0 <= inputCloseStatus && 0 <= outputCloseStatus) {

    }
}

int main(int argc, char *argv[]) {
    /*
     * Don't run anything if the filename isn't passed in via command line argument
     *
     * argv[0] = the program name, i.e. cp
     * argv[1] = the source destination of the program to cp
     * argv[2] the source destination of the file to cp
     */
    if (argc <= 2) {
        fprintf(stderr, "usage: %s [sourceFile] [destinationFile]\n", argv[0]);
        return 1;
    }

    // Store a reference pointer in memory to store the filename
    char *sourceFile = argv[1];
    char *destinationFile = argv[2];

    return cp(sourceFile, destinationFile);
}