diff --git a/src/cmd/builtin/Makefile b/src/cmd/builtin/Makefile new file mode 100644 index 000000000..a85ef746a --- /dev/null +++ b/src/cmd/builtin/Makefile @@ -0,0 +1,90 @@ +/* + * standalone versions of -lcmd builtins + */ + +:PACKAGE: ast cmd:space + +LICENSE = since=1992,author=gsf+dgk + +:ALL: + +:: cmdlib.h builtin.c + +CMD_BUILTIN == +CMD_DYNAMIC == +CMD_STANDALONE == + +LINKS = cp: ln mv cksum: md5sum sum +NOBIN = getconf + +CAT = $(STDCAT|"cat") +CMP = $(STDCMP|"cmp") 2>/dev/null +CP = $(STDCP|"cp") +LN = $(STDLN|"ln") +RM = $(STDRM|"rm") + +BUILTINS :COMMAND: cmdlist.h + $(SED) -e '/^CMDLIST(.*)$/!d' -e 's/CMDLIST(\(.*\))/\1/' $(NOBIN:C,.*,-e '/^&$/d',) $(LINKS:N!=*[:]:C,.*,-e '/^&$/d',) $(*) + +.INIT : .builtin + +.builtin : .MAKE .VIRTUAL .FORCE + local T I + if T = "$(NOBIN:D=$(BINDIR):B:S:T=F)" + { $(RM) $(RMFLAGS) $(T) } + end + :ALL: $(BUILTINS) + T = -lcmd + if "$(PACKAGE_OPTIONS:N=optimize-space)" && "$(T:T=F)" == "-lcmd" + T := $(BUILTINS:O=1) + $(T) :: CMD_BUILTIN=1 CMD_DYNAMIC=1 builtin.c -lcmd -ldll + for I $(BUILTINS:O>1) + $(I) :LINK: $(T) + end + for I $(LINKS) + if I != "*:" + $(I) :LINK: $(T) + end + end + else + $(BINDIR) :INSTALLDIR: $(BUILTINS) + $(BUILTINS) : .COMMAND $(&$("%.c":T=SM%)) CMD_BUILTIN=b_$$(<:B) builtin.c $(*$("%.c":T=SM%)) + $(CAT) $(*:N=*builtin.c) > $(<).c + $(RM) $(RMFLAGS) $(<) + $(@$("%.c":T=SM%):/.*builtin.c$/$(<).c/) + $(RM) $(RMFLAGS) $(<).c + for I $(LINKS) + if I == "*:" + T := $(I:/.$//) + else + $(I) :LINK: $(T) + end + end + end + +":BUILTIN:" : .MAKE .OPERATOR + local T P + for T $(>) + if T == "*=*" + $(P).o : $(T) + elif T == "[-+]l*" + $(P) : $(T) + else + eval + $(T) :: CMD_STANDALONE=b_$(T) $(T).c + end + P := $(T) + end + end + +:BUILTIN: dlls LICENSE=since=2002,author=gsf -ldll look mime \ + pty LICENSE=since=2001,author=gsf+dgk -lutil \ + nl LICENSE=since=2003,author=dgk asa od pr \ + strings tr uudecode -luu uuencode -luu what who + +dontcare -lutil + +:: PROMO.mm RELEASE + +:TEST: asa cat chmod chown cksum cmp cp cut date expr fmt head join ln \ + look mkdir mkfifo mktemp mv nl od paste rm tail tr uniq uuencode wc diff --git a/src/cmd/builtin/Mamfile b/src/cmd/builtin/Mamfile new file mode 100644 index 000000000..222adde8c --- /dev/null +++ b/src/cmd/builtin/Mamfile @@ -0,0 +1,95 @@ +info mam static 00000 1994-07-17 make (AT&T Research) 5.7 2012-06-20 +setv INSTALLROOT ../../.. +setv PACKAGE_ast_INCLUDE ${INSTALLROOT}/include/ast +setv PACKAGE_ast_LIB ${INSTALLROOT}/lib +setv PACKAGE_cmd ${INSTALLROOT} +setv PACKAGE_cmd_INCLUDE ${PACKAGE_cmd}/include +setv PACKAGE_cmd_LIB ${PACKAGE_cmd}/lib +setv PACKAGEROOT ../../../../.. +setv AR ${mam_cc_AR} ${mam_cc_AR_ARFLAGS} +setv ARFLAGS rc +setv AS as +setv ASFLAGS +setv CC cc +setv mam_cc_FLAGS +setv CCFLAGS ${-debug-symbols?1?${mam_cc_DEBUG} -D_BLD_DEBUG?${mam_cc_OPTIMIZE}?} +setv CCLDFLAGS ${-strip-symbols?1?${mam_cc_LD_STRIP}??} +setv COTEMP $$ +setv CPIO cpio +setv CPIOFLAGS +setv CPP "${CC} -E" +setv F77 f77 +setv HOSTCC ${CC} +setv IGNORE +setv LD ld +setv LDFLAGS +setv LEX lex +setv LEXFLAGS +setv LPR lpr +setv LPRFLAGS +setv M4FLAGS +setv NMAKE nmake +setv NMAKEFLAGS +setv PR pr +setv PRFLAGS +setv SHELL /bin/sh +setv SILENT +setv TAR tar +setv YACC yacc +setv YACCFLAGS -d +make ${PACKAGEROOT}/lib/package/ast.lic +done ${PACKAGEROOT}/lib/package/ast.lic +make .INIT +make ${PACKAGE_ast_INCLUDE}/cmdlist.h +make ${PACKAGE_ast_INCLUDE}/prototyped.h implicit +done ${PACKAGE_ast_INCLUDE}/prototyped.h dontcare +done ${PACKAGE_ast_INCLUDE}/cmdlist.h +exec - sed -e '/^CMDLIST(.*)$/!d' -e 's/CMDLIST(\(.*\))/\1/' -e '/^getconf$/d' -e '/^ln$/d' -e '/^mv$/d' -e '/^md5sum$/d' -e '/^sum$/d' ${PACKAGE_ast_INCLUDE}/cmdlist.h +bind -lcmd +done .INIT dontcare virtual +make install +make pty +make pty.o +make pty.c +make FEATURE/pty implicit +meta FEATURE/pty features/%>FEATURE/% features/pty pty +make features/pty +done features/pty +exec - iffe -v -c '${CC} ${mam_cc_FLAGS} ${CCFLAGS} ${LDFLAGS} ' ref ${mam_cc_L+-L${INSTALLROOT}/lib} -I${PACKAGE_ast_INCLUDE} -I${INSTALLROOT}/include ${mam_libast} ${mam_libcmd} : run features/pty +done FEATURE/pty generated +make ${PACKAGE_ast_INCLUDE}/ast_time.h implicit +done ${PACKAGE_ast_INCLUDE}/ast_time.h +prev ${PACKAGE_ast_INCLUDE}/vmalloc.h implicit +prev ${PACKAGE_ast_INCLUDE}/regex.h implicit +make ${PACKAGE_ast_INCLUDE}/proc.h implicit +prev ${PACKAGE_ast_INCLUDE}/ast.h implicit +prev ${PACKAGE_ast_INCLUDE}/prototyped.h implicit +done ${PACKAGE_ast_INCLUDE}/proc.h +prev ${PACKAGE_ast_INCLUDE}/error.h implicit +prev ${PACKAGE_ast_INCLUDE}/cmd.h implicit +done pty.c +meta pty.o %.c>%.o pty.c pty +prev pty.c +setv CMD_STANDALONE -DCMD_STANDALONE="b_pty" +setv LICENSE -DLICENSE="since=2001,author=gsf+dgk" +exec - ${CC} ${mam_cc_FLAGS} ${CCFLAGS} -I. -I${PACKAGE_ast_INCLUDE} -DERROR_CATALOG=\""builtin"\" -D_PACKAGE_ast -DCMD_STANDALONE=b_pty -DUSAGE_LICENSE=\""[-author?Glenn Fowler ][-author?David Korn ][-copyright?Copyright (c) 2001-2012 AT&T Intellectual Property][-license?http://www.eclipse.org/org/documents/epl-v10.html][--catalog?builtin]"\" -c pty.c +done pty.o generated +bind -lutil dontcare +setv CMD_STANDALONE -DCMD_STANDALONE="b_pty" +exec - ${CC} ${CCLDFLAGS} ${mam_cc_FLAGS} ${CCFLAGS} ${LDFLAGS} ${mam_cc_L+-L.} ${mam_cc_L+-L${INSTALLROOT}/lib} -o pty pty.o ${mam_libutil} ${mam_libast} ${mam_libcmd} +done pty generated +make ${INSTALLROOT}/bin +exec - if silent test ! -d ${INSTALLROOT}/bin +exec - then mkdir -p ${INSTALLROOT}/bin +exec - fi +done ${INSTALLROOT}/bin generated +make ${INSTALLROOT}/bin/pty +prev pty +exec - test '' = 'pty' || ${STDCMP} 2>/dev/null -s pty ${INSTALLROOT}/bin/pty || { ${STDMV} ${INSTALLROOT}/bin/pty ${INSTALLROOT}/bin/pty.old 2>/dev/null || true; ${STDCP} pty ${INSTALLROOT}/bin/pty ;} +done ${INSTALLROOT}/bin/pty generated +make ${INSTALLROOT}/bin +exec - if silent test ! -d ${INSTALLROOT}/bin +exec - then mkdir -p ${INSTALLROOT}/bin +exec - fi +done ${INSTALLROOT}/bin virtual +done install virtual diff --git a/src/cmd/builtin/RELEASE b/src/cmd/builtin/RELEASE new file mode 100644 index 000000000..86d2d313e --- /dev/null +++ b/src/cmd/builtin/RELEASE @@ -0,0 +1,10 @@ +12-02-28 pty.c: change --verbose[=level] to --debug=level +12-01-26 pty.c: fix --man docs +10-06-21 pty.c: add 4 sec timeout for initial handshake -- fix me!! +10-04-12 pty: fix sfpoll() result read/write bug +10-04-01 pty: add --tty='stty-settings' +10-03-19 pty: add --dialogue +10-03-15 pty: fix select() fd management +09-03-31 features/pty,Makefile: add pty.h and -lutil refs for linux -- great, another util library +09-01-30 pty.c: add (for fd_set!! on mvs.390) +06-07-20 pty.c: add diff --git a/src/cmd/builtin/features/pty b/src/cmd/builtin/features/pty new file mode 100755 index 000000000..548af118b --- /dev/null +++ b/src/cmd/builtin/features/pty @@ -0,0 +1,60 @@ +set prototyped + +header sys/types.h +header pty.h +header libutil.h +header sys/pty.h +header sys/ptyio.h +header sys/vty.h +header sys/ioctl.h +header stropts.h + +lib openpty,_getpty,ptsname -lutil +lib grantpt,unlockpt,posix_openpt stdlib.h + +tst - output{ + #include + #include + #if _lib_ptsname && _npt_ptsname + _BEGIN_EXTERNS_ + #if _STD_ + extern char* ptsname(int); + #else + extern char* ptsname(); + #endif + #endif + _END_EXTERNS_ + int main() + { + int i; + struct stat statb; + static char* pty[] = { "/dev/ptyp0000", "/dev/ptym/ptyp0", "/dev/ptyp0" }; + #if _lib_ptsname + int fd; + static char* ptc[] = { "/dev/ptmx", "/dev/ptc", "/dev/ptmx_bsd" }; + + for (i = 0; i < sizeof(ptc) / sizeof(ptc[0]); i++) + if((fd = open(ptc[i], 2))>=0) + { + if (ptsname(fd)) + { + printf("#define _pty_clone\t\"%s\"\n", ptc[i]); + close(fd); + break; + } + close(fd); + } + #endif + for (i = 0;; i++) + if(i >= (sizeof(pty) / sizeof(pty[0]) - 1) || stat(pty[i], &statb)>=0) + { + printf("#define _pty_first\t\"%s\"\n", pty[i]); + break; + } + return 0; + } +}end + +extern _getpty char* (int*, int, mode_t, int) +extern openpty int (int*, int*, char*, struct termios*, struct winsize*) +extern ptsname char* (int) diff --git a/src/cmd/builtin/pty.c b/src/cmd/builtin/pty.c new file mode 100644 index 000000000..b75320480 --- /dev/null +++ b/src/cmd/builtin/pty.c @@ -0,0 +1,1069 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1992-2012 AT&T Intellectual Property * +* and is licensed under the * +* Eclipse Public License, Version 1.0 * +* by AT&T Intellectual Property * +* * +* A copy of the License is available at * +* http://www.eclipse.org/org/documents/epl-v10.html * +* (with md5 checksum b35adb5213ca9657e911e9befb180842) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* Glenn Fowler * +* David Korn * +* * +***********************************************************************/ +#pragma prototyped + +static const char usage[] = +"[-?\n@(#)pty (AT&T Research) 2012-06-11\n]" +USAGE_LICENSE +"[+NAME?pty - create pseudo terminal and run command]" +"[+DESCRIPTION?\bpty\b creates a pseudo pty and then runs \bcommand\b " + "with arguments given by \aarg\a and the standard input, standard " + "output, and standard error connected to the pseudo terminal. By " + "default, the \bpty\b creates a new session.]" +"[+?If \bcommand\b does not contain a \b/\b, the \bPATH\b variable will " + "be used to locate the \bcommand\b.]" +"[+?Input to \bpty\b will be written to the standard input of this " + "command. The standard output and standard error from the command will " + "be written to the standard output of \bpty\b.]" +"[+?The \bpty\b commmand terminates when the command completes.]" +"[d:dialogue?Execute the dialogue on the standard input. A dialogue is a " + "sequence of commands, one command per line. All \are\a patterns are " + "extended regular expressions. The \are\a \b?1\b will print the subject " + "string on the standard error and match the string; the \are\a \b?0\b " + "will print the subject string on the standard error and not match the " + "string. The \are\a \b?.\b matches EOF. The commands are:]" + "{" + "[\b#\b \acomment\a?comment line]" + "[c \atext\a?write \atext\a to the master; C style escapes " + "in text are converted, including \\E for ESC and \\cX for " + "control-X]" + "[d \amilliseconds\a?set the delay before each master write to " + "\amilliseconds\a; the default is no delay]" + "[i \are\a?read a line from the master; if it matches \are\a " + "then execute lines until matching \be\b or \bf\b]" + "[e [re]]?else [if match re]] then execute lines until matching " + "\be\b or \bf\b]" + "[f?end of \bi\b/\be\b block]" + "[m \atext\a?write \atext\a to the standard error]" + "[p \atext\a?peek input until \atext\a is found at the beginning " + "of a line; input is not consumed]" + "[r [\are\a]]?read a line from the master [and it should match " + "re]]]" + "[s \amilliseconds\a?sleep for \amilliseconds\a]" + "[t \amilliseconds\a?set the master read timout to " + "\amilliseconds\a; the default is \b1000\b]" + "[u \are\a?read lines from the master until one matches \are\a]" + "[v \alevel\a?set the verbose trace \alevel\a, more output for " + "higher levels, disabled for level 0]" + "[w \atext\a?write \atext\a\\r\\n to the master; C style escapes " + "in text are converted, including \\E for ESC and \\cX for " + "control-X]" + "[x [\acode\a]]" + "?exit \bpty\b with exit code \b0\b [\acode\a]]]" + "[I \are\a?ignore master lines matching \are\a]" + "[L \alabel\a?prefix all diagnostics with \alabel\a:]" + "[P \atext\a?delay each master write until the beginning of " + "an unread input line exactly matches \atext\a]" + "}" +"[D:debug?Set the debug trace \alevel\a, higher levels produce more " + "output, disabled for level 0.]#[level]" +"[l:log?Log the master stdout and stderr to \afile\a.]:[file]" +"[m:messages?Redirect diagnostic message output to \afile\a.]:[file]" +"[s!:session?Create a separate session for the process started by " + "\bpty\b.]" +"[t:timeout?Set the master read timeout to " + "\amilliseconds\a.]#[milliseconds:=1000]" +"[T:tty?Pass \astty\a to the \bstty\b(1) command to initialize the " + "pty.]:[stty]" +"[w:delay?Set the delay before each master write to " + "\amilliseconds\a.]#[milliseconds:=0]" + +"\n" +"\ncommand [arg ...]\n" +"\n" + +"[+EXIT STATUS?If the command determined by \bcommand\b is run the exit " + "status of \bpty\b is that of this command. Otherwise, the exit status " + "is one of the following:]" + "{" + "[+127?The command is found but cannot be executed.]" + "[+128?The command could not be found.]" + "}" +"[+SEE ALSO?\bcommand\b(1), \bexec\b(1)]" +; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "FEATURE/pty" + +#define MODE_666 (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) +#define MAXNAME 64 + +#if !_lib_openpty && !_lib__getpty && !defined(_pty_clone) +# if !_lib_grantpt || !_lib_unlock +# if !_lib_ptsname + static char *slavename(const char *name) + { + static char sname[MAXNAME]; + char *last; + strncpy(sname,name,sizeof(sname)); + last = strrchr(sname,'/'); + last[1] = 't'; + return(sname); + } +# endif + + static char *master_name(char *name) + { + static char sname[MAXNAME]; + int n; + if(!name) + { + strcpy(sname,_pty_first); + return(sname); + } + n = strlen(_pty_first); + if(name[n-1]=='9') + name[n-1]='a'; + else if(name[n-1]=='f') + { + if(_pty_first[n-2]=='0' && name[n-2]=='9') + { + name[n-2]='0'; + if(name[n-3]=='9' || name[n-3]=='z') + return(NULL); + name[n-3]++; + } + if(_pty_first[n-2]=='p' && (name[n-2]=='z' || name[n-2]=='Z')) + { + if(name[n-2]=='z') + name[n-2]=='P'; + else + return(0); + } + else + name[n-2]++; + name[n-1]='0'; + } + else + name[n-1]++; + return(name); + } +#endif + +#if !_lib_openpty + static char *ptymopen(int *master) + { + char *slave=0; +# if _lib__getpty + return(_getpty(master,O_RDWR,MODE_666,0)); +# else +# if defined(_pty_clone) + *master = open(_pty_clone,O_RDWR|O_CREAT,MODE_666); + if(*master>=0) + slave = ptsname(*master); +# else + int fdm; + char *name=0; + while(name=master_name(name)) + { + fdm = open(name,O_RDWR|O_CREAT,MODE_666); + if(fdm >= 0) + { + *master = fdm; +# if _lib_ptsname + slave = ptsname(fdm); +# else + slave = slavename(name); +# endif + break; + } + } +# endif +# endif + return(slave); + } +# endif +#endif + +static int +mkpty(int* master, int* slave) +{ + struct termios tty; + struct termios tst; + struct termios* ttyp; +#ifdef TIOCGWINSZ + struct winsize win; + struct winsize* winp; +#endif +#if !_lib_openpty + char* sname; +#endif + /* + * some systems hang hard during the handshake + * if you know why then please let us know + */ + + alarm(4); + if (tcgetattr(STDERR_FILENO, &tty) >= 0) + ttyp = &tty; + else + { + ttyp = 0; + error(-1, "unable to get standard error terminal attributes"); + } +#ifdef TIOCGWINSZ + if (ioctl(STDERR_FILENO, TIOCGWINSZ, &win) >= 0) + winp = &win; + else + { + winp = 0; + error(-1, "unable to get standard error window size"); + } +#endif +#if _lib_openpty + if (openpty(master, slave, NULL, ttyp, winp) < 0) + return -1; +#else +#if _lib_grantpt && _lib_unlockpt +#if !_lib_posix_openpt +#ifndef _pty_clone +#define _pty_clone "/dev/ptmx" +#endif +#define posix_openpt(m) open(_pty_clone,m) +#endif + if ((*master = posix_openpt(O_RDWR)) < 0) + return -1; + if (grantpt(*master) || unlockpt(*master) || !(sname = ptsname(*master)) || (*slave = open(sname, O_RDWR|O_cloexec)) < 0) + { + close(*master); + return -1; + } +#else + if (!(sname = ptymopen(master)) || (*slave = open(sname, O_RDWR|O_cloexec)) < 0) + return -1; +#endif +#ifdef I_PUSH + if (tcgetattr(*slave, &tst) < 0 && (ioctl(*slave, I_PUSH, "ptem") < 0 || ioctl(*slave, I_PUSH, "ldterm") < 0)) + { + close(*slave); + close(*master); + return -1; + } +#endif +#endif + if (ttyp && tcsetattr(*slave, TCSANOW, ttyp) < 0) + error(ERROR_warn(0), "unable to set pty terminal attributes"); +#ifdef TIOCSWINSZ + if (winp && ioctl(*slave, TIOCSWINSZ, winp) < 0) + error(ERROR_warn(0), "unable to set pty window size"); +#endif + fcntl(*master, F_SETFD, FD_CLOEXEC); +#if !O_cloexec + fcntl(*slave, F_SETFD, FD_CLOEXEC); +#endif + alarm(0); + return 0; +} + +static Proc_t* +runcmd(char** argv, int slave, int session) +{ + long ops[4]; + + if (session) + { + ops[0] = PROC_FD_CTTY(slave); + ops[1] = 0; + } + else + { + ops[0] = PROC_FD_DUP(slave, 0, PROC_FD_CHILD); + ops[1] = PROC_FD_DUP(slave, 1, PROC_FD_CHILD); + ops[2] = PROC_FD_DUP(slave, 2, PROC_FD_CHILD); + ops[3] = 0; + } + return procopen(argv[0], argv, NiL, ops, 0); +} + +/* + * default master dance + */ + +static int +process(Sfio_t* mp, Sfio_t* lp, int delay, int timeout) +{ + int i; + int n; + int t; + ssize_t r; + char* s; + Sfio_t* ip; + Sfio_t* sps[2]; + + ip = sfstdin; + for (;;) + { + i = 0; + t = timeout; + if (mp) + sps[i++] = mp; + if (ip) + { + sps[i++] = ip; + t = -1; + } + if (!i) + break; + if ((n = sfpoll(sps, i, t)) <= 0) + { + if (n < 0) + error(ERROR_SYSTEM|2, "poll failed"); + if (t < 0) + break; + } + else + for (i = 0; i < n; i++) + { + if (!(sfvalue(sps[i]) & SF_READ)) + /*skip*/; + else if (sps[i] == mp) + { + if (!(s = (char*)sfreserve(mp, SF_UNBOUND, -1))) + { + sfclose(mp); + mp = 0; + } + else if ((r = sfvalue(mp)) > 0 && (sfwrite(sfstdout, s, r) != r || sfsync(sfstdout))) + { + error(ERROR_SYSTEM|2, "output write failed"); + goto done; + } + } + else + { + if (!(s = sfgetr(ip, '\n', 1))) + ip = 0; + else if (sfputr(mp, s, '\r') < 0 || sfsync(mp)) + { + error(ERROR_SYSTEM|2, "write failed"); + goto done; + } + } + } + } + done: + if (mp) + sfclose(mp); + return error_info.errors != 0; +} + +/* + * return 1 is extended re pattern matches text + */ + +static int +match(char* pattern, char* text, int must) +{ + regex_t* re; + int code; + char buf[64]; + + if (!pattern[0]) + return 1; + if (pattern[0] == '?' && pattern[1] && !pattern[2]) + { + switch (pattern[1]) + { + case '0': + case '1': + if (text) + error(2, "got \"%s\"", fmtesq(text, "\"")); + else + error(2, "got EOF"); + return pattern[1] == '1'; + case '.': + if (!text) + return 1; + if (must) + error(2, "expected EOF, got \"%s\"", fmtesq(text, "\"")); + return 0; + } + } + if (!text) + { + if (must) + error(2, "expected \"%s\", got EOF", pattern); + return 0; + } + if (!(re = regcache(pattern, REG_EXTENDED, &code))) + { + regerror(code, re, buf, sizeof(buf)); + error(2, "%s: %s", pattern, buf); + return 0; + } + if (regexec(re, text, 0, NiL, 0)) + { + if (must) + error(2, "expected \"%s\", got \"%s\"", pattern, fmtesq(text, "\"")); + return 0; + } + return 1; +} + +typedef struct Master_s +{ + Vmalloc_t* vm; /* allocation region */ + char* ignore; /* ignore master lines matching this re */ + char* peek; /* peek buffer pointer */ + char* cur; /* current line */ + char* nxt; /* next line */ + char* end; /* end of lines */ + char* max; /* end of buf */ + char* buf; /* current buffer */ + char* prompt; /* peek prompt */ + int cursor; /* cursor in buf, 0 if fresh line */ + int line; /* prompt line number */ + int restore; /* previous line save char */ +} Master_t; + +/* + * read one line from the master + */ + +#define MASTER_EOF (-1) +#define MASTER_TIMEOUT (-2) + +static char* +masterline(Sfio_t* mp, Sfio_t* lp, char* prompt, int must, int timeout, Master_t* bp) +{ + char* r; + char* s; + char* t; + ssize_t n; + ssize_t a; + size_t promptlen; + ptrdiff_t d; + char promptbuf[64]; + + if (prompt) + promptlen = sfsprintf(promptbuf, sizeof(promptbuf), prompt, ++bp->line); + again: + if (prompt) + { + if (bp->cur < bp->end && bp->restore >= 0) + *bp->cur = bp->restore; + if (strneq(bp->cur, promptbuf, promptlen)) + r = bp->cur; + else + r = 0; + if (bp->cur < bp->end && bp->restore >= 0) + *bp->cur = 0; + if (r) + { + error(-1, "p \"%s\"", fmtnesq(promptbuf, "\"", promptlen)); + return r; + } + if (r = bp->nxt) + { + if (strneq(r, promptbuf, promptlen)) + { + error(-1, "p \"%s\"", fmtnesq(promptbuf, "\"", promptlen)); + return r; + } + while (r = memchr(r, '\n', bp->end - r)) + { + if (strneq(r, promptbuf, promptlen)) + { + error(-1, "p \"%s\"", fmtnesq(promptbuf, "\"", promptlen)); + return r; + } + r++; + } + } + *bp->cur = 0; + } + else if (bp->nxt) + { + if (bp->restore >= 0) + *bp->cur = bp->restore; + r = bp->cur; + bp->restore = *bp->nxt; + *bp->nxt = 0; + if (bp->nxt >= bp->end) + { + bp->cur = bp->end = bp->buf; + bp->nxt = 0; + } + else + { + bp->cur = bp->nxt; + if (bp->nxt = memchr(bp->nxt + 1, '\n', bp->end - bp->nxt - 1)) + bp->nxt++; + } + goto done; + } + if ((n = sfpoll(&mp, 1, timeout)) <= 0 || !((int)sfvalue(mp) & SF_READ)) + { + if (n < 0) + { + if (must) + error(ERROR_SYSTEM|2, "poll failed"); + else + error(-1, "r poll failed"); + } + else if (bp->cur < bp->end) + { + if (bp->restore >= 0) + { + *bp->cur = bp->restore; + bp->restore = -1; + } + r = bp->cur; + *bp->end = 0; + bp->nxt = 0; + if (prompt && strneq(r, promptbuf, promptlen)) + { + error(-1, "p \"%s\"", fmtnesq(promptbuf, "\"", promptlen)); + return r; + } + bp->cur = bp->end = bp->buf; + goto done; + } + else if (must >= 0) + error(2, "read timeout"); + else + { + errno = 0; + error(-1, "r EOF"); + } + return 0; + } + if (!(s = sfreserve(mp, SF_UNBOUND, -1))) + { + if (!prompt) + { + if (bp->cur < bp->end) + { + if (bp->restore >= 0) + { + *bp->cur = bp->restore; + bp->restore = -1; + } + r = bp->cur; + *bp->end = 0; + bp->cur = bp->end = bp->buf; + bp->nxt = 0; + goto done; + } + else + { + errno = 0; + error(-1, "r EOF"); + } + } + return 0; + } + n = sfvalue(mp); + error(-2, "b \"%s\"", fmtnesq(s, "\"", n)); + if ((bp->max - bp->end) < n) + { + a = roundof(bp->max - bp->buf + n, SF_BUFSIZE); + r = bp->buf; + if (!(bp->buf = vmnewof(bp->vm, bp->buf, char, a, 0))) + { + error(ERROR_SYSTEM|2, "out of space"); + return 0; + } + bp->max = bp->buf + a; + if (bp->buf != r) + { + d = bp->buf - r; + bp->cur += d; + bp->end += d; + } + } + memcpy(bp->end, s, n); + bp->end += n; + if ((r = bp->cur) > bp->buf && bp->restore >= 0) + *r = bp->restore; + if (bp->cur = memchr(bp->cur, '\n', bp->end - bp->cur)) + { + bp->restore = *++bp->cur; + *bp->cur = 0; + if (bp->cur >= bp->end) + { + bp->cur = bp->end = bp->buf; + bp->nxt = 0; + } + else if (bp->nxt = memchr(bp->cur + 1, '\n', bp->end - bp->cur - 1)) + bp->nxt++; + if (prompt) + goto again; + } + else + { + bp->restore = -1; + bp->cur = r; + bp->nxt = 0; + must = 0; + goto again; + } + done: + error(-3, "Q \"%s\"", fmtesq(r, "\"")); + s = r; + if (bp->cursor) + { + r -= bp->cursor; + bp->cursor = 0; + } + for (t = 0, n = 0; *s; s++) + if (*s == '\n') + { + if (t) + { + *t++ = '\n'; + *t = 0; + t = 0; + n = 0; + } + } + else if (*s == '\r' && *(s + 1) != '\n') + { + if (t = strchr(s + 1, '\r')) + n += t - s; + else + n += strlen(s); + t = r; + } + else if (*s == '\a') + { + if (!t) + t = s; + *t = ' '; + n++; + } + else if (*s == '\b') + { + if (!t) + t = s; + if (t > r) + t--; + else + n++; + } + else if (t) + *t++ = *s; + if (t) + error(-3, "R \"%s\"", fmtesq(r, "\"")); + if (n) + *(r + strlen(r) - n) = 0; + error(-1, "r \"%s\"", fmtesq(r, "\"")); + if (lp) + sfputr(lp, fmtesq(r, "\""), '\n'); + if (t) + bp->cursor = t - r; + if (bp->ignore && match(bp->ignore, r, 0)) + goto again; + return r; +} + +/* + * execute dialogue script on stdin + */ + +#define NESTING 64 + +#define ELSE 0x01 +#define IF 0x02 +#define KEPT 0x04 +#define SKIP 0x08 + +struct Cond_s; +typedef struct Cond_s Cond_t; + +struct Cond_s +{ + Cond_t* next; + Cond_t* prev; + char* text; + int flags; +}; + +static int +dialogue(Sfio_t* mp, Sfio_t* lp, int delay, int timeout) +{ + int op; + int line; + int n; + char* s; + char* m; + char* e; + char* id; + Vmalloc_t* vm; + Cond_t* cond; + Master_t* master; + + int status = 0; + + if (!(vm = vmopen(Vmdcheap, Vmbest, 0)) || + !(cond = vmnewof(vm, 0, Cond_t, 1, 0)) || + !(master = vmnewof(vm, 0, Master_t, 1, 0)) || + !(master->buf = vmnewof(vm, 0, char, 2 * SF_BUFSIZE, 0))) + { + error(ERROR_SYSTEM|2, "out of space"); + goto done; + } + master->vm = vm; + master->cur = master->end = master->buf; + master->max = master->buf + 2 * SF_BUFSIZE - 1; + master->restore = -1; + errno = 0; + id = error_info.id; + error_info.id = 0; + line = error_info.line; + error_info.line = 0; + while (s = sfgetr(sfstdin, '\n', 1)) + { + error_info.line++; + while (isspace(*s)) + s++; + if ((op = *s++) && isspace(*s)) + s++; + switch (op) + { + case 0: + case '#': + break; + case 'c': + case 'w': + if (cond->flags & SKIP) + continue; + if (master->prompt && !masterline(mp, lp, master->prompt, 0, timeout, master)) + goto done; + if (delay) + usleep((unsigned long)delay * 1000); + if (op == 'w') + error(-1, "w \"%s\\r\"", s); + else + error(-1, "w \"%s\"", s); + if ((n = stresc(s)) >= 0) + s[n] = 0; + if (sfputr(mp, s, op == 'w' ? '\n' : -1) < 0 || sfsync(mp)) + { + error(ERROR_SYSTEM|2, "write failed"); + goto done; + } + if (delay) + usleep((unsigned long)delay * 1000); + break; + case 'd': + delay = (int)strtol(s, &e, 0); + if (*e) + error(2, "%s: invalid delay -- milliseconds expected", s); + break; + case 'i': + if (!cond->next && !(cond->next = vmnewof(vm, 0, Cond_t, 1, 0))) + { + error(ERROR_SYSTEM|2, "out of space"); + goto done; + } + cond = cond->next; + cond->flags = IF; + if ((cond->prev->flags & SKIP) && !(cond->text = 0) || !(cond->text = masterline(mp, lp, 0, 0, timeout, master))) + cond->flags |= KEPT|SKIP; + else if (match(s, cond->text, 0)) + cond->flags |= KEPT; + else + cond->flags |= SKIP; + break; + case 'e': + if (!(cond->flags & IF)) + { + error(2, "no matching i for e"); + goto done; + } + if (!*s) + { + if (cond->flags & ELSE) + { + error(2, "i block already has a default e"); + goto done; + } + cond->flags |= ELSE; + if (cond->flags & KEPT) + cond->flags |= SKIP; + else + { + cond->flags |= KEPT; + cond->flags &= ~SKIP; + } + } + else if ((cond->flags & KEPT) || !match(s, cond->text, 0)) + cond->flags |= SKIP; + else + cond->flags |= KEPT; + break; + case 'f': + if (!(cond->flags & IF)) + { + error(2, "no matching i for f"); + goto done; + } + cond = cond->prev; + break; + case 'm': + if (cond->flags & SKIP) + continue; + if (sfputr(sfstderr, s, '\n') < 0 || sfsync(sfstderr)) + { + error(ERROR_SYSTEM|2, "standard error write failed"); + goto done; + } + break; + case 'p': + if (cond->flags & SKIP) + continue; + if (!(m = masterline(mp, lp, s, 1, timeout, master))) + goto done; + break; + case 'r': + if (cond->flags & SKIP) + continue; + if (!(m = masterline(mp, lp, 0, s[0] == '?' && s[1] == '.' ? -1 : 1, timeout, master))) + goto done; + match(s, m, 1); + break; + case 's': + n = (int)strtol(s, &e, 0); + if (*e) + error(2, "%s: invalid delay -- milliseconds expected", s); + if (n) + usleep((unsigned long)n * 1000); + break; + case 't': + timeout = (int)strtol(s, &e, 0); + if (*e) + error(2, "%s: invalid timeout -- milliseconds expected", s); + break; + case 'u': + if (cond->flags & SKIP) + continue; + do + { + if (!(m = masterline(mp, lp, 0, -1, timeout, master))) + { + match(s, m, 1); + goto done; + } + } while (!match(s, m, 0)); + break; + case 'v': + error_info.trace = -(int)strtol(s, &e, 0); + if (*e) + error(2, "%s: invalid verbose level -- number expected", s); + break; + case 'x': + status = (int)strtol(s, &e, 0); + if (*e) + error(2, "%s: invalid exit code", s); + break; + case 'I': + if (master->ignore) + { + vmfree(vm, master->ignore); + master->ignore = 0; + } + if (*s && !(master->ignore = vmstrdup(vm, s))) + { + error(ERROR_SYSTEM|2, "out of space"); + goto done; + } + break; + case 'L': + if (error_info.id) + { + vmfree(vm, error_info.id); + error_info.id = 0; + } + if (*s && !(error_info.id = vmstrdup(vm, s))) + { + error(ERROR_SYSTEM|2, "out of space"); + goto done; + } + break; + case 'P': + if (master->prompt) + { + vmfree(vm, master->prompt); + master->prompt = 0; + } + if (*s && !(master->prompt = vmstrdup(vm, s))) + { + error(ERROR_SYSTEM|2, "out of space"); + goto done; + } + break; + default: + if (cond->flags & SKIP) + continue; + error(2, "'%c': unknown op", op); + goto done; + } + } + if (cond->prev) + error(2, "missing 1 or more f statements"); + done: + if (mp) + sfclose(mp); + error_info.id = id; + error_info.line = line; + if (vm) + vmclose(vm); + return status ? status : error_info.errors != 0; +} + +typedef struct Argv_s +{ + char** argv; + char* args; + int argc; +} Argv_t; + +int +b_pty(int argc, char** argv, Shbltin_t* context) +{ + int master; + int slave; + int fd; + int drop; + int n; + char* s; + Proc_t* proc; + Sfio_t* mp; + Sfio_t* lp; + Argv_t* ap; + char buf[64]; + + int delay = 0; + char* log = 0; + char* messages = 0; + char* stty = 0; + int session = 1; + int timeout = 1000; + int (*fun)(Sfio_t*,Sfio_t*,int,int) = process; + + cmdinit(argc, argv, context, ERROR_CATALOG, 0); + for (;;) + { + switch (optget(argv, usage)) + { + case 'd': + if (opt_info.num) + fun = dialogue; + continue; + case 'D': + error_info.trace = -(int)opt_info.num; + continue; + case 'l': + log = opt_info.arg; + case 'm': + messages = opt_info.arg; + continue; + case 's': + session = !!opt_info.num; + continue; + case 't': + timeout = (int)opt_info.num; + continue; + case 'T': + stty = opt_info.arg; + continue; + case 'w': + delay = (int)opt_info.num; + continue; + case ':': + break; + case '?': + error(ERROR_usage(2), "%s", opt_info.arg); + break; + } + break; + } + argv += opt_info.index; + if (!argv[0]) + error(ERROR_exit(1), "command must be specified"); + if (mkpty(&master, &slave) < 0) + error(ERROR_system(1), "unable to create pty"); + if (!(mp = sfnew(NiL, 0, SF_UNBOUND, master, SF_READ|SF_WRITE))) + error(ERROR_system(1), "cannot open master stream"); + if (stty) + { + n = 2; + for (s = stty; *s; s++) + if (isspace(*s)) + n++; + if (!(ap = newof(0, Argv_t, 1, (n + 2) * sizeof(char*) + (s - stty + 1)))) + error(ERROR_system(1), "out of space"); + ap->argc = n + 1; + ap->argv = (char**)(ap + 1); + ap->args = (char*)(ap->argv + n + 2); + strcpy(ap->args, stty); + ap->argv[0] = "stty"; + sfsprintf(ap->argv[1] = buf, sizeof(buf), "--fd=%d", slave); + ap->argv[2] = s = ap->args; + for (n = 2; *s; s++) + if (isspace(*s)) + { + *s = 0; + ap->argv[++n] = s + 1; + } + ap->argv[n + 1] = 0; + b_stty(ap->argc, ap->argv, 0); + } + if (!log) + lp = 0; + else if (!(lp = sfopen(NiL, log, "w"))) + error(ERROR_system(1), "%s: cannot write", log); + if (!(proc = runcmd(argv, slave, session))) + error(ERROR_system(1), "unable run %s", argv[0]); + close(slave); + if (messages) + { + drop = 1; + if (strneq(messages, "/dev/fd/", 8)) + fd = atoi(messages + 8); + else if (streq(messages, "/dev/stdout")) + fd = 1; + else if ((fd = open(messages, O_CREAT|O_WRONLY, MODE_666)) >= 0) + drop = 0; + else + error(ERROR_system(1), "%s: cannot redirect messages", messages); + close(2); + dup(fd); + if (drop) + close(fd); + } + slave = (*fun)(mp, lp, delay, timeout); + master = procclose(proc); + if (lp && sfclose(lp)) + error(ERROR_system(1), "%s: write error", log); + return slave ? slave : master; +}