/*
* shl - shell layer manager
*
* Gunnar Ritter, Freiburg i. Br., Germany, April 2001.
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute
* it freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*/
#if __GNUC__ >= 3 && __GNUC_MINOR__ >= 4 || __GNUC__ >= 4
#define USED __attribute__ ((used))
#elif defined __GNUC__
#define USED __attribute__ ((unused))
#else
#define USED
#endif
static const char sccsid[] USED = "@(#)shl.sl 1.29 (gritter) 12/25/06";
#if !defined (__FreeBSD__) && !defined (__hpux) && !defined (_AIX) && \
!defined (__NetBSD__) && !defined (__OpenBSD__) && \
!defined (__DragonFly__) && !defined (__APPLE__)
/*
* UnixWare 2.1 needs _KMEMUSER to access some flags for STREAMS. Maybe other
* SVR4 do too, so define it on non-Sun systems for now.
*/
#ifndef sun
#define _KMEMUSER
#endif
#include <sys/types.h>
#include <termios.h>
/*
* Structure for a single shell layer.
*/
struct layer {
struct layer *l_prev; /* previous node in layer list */
struct layer *l_next; /* next node in layer list */
struct termios l_tio; /* termios struct of layer */
char l_name[9]; /* name of layer */
char l_line[64]; /* device name for tty */
pid_t l_pid; /* pid/pgid of layer */
int l_pty; /* pty master */
int l_blk; /* output is blocked */
};
extern int sysv3;
#ifdef __dietlibc__
#define _XOPEN_SOURCE 600L
#endif
/*
* This implements the interface of the SVID3 shl command using regular
* SVR4-type pseudo terminal mechanisms. Each layer gets its own pty,
* and shl poll()s them and writes their output to its own tty. Input
* is collected by the same mechanism in reverse direction.
*/
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <libgen.h>
#include <limits.h>
#include <stdarg.h>
#if !defined (__dietlibc__) && !defined (__UCLIBC__)
#include <stropts.h>
#endif
#include <poll.h>
#include <stdio.h>
#include <ctype.h>
#include <time.h>
#include <utmpx.h>
#include <sigset.h>
#include <pathconf.h>
#if !defined (__dietlibc__) && !defined (__UCLIBC__)
#define WORDEXP
#endif
#ifdef WORDEXP
#include <wordexp.h>
#endif /* WORDEXP */
#include <pwd.h>
#include <locale.h>
#ifndef __linux__
#include <termio.h>
#include <sys/stream.h>
#include <sys/ptms.h>
#endif /* !__linux__ */
/*
* Linux systems call VSWTC what the SVID calls VSWTCH.
*/
#ifndef VSWTCH
#ifdef VSWTC
#define VSWTCH VSWTC
#else
#error "Cannot find a VSWTCH or VSWTC key definition"
#endif /* VSWTC */
#endif /* !VSWTCH */
/*
* The SVID specifies ^Z to be used as switch character if it is unset
* when shl starts. As this implementation can co-exist with job control
* within its layers, it might be a good idea to change either the susp
* or the switch key before starting shl.
*/
#define DEFSWTCH '\32' /* default switch character (^Z) */
/*
* Only used if $SHELL is not set.
*/
#define DEFSHELL "/bin/sh" /* default shell */
/*
* This implementation of shl can support an arbitrary number of layers. As
* pseudo-terminals are a sparse resource on most systems, the old default
* of seven layers as a maximum is retained here. It can be raised without
* problems if suitable.
*/
#define MAXLAYER 7 /* maximum layers */
/*
* Locale-independent whitespace recognition.
*/
#define blankchar(c) ((c) == ' ' || (c) == '\t')
/*
* Constants for utmp handling.
*/
enum {
UTMP_ADD, UTMP_DEL
};
/*
* This structure describes a shl command. A table of all commands resides
* at the bottom end of this file.
*/
struct command {
char *c_string; /* command's name */
int (*c_func)(char *); /* function to do it */
char *c_arg; /* argument to command */
char *c_desc; /* command's description */
int c_dnum; /* description's number in msg cat. */
};
unsigned errstat; /* error status of last command */
char *progname; /* argv[0] to main() */
char *shell; /* $SHELL */
char *dashell; /* -`basename $SHELL` */
pid_t mypid; /* shl's own pid */
uid_t myuid; /* shl's own uid */
uid_t myeuid; /* shl's own euid/saved set-user-ID */
gid_t mygid; /* shl's own gid */
gid_t myegid; /* shl's own egid/saved set-group-ID */
unsigned lcount; /* count of layers */
char cmdbuf[LINE_MAX]; /* buffer for commands */
char *cmdptr; /* current pointer for cmdbuf */
int hadquit; /* quit cmd executed but layers run */
struct layer *l0; /* begin of layer chain */
struct layer *lcur; /* current layer */
struct termios otio; /* old terminal attributes */
struct termios dtio; /* default terminal attributes */
struct termios rtio; /* raw terminal attributes */
struct winsize winsz; /* window sizes */
void (*oldhup)(int); /* old SIGHUP handler */
void (*oldint)(int); /* old SIGINT handler */
void (*oldquit)(int); /* old SIGQUIT handler */
void (*oldttou)(int); /* old SIGTTOU handler */
void (*oldtstp)(int); /* old SIGTSTP handler */
void (*oldttin)(int); /* old SIGTTIN handler */
void (*oldwinch)(int); /* old SIGWINCH handler */
/*
* Common english error messages.
*/
const char *syntax = "syntax error";
const char *lnotfound = "layer %s not found";
const char *nocurl = "no current layer";
/*
* The table of all shl commands residing at the bottom end of this file.
*/
extern struct command commands[];
/*
* Messages to stdout.
*/
void
msg(const char *fmt, ...)
{
char buf[256];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof buf - 1, fmt, ap);
va_end(ap);
strcat(buf, "\n");
write(2, buf, strlen(buf));
}
/*
* Memory allocation with check.
*/
void *
srealloc(void *vp, size_t nbytes)
{
void *p;
if ((p = (void *)realloc(vp, nbytes)) == NULL) {
write(2, "no memory\n", 10);
exit(077);
}
return p;
}
void *
smalloc(size_t nbytes)
{
return srealloc(NULL, nbytes);
}
/*
* Allocate pty master (APUE, p. 638).
*/
int
ptym_open(char *pts_name, size_t namsz)
{
char *ptr;
int fdm;
strcpy(pts_name, "/dev/ptmx");
if ((fdm = open(pts_name, O_RDWR)) < 0)
return -1;
if (grantpt(fdm) < 0) {
close(fdm);
return -2;
}
if (unlockpt(fdm) < 0) {
close(fdm);
return -3;
}
if ((ptr = ptsname(fdm)) == NULL) {
close(fdm);
return -4;
}
strncpy(pts_name, ptr, namsz);
#ifndef __linux__
if (ioctl(fdm, I_PUSH, "pckt") < 0) {
close(fdm);
return -5;
}
#endif /* !__linux__ */
return fdm;
}
/*
* Allocate a pty slave (APUE, p. 639).
*/
int
ptys_open(int fdm, char *pts_name)
{
int fds;
if ((fds = open(pts_name, O_RDWR)) < 0) {
close(fdm);
return -5;
}
#ifndef __linux__
if (ioctl(fds, I_PUSH, "ptem") < 0) {
close(fdm);
close(fds);
return -6;
}
if (ioctl(fds, I_PUSH, "ldterm") < 0) {
close(fdm);
close(fds);
return -7;
}
if (ioctl(fds, I_PUSH, "ttcompat") < 0) {
close(fdm);
close(fds);
return -8;
}
#endif /* !__linux__ */
return fds;
}
/*
* Add or delete an entry in the system's utmp file.
*/
void
doutmp(int action, struct layer *l)
{
/*
* Note that pututxline() may need privileges to work; but at least
* on Solaris, it does not as the libc arranges this for us. If shl
* has suid or sgid privileges, these are reset on startup and
* restored for utmp handling here.
*/
struct passwd *pwd = getpwuid(myuid);
struct utmpx utx;
char *id;
memset(&utx, 0, sizeof utx);
strncpy(utx.ut_line, l->l_line + 5, sizeof utx.ut_line);
strncpy(utx.ut_user, pwd->pw_name, sizeof utx.ut_user);
utx.ut_pid = l->l_pid;
gettimeofday(&utx.ut_tv, NULL);
if ((id = strrchr(l->l_line, '/')) != NULL)
strncpy(utx.ut_id, id, sizeof utx.ut_id);
switch (action) {
case UTMP_ADD:
utx.ut_type = USER_PROCESS;
break;
case UTMP_DEL:
utx.ut_type = DEAD_PROCESS;
break;
}
if (myuid != myeuid)
setuid(myeuid);
if (mygid != myegid)
setgid(myegid);
#ifndef __linux__
if (action == UTMP_DEL) {
/*
* On (at least) Solaris 8, /usr/lib/utmp_update will hang at
* ioctl(tty, TCGETA, ...) respective isatty() when the pty is
* in packet mode, but removing the pckt module we once pushed
* fails with EPERM in some circumstances (exact conditions
* unknown). If it does, close the pty master; pututxline()
* refuses to work then, but utmpd(1M) will remove the stale
* entry a few seconds later. This is not the ultimate
* solution, but better than hanging, of course. If shl needs
* privilegues to call pututxline(), these calls will not cause
* any harm since there is no dependency on the actual terminal
* device then.
*/
if (ioctl(l->l_pty, I_POP, 0) < 0) {
close(l->l_pty);
l->l_pty = -1;
}
}
#endif /* !__linux__ */
setutxent();
getutxline(&utx);
pututxline(&utx);
endutxent();
if (myuid != myeuid)
setuid(myuid);
if (mygid != myegid)
setgid(mygid);
}
/*
* Quit shl, killing all active layers.
*/
void
quit(int status)
{
struct layer *l;
for (l = l0; l; l = l->l_next) {
kill(0 - l->l_pid, SIGHUP);
doutmp(UTMP_DEL, l);
}
tcsetattr(0, TCSADRAIN, &otio);
exit(status);
}
/*
* Check for valid layer name.
*/
int
invlname(char *name)
{
int err = 0;
if (strpbrk(name, " \t"))
err++;
if (name[0] == '(' && isdigit(name[1] & 0377) && name[2] == ')'
&& name[3] == '\0')
err++;
if (err)
msg(syntax);
return err;
}
/*
* Find a layer by its pid.
*/
struct layer *
lbypid(pid_t pid)
{
struct layer *l;
for (l = l0; l; l = l->l_next)
if (l->l_pid == pid)
return l;
return NULL;
}
/*
* Find a layer by its pty.
*/
struct layer *
lbypty(int pty)
{
struct layer *l;
for (l = l0; l; l = l->l_next)
if (l->l_pty == pty)
return l;
return NULL;
}
/*
* Find a layer by its name.
*/
struct layer *
lbyname(char *name)
{
struct layer *l;
char bname[11];
for (l = l0; l; l = l->l_next)
if (strncmp(l->l_name, name, 8) == 0)
return l;
snprintf(bname, sizeof bname, "(%s)", name);
for (l = l0; l; l = l->l_next)
if (strncmp(l->l_name, bname, 8) == 0)
return l;
return NULL;
}
/*
* Guess a layer by a possibly incomplete name part.
*/
struct layer *
lguess(char *name)
{
struct layer *l, *lm = NULL;
size_t nmlen = strlen(name);
char bname[11];
if (nmlen > 8)
nmlen = 8;
for (l = l0; l; l = l->l_next) {
if (strncmp(l->l_name, name, nmlen) == 0) {
if (lm) {
msg("layer %s not unique", name);
return NULL;
}
lm = l;
}
}
if (lm == NULL) {
snprintf(bname, sizeof bname, "(%s)", name);
nmlen += 2;
for (l = l0; l; l = l->l_next) {
if (strncmp(l->l_name, bname, nmlen) == 0) {
if (lm) {
msg("layer %s not unique", name);
return NULL;
}
lm = l;
}
}
}
if (lm == NULL)
msg(lnotfound, name);
return lm;
}
/*
* Allocate a layer structure.
*/
struct layer *
lalloc(char *name, int accept)
{
struct layer *l, *lp;
if (lcount >= MAXLAYER) {
msg("only %u layers allowed", MAXLAYER);
return NULL;
}
if (accept == 0 && invlname(name))
return NULL;
if (lbyname(name)) {
msg("layer %s already exists", name);
return NULL;
}
l = (struct layer *)smalloc(sizeof *l);
if (l0 == NULL) {
l0 = l;
l->l_prev = NULL;
} else {
for (lp = l0; lp->l_next != NULL; lp = lp->l_next);
lp->l_next = l;
l->l_prev = lp;
}
l->l_next = NULL;
strncpy(l->l_name, name, 8);
l->l_name[8] = '\0';
l->l_pty = -1;
l->l_blk = 0;
lcount++;
return l;
}
/*
* Deallocate a layer structure.
*/
void
lfree(struct layer *l)
{
if (l->l_prev)
l->l_prev->l_next = l->l_next;
if (l->l_next)
l->l_next->l_prev = l->l_prev;
if (l == l0)
l0 = l->l_next;
close(l->l_pty);
free(l);
lcount--;
}
/*
* Block a layer.
*/
int
lblock(struct layer *l)
{
l->l_blk = 1;
return 0;
}
/*
* Unblock a layer.
*/
int
lunblock(struct layer *l)
{
l->l_blk = 0;
return 0;
}
/*
* Set a layer as current layer, that is, assign it to lcur and put it at
* the end of the layer list.
*/
void
setcur(struct layer *l)
{
struct layer *lp;
if (l->l_next) {
if (l->l_prev)
l->l_prev->l_next = l->l_next;
else
l0 = l->l_next;
l->l_next->l_prev = l->l_prev;
for (lp = l; lp->l_next; lp = lp->l_next);
l->l_prev = lp;
lp->l_next = l;
l->l_next = NULL;
}
lcur = l;
}
/*
* Set the previous layer to the current.
*/
void
setprev(int nullok)
{
struct layer *l = lcur;
if ((lcur = l->l_prev) == NULL) {
if (nullok)
l0 = NULL;
else
lcur = l0;
}
}
/*
* Pass input from terminal to pty, or read shl commands.
*/
int
doinput(void)
{
int seen = 0;
char c;
#ifdef __linux__
if (cmdptr == NULL)
tcgetattr(lcur->l_pty, &lcur->l_tio);
#endif /* __linux__ */
while (read(0, &c, 1) == 1) {
if (cmdptr) {
/*
* Do control character processing for shl commands.
*/
if ((c == '\n' && (otio.c_iflag & INLCR) == 0)
|| (c == '\r' && (otio.c_iflag & ICRNL))) {
write(1, "\r\n", 2);
*cmdptr = '\0';
seen++;
break;
} else if (cmdptr == cmdbuf &&
(c&0377) == (dtio.c_cc[VEOF]&0377)) {
/*EMPTY*/;
} else if ((c&0377) == (dtio.c_cc[VERASE]&0377)) {
if (cmdptr > cmdbuf)
*cmdptr-- = '\0';
write(1, "\b \b", 3);
} else if ((c&0377) == (dtio.c_cc[VKILL]&0377)) {
*(cmdptr = cmdbuf) = '\0';
write(1, "\r\n", 2);
} else {
write(1, &c, 1);
*cmdptr++ = c & 0377;
}
if (cmdptr == cmdbuf + sizeof cmdbuf) {
msg("\r\nInput string too long, limit %u",
sizeof cmdbuf - 1);
*--cmdptr = '\0';
write(1, "\r", 1);
write(1, cmdbuf, strlen(cmdbuf));
}
} else {
/*
* Pass input to the currently active layer and
* look for SWTCH character. If a LNEXT character
* is found, keep it so it can escape a following
* SWTCH.
*/
static char hadlnext;
#ifdef VLNEXT
if (c && (c&0377) == (lcur->l_tio.c_cc[VLNEXT]&0377) &&
hadlnext == 0) {
hadlnext = c;
} else
#endif /* VLNEXT */
{
if (c && (c&0377) ==
(lcur->l_tio.c_cc[VSWTCH]&0377)) {
if (hadlnext) {
hadlnext = 0;
} else {
seen++;
break;
}
}
if (hadlnext) {
write(lcur->l_pty, &hadlnext, 1);
hadlnext = 0;
}
write(lcur->l_pty, &c, 1);
}
}
}
return seen;
}
/*
* Write to a file descriptor with possible restart.
*/
ssize_t
swrite(int fd, const char *data, size_t sz)
{
ssize_t wo, wt = 0;
do {
if ((wo = write(fd, data + wt, sz - wt)) < 0) {
if (errno == EINTR)
continue;
else
return -1;
}
wt += wo;
} while (wt < sz);
return sz;
}
/*
* Pass output from pty to terminal. On Linux systems, this is a simple
* task as we can issue tcgetattr() on the pty master to get the slave's
* settings. On SVR4 however, we have to use getmsg() for this and must
* update our copy of the slave's settings whenever a M_IOCTL message
* is received.
*/
int
dooutput(int pty, struct layer *l)
#ifdef __linux__
{
char buf[4096];
ssize_t sz;
if ((sz = read(pty, buf, sizeof buf)) > 0)
swrite(1, buf, sz);
return 0;
}
#else /* !__linux__ */
{
char cbuf[64], dbuf[1024];
struct strbuf cs, ds;
struct iocblk *ib;
struct termio *to;
int more, flag, maybehup = 0;
if (l == NULL)
l = lbypty(pty);
for (;;) {
cs.maxlen = sizeof cbuf;
cs.buf = cbuf;
ds.maxlen = sizeof dbuf;
ds.buf = dbuf;
flag = 0;
if ((more = getmsg(l->l_pty, &cs, &ds, &flag)) < 0)
break;
switch (cbuf[0] & 0377) {
case M_IOCTL:
/*LINTED*/
ib = (struct iocblk *)dbuf;
switch (ib->ioc_cmd) {
case TCSETS:
case TCSETSW:
case TCSETSF:
memcpy(&l->l_tio, dbuf + sizeof *ib,
sizeof l->l_tio);
break;
case TCSETA:
case TCSETAW:
case TCSETAF:
/*LINTED*/
to = (struct termio *)(dbuf + sizeof *ib);
l->l_tio.c_iflag = to->c_iflag;
l->l_tio.c_oflag = to->c_oflag;
l->l_tio.c_cflag = to->c_cflag;
l->l_tio.c_lflag = to->c_lflag;
memcpy(l->l_tio.c_cc, to->c_cc, NCC);
break;
}
break;
case M_DATA:
if (ds.len == 0)
maybehup++;
else
swrite(1, ds.buf, ds.len);
break;
}
if (more == 0)
break;
}
return maybehup;
}
#endif /* !__linux__ */
/*
* Wait for children.
*/
struct layer *
await(void)
{
struct layer *l;
int status;
pid_t pid;
for (;;) {
if ((pid = waitpid(-1, &status, WNOHANG)) < 0)
return NULL;
if ((l = lbypid(pid)) == NULL) /* not our job */
continue;
errstat = status;
break;
}
return l;
}
/*
* Input/output loop. Poll for input or status change on stdin and all pty
* descriptors and invoke the appropriate handler.
*/
void
doio(void)
{
struct pollfd pfd[MAXLAYER + 1];
struct layer *l;
int nfds, nevents, i;
int endloop = 0;
tcsetattr(0, TCSADRAIN, &rtio);
pfd[0].fd = 0;
pfd[0].events = POLLIN;
pfd[0].revents = 0;
for (nfds = 0, l = l0; nfds <= MAXLAYER && l; l = l->l_next) {
nfds++;
pfd[nfds].fd = l->l_pty;
pfd[nfds].events = POLLIN;
pfd[nfds].revents = 0;
}
while (endloop == 0) {
nevents = poll(pfd, nfds + 1, -1);
if (nevents == -1 && errno == EINTR)
continue;
for (i = 0; i <= nfds && nevents; i++) {
if (pfd[i].revents == 0)
continue;
nevents--;
if (pfd[i].revents & POLLIN) {
if (i == 0)
endloop += doinput();
else {
/*
* Check whether output is blocked.
* i != nfds does the same as
* l != lcur but is faster here.
*/
if (i != nfds || cmdptr != NULL) {
l = lbypty(pfd[i].fd);
if (l->l_blk) {
pfd[i].events = 0;
continue;
}
} else
l = NULL;
if (dooutput(pfd[i].fd, l))
goto maybehup;
}
/*
* Do not check for other conditions as
* dooutput() possibly did not fetch all
* waiting data.
*/
continue;
}
if (pfd[i].revents & (POLLERR | POLLHUP)) {
if (i == 0) {
/*
* This is an error on input. What
* can we do to recover from here?
*/
msg("input error; retrying");
sleep(1);
continue;
}
maybehup:
l = await();
if (l == lcur)
endloop++;
if (l != NULL) {
if (l == lcur)
setprev(1);
doutmp(UTMP_DEL, l);
lfree(l);
}
}
}
}
tcsetattr(0, TCSADRAIN, &dtio);
}
/*
* Delete a layer, kill its residents.
*/
int
ldelete(struct layer *l)
{
kill(0 - l->l_pid, SIGHUP);
/*
* Do not remove the utmp entry now; this will be done once the
* process has been waited for.
*/
if (l == lcur)
setprev(0);
return 0;
}
/*
* Determine blocking status of a layer.
*/
char *
blockstat(struct layer *l)
{
/*
* There is an additional message "blocked on input", but its
* conditions are unclear.
*/
if (l->l_blk) {
struct pollfd pfd[1];
pfd[0].fd = l->l_pty;
pfd[0].events = POLLIN;
pfd[0].revents = 0;
if (poll(pfd, 1, 0) > 0 && (pfd[0].revents & POLLIN))
return "blocked on output";
}
return "executing or awaiting input";
}
/*
* List a layer, simple format.
*/
int
llist(struct layer *l)
{
msg("%s (%ld) %s", l->l_name, (long)l->l_pid, blockstat(l));
return 0;
}
/*
* List a layer, extended ("ps"-like) format.
*/
int
lpslist(struct layer *l)
{
pid_t pid;
int status;
llist(l);
switch (pid = fork()) {
case -1:
return 1;
default:
while (waitpid(pid, &status, 0) != pid);
msg("");
break;
case 0:
putenv("PATH=" SV3BIN ":" DEFBIN ":/bin:/usr/bin");
if (sysv3 && getenv("SYSV3") == NULL)
putenv("SYSV3=set");
dup2(2, 1);
execlp("ps", "ps", "-f", "-t", &l->l_line[5], NULL);
msg("no ps command found");
_exit(0177);
}
return status;
}
/*
* Resume a layer.
*/
int
lresume(struct layer *l)
{
msg("resuming %s", l->l_name);
setcur(l);
doio();
return 0;
}
/*
* Execute a command on a layer list.
*/
int
foreach(char *list, int (*func)(struct layer *))
{
struct layer *l;
char *cp = list;
int ecnt = 0;
do {
if ((cp = strpbrk(cp, " \t")) != NULL)
while (blankchar(*cp))
*cp++ = '\0';
if ((l = lbyname(list)) == NULL) {
msg(lnotfound, list);
ecnt++;
} else {
ecnt += func(l);
}
} while ((list = cp) != NULL);
return ecnt;
}
/*
* Child command for new layer.
*/
void
child(char *arg0, char *arg, char **args)
{
int estat;
sigset(SIGHUP, oldhup);
sigset(SIGINT, oldint);
sigset(SIGQUIT, oldquit);
sigset(SIGTTOU, oldttou);
sigset(SIGTSTP, oldtstp);
sigset(SIGTTIN, oldttin);
sigset(SIGWINCH, oldwinch);
if (args)
execvp(args[0], args);
else
execlp(arg0, arg, NULL);
switch (errno) {
case ELOOP:
case ENAMETOOLONG:
case ENOENT:
estat = 0176;
break;
default:
estat = 0177;
}
msg("cannot exec %s", args ? args[0] : arg0);
_exit(estat);
}
/*
* Execute a new layer. If args is not NULL, use it for execution, else,
* use arg0 and arg.
*/
pid_t
lspawn(struct layer *l, char *arg0, char *arg, char **args)
{
char *ps1;
size_t sz;
pid_t pid;
int fds;
if ((l->l_pty = ptym_open(l->l_line, sizeof l->l_line)) < 0) {
msg("control channel unavailable");
return -1;
}
fcntl(l->l_pty, F_SETFD, FD_CLOEXEC);
switch (pid = fork()) {
case -1:
msg("cannot fork");
break;
case 0:
setsid();
if ((fds = ptys_open(l->l_pty, l->l_line)) < 0) {
msg("virtual tty %s open failed", l->l_line);
_exit(0177);
}
close(l->l_pty);
if (tcsetattr(fds, TCSADRAIN, &dtio) < 0) {
msg("virtual tty %s stty failed", l->l_line);
_exit(0177);
}
if (winsz.ws_row)
ioctl(fds, TIOCSWINSZ, &winsz);
dup2(fds, 0);
dup2(fds, 1);
dup2(fds, 2);
close(fds);
ps1 = smalloc(sz = strlen(l->l_name) + 7);
snprintf(ps1, sz, "PS1=%s%s ", l->l_name, myuid ? "" : "#");
putenv(ps1);
child(arg0, arg, args);
/*NOTREACHED*/
default:
memcpy(&l->l_tio, &dtio, sizeof l->l_tio);
}
return pid;
}
/*
* Create a default layer name formed (digit).
*/
char *
defnumlnam(void)
{
static char str[6];
unsigned int count;
for (count = 1; count <= MAXLAYER; count++) {
snprintf(str, sizeof str, "(%u)", count);
if (lbyname(str) == NULL)
break;
}
return str;
}
/*
* Create a new layer.
*/
int
c_create(char *arg)
{
#ifdef WORDEXP
wordexp_t we;
int usewe = 0;
#endif /* WORDEXP */
char cmd0[_POSIX_PATH_MAX];
char *cmd;
const char *cp;
struct layer *l;
int accept = 0;
if (arg == NULL) {
arg = defnumlnam();
accept++;
}
if (*arg == '-') {
arg++;
cmd = dashell;
strncpy(cmd0, shell, sizeof cmd0);
} else if ((cmd = strpbrk(arg, " \t")) == NULL) {
cmd = dashell + 1;
strncpy(cmd0, shell, sizeof cmd0);
} else {
while (blankchar(*cmd))
*cmd++ = '\0';
#ifdef WORDEXP
usewe++;
switch (wordexp(cmd, &we, 0)) {
case 0: /* success */
cp = NULL;
break;
case WRDE_NOSPACE:
cp = "no memory";
break;
case WRDE_BADCHAR:
case WRDE_SYNTAX:
default:
cp = syntax;
}
if (cp != NULL) {
msg(cp);
return 1;
}
#else /* !WORDEXP */
cp = cmd + strlen(cmd) - 1;
while (blankchar(*cmd))
*cmd-- = '\0';
strncpy(cmd0, cmd, sizeof cmd0);
#endif /* !WORDEXP */
}
cmd0[sizeof cmd0 - 1] = '\0';
if ((l = lalloc(arg, accept)) == NULL)
return 1;
setcur(l);
if ((l->l_pid = lspawn(l, cmd0, cmd,
#ifdef WORDEXP
usewe ? we.we_wordv :
#endif /* WORDEXP */
NULL))
== -1) {
setprev(1);
lfree(l);
#ifdef WORDEXP
if (usewe)
wordfree(&we);
#endif /* WORDEXP */
return 1;
}
doutmp(UTMP_ADD, l);
#ifdef WORDEXP
if (usewe)
wordfree(&we);
#endif /* WORDEXP */
doio();
return 0;
}
/*
* Rename a layer.
*/
int
c_name(char *arg)
{
struct layer *l;
char *newname;
if (arg == NULL) {
msg(syntax);
return 1;
}
if ((newname = strpbrk(arg, " \t")) != NULL) {
while (blankchar(*newname))
*newname++ = '\0';
if ((l = lbyname(arg)) == NULL) {
msg(lnotfound, arg);
return 1;
}
} else {
newname = arg;
if ((l = lcur) == NULL) {
msg(nocurl);
return 1;
}
}
if (invlname(newname))
return 1;
strcpy(l->l_name, newname);
return 0;
}
/*
* Invoke a subshell on the same tty as shl.
*/
int
c_bang(char *arg)
{
if (arg == NULL)
arg = shell;
errstat = system(arg);
return 0;
}
/*
* Block layer output.
*/
int
c_block(char *arg)
{
if (arg == NULL) {
msg(syntax);
return 1;
}
return foreach(arg, lblock);
}
/*
* Delete a layer.
*/
int
c_delete(char *arg)
{
if (arg == NULL) {
msg(syntax);
return 1;
}
return foreach(arg, ldelete);
}
/*
* Print summary for all commands.
*/
/*ARGSUSED*/
int
c_help(char *arg)
{
int i;
/*msg("\nshl %s %s by Gunnar Ritter\n", shl_version, shl_date);*/
for (i = 0; commands[i].c_string; i++)
if (commands[i].c_desc)
msg("%s", commands[i].c_desc);
/*msg("");*/
return 0;
}
/*
* List current layers.
*/
int
c_layers(char *arg)
{
int (*lfunc)(struct layer *);
struct layer *l;
if (arg && arg[0] == '-' && arg[1] == 'l'
&& (arg[2] == '\0' || blankchar(arg[2]))) {
lfunc = lpslist;
arg += 2;
while (blankchar(*arg))
arg++;
if (*arg == '\0')
arg = NULL;
} else
lfunc = llist;
if (arg)
return foreach(arg, lfunc);
for (l = l0; l; l = l->l_next)
lfunc(l);
return 0;
}
/*
* Resume a background layer.
*/
int
c_resume(char *arg)
{
struct layer *l;
if (arg != NULL) {
if ((l = lbyname(arg)) == NULL) {
msg(lnotfound, arg);
return 1;
}
} else {
if ((l = lcur) == NULL) {
msg(nocurl);
return 1;
}
}
return lresume(l);
}
/*
* Resume the previously backgrounded layer.
*/
/*ARGSUSED*/
int
c_toggle(char *arg)
{
if (lcur != NULL) {
if (lcur->l_prev != NULL)
setcur(lcur->l_prev);
} else {
msg(nocurl);
return 1;
}
return lresume(lcur);
}
/*
* Unblock a layer.
*/
int
c_unblock(char *arg)
{
if (arg == NULL) {
msg(syntax);
return 1;
}
return foreach(arg, lunblock);
}
/*
* Quit shl.
*/
int
c_quit(char *arg)
{
if (arg) {
msg(syntax);
return 1;
}
if (l0 != NULL && hadquit == 0) {
msg("warning: there may still be shl layers running");
hadquit++;
return 0;
}
quit(errstat);
/*NOTREACHED*/
return 0;
}
/*
* Do nothing.
*/
/*ARGSUSED*/
int
c_null(char *arg)
{
return 0;
}
/*
* Read a line from standard input, without terminating newline.
*/
char *
getaline(void)
{
cmdbuf[0] = '\n';
cmdptr = cmdbuf;
while (cmdbuf[0] == '\n')
doio();
if (cmdptr == NULL)
return NULL;
cmdptr = NULL;
return cmdbuf;
}
/*
* Prompt for command and do initial splitting.
*/
struct command *
prompt(void)
{
static struct command pc;
char *cmdstr = NULL, *argument;
size_t cmdlen;
int i;
write(1, ">>> ", 4);
if ((cmdstr = getaline()) == NULL) {
pc.c_func = c_quit;
pc.c_arg = NULL;
return &pc;
}
while (blankchar(*cmdstr))
cmdstr++;
argument = cmdstr + strlen(cmdstr) - 1;
while (blankchar(*argument))
*argument-- = '\0';
if (*cmdstr == '!' && cmdstr[1] != '\0') {
cmdlen = 1;
pc.c_arg = argument = &cmdstr[1];
} else {
if ((argument = strpbrk(cmdstr, " \t")) != NULL)
while (blankchar(*argument))
*argument++ = '\0';
pc.c_arg = argument;
cmdlen = strlen(cmdstr);
}
pc.c_func = c_null;
if (cmdlen == 0)
return &pc;
for (i = 0; commands[i].c_string; i++) {
if (strncmp(commands[i].c_string, cmdstr, cmdlen) == 0) {
if (pc.c_func != c_null) {
msg(syntax);
pc.c_func = c_null;
return &pc;
}
pc.c_func = commands[i].c_func;
}
}
if (pc.c_func == c_null && *cmdstr) {
struct layer *l;
if ((l = lguess(cmdstr)) != NULL)
lresume(l);
}
if (pc.c_func != c_quit)
hadquit = 0;
return &pc;
}
/*
* SIGHUP handler.
*/
void
onhup(int signum)
{
quit(signum | 0200);
}
/*
* SIGWINCH handler.
*/
/*ARGSUSED*/
void
onwinch(int signum)
{
struct layer *l;
if (ioctl(0, TIOCGWINSZ, &winsz) != -1)
for (l = l0; l; l = l->l_next)
ioctl(l->l_pty, TIOCSWINSZ, &winsz);
}
/*ARGSUSED*/
int
main(int argc, char **argv)
{
struct command *p;
char *cp;
char vdis;
progname = basename(argv[0]);
mypid = getpid();
myuid = getuid();
myeuid = geteuid();
mygid = getgid();
myegid = getegid();
if (myuid != myeuid)
setuid(myuid);
if (mygid != myegid)
setgid(mygid);
if (getenv("SYSV3") != 0)
sysv3 = 1;
if ((cp = getenv("SHELL")) == NULL)
cp = DEFSHELL;
shell = smalloc(strlen(cp) + 1);
strcpy(shell, cp);
cp = basename(cp);
dashell = smalloc(strlen(cp) + 2);
*dashell = '-';
strcpy(dashell + 1, cp);
if ((oldhup = sigset(SIGHUP, SIG_IGN)) != SIG_IGN)
sigset(SIGHUP, onhup);
oldint = sigset(SIGINT, SIG_IGN);
oldquit = sigset(SIGQUIT, SIG_IGN);
oldttou = sigset(SIGTTOU, SIG_IGN);
oldtstp = sigset(SIGTSTP, SIG_IGN);
oldttin = sigset(SIGTTIN, SIG_IGN);
oldwinch = sigset(SIGWINCH, onwinch);
if (tcgetattr(0, &otio) < 0) {
msg("not using a tty device");
quit(1);
}
memcpy(&dtio, &otio, sizeof dtio);
vdis = fpathconf(0, _PC_VDISABLE);
if (dtio.c_cc[VSWTCH] == vdis) {
if (dtio.c_cc[VSUSP] == DEFSWTCH) {
msg("warning: default swtch same as susp, disabling susp");
dtio.c_cc[VSUSP] = vdis;
}
dtio.c_cc[VSWTCH] = DEFSWTCH;
}
memcpy(&rtio, &dtio, sizeof rtio);
rtio.c_lflag &= ~(ISIG | ICANON | ECHO | IEXTEN);
rtio.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
rtio.c_cflag &= ~(CSIZE | PARENB);
rtio.c_cflag |= CS8;
rtio.c_oflag &= ~(OPOST);
rtio.c_cc[VMIN] = 0;
rtio.c_cc[VTIME] = 0;
if (ioctl(0, TIOCGWINSZ, &winsz) == -1)
winsz.ws_row = 0;
while ((p = prompt()) != NULL)
p->c_func(p->c_arg);
quit(errstat);
/*NOTREACHED*/
return 0;
}
/*
* Table of shl commands.
*/
struct command commands[] = {
{ "block", c_block, NULL,
"block name [name ...]", 23 },
{ "create", c_create, NULL,
"create [-[name] | name [command]]", 20 },
{ "delete", c_delete, NULL,
"delete name [name ...]", 24 },
{ "help", c_help, NULL,
"help or ?", 25 },
{ "?", c_help, NULL,
NULL, 25 },
{ "layers", c_layers, NULL,
"layers [-l] [name ...]", 26 },
{ "name", c_name, NULL,
"name [oldname] newname", 21 },
{ "quit", c_quit, NULL,
"quit", 30 },
{ "toggle", c_toggle, NULL,
"toggle", 28 },
{ "resume", c_resume, NULL,
"resume [name]", 27 },
{ "unblock", c_unblock, NULL,
"unblock name [name ...]", 29 },
{ "!", c_bang, NULL,
"! [command]", 22 },
{ NULL, NULL, NULL,
NULL, 0 }
};
#else /* __FreeBSD__, __hpux, _AIX, __NetBSD__, __OpenBSD__, __DragonFly__,
__APPLE__ */
#include <stdio.h>
int
main(void)
{
fprintf(stderr, "command not supported\n");
return 1;
}
#endif /* __FreeBSD__, __hpux, _AIX, __NetBSD__, __OpenBSD__, __DragonFly__,
__APPLE__ */
syntax highlighted by Code2HTML, v. 0.9.1