Notes

Source: CS:APP3e, Bryant and O’Hallaron: Practice Problem 10.2, Practice Problem 10.3, Practice Problem 10.5, Homework Problem 10.6.

1
2
3
4
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

Provided wrapper functions and associated helper functions

Catch errors and perform other useful tasks.

unix_error()

1
2
3
4
void unix_error(char * msg) {
    fprintf(stderr, "%s: %s\n", msg, strerror(errno));
    exit(0);
}

Close()

1
2
3
4
void Close(int fd) {
    int rc;
    if ((rc = close(fd)) < 0) unix_error("Close error");
}

Dup2()

1
2
3
4
5
int Dup2(int fd1, int fd2) {
    int rc;
    if ((rc = dup2(fd1, fd2)) < 0) unix_error("Dup2 error");
    return rc;
}

Fork()

1
2
3
4
5
pid_t Fork(void) {
    pid_t pid;
    if ((pid = fork()) < 0) unix_error("Fork error");
    return pid;
}

Open()

1
2
3
4
5
6
7
int Open(const char * pathname, int flags, mode_t mode) {
    int rc;
    if ((rc = open(pathname, flags, mode))  < 0) {
        unix_error("Open error");
    }
    return rc;
}

Read()

1
2
3
4
5
6
7
ssize_t Read(int fd, void * buf, size_t count) {
    ssize_t rc;
    if ((rc = read(fd, buf, count)) < 0) {
        unix_error("Read error");
    }
    return rc;
}

Wait()

1
2
3
4
5
pid_t Wait(int * status) {
    pid_t pid;
    if ((pid  = wait(status)) < 0) unix_error("Wait error");
    return pid;
}

Practice problems

Practice Problem 10.2

The disk file foobar.txt contains the six ASCII characters foobar.

Output: c = f

open() converts a filename to a file descriptor and returns the file descriptor number or -1 on error. (The wrapper function Open() catches the error.) read() copies n bytes from the current file position of a file descriptor and stores it in a buffer passed as an argument, returning the number of bytes copied, or 0 if EOF, or -1 on error. (The wrapper function Read() catches the error.)

Here, the integer variables fd1 and fd2 are assigned two different file descriptors for the same file. This means that when we read 1 byte from foobar.txt and assign the result to the buffer c, the character read from foobar.txt is the first character, “f”, each time. In other words, the statement Read(fd1, &c, 1) copies the byte representing the first character, “f”, to the buffer c, and the statement Read(fd2, &c, 1) copies the same byte again. Each file descriptor maintains its own position in the file.

The authors of CSAPP3e suggest that one might easily misread this code and suppose that the statement Read(fd2, &c, 1) reads the second character, “o”, from foobar.txt, instead of the first character, “f”. But that isn’t the case. I did not find this example as confusing as they expected.

1
2
3
4
5
6
7
8
9
10
void pp10_2() {
    int fd1, fd2;
    char c;

    fd1 = Open("foobar.txt", O_RDONLY, 0);
    fd2 = Open("foobar.txt", O_RDONLY, 0);
    Read(fd1, &c, 1);
    Read(fd2, &c, 1);
    printf("c = %c\n", c);
}

Practice Problem 10.3

The disk file foobar.txt contains the six ASCII characters foobar.

Output: c = o

fork() returns 0 to the newly created child process. Here, if we are in the child process, we read one byte of foobar.txt and store it in the buffer c. At this point, the buffer c stores a byte representing the character “f”.

Next, we wait for the (for any) child process to complete, then we read another byte from foobar.txt. Since the child process inherited the file descriptor from the parent process, and has now completed, and we are reading from the same file descriptor, this second read() operation now stores a byte representing the second character in “foobar”, which is “o”.

1
2
3
4
5
6
7
8
9
10
11
12
13
void pp10_3() {
    int fd;
    char c;

    fd = Open("foobar.txt", O_RDONLY, 0);
    if (Fork() == 0) {
        Read(fd, &c, 1);
        exit(0);
    }
    Wait(NULL);
    Read(fd, &c, 1);
    printf("c = %c\n", c);
}

Practice Problem 10.5

The disk file foobar.txt contains the six ASCII characters foobar.

Output: c = o

dup2() duplicates the specified file descriptor. Here, we read the first byte of foobar.txt into c using file descriptor fd2. fd2 is now at byte position 1 in foobar.txt. Next, we call dup2() on fd2, assigning it to fd1. Now, fd1 is also at byte position 1 in foobar.txt. When we read one more byte from foobar.txt using fd1, the buffer c stores the second character, in “foobar”, which is “o”.

1
2
3
4
5
6
7
8
9
10
11
void pp10_5() {
    int fd1, fd2;
    char c;

    fd1 = Open("foobar.txt", O_RDONLY, 0);
    fd2 = Open("foobar.txt", O_RDONLY, 0);
    Read(fd2, &c, 1);
    Dup2(fd2, fd1);
    Read(fd1, &c, 1);
    printf("c = %c\n", c);
}

Homework Problem 10.6

Output: fd2 = 4.

stdin, stdout, and stderr are assigned file descriptors 0, 1, and 2. So, if we create two more file descriptors, those are 3 and 4. If we then close one, we’re back to 3. If we open one more, we’re back up to 4.

1
2
3
4
5
6
7
8
void hwp10_6() {
    int fd1, fd2;
    fd1 = Open("foo.txt", O_RDONLY, 0);
    fd2 = Open("bar.txt", O_RDONLY, 0);
    Close(fd2);
    fd2 = Open("baz.txt", O_RDONLY, 0);
    printf("fd2 = %d\n", fd2);
}

main()

After calling pp10_5() and before calling hwp10_6(), we need to close all file descriptors opened in the previous function calls, so we can get an accurate value when calling hwp10_6(). stdin, stdout, and stderr are assigned file descriptors 0, 1, and 2. We created two more file descriptors (3 and 4) in pp10_2(), one more (5) in pp10_3(), and two more (6 and 7) in pp10_5(). So, before calling hwp10_6(), we need to close file descriptors 3 through 7, inclusive.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main() {
    printf("Calling pp10_2...\n");
    pp10_2();
    printf("\n");

    printf("Calling pp10_3...\n");
    pp10_3();
    printf("\n");

    printf("Calling pp10_5...\n");
    pp10_5();
    printf("\n");

    int fd;
    for (fd = 3; fd <= 7; fd++) Close(fd);

    printf("Calling hwp10_6...\n");
    hwp10_6();
}

Sample output:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ gcc scratch.c && ./a.out
Calling pp10_2...
c = f

Calling pp10_3...
c = o

Calling pp10_5...
c = o

Calling hwp10_6...
fd2 = 4
$