#include <stdio.h> #include <stdlib.h> #include <signal.h> void sigpipe_handler(int i) { printf("Got it\n"); exit(0); } main() { int fd[2]; signal(SIGPIPE, sigpipe_handler); pipe(fd); close(fd[0]); write(fd[1], "Hi\n", 3); } |
Part B: You need a program that is going to write to a pipe whose read end is closed. The simple example is:
UNIX> cat f1.txt | headf1.txt should have at least 4K bytes after its first 10 lines, in case the operating system does some buffering. head will print out the first 10 lines and then exit. At that point, there is no read end to the pipe to which cat is writing. While the operating system may buffer some of the write calls to the pipe before head exits, the buffer is typically 4K, so cat will be put into the situation where it is writing, and the read end of the pipe is gone. That generates SIGPIPE.
Part C: There are five potential outputs:
UNIX> a.out 2 2 2 T: 0 S: 0 A: 2 M: 2 UNIX>Command B: Again i equals 2, so setjmp() is called, which returns zero: "T: 0". Next, simons_code() is called, and since j does not equal 2, s is simply set to 1: "S: 1". simons_code() now calls longjmp() which returns from setjmp() in theodores_code, with a value of 2: "T: 2". theodores_code() now returns 2 to main(): "M: 2". The final output:
UNIX> a.out 2 1 2 T: 0 S: 1 T: 2 M: 2 UNIX>Command C: This will print "T: 1", and then call longjmp(). However, no one has called setjmp(), so you're begging for a seg fault. That's what I got on my Macintosh:
UNIX> a.out 1 0 0 T: 1 Segmentation fault UNIX>Command D: Now, theodores_code() prints "T: 0" and then calls simons_code(). This calls setjmp(), prints "S: 0" and calls alvins_code(). alvins_code() prints "A: 0" and calls longjmp(SharedBuf, 1). This ends up returning from the setjmp() call in simons_code() with a return value of 1: "S: 1". Now simons_code() calls longjmp(SharedBuf, 2), which again returns from the setjmp() in simons_code(), this time with a return value of 2: "S: 2". Now it returns 2 to theodores_code, which returns 2 to main(): "M: 2". The whole output:
UNIX> a.out 0 2 0 T: 0 S: 0 A: 0 S: 1 S: 2 M: 2 UNIX>Command E: On this last command, theodores_code() calls setjmp(), prints "T: 0" and then calls simons_code(). simons_code() prints "S: 0" and calls alvins_code(). alvins_code() prints "A: 0" and calls longjmp(SharedBuf, 1). This ends up returning from the setjmp() call in theodores_code() with a return value of 1: "T: 1". Now theodores_code() calls longjmp(SharedBuf, 1), which again returns from the setjmp() in theodores_code() with a return value of 1: "T: 1". And you should see that we're in an infinite loop:
UNIX> a.out 2 0 0 T: 0 S: 0 A: 0 T: 1 T: 1 ..... repeating infinitely
The child will make the appropriate dup2() calls to have one pipe go to standard input and the other go to standard output. It will then close all the pipe file descriptors and call execl() or execv() on chpmnk.
The parent will close parts of the pipes that it won't use, and set up standard IO streams (using fdopen() for the write end of the first pipe and the read end of the second pipe. Then it reads lines from standard input, calls preprocess_line() and fputs() or fprintf() to send the lines to chmpnk. It calls fgets() to read output from chmpnk, then calls postprocess_line() and prints out the resulting line. That's it.
The whole code is in q4a.c. In this code, I make up a preprocess_line() that converts all lowercase characters to uppercase, and postprocess_line() that prepends "Output: "to the strings, if there is room. You didn't need to do anything with preprocess_line() or postprocess_line() -- I just wrote them so that the program would compile completely.
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> void preprocess_line(char *s) { int i; for (i = 0; s[i] != '\0'; i++) { if (s[i] <= 'z' && s[i] >= 'a') s[i] += ('A'-'a'); } } void postprocess_line(char *s) { int i; char buf[500]; if (strlen(s) > 490) return; sprintf(buf, "Output: %s", s); strcpy(s, buf); return; } main() { char line[500]; int p1[2], p2[2]; FILE *fin, *fout; pipe(p1); pipe(p2); if (fork() == 0) { dup2(p1[0], 0); dup2(p2[1], 1); close(p1[0]); close(p1[1]); close(p2[0]); close(p2[1]); execl("./chpmnk", "chpmnk", NULL); perror("execl chpmnk"); exit(1); } close(p1[0]); close(p2[1]); fin = fdopen(p2[0], "r"); fout = fdopen(p1[1], "w"); while (fgets(line, 500, stdin) != NULL) { preprocess_line(line); fputs(line, fout); fflush(fout); if (fgets(line, 500, fin) == NULL) { fprintf(stderr, "Alvin, Simon and Theodore messed up again.\n"); exit(1); } postprocess_line(line); fputs(line, stdout); } exit(0); } |
(This is just for people studying from this question for future exams): You can test this out as is -- the child process will die because there is no chpmnk, but the parent doesn't die until you put a line of input into it. How does it die? On SIGPIPE, since there is no read end of p1:
UNIX> q4a execl chpmnk: No such file or directory Example input Broken pipe UNIX>You can test it for real by copying /bin/cat to chmpnk:
UNIX> cp /bin/cat chpmnk UNIX> q4a Example input Output: EXAMPLE INPUT You probably hated answering this question, but it's a fun one! Output: YOU PROBABLY HATED ANSWERING THIS QUESTION, BUT IT'S A FUN ONE! UNIX>Some of you called fork() for each line of standard input. While technically that would be correct, it's very inefficient. You also had to do it correctly (e.g. some of you set up a pipe before the loop to read standard input. That cannot work.).