System I/O
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
$