COMS 3157 Advanced Programming

Recitation NN: C FILE I/O

You can obtain the skeleton code and solutions for this recitation on CLAC:

git clone ~j-hui/cs3157-pub/examples/cats

Part 0: decho and dcat

In this exercise, we will use two programs, decho and dcat, to experiment with FILE I/O functions provided by the C standard library.

decho takes decimal integers as command-line arguments, and writes those decimal values as individual bytes to stdout:

// decho.c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main(int argc, char **argv) {
    for (int i = 1; i < argc; i++) {
        unsigned char d = atoi(argv[i]);
        fwrite(&d, 1, 1, stdout);
    }
}

dcat receives bytes from its stdin, and prints them as comma-separated decimal integers to its stdout:


// dcat.c
#include <stdio.h>

int main(void) {

    unsigned char d;
    while (fread(&d, 1, 1, stdin))
        printf("%d, ", d);

    printf("\n");
}

We can use these programs together to construct and inspect file streams at a byte-by-byte level:

$ gcc -o decho decho.c && gcc -o dcat dcat.c

$ ./decho 0 1 2 5 10 | ./dcat
0, 1, 2, 5, 10,

Part 1: Cats

Consider the following program, named cats:

// cats.c: cats play with strings!
#include <stdio.h>

int main(void) {
    char buf[8];

    fgets(buf, 8, stdin);
    fputs(buf, stdout);

    return 0;
}

We build and run it as follows:

$ gcc -o cats cats.c

$ ./decho  1  2  3  4  5  6  7  8 | ./cats | ./dcat

    (1.1)

$ ./decho  9 10 11 12 13 14 15 16 | ./cats | ./dcat

    (1.2)

$ ./decho  1  1  0  1  0          | ./cats | ./dcat

    (1.3)

What is the output shown at 1.1–1.3?

Part 2: Jellicle

Now, consider the following program, named jellicle:

// jellicle.c: like cats, except nobody asked for this.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <arpa/inet.h>

int main(int argc, char **argv) {
    assert(argc == 2 && atoi("10") == '\n' && '-' == 45 &&
        '0' == 48 && '1' == 49 && '2' == 50 && '3' == 51 && '4' == 52 &&
        '5' == 53 && '6' == 54 && '7' == 55 && '8' == 56 && '9' == 57);

    char buf[8];
    uint32_t xs[2];

    if (argv[1][0] == '4') {
        fgets((char *)xs, sizeof(xs), stdin);
        if (xs[0])
            xs[0] >>= xs[1];
        else
            xs[0] = ntohl(xs[1]);
        printf("%d\n", *xs);

    } else if (argv[1][0] == '3') {
        char *s = buf + 4;
        while (fread(--s, 1, 1, stdin) > 0)
            ;
        printf("%s\n", buf);

    } else if (argv[1][0] == '2') {
        while (fread(buf, 1, 4, stdin) > 0)
            fputs(buf, stdout);

    } else if (argv[1][0] == '1') {
        argv[1][0] = '4';
        argv[1] = buf;
        argv[0] = "./decho";
        fgets(buf, 3, stdin);
        execv(argv[0], argv);
    }
}

We build and run it as follows:

$ gcc -o jellicle jellicle.c

$ ./decho  0  0  0  0  0  0  0  0 | ./jellicle 4 | ./dcat

    (2.1)

$ ./decho  0  0  0  0  0  0  1  1 | ./jellicle 4 | ./dcat

    (2.2)

$ ./decho  2  0  0  0  0  0  0  0 | ./jellicle 4 | ./dcat

    (2.3)

$ ./decho  0  2  0  0  2  0  0  0 | ./jellicle 4 | ./dcat

    (2.4)

$ ./decho  0  1  2  3             | ./jellicle 3 | ./dcat

    (2.5)

$ ./decho  4  5  6  7             | ./jellicle 3 | ./dcat

    (2.6)

$ ./decho  1  0  1                | ./jellicle 3 | ./dcat

    (2.7)

$ ./decho  4  2  0  0  2  4       | ./jellicle 2 | ./dcat

    (2.8)

$ ./decho  4  2 10 10  2  4       | ./jellicle 2 | ./dcat

    (2.9)

$ ./decho 55 55 55 55 55          | ./jellicle 1 | ./dcat

    (2.10)

What is the outcome of running each command? For 2.1–2.10, if there is a memory error, write ERROR; otherwise, if the output is unpredictable, write UNPREDICTABLE; otherwise, write the output (including commas).