Question 5.2 The Linux Programming Interface

Write a program that opens an existing file for writing with the O_APPEND flag, and then seeks to the beginning of the file before writing some data. Where does the data appear in the file? Why?</P>

Implementation


/**
 * The Linux Programming Interface by Michael Kerrisk
 *
 * Chapter 5 Question 2
 *
 * Write a program that opens an existing file for writing with the O_APPEND flag,
 * and then seeks to the beginning of the file before writing some data.
 * Where does the data appear in the file? Why?
 *
 * Answer: Code below
 * When you seek to the beginning of the file it simply adjusts the kernel's record of the file offset associated with
 * a file descriptor. It does not cause any physical device access.  Applying lseek to a pipe, FIFO, socket or terminal
 * is not permitted and will fail with ESPIPE.
 *
 * In this case since we are seeking to the beginning of the file we will be updating the file offset to the
 * beginning of the file and this is were the application will write to the file.
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

// Use a small buffer size as the whole buffer will be overwritten
#define BUF_SIZE 16

/**
 * 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", strerror(errnoSaved));
    return -1;
}

int chapterFiveQuestionTwo(char *filename) {
    /*
    * 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 writeStatus;

    int writeFd, errnoSaved;

    /*
     * O_RDONLY open for read only
     * O_NONBLOCK open file in nonblocking mode
     * O_APPEND append to the end of the file
     */
    fprintf(stdout, "Attempting to open file %s\n", filename);
    writeFd = open(filename, O_RDWR, O_NONBLOCK, O_APPEND);
    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 > writeFd) {
        return (int) rudimentaryErrorHandler(errnoSaved);;
    }

    /*
     * Change the file offset to the beginning of the file.
     *
     * For each open file, the kernel records a file offset, sometimes also called the read-write
     * offset or pointer. This is the location in the file at which the next read() or write() will commence.
     * The file offset is expressed as an ordinal byte position relative to the start of the file.
     * The first byte of the file is at offset 0.
     * lseek(fd, 0, SEEK_SET);     Start of file
     * lseek(fd, 0, SEEK_END);     Next byte after the end of the file
     * lseek(fd, -1, SEEK_END);    Last byte of file
     * lseek(fd, -10, SEEK_CUR);   Ten bytes prior to current location
     * lseek(fd, 10000, SEEK_END); 10001 bytes past last byte of file
     */
    fprintf(stdout, "Attempting to seek to beginning of file %s\n", filename);
    if (-1 == lseek(writeFd, 0, SEEK_SET)) {
        return (int) rudimentaryErrorHandler(errnoSaved);
    }

    /*
     * Write a string into the file after we seek to the beginning of the file
     */
    fprintf(stdout, "Writing test garbage into %s\n", filename);
    char buffer[BUF_SIZE] = "TEST1234\n";
    writeStatus = write(writeFd, buffer, BUF_SIZE);

    errnoSaved = errno;
    if (-1 == writeStatus) {
        return (int) rudimentaryErrorHandler(errnoSaved);
    }

    // Close the file and ensure it closes successfully
    writeStatus = close(writeFd);
    errnoSaved = errno;
    if (-1 == writeStatus) {
        return (int) rudimentaryErrorHandler(errnoSaved);
    }
}

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 file to write to
     */
    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];

    return chapterFiveQuestionTwo(fileName);
}