CSC 357 Lecture Notes Week 9
Additional Pipe Topics Additional Signal Topics Shells and Programming Assignment 6



  1. Reading.
    1. Stevens Chapters 9 and 10.
    2. Programming Assignment 6 writeup.

  2. What exactly happens when stdout (or stdin) is dup'd to a pipe.
    1. The following program explores details of programmatic redirection of stdout.
    2. We'll dissect the program during lecture.
       1  /*
       2   * This program explores what's going on when dup2 is used to redirect stdout
       3   * to a pipe.  The key aspect of the exploration is that the normal stdout
       4   * stream is closed after the dup2.  This means that system calls like printf
       5   * do not send data to the normal stdout stream, but rather to the pipe.
       6   *
       7   * The "normal" stdout stream is the terminal, for programs like this that are
       8   * run from a shell.  Hence, before the dup2, printf output appears on the
       9   * terminal.  After dup2, it does not appear on the terminal.
      10   *
      11   * To be able to restore normal printing, we must save a copy of STDOUT_FILENO
      12   * before the dup2.  When can then use dup2 again, to copy the saved value back
      13   * into STDOUT_FILENO.  Subsequently, the stdout stream is restored to its
      14   * original value, i.e., the terminal when we're running from a shell.
      15   *
      16   * See the code comments for further explanatory details.
      17   */
      18
      19  #include <unistd.h>
      20  #include <stdio.h>
      21  #include <fcntl.h>
      22  #include <string.h>
      23
      24  int main() {
      25      int stdout_save;         /* saved copy of stdout fd */
      26      int stdout_open;         /* fd for explicitly opened /dev/stdout */
      27      int pfds[2];             /* pipe fds */
      28      FILE* fp;                /* file pointer to attempt stdout reopen */
      29
      30      /* Print a message explaining what the output should look like. */
      31      printf("\n\
      32        The output that follows should be five lines containing the string\n\
      33            \"Can see this.\"\n\
      34        and no lines containing the string\n\
      35            \"CanNOT see this.\"\n");
      36
      37      /* Save a duplicate of STDOUT_FILENO.  This will allow the restoration of
      38       * writing to the normal stdout stream, via printf and write. */
      39      stdout_save = dup(STDOUT_FILENO);
      40
      41      /* Get another fd for stdout by explicitly opening /dev/stdout.  Note that
      42         this only works before STDOUT_FILENO is closed, since after closure,
      43         this process does not have permission to open /dev/stdout. */
      44      stdout_open = open("/dev/stdout", 0, 0);
      45
      46      /* Create a pipe and dup2 stdout onto the pipe's output end.  What this
      47         does is redirect stdout from its normal location to the pipe.  This
      48         means that system calls that use stdout will not write to the normal
      49         stdout stream, but rather to the pipe.  So, for example, a printf from
      50         this program will not appear on the terminal, when this program is run
      51         from the shell.
      52
      53         Furthermore, since dup2 closes its second argument, this means that this
      54         program cannot write to the normal stdout stream unless the saved stdout
      55         descriptor is dup'd back into STDOUT_FILENO. */
      56      pipe(pfds);
      57      dup2(pfds[1], STDOUT_FILENO);
      58
      59      /* At this point, output from printf will not show up on the terminal;
      60         rather, it's going to the pipe.  Since we're not doing anything with
      61         the pipe, it's going in the bit bucket, i.e., nowhere. */
      62      printf("CanNOT see this.\n");
      63      fflush(stdout);
      64
      65      /* The same goes for output from write. */
      66      write(STDOUT_FILENO, "CanNOT see this.\n", strlen("CanNOT see this.\n"));
      67
      68      /* Also, confirm that /dev/stdout is not now openable. */
      69      if (open("/dev/stdout", 0, 0) != -1) {
      70          fprintf(stderr, "CanNOT see this.\n");
      71      }
      72
      73      /* If we write explicitly to the saved stdout fd, or to the fd obtained from
      74         opening /dev/stdout, output will appear on the normal fd stream, i.e.,
      75         the terminal if we're running from the shell. */
      76      write(stdout_save, "Can see this.\n", strlen("Can see this.\n"));
      77      write(stdout_open, "Can see this.\n", strlen("Can see this.\n"));
      78
      79      /* We can also do terminal output through a FILE* obtained from fdopen on
      80         the saved stdout fd. */
      81      fprintf(fdopen(stdout_save, "w+"), "Can see this.\n");
      82
      83      /* If we restore the normal value of STDOUT_FILENO by dup'ing the saved
      84         value, printf will now go to the terminal, again assuming we're running
      85         from the shell. */
      86      dup2(stdout_save, STDOUT_FILENO);
      87      printf("Can see this.\n");
      88      write(STDOUT_FILENO, "Can see this.\n", strlen("Can see this.\n"));
      89
      90      /* The following code demonstrates that the FILE* named "stdout" is
      91         disabled when the STDOUT_FILENO fd is closed or redirected.  In
      92         particular, an attempt to reopen stdout onto another file pointer will
      93         fail. */
      94      dup2(pfds[1], STDOUT_FILENO);
      95      write(STDOUT_FILENO, "CanNOT see this.\n", strlen("CanNOT see this.\n"));
      96      printf("CanNOT see this.\n");
      97      if ((fp = freopen(NULL, "w+", stdout)) != NULL) {
      98          printf("CanNOT see this.\n");
      99      }
     100  }
    

  3. Interrupted system calls (Sect 10.5).
    1. In the simple-timer.c example from last week's notes, what happens if change line 138 from
      while (getchar() != 'q') {}
      
      to
      getchar();
      

    2. The answer requires that we understand the default behavior for signal interruption of "slow" systems calls; namely, it causes termination of the call.
    3. Slow calls include:
      1. blocking read/write
      2. pause
      3. some IPC
    4. Termination behavior can be changed using sigaction flag.
    5. It's named SA_RESTART.
    6. E.g., in simple-timer program:
    7. Change line 104 from
      action.sa_flags   = 0;
      
      to
      action.sa_flags = SA_RESTART;
      
    8. Given this, the change above to line 138 will have the hoped-for behavior.
    9. Namely, getchar will resume after it is interrupted by SIGALRM, so the surrounding while look is not needed on line 138.

  4. Reentrant functions (Section 10.6).
    1. Reentrant means a function can be re-entered after being interrupted.
    2. It requires that the function uses only local data, or save and restore global data used by other functions.
    3. Calling non-reentrant functions from signal handlers can produces bad results, e.g.,
      1. At a point of interruption a program is executing the statement "x = x + 1", with global variable "x" currently set to a non-zero value.
      2. The signal handler sets the global x to zero.
      3. When the interrupted assignment statement resumes, it will likely produce unexpected results.
    4. The malloc function is decidedly non-reentrant, and should never be called from a signal handler.
    5. Table 10.4 on Page 306 lists reentrant system functions that can be called from a signal handler.

  5. Catching SIGBUS and SIGSEGV.
    1. The following example illustrates some additional interesting signal handling issues.
    2. It's in the file 357/examples/handle-bus-error.c
    3. Of particular note are:
      1. the handling of SIGSEGV and SIGBUS, which programs can do to avoid completely fatal termination;
      2. the Solaris-specific implementation of the signal function (see Stevens Page 328, Figure 10.18);
      3. the use of siglongjmp from the handlers, to avoid repeated re- activation of the signal due to the offending code being resumed (see Stevens Section 10.15).
     1  #include <signal.h>
     2  #include <setjmp.h>
     3  #include <stdlib.h>
     4  #include <stdio.h>
     5
     6  #ifdef SOLARIS
     7  typedef void Sigfunc(int);
     8
     9  Sigfunc* signal(int signo, Sigfunc* func) {
    10      struct sigaction act, oact;
    11      act.sa_handler = func;
    12      sigemptyset(&act.sa_mask);
    13      act.sa_flags = 0;
    14      if (signo == SIGALRM) {
    15  #ifdef SA_INTERRUPT
    16          act.sa_flags |= SA_INTERRUPT;
    17  #endif
    18      }
    19      else {
    20  #ifdef SA_RESTART
    21          act.sa_flags |= SA_RESTART;
    22  #endif
    23      }
    24      if (sigaction(signo, &act, &oact) < 0) {
    25          return(SIG_ERR);
    26      }
    27      return(oact.sa_handler);
    28  }
    29  #endif
    30
    31  static sigjmp_buf jbuf;
    32
    33  void bus_handler(int signum) {
    34      printf("Oops, I just bus erred.\n");
    35      siglongjmp(jbuf, 0);
    36  }
    37
    38  void segv_handler(int signum) {
    39      printf("Oops, I just seg faulted.\n");
    40      siglongjmp(jbuf, 0);
    41  }
    42
    43  void alrm_handler(int signum) {
    44      printf("Got an alarm.\n");
    45  }
    46
    47  int main(int argc, char** argv) {
    48      char* p = 0;
    49      char c;
    50
    51      signal(SIGBUS, bus_handler);
    52      signal(SIGSEGV, segv_handler);
    53      signal(SIGALRM, alrm_handler);
    54
    55      if (sigsetjmp(jbuf, 0)) {
    56          printf("End of main after error.\n");
    57          printf("exit status: %d\n", EXIT_FAILURE);
    58          exit(EXIT_FAILURE);
    59      }
    60
    61      /*
    62       * If there is any command-line arg, cause an error.
    63       */
    64      if (argc > 1) {
    65          c = *p;
    66      }
    67
    68      kill(getpid(), SIGALRM);
    69
    70      printf("End of main without error.\n");
    71      printf("exit status: %d\n", EXIT_SUCCESS);
    72      exit(EXIT_SUCCESS);
    73  }
    

  6. Summary of how to handle (aka, catch), block, and ignore signals.
    1. To handle a signal, use the function sigaction, with the name of the signal as the first argument, and a pointer to the handling function within the second argument.
    2. To block a signal, use the function sigprocmask, with the first argument set to SIG_BLOCK, and appropriate values for the second and third arguments.
    3. To unblock a signal, use the function sigprocmask, with the first argument set to SIG_UNBLOCK, and appropriate values for the second and third arguments.
    4. To ignore a signal, use sigaction with the handling function set to SIG_IGN.
    5. To restore a signal to it default handler, use sigaction with the handling function set to SIG_DFL.
    6. Stevens has very good discussions of these functions and their arguments.




    Now onto Programming Assignment 6, which entails the implementation of a very simple shell

  7. Go over the assignment writeup.

  8. Additional shell-related topics from Stevens Chapter 9.
    1. Terminal logins (Sect 9.2).
      1. init process assigns terminals (ttys).
      2. getty opens terminal device.
      3. login does the following:
        1. changes to home dir
        2. makes user terminal owner
        3. sets terminal access permissions
        4. initializes shell environment
        5. changes to user's ID and invokes shell
    2. Process groups and sessions (Sects 9.4 and 9.5).
      1. You may want to use setgpid and setsid functions in the job-control aspects of program 6.
      2. There use is not necessary to do so, but may be helpful.
    3. Controlling terminal (Sect 9.5).
      1. Sessions under the control of a shell have a single controlling terminal.
      2. Typing the interrupt key sends SIGINT to all processes in a session's foreground process group, which in the case of a shell program, means the parent shell and all of its children.
    4. Job control and execution of shell programs (Sects 9.8 and 9.9).
      1. These sections of Stevens provide useful background information for program 6.
      2. I recommend a read of them.
  9. A note on the WNOHANG option for waitpid.
    1. By default, the waitpid function does not return until the awaited process exits, i.e., the waiting process hangs on the wait.
    2. With the WNOHANG option, waitpid does not hang, but rather returns a status immediately, whether or not the awaited process has exited.
    3. The WNOHANG option can be particularly useful in implementing the job-control functionality of vssh.


index | lectures | labs | programs | handouts | solutions | examples | documentation | bin