home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #19 / NN_1992_19.iso / spool / alt / hackers / 1336 < prev    next >
Encoding:
Text File  |  1992-08-30  |  5.8 KB  |  145 lines

  1. Newsgroups: alt.hackers
  2. Path: sparky!uunet!munnari.oz.au!manuel!sserve!csadfa.cs.adfa.oz.au!wkt
  3. From: wkt@csadfa.cs.adfa.oz.au (Warren Toomey)
  4. Subject: Hack: Job Control under 7th Edition Unix
  5. Message-ID: <1992Aug31.042739.29219@sserve.cc.adfa.oz.au>
  6. Keywords: hack fun but not very proud of it
  7. Sender: news@sserve.cc.adfa.oz.au
  8. Organization: Australian Defence Force Academy, Canberra, Australia
  9. Date: Mon, 31 Aug 1992 04:27:39 GMT
  10. Approved: heart@foundation.org.au (The National Heart Foundation)
  11. Lines: 132
  12.  
  13. [sorry for the size, but if you're gonna explain a good hack, you might
  14.  as well do it properly :-)    Warren]
  15.  
  16.  
  17.             7th Edition Job Control
  18.  
  19. Job control can be achieved under early versions of Unix, such as 7th Edition,
  20. by using the ptrace(2) system call in a manner not intended by its
  21. designers.
  22.  
  23. Ptrace() was designed to allow a parent process to trace the excution
  24. of a child process, stopping the child under certain conditions, examining
  25. or modifying the contents of the child's memory, and restarting the child.
  26. We use the stopping/restarting abilities of ptrace() to provide job
  27. control.
  28.  
  29.  
  30. To permit a child process to be stopped, it must inform the parent that it
  31. wants its execution to be traced, which it does by ptrace(0,0,0,0).
  32. Fortunately, this can be done after the fork() and before the
  33. exec() in the invoke() routine, so that none of our programs
  34. need to be modified to add the ptrace().
  35.  
  36. When a traced process is executing, it is stopped under the following
  37. conditions:
  38.  
  39.     + the process receives a signal, or
  40.     + the process exec()s
  41.  
  42. If the shell is wait()ing on the child, it will be informed that the
  43. child has stopped, and can determine the signal that caused the process
  44. to stop (SIGTRAP in the case that the process exec()d). It is then
  45. able, using ptrace() with various arguments, to terminate or restart
  46. the process. At the same time, the shell can also deliver the signal to
  47. the restarted process, or not deliver the signal -- see the manual for
  48. ptrace(2).
  49.  
  50. 7th Edition job control, thus, is not so much a matter of stopping a process
  51. when requested to by the user, as ensuring that the process is always
  52. restarted, except when the user wants it to stop.
  53.  
  54.  
  55. Restarting stopped processes is straightforward. Stopping a running foreground
  56. process, however, is difficult, as there is no terminal key that, when pressed,
  57. will inform the shell to stop the process; indeed, the shell is most likely
  58. blocked wait()ing for the process to terminate.
  59.  
  60. Two keys that do affect the execution of a foreground process are Int
  61. (usually ^C or DEL), which sends a SIGINT to the process, and
  62. Quit (usually ^\), which sends a SIGQUIT to the process. The
  63. latter cannot be caught or ignored by the process, and the delivery of
  64. SIGQUIT causes the process to terminate, usually with a core dump. However,
  65. when a process is being traced, pending signals are not delivered;
  66. instead, the process is stopped, and the parent informed about the pending
  67. signal. The parent can choose to terminate or restart the process, delivering
  68. or ignoring the signal as described above.
  69.  
  70.  
  71. Therefore, with ^C being frequently used, and ^\ rarely used,
  72. we decided to reinterpret the meaning of ^\ and SIGQUIT to mean
  73. ``stop the process''.  The SIGQUIT from ^\ is never delivered by the
  74. shell, but all other signals (including the SIGINT from ^C) are
  75. delivered. Users can then re-bind the Quit key with stty(1) to
  76. be the more traditional Stop key, ^Z.
  77.  
  78. The waitfor() routine must be modified to restart a process stopped
  79. on any signal but SIGQUIT, and deliver any signal except SIGTRAP, which is
  80. caused when the process exec()s. The code is shown below:
  81.  
  82. void waitfor(pid)
  83.   int pid;
  84. {
  85.   struct job *thisjob;
  86.   int wpid;
  87.   bool subshell=FALSE;
  88.   int stopsig;
  89.   int status;
  90.  
  91.   if (pid<0) { pid= -pid; subshell= TRUE; }
  92.   while (1)
  93.   {
  94.     if (pid == 0) return;
  95.     wpid = wait(&status);               /* Get a process' status */
  96.     if (wpid == -1 || wpid == 0) break;
  97.     thisjob = findjob(wpid);
  98.     if (thisjob == NULL) continue;
  99.     if (WIFSTOPPED(status))             /* If it was stopped, */
  100.      { stopsig= WSTOPSIG(status);
  101.        if (stopsig!=SIGQUIT)            /* ignore any SIGQUITs, */
  102.         { if (stopsig== SIGTRAP)
  103.             ptrace(7,wpid,1,0);         /* keep it going after exec */
  104.           else
  105.             ptrace(7,wpid,1,stopsig);   /* or deliver the signal */
  106.           continue;
  107.         }
  108.      }     /* SIGQUIT falls out to below where job is marked stopped */
  109.     thisjob->status = status;
  110.     thisjob->changed = TRUE;
  111.     if (pid == wpid) return;
  112.   }
  113. }
  114.  
  115.  
  116. WIth a method of stopping foreground jobs now available, we can introduce
  117. the builtins used to stop a background job, and to move a stopped job to
  118. the foreground or background.
  119.  
  120. The bg builtin is implemented by bg(), which converts its
  121. arguments to a process-id, and then restarts that process by doing
  122. ptrace(7,pid,1,0); the job's status is marked RUNBG, and the current
  123. job pointer is updated.
  124.  
  125. The fg builtin is implemented by fg(). Fg can bring to the
  126. foreground either a background or a stopped job. The former is already running;
  127. the latter must be restarted with ptrace(7,pid,1,0). Once the job is
  128. running, the current job pointer is updated, and the pid returned so that
  129. the shell can waitfor() the new foreground job.
  130.  
  131.  
  132. Finally, two more modifications must be made to the shell before job control
  133. can work. The kill builtin cannot deliver a signal to a stopped job.
  134. Instead, if the specified job is stopped, Kill() must restart the job
  135. and deliver the signal with ptrace(7,pid,1,signal).
  136.  
  137. Secondly, when a background job is invoke()d, its exec() will
  138. cause it to be stopped on a SIGTRAP. However, the shell will never
  139. waitfor() the job, and so it will remain stopped until waitfor()
  140. is called when a foreground job is exec()d. Therefore, invoke()
  141. must be modified to restart an exec()d background job with
  142. ptrace(7,pid,1,0).
  143.  
  144.  
  145.