[net.sources] A simple ``jobs'' library for non-Berkeley systems

bsa@ncoast.UUCP (Brandon Allbery) (03/01/85)

I wrote this as a simple way of stopping programs temporarily to trap signals
or get a shell quickly, get a core image, or push it into the background.
If anyone ever figures out how to get ``suspend'' out of an unmodified V7
kernel, please let me know.

Following are a man page and a C program for the "setjctl()" function.
Read the manual page for details.

#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
-----cut here-----cut here-----cut here-----cut here-----
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	src/jobs.c
#	jobs.3
# This archive created: Fri Mar  1 10:34:12 1985
cat << \SHAR_EOF > src/jobs.c
/*
 * jobs.c -- simple job control routines
 *
 * These subroutines are Copyright (C) 1985 by Brandon Allbery.
 * Permission is hereby granted to copy and distribute this code, provided
 * that this notice is not removed.
 */

#include <stdio.h>
#include <signal.h>
#ifndef SYS3
#  include <sgtty.h>

struct sgttyb __origtty;

#define Index(s, c)	index(s, c)
char *index();
#else
#  include <termio.h>

struct termio __origtty;

#define Index(s, c)	strchr(s, c)
char *strchr();
#endif

int (*_oldsig)(), __sig, abort();

_jobctrl()
    {
    char line[256];
    FILE *tfd;
    short flg, retval = 0;
#ifndef SYS3
    struct sgttyb sgbuf, oldbuf;
#else
    struct termio tbuf, oldtbuf;
#endif

    signal(__sig, _jobctrl);
    if (isatty(0))
	tfd = stdin;
    else
	tfd = fopen("/dev/tty", "r");

#ifndef SYS3
    ioctl(fileno(tfd), TIOCGETP, &sgbuf);
    oldbuf = sgbuf;
    sgbuf.sg_flags &= ~(ECHO|RAW|CBREAK);
    ioctl(fileno(tfd), TIOCSETN, &sgbuf);
#else
    ioctl(fileno(tfd), TCGETA, &tbuf);
    oldtbuf = tbuf;
    tbuf.lflags &= ~ECHO;
    ioctl(fileno(tfd), TCSETAW, &tbuf);
#endif

    fgets(line, 256, tfd);

#ifndef SYS3
    ioctl(fileno(tfd), TIOCSETN, &oldbuf);
#else
    ioctl(fileno(tfd), TCSETAW, &oldtbuf);
#endif

    if (tfd != stdin)
	fclose(tfd);
    line[strlen(line) - 1] = '\0';
    if (strlen(line) >= 2)
	{
	if (strncmp(line, "ab", 2) == 0)
	    {
	    _resetty();
	    exit(100);
	    }
	else if (strncmp(line, "ci", 2) == 0)
	    {
	    _resetty();
	    abort();
	    }
	else if (strncmp(line, "cn", 2) == 0)
	    ;
	else if (strncmp(line, "bg", 2) == 0)
	    {
	    switch(fork())
		{
		case -1:
		    break;
		case 0:
		    if (_oldsig == SIG_DFL)
			signal(__sig, SIG_IGN);
		    else
			signal(__sig, _oldsig);
		    if (strlen(line) == 2 && isatty(1))
			{
			fflush(stdout);
			freopen("./background.out", "a", stdout);
			}
		    else if (line[2] != ',')
			{
			char *cp;

			if ((cp = Index(line, ',')) != NULL)
			    *cp = '\0';
			freopen(&line[2], "a", stdout);
			if (cp != NULL)
			    *cp = ',';
			}
		    if (Index(line, ',') == NULL && isatty(0))
			freopen("/dev/null", "r", stdin);
		    else if (Index(line, ',') != NULL)
			freopen(Index(line, ','), "r", stdin);
		    break;
		default:
		    _resetty();
		    exit(101);
		}
	    }
	else if (strncmp(line, "ig", 2) == 0)
	    if (strlen(line) > 2)
		signal(atoi(&line[2]), SIG_IGN);
	    else
		signal(__sig, SIG_IGN);
	else if (strncmp(line, "dc", 2) == 0)
	    if (strlen(line) > 2)
		signal(atoi(&line[2]), abort);
	    else
		signal(__sig, abort);
	else if (strncmp(line, "df", 2) == 0)
	    if (strlen(line) > 2)
		signal(atoi(&line[2]), SIG_DFL);
	    else
		signal(__sig, SIG_DFL);
	else if (strncmp(line, "kp", 2) == 0)
	    if (strlen(line) > 2)
		kill(getpid(), atoi(&line[2]));
	    else
		kill(getpid(), 9);
	else if (strncmp(line, "sh", 2) == 0)
            _doshell();	
	else
	    {
	    signal(__sig, _oldsig);
	    kill(getpid(), __sig);
	    }
	}
    }

_resetty()
    {
    FILE *f;

    if (isatty(0))
	f = stdin;
    else
	f = fopen("/dev/tty", "w");

#ifndef SYS3
    ioctl(fileno(f), TIOCSETN, &__origtty);
#else
    ioctl(fileno(f), TCSETAN, &__origtty);
#endif

    if (f != stdin)
	fclose(f);
    }

/* this is the only externally visible function */

setjctl(sig)
    int sig;
    {
    FILE *f;

    __sig = (sig? sig: SIGINT);
    if (isatty(stdin))
	f = stdin;
    else
	f = fopen("/dev/tty", "w");

#ifndef SYS3
    ioctl(fileno(f), TIOCGETP, &__origtty);
#else
    ioctl(fileno(f), TCGETA, &__origtty);
#endif

    if (f != stdin)
	fclose(f);
    _oldsig = signal((sig? sig: SIGINT), _jobctrl);
    }

_doshell()
    {
    FILE *fp;
#if defined(SIGCHLD) || defined(SIGCLD)
    int (*oldsig());
#endif
#ifndef SYS3
    struct sgttyb curtty;
#else
    struct termio curtty;
#endif

    switch(fork())
        {
        case -1:
            break;
        case 0:
            freopen("/dev/tty", "r", stdin);
            freopen("/dev/tty", "w", stdout);
            execl(getenv(SHELL)? getenv(SHELL): "/bin/sh", "sh", (char *) 0);
            exit(-100);
        default:
            if (isatty(0))
                fp = stdin;
            else
                fp = fopen("/dev/tty", "w");
#ifndef SYS3
            ioctl(fileno(fp), TIOCGETP, &curtty);
            ioctl(fileno(fp), TIOCSETN, &__origtty);
#else
            ioctl(fileno(fp), TCGETA, &curtty);
            ioctl(fileno(fp), TCSETAW, &__origtty);
#endif
#ifdef SIGCLD
            oldsig = signal(SIGCLD, SIG_IGN);
#endif
#ifdef SIGCHLD
            oldsig = signal(SIGCHLD, SIG_IGN);
#endif
            wait((int *) 0);
#ifdef SIGCLD
            signal(SIGCLD, oldsig);
#endif
#ifdef SIGCHLD
            signal(SIGCHLD, oldsig);
#endif
#ifndef SYS3
            ioctl(fileno(fp), TIOCSETN, &curtty);
#else
            ioctl(fileno(fp), TCSETAW, &curtty);
#endif
            if (fp != stdin)
                fclose(fp);
        }
    }
SHAR_EOF
cat << \SHAR_EOF > jobs.3
.TH JOBS 3
.SH NAME
jobs \- job control routines for non\-Berkeley systems
.SH SYNTAX
.RB cc prog.c ... \-l\c
.I jobs
.SH DESCRIPTION
The
.I jobs
library is a set of routines to allow programs to be
interrupted, signals to be trapped or ignored, and restarted,
aborted on any signal, or a core image to be generated.
.PP
In a program using this library, a call to
.I setjctl(signo)
should be made immediately upon program entry, in order to set signal
traps, save terminal values for a possible program abort, etc.
The call can be made as
.I setjctl(0)
which uses the SIGINT signal, thus making unnecessary the inclusion of
the <signal.h> header file.  No other changes need be made to the program.
.PP
When the specified signal is received, the trap routine waits for
input without prompting or echoing, so as not to disturb the screen
display.  The commands supported are:
.RS
.HP "ab \-"
abort execution of the program, taking the default action for the
signal used to invoke the job control routines
.HP "ci \-"
abort execution, generating a core image via abort(3)
.HP "cn \-"
continue program execution as if job control had not been invoked
.HP "bg \-"
continue execution in the background, see BACKGROUND EXECUTION below
.HP "ig \-"
ignore the specified signal (default is the job control signal)
.HP "dc \-"
dump core on receipt of signal (as ig above)
.HP "df \-"
take default action on receipt of signal, as above
.HP "kp \-"
send process specified signal \- default is signal 9 (SIGKILL)
.HP "sh \-"
fork a subshell ($SHELL or /bin/sh); on exit, return to the program
.RE
.SH ARGUMENTS
When job control is invoked, a line of input is read from the terminal
without prompting or echoing.  The first two characters of the line
are taken as a command; a null command defaults to `ab'.  The rest of
the line will be ignored by some commands or taken as arguments by others.
The most common argument is a
.I signo
taken by the
.I kp, ig, dc,
and
.I df
commands to specify the signal to use; it must be a decimal number.
.SH BACKGROUND EXECUTION
The
.I bg
command, without any arguments, redirects stdio if it is coming from /
going to a terminal, directing stdin to /dev/null and output to be
appended to the file `background.out' in the current directory.
With a single argument, output is sent to the given file (or left as
it is, overriding the default, if the argument is `\-').  With two comma-
separated arguments, input is redirected as per the second argument,
with `\-' meaning to leave the input alone.  Input can be specified without
changing the output action.  Note that extraneous spaces will probably
end up in a filename somewhere.
.PP
After this file swapping is performed, the process forks, and the main path
resets the tty to the values stored at the time of the call to
.I setjctl.
The background path returns to program execution.
.SH USE WITH SCREEN HANDLERS
The easiest way to use this is to trap SIGUSR1 or some other normally
unused signal with
.I setjctl,
then trap the job control signal to a user-written subroutine to
kill itself with the
.I setjctl
signal, then rebuild the screen (in case the `sh' option was used).
If your signal handler gets its signals crossed when you try this, insert
a call to the primitive
.I _jobctrl()
which will also return non-zero if the screen needs an update because the
\&`sh' command was used.
.SH FILES
/usr/lib/libjobs.a
/dev/null
./background.out
.SH SEE ALSO
signal(2), kill(2), fork(2), exec(2), getenv(3), freopen(3)
.SH AUTHOR
Brandon Allbery, North Coast Xenix (decvax!cwruecmp!ncoast!bsa)
.SH BUGS
\&`sh' does not check to see if it is setuid; therefore, it is definitely
dangerous to use this package in a setuid program.
SHAR_EOF
#	End of shell archive
exit 0
-- 
Brandon Allbery, decvax!cwruecmp!ncoast!bsa, ncoast!bsa@case.csnet (etc.)
6504 Chestnut Road Independence, Ohio 44131 +1 216 524 1416 -- CIS 74106,1032
		 -=> Does the Doctor make house calls? <=-