Redirection
Processes
COP-3402
Table of Contents
Recall: redirection in bash
We can use bash to change (redirect) where stdio goes to and comes from.
cat > outfile.txt
grep define < /usr/include/stdio.h
How does bash implement this?
What's the role of standard I/O in making this possible?
Goal
Replace the original standard I/O with a different file.
Let's see how bash implements redirection
Diagram:
- ls
- original stdout
- open a new file
- replace stdout
- exec ls
Why std i/o here? We don't have to or want to rewrite ls, instead we can alter standard i/o, then execute ls as usual.
Recall: file I/O
- open() - open a file for I/O
- read() - read bytes from an open file
- write() - write bytes from an open file
After we open a file, how do we reference that open file?
File descriptors
LPI Figure 5-2
- Index into kernel table of open files
- Returned by open
- Used in calls to read/write
Writing to standard out
write_stdout.c
#include <fcntl.h> // O_RDONLY, open
#include <unistd.h> // read, write
#include <stdlib.h> // exit
#include <stdio.h> // perror, fprintf
#include <string.h> // strlen
int main(int argc, char **argv) {
char *msg1 = "hello, world!\n";
ssize_t msg1c = strlen(msg1);
if (-1 == write(STDOUT_FILENO, msg1, msg1c)) {
perror("write");
exit(EXIT_FAILURE);
}
}
What's the difference between stdout and STDOUT_FILENO?
printf vs. write?
Why don't we have to open STDOUT_FILENO?
Duplicate a file descriptor
man 2 dup # LPI chapter 5
Copies the file descriptor, so that we have two.
Both can write to the same open file.
duplicate_stdout.c
#include <fcntl.h> // O_RDONLY, open
#include <unistd.h> // dup, dup2, read, write
#include <stdlib.h> // exit
#include <stdio.h> // perror, fprintf
#include <string.h> // strlen
int main(int argc, char **argv) {
char *msg1 = "hello, world!\n";
ssize_t msg1c = strlen(msg1);
if (-1 == write(STDOUT_FILENO, msg1, msg1c)) {
perror("write");
exit(EXIT_FAILURE);
}
int dupfd = dup(STDOUT_FILENO);
if (-1 == dupfd) {
perror("dup");
exit(EXIT_FAILURE);
}
sleep(2);
char *msg2 = "goodbye!\n";
ssize_t msg2c = strlen(msg2);
if (-1 == write(dupfd, msg2, msg2c)) {
perror("write");
exit(EXIT_FAILURE);
}
}
Notice how we write to the new file descriptor, yet it still leads to the original stdout (our terminal in this case).
(Optional) duplicate_fd.c
#include <fcntl.h> // O_RDONLY, open
#include <unistd.h> // dup, dup2, read, write
#include <stdlib.h> // exit
#include <stdio.h> // perror, fprintf
#include <string.h> // strlen
int main(int argc, char **argv) {
char *path = "dupfd.out";
int fd = open(path, O_RDWR | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
if (-1 == fd) {
perror("open");
exit(1);
}
char *msg1 = "hello, world!\n";
ssize_t msg1c = strlen(msg1);
if (-1 == write(fd, msg1, msg1c)) {
perror("write");
exit(EXIT_FAILURE);
}
int dupfd = dup(fd);
if (-1 == dupfd) {
perror("dup");
exit(EXIT_FAILURE);
}
char *msg2 = "goodbye!\n";
ssize_t msg2c = strlen(msg2);
if (-1 == write(fd, msg2, msg2c)) {
perror("write");
exit(EXIT_FAILURE);
}
}
This is the same as duplicating stdout, but in this case we open a file and duplicate that open file's descriptor.
Why does dup exist?
Redirection is one use!
The dup2 variant
dup2replaces an existing file descriptor
man 2 dup2 # LPI chapter 5
Redirection is such a common use for dup, dup2 does the replacement for you.
- First it closes any open file at newfd, then updates newfd to point to the same open file as oldfd
See LPI Figure 5-2 for hints about how this could be implemented
Standard I/O file descriptors
What are the file descriptors for stdin, stdout, and stderr?
/usr/include/unistd.h
#define STDIN_FILENO 0 /* Standard input. */ #define STDOUT_FILENO 1 /* Standard output. */ #define STDERR_FILENO 2 /* Standard error output. */
This is the unix convention. The macros abstract away the use of magic numbers for the file descriptors.
Replacing a file descriptor
dup2(oldfd, newfd)
To replace stdout:
dup2(oldfd, STDOUT_FILENO)
redirect_stdout.c
#include <fcntl.h> // O_RDONLY, open
#include <unistd.h> // dup, dup2, read, write
#include <stdlib.h> // exit
#include <stdio.h> // perror, fprintf
#include <string.h> // strlen
int main(int argc, char **argv) {
char *msg1 = "write to original stdout\n";
ssize_t msg1c = strlen(msg1);
if (-1 == write(STDOUT_FILENO, msg1, msg1c)) {
perror("write");
exit(EXIT_FAILURE);
}
char *path = "newfile_redirect_stdout.txt";
int fd = open(path, O_RDWR | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
if (-1 == fd) {
perror("open");
exit(1);
}
if (-1 == dup2(fd, STDOUT_FILENO)) {
perror("dup2");
exit(EXIT_FAILURE);
}
char *msg2 = "written to redirected stdout\n";
ssize_t msg2c = strlen(msg2);
if (-1 == write(STDOUT_FILENO, msg2, msg2c)) {
perror("write");
exit(EXIT_FAILURE);
}
printf("where will printf go now?\n");
}
Why does printf now go to the file instead of the terminal?
(Diagram)
- bash creates process for redirect_stdout, giving it the terminal as stdout
- redirect_stdout writes to stdout (the terminal)
- redirect_stdout opens a new file (redirect_stdout.out)
- redirect_stdout redirects stdout to that file (dup2)
- redirect_stdout writes to stdout, which is now the file
(Optional) Redirecting stdin
#include <fcntl.h> // O_RDONLY, open
#include <unistd.h> // dup, dup2, read, write
#include <stdlib.h> // exit
#include <stdio.h> // perror, fprintf
#include <string.h> // strlen
int main(int argc, char **argv) {
pid_t pid;
if (argc < 2) {
printf("USAGE: %s file_to_read\n", argv[0]);
exit(EXIT_FAILURE);
}
char *path = argv[1];
const int BUFSIZE = 10;
char buf[BUFSIZE];
ssize_t bytes = read(STDIN_FILENO, buf, BUFSIZE);
if (-1 == bytes) {
perror("read");
exit(1);
}
printf("bytes from original stdin: %ld\n", bytes);
for (int i = 0; i < bytes; i++) {
putchar(buf[i]);
}
putchar('\n');
int fd = open(path, O_RDONLY);
if (-1 == fd) {
perror("open");
exit(1);
}
if (-1 == dup2(fd, STDIN_FILENO)) {
perror("dup2");
exit(EXIT_FAILURE);
}
bytes = read(STDIN_FILENO, buf, BUFSIZE);
if (-1 == bytes) {
perror("read");
exit(1);
}
printf("bytes from redirected stdin: %ld\n", bytes);
for (int i = 0; i < bytes; i++) {
putchar(buf[i]);
}
putchar('\n');
}
Redirecting a child process's I/O
- now let's redirect a child's process
- open file in parent
- fork
- gets inherited
- replace stdout
- then call exec
- if program just writes to whatever stdout is, this will redirect to the file
- this is the beauty of stdio and why it works
- programs are written to just use whatever stdio file descriptors are there (which the program can assume have already been opened, which fork guarantees (as long as parent keeps them open), and which the shell controls where to direct i/o to)
- try with ls to illustrate this
child.c
#include <fcntl.h> // O_RDONLY, open
#include <unistd.h> // dup, dup2, read, write, fork
#include <stdlib.h> // exit
#include <stdio.h> // perror, fprintf
#include <string.h> // strlen
#include <inttypes.h> // intmax_t
#include <sys/wait.h> // wait
int main(int argc, char **argv) {
pid_t pid;
if (argc < 2) {
printf("USAGE: %s file_to_write\n", argv[0]);
exit(EXIT_FAILURE);
}
char *path = argv[1];
int fd = open(path, O_RDWR | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
if (-1 == fd) {
perror("open");
exit(1);
}
switch (pid = fork()) {
case -1:
perror("fork");
exit(EXIT_FAILURE);
break;
case 0:
// child
if (-1 == dup2(fd, STDOUT_FILENO)) {
perror("dup2");
exit(EXIT_FAILURE);
}
printf("child process\n");
exit(EXIT_SUCCESS);
break;
default:
// parent
printf("parent process\n");
exit(EXIT_SUCCESS);
break;
}
}
child_exec.c
#include <fcntl.h> // O_RDONLY, open
#include <unistd.h> // dup, dup2, read, write, fork
#include <stdlib.h> // exit
#include <stdio.h> // perror, fprintf
#include <string.h> // strlen
#include <inttypes.h> // intmax_t
#include <sys/wait.h> // wait
int main(int argc, char **argv) {
pid_t pid;
if (argc < 2) {
printf("USAGE: %s file_to_write\n", argv[0]);
exit(EXIT_FAILURE);
}
char *path = argv[1];
int fd = open(path, O_RDWR | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
if (-1 == fd) {
perror("open");
exit(1);
}
switch (pid = fork()) {
case -1:
perror("fork");
exit(EXIT_FAILURE);
break;
case 0:
// child
if (-1 == dup2(fd, STDOUT_FILENO)) {
perror("dup2");
exit(EXIT_FAILURE);
}
char *prog = "/usr/bin/ls";
char *newargv[] = { prog, "/", NULL };
char *newenv[] = { NULL };
execve(prog, newargv, newenv);
perror("execve");
_exit(EXIT_FAILURE);
break;
default:
// parent
printf("parent process\n");
exit(EXIT_SUCCESS);
break;
}
}