home *** CD-ROM | disk | FTP | other *** search
- Newsgroups: alt.hackers
- Path: sparky!uunet!munnari.oz.au!manuel!sserve!csadfa.cs.adfa.oz.au!wkt
- From: wkt@csadfa.cs.adfa.oz.au (Warren Toomey)
- Subject: Hack: Job Control under 7th Edition Unix
- Message-ID: <1992Aug31.042739.29219@sserve.cc.adfa.oz.au>
- Keywords: hack fun but not very proud of it
- Sender: news@sserve.cc.adfa.oz.au
- Organization: Australian Defence Force Academy, Canberra, Australia
- Date: Mon, 31 Aug 1992 04:27:39 GMT
- Approved: heart@foundation.org.au (The National Heart Foundation)
- Lines: 132
-
- [sorry for the size, but if you're gonna explain a good hack, you might
- as well do it properly :-) Warren]
-
-
- 7th Edition Job Control
-
- Job control can be achieved under early versions of Unix, such as 7th Edition,
- by using the ptrace(2) system call in a manner not intended by its
- designers.
-
- Ptrace() was designed to allow a parent process to trace the excution
- of a child process, stopping the child under certain conditions, examining
- or modifying the contents of the child's memory, and restarting the child.
- We use the stopping/restarting abilities of ptrace() to provide job
- control.
-
-
- To permit a child process to be stopped, it must inform the parent that it
- wants its execution to be traced, which it does by ptrace(0,0,0,0).
- Fortunately, this can be done after the fork() and before the
- exec() in the invoke() routine, so that none of our programs
- need to be modified to add the ptrace().
-
- When a traced process is executing, it is stopped under the following
- conditions:
-
- + the process receives a signal, or
- + the process exec()s
-
- If the shell is wait()ing on the child, it will be informed that the
- child has stopped, and can determine the signal that caused the process
- to stop (SIGTRAP in the case that the process exec()d). It is then
- able, using ptrace() with various arguments, to terminate or restart
- the process. At the same time, the shell can also deliver the signal to
- the restarted process, or not deliver the signal -- see the manual for
- ptrace(2).
-
- 7th Edition job control, thus, is not so much a matter of stopping a process
- when requested to by the user, as ensuring that the process is always
- restarted, except when the user wants it to stop.
-
-
- Restarting stopped processes is straightforward. Stopping a running foreground
- process, however, is difficult, as there is no terminal key that, when pressed,
- will inform the shell to stop the process; indeed, the shell is most likely
- blocked wait()ing for the process to terminate.
-
- Two keys that do affect the execution of a foreground process are Int
- (usually ^C or DEL), which sends a SIGINT to the process, and
- Quit (usually ^\), which sends a SIGQUIT to the process. The
- latter cannot be caught or ignored by the process, and the delivery of
- SIGQUIT causes the process to terminate, usually with a core dump. However,
- when a process is being traced, pending signals are not delivered;
- instead, the process is stopped, and the parent informed about the pending
- signal. The parent can choose to terminate or restart the process, delivering
- or ignoring the signal as described above.
-
-
- Therefore, with ^C being frequently used, and ^\ rarely used,
- we decided to reinterpret the meaning of ^\ and SIGQUIT to mean
- ``stop the process''. The SIGQUIT from ^\ is never delivered by the
- shell, but all other signals (including the SIGINT from ^C) are
- delivered. Users can then re-bind the Quit key with stty(1) to
- be the more traditional Stop key, ^Z.
-
- The waitfor() routine must be modified to restart a process stopped
- on any signal but SIGQUIT, and deliver any signal except SIGTRAP, which is
- caused when the process exec()s. The code is shown below:
-
- void waitfor(pid)
- int pid;
- {
- struct job *thisjob;
- int wpid;
- bool subshell=FALSE;
- int stopsig;
- int status;
-
- if (pid<0) { pid= -pid; subshell= TRUE; }
- while (1)
- {
- if (pid == 0) return;
- wpid = wait(&status); /* Get a process' status */
- if (wpid == -1 || wpid == 0) break;
- thisjob = findjob(wpid);
- if (thisjob == NULL) continue;
- if (WIFSTOPPED(status)) /* If it was stopped, */
- { stopsig= WSTOPSIG(status);
- if (stopsig!=SIGQUIT) /* ignore any SIGQUITs, */
- { if (stopsig== SIGTRAP)
- ptrace(7,wpid,1,0); /* keep it going after exec */
- else
- ptrace(7,wpid,1,stopsig); /* or deliver the signal */
- continue;
- }
- } /* SIGQUIT falls out to below where job is marked stopped */
- thisjob->status = status;
- thisjob->changed = TRUE;
- if (pid == wpid) return;
- }
- }
-
-
- WIth a method of stopping foreground jobs now available, we can introduce
- the builtins used to stop a background job, and to move a stopped job to
- the foreground or background.
-
- The bg builtin is implemented by bg(), which converts its
- arguments to a process-id, and then restarts that process by doing
- ptrace(7,pid,1,0); the job's status is marked RUNBG, and the current
- job pointer is updated.
-
- The fg builtin is implemented by fg(). Fg can bring to the
- foreground either a background or a stopped job. The former is already running;
- the latter must be restarted with ptrace(7,pid,1,0). Once the job is
- running, the current job pointer is updated, and the pid returned so that
- the shell can waitfor() the new foreground job.
-
-
- Finally, two more modifications must be made to the shell before job control
- can work. The kill builtin cannot deliver a signal to a stopped job.
- Instead, if the specified job is stopped, Kill() must restart the job
- and deliver the signal with ptrace(7,pid,1,signal).
-
- Secondly, when a background job is invoke()d, its exec() will
- cause it to be stopped on a SIGTRAP. However, the shell will never
- waitfor() the job, and so it will remain stopped until waitfor()
- is called when a foreground job is exec()d. Therefore, invoke()
- must be modified to restart an exec()d background job with
- ptrace(7,pid,1,0).
-
-
-