diff options
author | June McEnroe <june@causal.agency> | 2019-04-18 20:30:54 -0400 |
---|---|---|
committer | June McEnroe <june@causal.agency> | 2019-04-18 20:30:54 -0400 |
commit | fb20e8c32eddb5ea4aedd44cfc0c49402ff9184d (patch) | |
tree | d9873161c7d43e452e47fdf8184439c1b6d85ca6 /bin/catsh | |
parent | Color python imports as Statement (diff) | |
download | src-fb20e8c32eddb5ea4aedd44cfc0c49402ff9184d.tar.gz src-fb20e8c32eddb5ea4aedd44cfc0c49402ff9184d.zip |
Rename cash to catsh
Diffstat (limited to 'bin/catsh')
120 files changed, 47376 insertions, 0 deletions
diff --git a/bin/catsh/.gitignore b/bin/catsh/.gitignore new file mode 100644 index 00000000..b17c0219 --- /dev/null +++ b/bin/catsh/.gitignore @@ -0,0 +1,13 @@ +*.o +.depend +builtins.c +builtins.h +catsh +config.mk +mknodes +mksyntax +nodes.c +nodes.h +syntax.c +syntax.h +token.h diff --git a/bin/catsh/Makefile b/bin/catsh/Makefile new file mode 100644 index 00000000..b3dd4779 --- /dev/null +++ b/bin/catsh/Makefile @@ -0,0 +1,94 @@ +PREFIX = /usr/local +MANDIR = $(PREFIX)/share/man + +CFLAGS += -std=c99 -Wall -Wextra -DSHELL -Ilibedit +LDLIBS = -lcurses + +-include config.mk + +SRCS += alias.c +SRCS += arith_yacc.c +SRCS += arith_yylex.c +SRCS += cd.c +SRCS += echo.c +SRCS += error.c +SRCS += eval.c +SRCS += exec.c +SRCS += expand.c +SRCS += histedit.c +SRCS += input.c +SRCS += jobs.c +SRCS += kill.c +SRCS += mail.c +SRCS += main.c +SRCS += memalloc.c +SRCS += miscbltin.c +SRCS += mystring.c +SRCS += options.c +SRCS += output.c +SRCS += parser.c +SRCS += printf.c +SRCS += redir.c +SRCS += show.c +SRCS += test.c +SRCS += trap.c +SRCS += var.c + +GENSRCS = builtins.c nodes.c syntax.c +GENHDRS = builtins.h nodes.h syntax.h token.h + +SRCS += $(GENSRCS) +OBJS = $(SRCS:.c=.o) libedit/libedit.a + +MAN1 = catsh.1 catsh-kill.1 catsh-printf.1 catsh-test.1 + +all: tags catsh + +catsh: $(OBJS) + $(CC) $(LDFLAGS) $(OBJS) $(LDLIBS) -o $@ + +$(OBJS): $(GENHDRS) + +libedit/libedit.a: + $(MAKE) -C libedit libedit.a + +builtins.c builtins.h: mkbuiltins builtins.def + sh mkbuiltins . + +nodes.c nodes.h: mknodes nodetypes nodes.c.pat + ./mknodes nodetypes nodes.c.pat + +syntax.c syntax.h: mksyntax + ./mksyntax + +token.h: mktokens + sh mktokens + +tags: *.h *.c + ctags -w *.h *.c + +depend: $(SRCS) $(GENHDRS) + $(CC) $(CFLAGS) -MM $(SRCS) > .depend + +-include .depend + +clean: + rm -f catsh $(OBJS) mknodes mksyntax $(GENSRCS) $(GENHDRS) tags .depend + +install: catsh $(MAN1) + install -d $(PREFIX)/bin $(MANDIR)/man1 + install catsh $(PREFIX)/bin + install -m 644 $(MAN1) $(MANDIR)/man1 + +uninstall: + rm -f $(PREFIX)/bin/catsh $(MAN1:%=$(MANDIR)/man1/%) + +shell: + grep -q '^$(PREFIX)/bin/catsh$$' /etc/shells \ + || echo '$(PREFIX)/bin/catsh' >> /etc/shells + +unshell: + sed -i sed '\;^$(PREFIX)/bin/catsh$$;d' /etc/shells + +README: catsh.7 + mandoc catsh.7 | col -bx > README diff --git a/bin/catsh/README b/bin/catsh/README new file mode 100644 index 00000000..ccd41938 --- /dev/null +++ b/bin/catsh/README @@ -0,0 +1,28 @@ +CATSH(7) Miscellaneous Information Manual CATSH(7) + +NAME + catsh – a shell + +DESCRIPTION + catsh is a shell derived from FreeBSD sh(1), which is in turn derived + from Almquist shell. It includes editline(3) from NetBSD. + + Differences from sh(1) + • ENV defaults to ‘${XDG_CONFIG_HOME:-${HOME}/.config}/catsh/env.sh’. + + • PS0 is printed before each prompt, allowing multi-line prompts. + + • Right-aligned prompts can be set with RPS1 and RPS2. + + • PSlit can be used to embed terminal escape sequences in prompts, as + in NetBSD sh(1). + + • HOME is shortened to ‘~’ in prompt expansion. + + • fc -s =new allows prefixing commands with new. + +HISTORY + sh(1) sources were imported from FreeBSD 12.0. editline(3) sources were + imported from NetBSD 8.0. + +Causal Agency January 14, 2019 Causal Agency diff --git a/bin/catsh/TOUR b/bin/catsh/TOUR new file mode 100644 index 00000000..68330af0 --- /dev/null +++ b/bin/catsh/TOUR @@ -0,0 +1,301 @@ +# @(#)TOUR 8.1 (Berkeley) 5/31/93 +# $FreeBSD: releng/12.0/bin/sh/TOUR 317882 2017-05-06 13:28:42Z jilles $ + +NOTE -- This is the original TOUR paper distributed with ash and +does not represent the current state of the shell. It is provided anyway +since it provides helpful information for how the shell is structured, +but be warned that things have changed -- the current shell is +still under development. + +================================================================ + + A Tour through Ash + + Copyright 1989 by Kenneth Almquist. + + +DIRECTORIES: The subdirectory bltin contains commands which can +be compiled stand-alone. The rest of the source is in the main +ash directory. + +SOURCE CODE GENERATORS: Files whose names begin with "mk" are +programs that generate source code. A complete list of these +programs is: + + program input files generates + ------- ----------- --------- + mkbuiltins builtins.def builtins.h builtins.c + mknodes nodetypes nodes.h nodes.c + mksyntax - syntax.h syntax.c + mktokens - token.h + +There are undoubtedly too many of these. + +EXCEPTIONS: Code for dealing with exceptions appears in +exceptions.c. The C language doesn't include exception handling, +so I implement it using setjmp and longjmp. The global variable +exception contains the type of exception. EXERROR is raised by +calling error. EXINT is an interrupt. + +INTERRUPTS: In an interactive shell, an interrupt will cause an +EXINT exception to return to the main command loop. (Exception: +EXINT is not raised if the user traps interrupts using the trap +command.) The INTOFF and INTON macros (defined in exception.h) +provide uninterruptible critical sections. Between the execution +of INTOFF and the execution of INTON, interrupt signals will be +held for later delivery. INTOFF and INTON can be nested. + +MEMALLOC.C: Memalloc.c defines versions of malloc and realloc +which call error when there is no memory left. It also defines a +stack oriented memory allocation scheme. Allocating off a stack +is probably more efficient than allocation using malloc, but the +big advantage is that when an exception occurs all we have to do +to free up the memory in use at the time of the exception is to +restore the stack pointer. The stack is implemented using a +linked list of blocks. + +STPUTC: If the stack were contiguous, it would be easy to store +strings on the stack without knowing in advance how long the +string was going to be: + p = stackptr; + *p++ = c; /* repeated as many times as needed */ + stackptr = p; +The following three macros (defined in memalloc.h) perform these +operations, but grow the stack if you run off the end: + STARTSTACKSTR(p); + STPUTC(c, p); /* repeated as many times as needed */ + grabstackstr(p); + +We now start a top-down look at the code: + +MAIN.C: The main routine performs some initialization, executes +the user's profile if necessary, and calls cmdloop. Cmdloop +repeatedly parses and executes commands. + +OPTIONS.C: This file contains the option processing code. It is +called from main to parse the shell arguments when the shell is +invoked, and it also contains the set builtin. The -i and -m op- +tions (the latter turns on job control) require changes in signal +handling. The routines setjobctl (in jobs.c) and setinteractive +(in trap.c) are called to handle changes to these options. + +PARSING: The parser code is all in parser.c. A recursive des- +cent parser is used. Syntax tables (generated by mksyntax) are +used to classify characters during lexical analysis. There are +four tables: one for normal use, one for use when inside single +quotes and dollar single quotes, one for use when inside double +quotes and one for use in arithmetic. The tables are machine +dependent because they are indexed by character variables and +the range of a char varies from machine to machine. + +PARSE OUTPUT: The output of the parser consists of a tree of +nodes. The various types of nodes are defined in the file node- +types. + +Nodes of type NARG are used to represent both words and the con- +tents of here documents. An early version of ash kept the con- +tents of here documents in temporary files, but keeping here do- +cuments in memory typically results in significantly better per- +formance. It would have been nice to make it an option to use +temporary files for here documents, for the benefit of small +machines, but the code to keep track of when to delete the tem- +porary files was complex and I never fixed all the bugs in it. +(AT&T has been maintaining the Bourne shell for more than ten +years, and to the best of my knowledge they still haven't gotten +it to handle temporary files correctly in obscure cases.) + +The text field of a NARG structure points to the text of the +word. The text consists of ordinary characters and a number of +special codes defined in parser.h. The special codes are: + + CTLVAR Parameter expansion + CTLENDVAR End of parameter expansion + CTLBACKQ Command substitution + CTLBACKQ|CTLQUOTE Command substitution inside double quotes + CTLARI Arithmetic expansion + CTLENDARI End of arithmetic expansion + CTLESC Escape next character + +A variable substitution contains the following elements: + + CTLVAR type name '=' [ alternative-text CTLENDVAR ] + +The type field is a single character specifying the type of sub- +stitution. The possible types are: + + VSNORMAL $var + VSMINUS ${var-text} + VSMINUS|VSNUL ${var:-text} + VSPLUS ${var+text} + VSPLUS|VSNUL ${var:+text} + VSQUESTION ${var?text} + VSQUESTION|VSNUL ${var:?text} + VSASSIGN ${var=text} + VSASSIGN|VSNUL ${var:=text} + VSTRIMLEFT ${var#text} + VSTRIMLEFTMAX ${var##text} + VSTRIMRIGHT ${var%text} + VSTRIMRIGHTMAX ${var%%text} + VSLENGTH ${#var} + VSERROR delayed error + +In addition, the type field will have the VSQUOTE flag set if the +variable is enclosed in double quotes and the VSLINENO flag if +LINENO is being expanded (the parameter name is the decimal line +number). The parameter's name comes next, terminated by an equals +sign. If the type is not VSNORMAL (including when it is VSLENGTH), +then the text field in the substitution follows, terminated by a +CTLENDVAR byte. + +The type VSERROR is used to allow parsing bad substitutions like +${var[7]} and generate an error when they are expanded. + +Commands in back quotes are parsed and stored in a linked list. +The locations of these commands in the string are indicated by +CTLBACKQ and CTLBACKQ+CTLQUOTE characters, depending upon whether +the back quotes were enclosed in double quotes. + +Arithmetic expansion starts with CTLARI and ends with CTLENDARI. + +The character CTLESC escapes the next character, so that in case +any of the CTL characters mentioned above appear in the input, +they can be passed through transparently. CTLESC is also used to +escape '*', '?', '[', and '!' characters which were quoted by the +user and thus should not be used for file name generation. + +CTLESC characters have proved to be particularly tricky to get +right. In the case of here documents which are not subject to +variable and command substitution, the parser doesn't insert any +CTLESC characters to begin with (so the contents of the text +field can be written without any processing). Other here docu- +ments, and words which are not subject to file name generation, +have the CTLESC characters removed during the variable and command +substitution phase. Words which are subject to file name +generation have the CTLESC characters removed as part of the file +name phase. + +EXECUTION: Command execution is handled by the following files: + eval.c The top level routines. + redir.c Code to handle redirection of input and output. + jobs.c Code to handle forking, waiting, and job control. + exec.c Code to do path searches and the actual exec sys call. + expand.c Code to evaluate arguments. + var.c Maintains the variable symbol table. Called from expand.c. + +EVAL.C: Evaltree recursively executes a parse tree. The exit +status is returned in the global variable exitstatus. The alter- +native entry evalbackcmd is called to evaluate commands in back +quotes. It saves the result in memory if the command is a buil- +tin; otherwise it forks off a child to execute the command and +connects the standard output of the child to a pipe. + +JOBS.C: To create a process, you call makejob to return a job +structure, and then call forkshell (passing the job structure as +an argument) to create the process. Waitforjob waits for a job +to complete. These routines take care of process groups if job +control is defined. + +REDIR.C: Ash allows file descriptors to be redirected and then +restored without forking off a child process. This is accom- +plished by duplicating the original file descriptors. The redir- +tab structure records where the file descriptors have been dupli- +cated to. + +EXEC.C: The routine find_command locates a command, and enters +the command in the hash table if it is not already there. The +third argument specifies whether it is to print an error message +if the command is not found. (When a pipeline is set up, +find_command is called for all the commands in the pipeline be- +fore any forking is done, so to get the commands into the hash +table of the parent process. But to make command hashing as +transparent as possible, we silently ignore errors at that point +and only print error messages if the command cannot be found +later.) + +The routine shellexec is the interface to the exec system call. + +EXPAND.C: As the routine argstr generates words by parameter +expansion, command substitution and arithmetic expansion, it +performs word splitting on the result. As each word is output, +the routine expandmeta performs file name generation (if enabled). + +VAR.C: Variables are stored in a hash table. Probably we should +switch to extensible hashing. The variable name is stored in the +same string as the value (using the format "name=value") so that +no string copying is needed to create the environment of a com- +mand. Variables which the shell references internally are preal- +located so that the shell can reference the values of these vari- +ables without doing a lookup. + +When a program is run, the code in eval.c sticks any environment +variables which precede the command (as in "PATH=xxx command") in +the variable table as the simplest way to strip duplicates, and +then calls "environment" to get the value of the environment. + +BUILTIN COMMANDS: The procedures for handling these are scat- +tered throughout the code, depending on which location appears +most appropriate. They can be recognized because their names al- +ways end in "cmd". The mapping from names to procedures is +specified in the file builtins.def, which is processed by the +mkbuiltins command. + +A builtin command is invoked with argc and argv set up like a +normal program. A builtin command is allowed to overwrite its +arguments. Builtin routines can call nextopt to do option pars- +ing. This is kind of like getopt, but you don't pass argc and +argv to it. Builtin routines can also call error. This routine +normally terminates the shell (or returns to the main command +loop if the shell is interactive), but when called from a non- +special builtin command it causes the builtin command to +terminate with an exit status of 2. + +The directory bltins contains commands which can be compiled in- +dependently but can also be built into the shell for efficiency +reasons. The header file bltin.h takes care of most of the +differences between the ash and the stand-alone environment. +The user should call the main routine "main", and #define main to +be the name of the routine to use when the program is linked into +ash. This #define should appear before bltin.h is included; +bltin.h will #undef main if the program is to be compiled +stand-alone. A similar approach is used for a few utilities from +bin and usr.bin. + +CD.C: This file defines the cd and pwd builtins. + +SIGNALS: Trap.c implements the trap command. The routine set- +signal figures out what action should be taken when a signal is +received and invokes the signal system call to set the signal ac- +tion appropriately. When a signal that a user has set a trap for +is caught, the routine "onsig" sets a flag. The routine dotrap +is called at appropriate points to actually handle the signal. +When an interrupt is caught and no trap has been set for that +signal, the routine "onint" in error.c is called. + +OUTPUT: Ash uses its own output routines. There are three out- +put structures allocated. "Output" represents the standard out- +put, "errout" the standard error, and "memout" contains output +which is to be stored in memory. This last is used when a buil- +tin command appears in backquotes, to allow its output to be col- +lected without doing any I/O through the UNIX operating system. +The variables out1 and out2 normally point to output and errout, +respectively, but they are set to point to memout when appropri- +ate inside backquotes. + +INPUT: The basic input routine is pgetc, which reads from the +current input file. There is a stack of input files; the current +input file is the top file on this stack. The code allows the +input to come from a string rather than a file. (This is for the +-c option and the "." and eval builtin commands.) The global +variable plinno is saved and restored when files are pushed and +popped from the stack. The parser routines store the number of +the current line in this variable. + +DEBUGGING: If DEBUG is defined in shell.h, then the shell will +write debugging information to the file $HOME/trace. Most of +this is done using the TRACE macro, which takes a set of printf +arguments inside two sets of parenthesis. Example: +"TRACE(("n=%d0, n))". The double parenthesis are necessary be- +cause the preprocessor can't handle functions with a variable +number of arguments. Defining DEBUG also causes the shell to +generate a core dump if it is sent a quit signal. The tracing +code is in show.c. diff --git a/bin/catsh/alias.c b/bin/catsh/alias.c new file mode 100644 index 00000000..593e7457 --- /dev/null +++ b/bin/catsh/alias.c @@ -0,0 +1,256 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)alias.c 8.3 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/alias.c 317039 2017-04-16 22:10:02Z jilles $"); + +#include <stdlib.h> +#include "shell.h" +#include "output.h" +#include "error.h" +#include "memalloc.h" +#include "mystring.h" +#include "alias.h" +#include "options.h" +#include "builtins.h" + +#define ATABSIZE 39 + +static struct alias *atab[ATABSIZE]; +static int aliases; + +static void setalias(const char *, const char *); +static int unalias(const char *); +static struct alias **hashalias(const char *); + +static +void +setalias(const char *name, const char *val) +{ + struct alias *ap, **app; + + unalias(name); + app = hashalias(name); + INTOFF; + ap = ckmalloc(sizeof (struct alias)); + ap->name = savestr(name); + ap->val = savestr(val); + ap->flag = 0; + ap->next = *app; + *app = ap; + aliases++; + INTON; +} + +static void +freealias(struct alias *ap) +{ + ckfree(ap->name); + ckfree(ap->val); + ckfree(ap); +} + +static int +unalias(const char *name) +{ + struct alias *ap, **app; + + app = hashalias(name); + + for (ap = *app; ap; app = &(ap->next), ap = ap->next) { + if (equal(name, ap->name)) { + /* + * if the alias is currently in use (i.e. its + * buffer is being used by the input routine) we + * just null out the name instead of freeing it. + * We could clear it out later, but this situation + * is so rare that it hardly seems worth it. + */ + if (ap->flag & ALIASINUSE) + *ap->name = '\0'; + else { + INTOFF; + *app = ap->next; + freealias(ap); + INTON; + } + aliases--; + return (0); + } + } + + return (1); +} + +static void +rmaliases(void) +{ + struct alias *ap, **app; + int i; + + INTOFF; + for (i = 0; i < ATABSIZE; i++) { + app = &atab[i]; + while (*app) { + ap = *app; + if (ap->flag & ALIASINUSE) { + *ap->name = '\0'; + app = &(*app)->next; + } else { + *app = ap->next; + freealias(ap); + } + } + } + aliases = 0; + INTON; +} + +struct alias * +lookupalias(const char *name, int check) +{ + struct alias *ap; + + if (aliases == 0) + return (NULL); + for (ap = *hashalias(name); ap; ap = ap->next) { + if (equal(name, ap->name)) { + if (check && (ap->flag & ALIASINUSE)) + return (NULL); + return (ap); + } + } + + return (NULL); +} + +static int +comparealiases(const void *p1, const void *p2) +{ + const struct alias *const *a1 = p1; + const struct alias *const *a2 = p2; + + return strcmp((*a1)->name, (*a2)->name); +} + +static void +printalias(const struct alias *a) +{ + out1fmt("%s=", a->name); + out1qstr(a->val); + out1c('\n'); +} + +static void +printaliases(void) +{ + int i, j; + struct alias **sorted, *ap; + + INTOFF; + sorted = ckmalloc(aliases * sizeof(*sorted)); + j = 0; + for (i = 0; i < ATABSIZE; i++) + for (ap = atab[i]; ap; ap = ap->next) + if (*ap->name != '\0') + sorted[j++] = ap; + qsort(sorted, aliases, sizeof(*sorted), comparealiases); + for (i = 0; i < aliases; i++) { + printalias(sorted[i]); + if (int_pending()) + break; + } + ckfree(sorted); + INTON; +} + +int +aliascmd(int argc __unused, char **argv __unused) +{ + char *n, *v; + int ret = 0; + struct alias *ap; + + nextopt(""); + + if (*argptr == NULL) { + printaliases(); + return (0); + } + while ((n = *argptr++) != NULL) { + if ((v = strchr(n+1, '=')) == NULL) /* n+1: funny ksh stuff */ + if ((ap = lookupalias(n, 0)) == NULL) { + warning("%s: not found", n); + ret = 1; + } else + printalias(ap); + else { + *v++ = '\0'; + setalias(n, v); + } + } + + return (ret); +} + +int +unaliascmd(int argc __unused, char **argv __unused) +{ + int i; + + while ((i = nextopt("a")) != '\0') { + if (i == 'a') { + rmaliases(); + return (0); + } + } + for (i = 0; *argptr; argptr++) + i |= unalias(*argptr); + + return (i); +} + +static struct alias ** +hashalias(const char *p) +{ + unsigned int hashval; + + hashval = (unsigned char)*p << 4; + while (*p) + hashval+= *p++; + return &atab[hashval % ATABSIZE]; +} diff --git a/bin/catsh/alias.h b/bin/catsh/alias.h new file mode 100644 index 00000000..837f1305 --- /dev/null +++ b/bin/catsh/alias.h @@ -0,0 +1,45 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)alias.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/alias.h 314436 2017-02-28 23:42:47Z imp $ + */ + +#define ALIASINUSE 1 + +struct alias { + struct alias *next; + char *name; + char *val; + int flag; +}; + +struct alias *lookupalias(const char *, int); diff --git a/bin/catsh/arith.h b/bin/catsh/arith.h new file mode 100644 index 00000000..364092fa --- /dev/null +++ b/bin/catsh/arith.h @@ -0,0 +1,37 @@ +/*- + * Copyright (c) 1995 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)arith.h 1.1 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/arith.h 315511 2017-03-18 20:41:07Z jilles $ + */ + +#include "shell.h" + +#define DIGITS(var) (3 + (2 + CHAR_BIT * sizeof((var))) / 3) + +arith_t arith(const char *); diff --git a/bin/catsh/arith_yacc.c b/bin/catsh/arith_yacc.c new file mode 100644 index 00000000..604096aa --- /dev/null +++ b/bin/catsh/arith_yacc.c @@ -0,0 +1,381 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2007 + * Herbert Xu <herbert@gondor.apana.org.au>. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/arith_yacc.c 270246 2014-08-20 20:15:43Z jilles $"); + +#include <limits.h> +#include <errno.h> +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> +#include "arith.h" +#include "arith_yacc.h" +#include "expand.h" +#include "shell.h" +#include "error.h" +#include "memalloc.h" +#include "output.h" +#include "options.h" +#include "var.h" + +#if ARITH_BOR + 11 != ARITH_BORASS || ARITH_ASS + 11 != ARITH_EQ +#error Arithmetic tokens are out of order. +#endif + +static const char *arith_startbuf; + +const char *arith_buf; +union yystype yylval; + +static int last_token; + +#define ARITH_PRECEDENCE(op, prec) [op - ARITH_BINOP_MIN] = prec + +static const char prec[ARITH_BINOP_MAX - ARITH_BINOP_MIN] = { + ARITH_PRECEDENCE(ARITH_MUL, 0), + ARITH_PRECEDENCE(ARITH_DIV, 0), + ARITH_PRECEDENCE(ARITH_REM, 0), + ARITH_PRECEDENCE(ARITH_ADD, 1), + ARITH_PRECEDENCE(ARITH_SUB, 1), + ARITH_PRECEDENCE(ARITH_LSHIFT, 2), + ARITH_PRECEDENCE(ARITH_RSHIFT, 2), + ARITH_PRECEDENCE(ARITH_LT, 3), + ARITH_PRECEDENCE(ARITH_LE, 3), + ARITH_PRECEDENCE(ARITH_GT, 3), + ARITH_PRECEDENCE(ARITH_GE, 3), + ARITH_PRECEDENCE(ARITH_EQ, 4), + ARITH_PRECEDENCE(ARITH_NE, 4), + ARITH_PRECEDENCE(ARITH_BAND, 5), + ARITH_PRECEDENCE(ARITH_BXOR, 6), + ARITH_PRECEDENCE(ARITH_BOR, 7), +}; + +#define ARITH_MAX_PREC 8 + +int letcmd(int, char **); + +static __dead2 void yyerror(const char *s) +{ + error("arithmetic expression: %s: \"%s\"", s, arith_startbuf); + /* NOTREACHED */ +} + +static arith_t arith_lookupvarint(char *varname) +{ + const char *str; + char *p; + arith_t result; + + str = lookupvar(varname); + if (uflag && str == NULL) + yyerror("variable not set"); + if (str == NULL || *str == '\0') + str = "0"; + errno = 0; + result = strtoarith_t(str, &p, 0); + if (errno != 0 || *p != '\0') + yyerror("variable conversion error"); + return result; +} + +static inline int arith_prec(int op) +{ + return prec[op - ARITH_BINOP_MIN]; +} + +static inline int higher_prec(int op1, int op2) +{ + return arith_prec(op1) < arith_prec(op2); +} + +static arith_t do_binop(int op, arith_t a, arith_t b) +{ + + switch (op) { + default: + case ARITH_REM: + case ARITH_DIV: + if (!b) + yyerror("division by zero"); + if (a == ARITH_MIN && b == -1) + yyerror("divide error"); + return op == ARITH_REM ? a % b : a / b; + case ARITH_MUL: + return (uintmax_t)a * (uintmax_t)b; + case ARITH_ADD: + return (uintmax_t)a + (uintmax_t)b; + case ARITH_SUB: + return (uintmax_t)a - (uintmax_t)b; + case ARITH_LSHIFT: + return (uintmax_t)a << (b & (sizeof(uintmax_t) * CHAR_BIT - 1)); + case ARITH_RSHIFT: + return a >> (b & (sizeof(uintmax_t) * CHAR_BIT - 1)); + case ARITH_LT: + return a < b; + case ARITH_LE: + return a <= b; + case ARITH_GT: + return a > b; + case ARITH_GE: + return a >= b; + case ARITH_EQ: + return a == b; + case ARITH_NE: + return a != b; + case ARITH_BAND: + return a & b; + case ARITH_BXOR: + return a ^ b; + case ARITH_BOR: + return a | b; + } +} + +static arith_t assignment(int var, int noeval); + +static arith_t primary(int token, union yystype *val, int op, int noeval) +{ + arith_t result; + +again: + switch (token) { + case ARITH_LPAREN: + result = assignment(op, noeval); + if (last_token != ARITH_RPAREN) + yyerror("expecting ')'"); + last_token = yylex(); + return result; + case ARITH_NUM: + last_token = op; + return val->val; + case ARITH_VAR: + last_token = op; + return noeval ? val->val : arith_lookupvarint(val->name); + case ARITH_ADD: + token = op; + *val = yylval; + op = yylex(); + goto again; + case ARITH_SUB: + *val = yylval; + return -primary(op, val, yylex(), noeval); + case ARITH_NOT: + *val = yylval; + return !primary(op, val, yylex(), noeval); + case ARITH_BNOT: + *val = yylval; + return ~primary(op, val, yylex(), noeval); + default: + yyerror("expecting primary"); + } +} + +static arith_t binop2(arith_t a, int op, int precedence, int noeval) +{ + for (;;) { + union yystype val; + arith_t b; + int op2; + int token; + + token = yylex(); + val = yylval; + + b = primary(token, &val, yylex(), noeval); + + op2 = last_token; + if (op2 >= ARITH_BINOP_MIN && op2 < ARITH_BINOP_MAX && + higher_prec(op2, op)) { + b = binop2(b, op2, arith_prec(op), noeval); + op2 = last_token; + } + + a = noeval ? b : do_binop(op, a, b); + + if (op2 < ARITH_BINOP_MIN || op2 >= ARITH_BINOP_MAX || + arith_prec(op2) >= precedence) + return a; + + op = op2; + } +} + +static arith_t binop(int token, union yystype *val, int op, int noeval) +{ + arith_t a = primary(token, val, op, noeval); + + op = last_token; + if (op < ARITH_BINOP_MIN || op >= ARITH_BINOP_MAX) + return a; + + return binop2(a, op, ARITH_MAX_PREC, noeval); +} + +static arith_t and(int token, union yystype *val, int op, int noeval) +{ + arith_t a = binop(token, val, op, noeval); + arith_t b; + + op = last_token; + if (op != ARITH_AND) + return a; + + token = yylex(); + *val = yylval; + + b = and(token, val, yylex(), noeval | !a); + + return a && b; +} + +static arith_t or(int token, union yystype *val, int op, int noeval) +{ + arith_t a = and(token, val, op, noeval); + arith_t b; + + op = last_token; + if (op != ARITH_OR) + return a; + + token = yylex(); + *val = yylval; + + b = or(token, val, yylex(), noeval | !!a); + + return a || b; +} + +static arith_t cond(int token, union yystype *val, int op, int noeval) +{ + arith_t a = or(token, val, op, noeval); + arith_t b; + arith_t c; + + if (last_token != ARITH_QMARK) + return a; + + b = assignment(yylex(), noeval | !a); + + if (last_token != ARITH_COLON) + yyerror("expecting ':'"); + + token = yylex(); + *val = yylval; + + c = cond(token, val, yylex(), noeval | !!a); + + return a ? b : c; +} + +static arith_t assignment(int var, int noeval) +{ + union yystype val = yylval; + int op = yylex(); + arith_t result; + char sresult[DIGITS(result) + 1]; + + if (var != ARITH_VAR) + return cond(var, &val, op, noeval); + + if (op != ARITH_ASS && (op < ARITH_ASS_MIN || op >= ARITH_ASS_MAX)) + return cond(var, &val, op, noeval); + + result = assignment(yylex(), noeval); + if (noeval) + return result; + + if (op != ARITH_ASS) + result = do_binop(op - 11, arith_lookupvarint(val.name), result); + snprintf(sresult, sizeof(sresult), ARITH_FORMAT_STR, result); + setvar(val.name, sresult, 0); + return result; +} + +arith_t arith(const char *s) +{ + struct stackmark smark; + arith_t result; + + setstackmark(&smark); + + arith_buf = arith_startbuf = s; + + result = assignment(yylex(), 0); + + if (last_token) + yyerror("expecting EOF"); + + popstackmark(&smark); + + return result; +} + +/* + * The exp(1) builtin. + */ +int +letcmd(int argc, char **argv) +{ + const char *p; + char *concat; + char **ap; + arith_t i; + + if (argc > 1) { + p = argv[1]; + if (argc > 2) { + /* + * Concatenate arguments. + */ + STARTSTACKSTR(concat); + ap = argv + 2; + for (;;) { + while (*p) + STPUTC(*p++, concat); + if ((p = *ap++) == NULL) + break; + STPUTC(' ', concat); + } + STPUTC('\0', concat); + p = grabstackstr(concat); + } + } else + p = ""; + + i = arith(p); + + out1fmt(ARITH_FORMAT_STR "\n", i); + return !i; +} diff --git a/bin/catsh/arith_yacc.h b/bin/catsh/arith_yacc.h new file mode 100644 index 00000000..2dc43526 --- /dev/null +++ b/bin/catsh/arith_yacc.h @@ -0,0 +1,93 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2007 + * Herbert Xu <herbert@gondor.apana.org.au>. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: releng/12.0/bin/sh/arith_yacc.h 279503 2015-03-01 21:46:55Z jilles $ + */ + +#define ARITH_ASS 1 + +#define ARITH_OR 2 +#define ARITH_AND 3 +#define ARITH_BAD 4 +#define ARITH_NUM 5 +#define ARITH_VAR 6 +#define ARITH_NOT 7 + +#define ARITH_BINOP_MIN 8 +#define ARITH_LE 8 +#define ARITH_GE 9 +#define ARITH_LT 10 +#define ARITH_GT 11 +#define ARITH_EQ 12 +#define ARITH_REM 13 +#define ARITH_BAND 14 +#define ARITH_LSHIFT 15 +#define ARITH_RSHIFT 16 +#define ARITH_MUL 17 +#define ARITH_ADD 18 +#define ARITH_BOR 19 +#define ARITH_SUB 20 +#define ARITH_BXOR 21 +#define ARITH_DIV 22 +#define ARITH_NE 23 +#define ARITH_BINOP_MAX 24 + +#define ARITH_ASS_MIN 24 +#define ARITH_REMASS 24 +#define ARITH_BANDASS 25 +#define ARITH_LSHIFTASS 26 +#define ARITH_RSHIFTASS 27 +#define ARITH_MULASS 28 +#define ARITH_ADDASS 29 +#define ARITH_BORASS 30 +#define ARITH_SUBASS 31 +#define ARITH_BXORASS 32 +#define ARITH_DIVASS 33 +#define ARITH_ASS_MAX 34 + +#define ARITH_LPAREN 34 +#define ARITH_RPAREN 35 +#define ARITH_BNOT 36 +#define ARITH_QMARK 37 +#define ARITH_COLON 38 + +extern const char *arith_buf; + +union yystype { + arith_t val; + char *name; +}; + +extern union yystype yylval; + +int yylex(void); diff --git a/bin/catsh/arith_yylex.c b/bin/catsh/arith_yylex.c new file mode 100644 index 00000000..0f38c878 --- /dev/null +++ b/bin/catsh/arith_yylex.c @@ -0,0 +1,248 @@ +/*- + * Copyright (c) 2002 + * Herbert Xu. + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/arith_yylex.c 279503 2015-03-01 21:46:55Z jilles $"); + +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> +#include "shell.h" +#include "arith_yacc.h" +#include "expand.h" +#include "error.h" +#include "memalloc.h" +#include "parser.h" +#include "syntax.h" + +#if ARITH_BOR + 11 != ARITH_BORASS || ARITH_ASS + 11 != ARITH_EQ +#error Arithmetic tokens are out of order. +#endif + +int +yylex(void) +{ + int value; + const char *buf = arith_buf; + char *end; + const char *p; + + for (;;) { + value = *buf; + switch (value) { + case ' ': + case '\t': + case '\n': + buf++; + continue; + default: + return ARITH_BAD; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + yylval.val = strtoarith_t(buf, &end, 0); + arith_buf = end; + return ARITH_NUM; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '_': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + p = buf; + while (buf++, is_in_name(*buf)) + ; + yylval.name = stalloc(buf - p + 1); + memcpy(yylval.name, p, buf - p); + yylval.name[buf - p] = '\0'; + value = ARITH_VAR; + goto out; + case '=': + value += ARITH_ASS - '='; +checkeq: + buf++; +checkeqcur: + if (*buf != '=') + goto out; + value += 11; + break; + case '>': + switch (*++buf) { + case '=': + value += ARITH_GE - '>'; + break; + case '>': + value += ARITH_RSHIFT - '>'; + goto checkeq; + default: + value += ARITH_GT - '>'; + goto out; + } + break; + case '<': + switch (*++buf) { + case '=': + value += ARITH_LE - '<'; + break; + case '<': + value += ARITH_LSHIFT - '<'; + goto checkeq; + default: + value += ARITH_LT - '<'; + goto out; + } + break; + case '|': + if (*++buf != '|') { + value += ARITH_BOR - '|'; + goto checkeqcur; + } + value += ARITH_OR - '|'; + break; + case '&': + if (*++buf != '&') { + value += ARITH_BAND - '&'; + goto checkeqcur; + } + value += ARITH_AND - '&'; + break; + case '!': + if (*++buf != '=') { + value += ARITH_NOT - '!'; + goto out; + } + value += ARITH_NE - '!'; + break; + case 0: + goto out; + case '(': + value += ARITH_LPAREN - '('; + break; + case ')': + value += ARITH_RPAREN - ')'; + break; + case '*': + value += ARITH_MUL - '*'; + goto checkeq; + case '/': + value += ARITH_DIV - '/'; + goto checkeq; + case '%': + value += ARITH_REM - '%'; + goto checkeq; + case '+': + if (buf[1] == '+') + return ARITH_BAD; + value += ARITH_ADD - '+'; + goto checkeq; + case '-': + if (buf[1] == '-') + return ARITH_BAD; + value += ARITH_SUB - '-'; + goto checkeq; + case '~': + value += ARITH_BNOT - '~'; + break; + case '^': + value += ARITH_BXOR - '^'; + goto checkeq; + case '?': + value += ARITH_QMARK - '?'; + break; + case ':': + value += ARITH_COLON - ':'; + break; + } + break; + } + + buf++; +out: + arith_buf = buf; + return value; +} diff --git a/bin/catsh/bltin.h b/bin/catsh/bltin.h new file mode 100644 index 00000000..e9adb878 --- /dev/null +++ b/bin/catsh/bltin.h @@ -0,0 +1,81 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)bltin.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/bltin/bltin.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +/* + * This file is included by programs which are optionally built into the + * shell. If SHELL is defined, we try to map the standard UNIX library + * routines to ash routines using defines. + */ + +#include "shell.h" +#include "mystring.h" +#ifdef SHELL +#include "error.h" +#include "output.h" +#include "builtins.h" +#define FILE struct output +#undef stdout +#define stdout out1 +#undef stderr +#define stderr out2 +#define printf out1fmt +#undef putc +#define putc(c, file) outc(c, file) +#undef putchar +#define putchar(c) out1c(c) +#define fprintf outfmt +#define fputs outstr +#define fwrite(ptr, size, nmemb, file) outbin(ptr, (size) * (nmemb), file) +#define fflush flushout +#define INITARGS(argv) +#define warnx warning +#define warn(fmt, ...) warning(fmt ": %s", __VA_ARGS__, strerror(errno)) +#define errx(exitstatus, ...) error(__VA_ARGS__) + +#else +#undef NULL +#include <stdio.h> +#undef main +#define INITARGS(argv) if ((commandname = argv[0]) == NULL) {fputs("Argc is zero\n", stderr); exit(2);} else +#endif + +#include <unistd.h> + +pointer stalloc(int); +int killjob(const char *, int); + +extern char *commandname; diff --git a/bin/catsh/builtins.def b/bin/catsh/builtins.def new file mode 100644 index 00000000..b3295a9b --- /dev/null +++ b/bin/catsh/builtins.def @@ -0,0 +1,96 @@ +#!/bin/sh - + +#- +# Copyright (c) 1991, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Kenneth Almquist. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# @(#)builtins.def 8.4 (Berkeley) 5/4/95 +# $FreeBSD: releng/12.0/bin/sh/builtins.def 319576 2017-06-04 21:02:48Z bdrewery $ + +# +# This file lists all the builtin commands. The first column is the name +# of a C routine. +# The -j flag specifies that this command is to be excluded from systems +# without job control. +# The -h flag specifies that this command is to be excluded from systems +# based on the NO_HISTORY compile-time symbol. +# The -n flag specifies that this command can safely be run in the same +# process when it is the only command in a command substitution. Some +# commands have special logic defined in safe_builtin(). +# The -s flag specifies that this is a POSIX 'special built-in' command. +# The rest of the line specifies the command name or names used to run the +# command. The entry for bltincmd, which is run when the user does not specify +# a command, must come first. +# +# NOTE: bltincmd must come first! + +bltincmd -n builtin +aliascmd alias +bgcmd -j bg +bindcmd bind +breakcmd -s break -s continue +cdcmd cd chdir +commandcmd -n command +dotcmd -s . +echocmd -n echo +evalcmd -s eval +execcmd -s exec +exitcmd -s exit +letcmd let +exportcmd -s export -s readonly +#exprcmd expr +falsecmd -n false +fgcmd -j fg +freebsd_wordexpcmd freebsd_wordexp +getoptscmd getopts +hashcmd hash +histcmd -h fc +jobidcmd -n jobid +jobscmd -n jobs +killcmd -n kill +localcmd local +printfcmd -n printf +pwdcmd -n pwd +readcmd read +returncmd -s return +setcmd -s set +setvarcmd setvar +shiftcmd -s shift +testcmd -n test [ +timescmd -n -s times +trapcmd -s trap +truecmd -n -s : true +typecmd -n type +ulimitcmd ulimit +umaskcmd umask +unaliascmd unalias +unsetcmd -s unset +waitcmd wait +wordexpcmd wordexp diff --git a/bin/catsh/catsh-kill.1 b/bin/catsh/catsh-kill.1 new file mode 100644 index 00000000..405efff2 --- /dev/null +++ b/bin/catsh/catsh-kill.1 @@ -0,0 +1,157 @@ +.\"- +.\" Copyright (c) 1980, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)kill.1 8.2 (Berkeley) 4/28/95 +.\" $FreeBSD: releng/12.0/bin/kill/kill.1 314436 2017-02-28 23:42:47Z imp $ +.\" +.Dd October 3, 2016 +.Dt CATSH-KILL 1 +.Os +.Sh NAME +.Nm kill +.Nd terminate or signal a process +.Sh SYNOPSIS +.Nm +.Op Fl s Ar signal_name +.Ar pid ... +.Nm +.Fl l +.Op Ar exit_status +.Nm +.Fl Ar signal_name +.Ar pid ... +.Nm +.Fl Ar signal_number +.Ar pid ... +.Sh DESCRIPTION +The +.Nm +utility sends a signal to the processes specified by the +.Ar pid +operands. +.Pp +Only the super-user may send signals to other users' processes. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl s Ar signal_name +A symbolic signal name specifying the signal to be sent instead of the +default +.Dv TERM . +.It Fl l Op Ar exit_status +If no operand is given, list the signal names; otherwise, write +the signal name corresponding to +.Ar exit_status . +.It Fl Ar signal_name +A symbolic signal name specifying the signal to be sent instead of the +default +.Dv TERM . +.It Fl Ar signal_number +A non-negative decimal integer, specifying the signal to be sent instead +of the default +.Dv TERM . +.El +.Pp +The following PIDs have special meanings: +.Bl -tag -width indent +.It -1 +If superuser, broadcast the signal to all processes; otherwise broadcast +to all processes belonging to the user. +.El +.Pp +Some of the more commonly used signals: +.Pp +.Bl -tag -width indent -compact +.It 1 +HUP (hang up) +.It 2 +INT (interrupt) +.It 3 +QUIT (quit) +.It 6 +ABRT (abort) +.It 9 +KILL (non-catchable, non-ignorable kill) +.It 14 +ALRM (alarm clock) +.It 15 +TERM (software termination signal) +.El +.Pp +Some shells may provide a builtin +.Nm +command which is similar or identical to this utility. +Consult the +.Xr builtin 1 +manual page. +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +Terminate +the processes with PIDs 142 and 157: +.Pp +.Dl "kill 142 157" +.Pp +Send the hangup signal +.Pq Dv SIGHUP +to the process with PID 507: +.Pp +.Dl "kill -s HUP 507" +.Pp +Terminate the process group with PGID 117: +.Pp +.Dl "kill -- -117" +.Sh SEE ALSO +.Xr builtin 1 , +.Xr csh 1 , +.Xr killall 1 , +.Xr ps 1 , +.Xr sh 1 , +.Xr kill 2 , +.Xr sigaction 2 +.Sh STANDARDS +The +.Nm +utility is expected to be +.St -p1003.2 +compatible. +.Sh HISTORY +A +.Nm +command appeared in +.At v3 +in section 8 of the manual. +.Sh BUGS +A replacement for the command +.Dq Li kill 0 +for +.Xr csh 1 +users should be provided. diff --git a/bin/catsh/catsh-printf.1 b/bin/catsh/catsh-printf.1 new file mode 100644 index 00000000..5c8a4fa3 --- /dev/null +++ b/bin/catsh/catsh-printf.1 @@ -0,0 +1,382 @@ +.\" Copyright (c) 1989, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)printf.1 8.1 (Berkeley) 6/6/93 +.\" $FreeBSD: releng/12.0/usr.bin/printf/printf.1 314436 2017-02-28 23:42:47Z imp $ +.\" +.Dd April 21, 2014 +.Dt CATSH-PRINTF 1 +.Os +.Sh NAME +.Nm printf +.Nd formatted output +.Sh SYNOPSIS +.Nm +.Ar format Op Ar arguments ... +.Sh DESCRIPTION +The +.Nm +utility formats and prints its arguments, after the first, under control +of the +.Ar format . +The +.Ar format +is a character string which contains three types of objects: plain characters, +which are simply copied to standard output, character escape sequences which +are converted and copied to the standard output, and format specifications, +each of which causes printing of the next successive +.Ar argument . +.Pp +The +.Ar arguments +after the first are treated as strings if the corresponding format is +either +.Cm c , b +or +.Cm s ; +otherwise it is evaluated as a C constant, with the following extensions: +.Pp +.Bl -bullet -offset indent -compact +.It +A leading plus or minus sign is allowed. +.It +If the leading character is a single or double quote, the value is the +character code of the next character. +.El +.Pp +The format string is reused as often as necessary to satisfy the +.Ar arguments . +Any extra format specifications are evaluated with zero or the null +string. +.Pp +Character escape sequences are in backslash notation as defined in the +.St -ansiC , +with extensions. +The characters and their meanings +are as follows: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It Cm \ea +Write a <bell> character. +.It Cm \eb +Write a <backspace> character. +.It Cm \ec +Ignore remaining characters in this string. +.It Cm \ef +Write a <form-feed> character. +.It Cm \en +Write a <new-line> character. +.It Cm \er +Write a <carriage return> character. +.It Cm \et +Write a <tab> character. +.It Cm \ev +Write a <vertical tab> character. +.It Cm \e\' +Write a <single quote> character. +.It Cm \e\e +Write a backslash character. +.It Cm \e Ns Ar num +Write a byte whose +value is the 1-, 2-, or 3-digit +octal number +.Ar num . +Multibyte characters can be constructed using multiple +.Cm \e Ns Ar num +sequences. +.El +.Pp +Each format specification is introduced by the percent character +(``%''). +The remainder of the format specification includes, +in the following order: +.Bl -tag -width Ds +.It "Zero or more of the following flags:" +.Bl -tag -width Ds +.It Cm # +A `#' character +specifying that the value should be printed in an ``alternate form''. +For +.Cm b , c , d , s +and +.Cm u +formats, this option has no effect. +For the +.Cm o +formats the precision of the number is increased to force the first +character of the output string to a zero. +For the +.Cm x +.Pq Cm X +format, a non-zero result has the string +.Li 0x +.Pq Li 0X +prepended to it. +For +.Cm a , A , e , E , f , F , g +and +.Cm G +formats, the result will always contain a decimal point, even if no +digits follow the point (normally, a decimal point only appears in the +results of those formats if a digit follows the decimal point). +For +.Cm g +and +.Cm G +formats, trailing zeros are not removed from the result as they +would otherwise be; +.It Cm \&\- +A minus sign `\-' which specifies +.Em left adjustment +of the output in the indicated field; +.It Cm \&+ +A `+' character specifying that there should always be +a sign placed before the number when using signed formats. +.It Sq \&\ \& +A space specifying that a blank should be left before a positive number +for a signed format. +A `+' overrides a space if both are used; +.It Cm \&0 +A zero `0' character indicating that zero-padding should be used +rather than blank-padding. +A `\-' overrides a `0' if both are used; +.El +.It "Field Width:" +An optional digit string specifying a +.Em field width ; +if the output string has fewer bytes than the field width it will +be blank-padded on the left (or right, if the left-adjustment indicator +has been given) to make up the field width (note that a leading zero +is a flag, but an embedded zero is part of a field width); +.It Precision: +An optional period, +.Sq Cm \&.\& , +followed by an optional digit string giving a +.Em precision +which specifies the number of digits to appear after the decimal point, +for +.Cm e +and +.Cm f +formats, or the maximum number of bytes to be printed +from a string; if the digit string is missing, the precision is treated +as zero; +.It Format: +A character which indicates the type of format to use (one of +.Cm diouxXfFeEgGaAcsb ) . +The uppercase formats differ from their lowercase counterparts only in +that the output of the former is entirely in uppercase. +The floating-point format specifiers +.Pq Cm fFeEgGaA +may be prefixed by an +.Cm L +to request that additional precision be used, if available. +.El +.Pp +A field width or precision may be +.Sq Cm \&* +instead of a digit string. +In this case an +.Ar argument +supplies the field width or precision. +.Pp +The format characters and their meanings are: +.Bl -tag -width Fl +.It Cm diouXx +The +.Ar argument +is printed as a signed decimal (d or i), unsigned octal, unsigned decimal, +or unsigned hexadecimal (X or x), respectively. +.It Cm fF +The +.Ar argument +is printed in the style `[\-]ddd.ddd' where the number of d's +after the decimal point is equal to the precision specification for +the argument. +If the precision is missing, 6 digits are given; if the precision +is explicitly 0, no digits and no decimal point are printed. +The values \*[If] and \*[Na] are printed as +.Ql inf +and +.Ql nan , +respectively. +.It Cm eE +The +.Ar argument +is printed in the style +.Cm e +.Sm off +.Sq Op - Ar d.ddd No \(+- Ar dd +.Sm on +where there +is one digit before the decimal point and the number after is equal to +the precision specification for the argument; when the precision is +missing, 6 digits are produced. +The values \*[If] and \*[Na] are printed as +.Ql inf +and +.Ql nan , +respectively. +.It Cm gG +The +.Ar argument +is printed in style +.Cm f +.Pq Cm F +or in style +.Cm e +.Pq Cm E +whichever gives full precision in minimum space. +.It Cm aA +The +.Ar argument +is printed in style +.Sm off +.Sq Op - Ar h.hhh No \(+- Li p Ar d +.Sm on +where there is one digit before the hexadecimal point and the number +after is equal to the precision specification for the argument; +when the precision is missing, enough digits are produced to convey +the argument's exact double-precision floating-point representation. +The values \*[If] and \*[Na] are printed as +.Ql inf +and +.Ql nan , +respectively. +.It Cm c +The first byte of +.Ar argument +is printed. +.It Cm s +Bytes from the string +.Ar argument +are printed until the end is reached or until the number of bytes +indicated by the precision specification is reached; however if the +precision is 0 or missing, the string is printed entirely. +.It Cm b +As for +.Cm s , +but interpret character escapes in backslash notation in the string +.Ar argument . +The permitted escape sequences are slightly different in that +octal escapes are +.Cm \e0 Ns Ar num +instead of +.Cm \e Ns Ar num . +.It Cm n$ +Allows reordering of the output according to +.Ar argument . +.It Cm \&% +Print a `%'; no argument is used. +.El +.Pp +The decimal point +character is defined in the program's locale (category +.Dv LC_NUMERIC ) . +.Pp +In no case does a non-existent or small field width cause truncation of +a field; padding takes place only if the specified field width exceeds +the actual width. +.Pp +Some shells may provide a builtin +.Nm +command which is similar or identical to this utility. +Consult the +.Xr builtin 1 +manual page. +.Sh EXIT STATUS +.Ex -std +.Sh COMPATIBILITY +The traditional +.Bx +behavior of converting arguments of numeric formats not beginning +with a digit to the +.Tn ASCII +code of the first character is not supported. +.Sh SEE ALSO +.Xr builtin 1 , +.Xr echo 1 , +.Xr sh 1 , +.Xr printf 3 +.Sh STANDARDS +The +.Nm +command is expected to be compatible with the +.St -p1003.2 +specification. +.Sh HISTORY +The +.Nm +command appeared in +.Bx 4.3 Reno . +It is modeled +after the standard library function, +.Xr printf 3 . +.Sh CAVEATS +.Tn ANSI +hexadecimal character constants were deliberately not provided. +.Pp +Trying to print a dash ("-") as the first character causes +.Nm +to interpret the dash as a program argument. +.Nm -- +must be used before +.Ar format . +.Pp +If the locale contains multibyte characters +(such as UTF-8), +the +.Cm c +format and +.Cm b +and +.Cm s +formats with a precision +may not operate as expected. +.Sh BUGS +Since the floating point numbers are translated from +.Tn ASCII +to floating-point and +then back again, floating-point precision may be lost. +(By default, the number is translated to an IEEE-754 double-precision +value before being printed. +The +.Cm L +modifier may produce additional precision, depending on the hardware platform.) +.Pp +The escape sequence \e000 is the string terminator. +When present in the argument for the +.Cm b +format, the argument will be truncated at the \e000 character. +.Pp +Multibyte characters are not recognized in format strings (this is only +a problem if +.Ql % +can appear inside a multibyte character). diff --git a/bin/catsh/catsh-test.1 b/bin/catsh/catsh-test.1 new file mode 100644 index 00000000..fa97af5a --- /dev/null +++ b/bin/catsh/catsh-test.1 @@ -0,0 +1,395 @@ +.\"- +.\" Copyright (c) 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)test.1 8.1 (Berkeley) 5/31/93 +.\" $FreeBSD: releng/12.0/bin/test/test.1 314436 2017-02-28 23:42:47Z imp $ +.\" +.Dd October 5, 2016 +.Dt CATSH-TEST 1 +.Os +.Sh NAME +.Nm test , +.Nm \&[ +.Nd condition evaluation utility +.Sh SYNOPSIS +.Nm +.Ar expression +.Nm \&[ +.Ar expression Cm \&] +.Sh DESCRIPTION +The +.Nm +utility evaluates the expression and, if it evaluates +to true, returns a zero (true) exit status; otherwise +it returns 1 (false). +If there is no expression, +.Nm +also +returns 1 (false). +.Pp +All operators and flags are separate arguments to the +.Nm +utility. +.Pp +The following primaries are used to construct expression: +.Bl -tag -width Ar +.It Fl b Ar file +True if +.Ar file +exists and is a block special +file. +.It Fl c Ar file +True if +.Ar file +exists and is a character +special file. +.It Fl d Ar file +True if +.Ar file +exists and is a directory. +.It Fl e Ar file +True if +.Ar file +exists (regardless of type). +.It Fl f Ar file +True if +.Ar file +exists and is a regular file. +.It Fl g Ar file +True if +.Ar file +exists and its set group ID flag +is set. +.It Fl h Ar file +True if +.Ar file +exists and is a symbolic link. +This operator is retained for compatibility with previous versions of +this program. +Do not rely on its existence; use +.Fl L +instead. +.It Fl k Ar file +True if +.Ar file +exists and its sticky bit is set. +.It Fl n Ar string +True if the length of +.Ar string +is nonzero. +.It Fl p Ar file +True if +.Ar file +is a named pipe +.Pq Tn FIFO . +.It Fl r Ar file +True if +.Ar file +exists and is readable. +.It Fl s Ar file +True if +.Ar file +exists and has a size greater +than zero. +.It Fl t Ar file_descriptor +True if the file whose file descriptor number +is +.Ar file_descriptor +is open and is associated with a terminal. +.It Fl u Ar file +True if +.Ar file +exists and its set user ID flag +is set. +.It Fl w Ar file +True if +.Ar file +exists and is writable. +True +indicates only that the write flag is on. +The file is not writable on a read-only file +system even if this test indicates true. +.It Fl x Ar file +True if +.Ar file +exists and is executable. +True +indicates only that the execute flag is on. +If +.Ar file +is a directory, true indicates that +.Ar file +can be searched. +.It Fl z Ar string +True if the length of +.Ar string +is zero. +.It Fl L Ar file +True if +.Ar file +exists and is a symbolic link. +.It Fl O Ar file +True if +.Ar file +exists and its owner matches the effective user id of this process. +.It Fl G Ar file +True if +.Ar file +exists and its group matches the effective group id of this process. +.It Fl S Ar file +True if +.Ar file +exists and is a socket. +.It Ar file1 Fl nt Ar file2 +True if +.Ar file1 +exists and is newer than +.Ar file2 . +.It Ar file1 Fl ot Ar file2 +True if +.Ar file1 +exists and is older than +.Ar file2 . +.It Ar file1 Fl ef Ar file2 +True if +.Ar file1 +and +.Ar file2 +exist and refer to the same file. +.It Ar string +True if +.Ar string +is not the null +string. +.It Ar s1 Cm = Ar s2 +True if the strings +.Ar s1 +and +.Ar s2 +are identical. +.It Ar s1 Cm != Ar s2 +True if the strings +.Ar s1 +and +.Ar s2 +are not identical. +.It Ar s1 Cm < Ar s2 +True if string +.Ar s1 +comes before +.Ar s2 +based on the binary value of their characters. +.It Ar s1 Cm > Ar s2 +True if string +.Ar s1 +comes after +.Ar s2 +based on the binary value of their characters. +.It Ar n1 Fl eq Ar n2 +True if the integers +.Ar n1 +and +.Ar n2 +are algebraically +equal. +.It Ar n1 Fl ne Ar n2 +True if the integers +.Ar n1 +and +.Ar n2 +are not +algebraically equal. +.It Ar n1 Fl gt Ar n2 +True if the integer +.Ar n1 +is algebraically +greater than the integer +.Ar n2 . +.It Ar n1 Fl ge Ar n2 +True if the integer +.Ar n1 +is algebraically +greater than or equal to the integer +.Ar n2 . +.It Ar n1 Fl lt Ar n2 +True if the integer +.Ar n1 +is algebraically less +than the integer +.Ar n2 . +.It Ar n1 Fl le Ar n2 +True if the integer +.Ar n1 +is algebraically less +than or equal to the integer +.Ar n2 . +.El +.Pp +If +.Ar file +is a symbolic link, +.Nm +will fully dereference it and then evaluate the expression +against the file referenced, except for the +.Fl h +and +.Fl L +primaries. +.Pp +These primaries can be combined with the following operators: +.Bl -tag -width Ar +.It Cm \&! Ar expression +True if +.Ar expression +is false. +.It Ar expression1 Fl a Ar expression2 +True if both +.Ar expression1 +and +.Ar expression2 +are true. +.It Ar expression1 Fl o Ar expression2 +True if either +.Ar expression1 +or +.Ar expression2 +are true. +.It Cm \&( Ar expression Cm \&) +True if expression is true. +.El +.Pp +The +.Fl a +operator has higher precedence than the +.Fl o +operator. +.Pp +Some shells may provide a builtin +.Nm +command which is similar or identical to this utility. +Consult the +.Xr builtin 1 +manual page. +.Sh GRAMMAR AMBIGUITY +The +.Nm +grammar is inherently ambiguous. +In order to assure a degree of consistency, +the cases described in the +.St -p1003.2 , +section D11.2/4.62.4, standard +are evaluated consistently according to the rules specified in the +standards document. +All other cases are subject to the ambiguity in the +command semantics. +.Pp +In particular, only expressions containing +.Fl a , +.Fl o , +.Cm \&( +or +.Cm \&) +can be ambiguous. +.Sh EXIT STATUS +The +.Nm +utility exits with one of the following values: +.Bl -tag -width indent +.It 0 +expression evaluated to true. +.It 1 +expression evaluated to false or expression was +missing. +.It >1 +An error occurred. +.El +.Sh EXAMPLES +Implement +.Li test FILE1 -nt FILE2 +using only +.Tn POSIX +functionality: +.Pp +.Dl test -n \&"$(find -L -- FILE1 -prune -newer FILE2 2>/dev/null)\&" +.Pp +This can be modified using non-standard +.Xr find 1 +primaries like +.Cm -newerca +to compare other timestamps. +.Sh COMPATIBILITY +For compatibility with some other implementations, +the +.Cm = +primary can be substituted with +.Cm == +with the same meaning. +.Sh SEE ALSO +.Xr builtin 1 , +.Xr expr 1 , +.Xr find 1 , +.Xr sh 1 , +.Xr stat 1 , +.Xr symlink 7 +.Sh STANDARDS +The +.Nm +utility implements a superset of the +.St -p1003.2 +specification. +The primaries +.Cm < , +.Cm == , +.Cm > , +.Fl ef , +.Fl nt , +.Fl ot , +.Fl G , +and +.Fl O +are extensions. +.Sh HISTORY +A +.Nm +utility appeared in +.At v7 . +.Sh BUGS +Both sides are always evaluated in +.Fl a +and +.Fl o . +For instance, the writable status of +.Pa file +will be tested by the following command even though the former expression +indicated false, which results in a gratuitous access to the file system: +.Dl "[ -z abc -a -w file ]" +To avoid this, write +.Dl "[ -z abc ] && [ -w file ]" diff --git a/bin/catsh/catsh.1 b/bin/catsh/catsh.1 new file mode 100644 index 00000000..ba7bab73 --- /dev/null +++ b/bin/catsh/catsh.1 @@ -0,0 +1,3236 @@ +.\"- +.\" Copyright (c) 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" Kenneth Almquist. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)sh.1 8.6 (Berkeley) 5/4/95 +.\" $FreeBSD: releng/12.0/bin/sh/sh.1 336483 2018-07-19 13:09:29Z 0mp $ +. +.Dd January 10, 2019 +.Dt CATSH 1 +.Os +. +.Sh NAME +.Nm catsh +.Nd a shell +. +.Sh SYNOPSIS +.Nm +.Op Fl /+abCEefhIimnPpTuVvx +.Op Fl /+o Ar longname +.Oo +.Ar script +.Op Ar arg ... +.Oc +. +.Nm +.Op Fl /+abCEefhIimnPpTuVvx +.Op Fl /+o Ar longname +.Fl c Ar string +.Oo +.Ar name +.Op Ar arg ... +.Oc +. +.Nm +.Op Fl /+abCEefhIimnPpTuVvx +.Op Fl /+o Ar longname +.Fl s +.Op Ar arg ... +. +.Sh DESCRIPTION +The +.Nm +utility is a command interpreter. +The current version of +.Nm +is close to the +.St -p1003.1 +specification for the shell. +It only supports features +designated by POSIX, +plus a few Berkeley extensions. +This man page is not intended to be a tutorial nor a complete +specification of the shell. +. +.Ss Overview +The shell is a command that reads lines from +either a file or the terminal, interprets them, and +generally executes other commands. +It is the program that is started when a user logs into the system, +although a user can select a different shell with the +.Xr chsh 1 +command. +The shell +implements a language that has flow control constructs, +a macro facility that provides a variety of features in +addition to data storage, along with built-in history and line +editing capabilities. +It incorporates many features to +aid interactive use and has the advantage that the interpretative +language is common to both interactive and non-interactive +use (shell scripts). +That is, commands can be typed directly +to the running shell or can be put into a file, +which can be executed directly by the shell. +. +.Ss Invocation +.\" +.\" XXX This next sentence is incredibly confusing. +.\" +If no arguments are present and if the standard input of the shell +is connected to a terminal +(or if the +.Fl i +option is set), +the shell is considered an interactive shell. +An interactive shell +generally prompts before each command and handles programming +and command errors differently (as described below). +When first starting, the shell inspects argument 0, and +if it begins with a dash +.Pq Ql - , +the shell is also considered a login shell. +This is normally done automatically by the system +when the user first logs in. +A login shell first reads commands +from the files +.Pa /etc/profile +and then +.Pa .profile +in a user's home directory, +if they exist. +If the environment variable +.Ev ENV +is set on entry to a shell, or is set in the +.Pa .profile +of a login shell, the shell then subjects its value to parameter expansion +and arithmetic expansion and reads commands from the named file. +Therefore, a user should place commands that are to be executed only +at login time in the +.Pa .profile +file, and commands that are executed for every shell inside the +.Ev ENV +file. +The default value of +.Ev ENV +is: +.Pp +.Dl "ENV=${XDG_CONFIG_HOME:-${HOME}/.config}/catsh/env.sh" +. +.Pp +The first non-option argument specified on the command line +will be treated as the +name of a file from which to read commands (a shell script), and +the remaining arguments are set as the positional parameters +of the shell +.Li ( $1 , $2 , +etc.). +Otherwise, the shell reads commands +from its standard input. +. +.Pp +Unlike older versions of +.Xr sh 1 +the +.Ev ENV +script is only sourced on invocation of interactive shells. +This +closes a well-known, and sometimes easily exploitable security +hole related to poorly thought out +.Ev ENV +scripts. +. +.Ss Argument List Processing +All of the single letter options to +.Nm +have a corresponding long name, +with the exception of +.Fl c +and +.Fl /+o . +These long names are provided next to the single letter options +in the descriptions below. +The long name for an option may be specified as an argument to the +.Fl /+o +option of +.Nm . +Once the shell is running, +the long name for an option may be specified as an argument to the +.Fl /+o +option of the +.Ic set +built-in command +(described later in the section called +.Sx Built-in Commands ) . +Introducing an option with a dash +.Pq Ql - +enables the option, +while using a plus +.Pq Ql + +disables the option. +A +.Dq Li -- +or plain +.Ql - +will stop option processing and will force the remaining +words on the command line to be treated as arguments. +The +.Fl /+o +and +.Fl c +options do not have long names. +They take arguments and are described after the single letter options. +. +.Bl -tag -width indent +.It Fl a Li allexport +Flag variables for export when assignments are made to them. +. +.It Fl b Li notify +Enable asynchronous notification of background job +completion. +(UNIMPLEMENTED) +. +.It Fl C Li noclobber +Do not overwrite existing files with +.Ql > . +. +.It Fl E Li emacs +Enable the built-in +.Xr emacs 1 +command line editor (disables the +.Fl V +option if it has been set; +set automatically when interactive on terminals). +. +.It Fl e Li errexit +Exit immediately if any untested command fails in non-interactive mode. +The exit status of a command is considered to be +explicitly tested if the command is part of the list used to control +an +.Ic if , elif , while , +or +.Ic until ; +if the command is the left +hand operand of an +.Dq Li && +or +.Dq Li || +operator; or if the command is a pipeline preceded by the +.Ic !\& +keyword. +If a shell function is executed and its exit status is explicitly +tested, all commands of the function are considered to be tested as +well. +. +.Pp +It is recommended to check for failures explicitly +instead of relying on +.Fl e +because it tends to behave in unexpected ways, +particularly in larger scripts. +. +.It Fl f Li noglob +Disable pathname expansion. +. +.It Fl h Li trackall +A do-nothing option for POSIX compliance. +. +.It Fl I Li ignoreeof +Ignore +.Dv EOF Ap s +from input when in interactive mode. +. +.It Fl i Li interactive +Force the shell to behave interactively. +. +.It Fl m Li monitor +Turn on job control (set automatically when interactive). +A new process group is created for each pipeline (called a job). +It is possible to suspend jobs or to have them run in the foreground or +in the background. +In a non-interactive shell, +this option can be set even if no terminal is available +and is useful to place processes in separate process groups. +. +.It Fl n Li noexec +If not interactive, read commands but do not +execute them. +This is useful for checking the +syntax of shell scripts. +. +.It Fl P Li physical +Change the default for the +.Ic cd +and +.Ic pwd +commands from +.Fl L +(logical directory layout) +to +.Fl P +(physical directory layout). +. +.It Fl p Li privileged +Turn on privileged mode. +This mode is enabled on startup +if either the effective user or group ID is not equal to the +real user or group ID. +Turning this mode off sets the +effective user and group IDs to the real user and group IDs. +When this mode is enabled for interactive shells, the file +.Pa /etc/suid_profile +is sourced instead of +.Pa ~/.profile +after +.Pa /etc/profile +is sourced, and the contents of the +.Ev ENV +variable are ignored. +. +.It Fl s Li stdin +Read commands from standard input (set automatically +if no file arguments are present). +This option has +no effect when set after the shell has already started +running (i.e., when set with the +.Ic set +command). +. +.It Fl T Li trapsasync +When waiting for a child, execute traps immediately. +If this option is not set, +traps are executed after the child exits, +as specified in +.St -p1003.2 . +This nonstandard option is useful for putting guarding shells around +children that block signals. +The surrounding shell may kill the child +or it may just return control to the tty and leave the child alone, +like this: +.Bd -literal -offset indent +catsh -T -c "trap 'exit 1' 2 ; some-blocking-program" +.Ed +. +.It Fl u Li nounset +Write a message to standard error when attempting +to expand a variable, a positional parameter or +the special parameter +.Va \&! +that is not set, and if the +shell is not interactive, exit immediately. +. +.It Fl V Li vi +Enable the built-in +.Xr vi 1 +command line editor (disables +.Fl E +if it has been set). +. +.It Fl v Li verbose +The shell writes its input to standard error +as it is read. +Useful for debugging. +. +.It Fl x Li xtrace +Write each command +(preceded by the value of the +.Va PS4 +variable subjected to parameter expansion and arithmetic expansion) +to standard error before it is executed. +Useful for debugging. +. +.It Li nolog +Another do-nothing option for POSIX compliance. +It only has a long name. +.El +. +.Pp +The +.Fl c +option causes the commands to be read from the +.Ar string +operand instead of from the standard input. +Keep in mind that this option only accepts a single string as its +argument, hence multi-word strings must be quoted. +. +.Pp +The +.Fl /+o +option takes as its only argument the long name of an option +to be enabled or disabled. +For example, the following two invocations of +.Nm +both enable the built-in +.Xr emacs 1 +command line editor: +.Bd -literal -offset indent +set -E +set -o emacs +.Ed +. +.Pp +If used without an argument, the +.Fl o +option displays the current option settings in a human-readable format. +If +.Cm +o +is used without an argument, the current option settings are output +in a format suitable for re-input into the shell. +. +.Ss Lexical Structure +The shell reads input in terms of lines from a file and breaks +it up into words at whitespace (blanks and tabs), and at +certain sequences of +characters called +.Dq operators , +which are special to the shell. +There are two types of operators: control operators and +redirection operators (their meaning is discussed later). +The following is a list of valid operators: +. +.Bl -tag -width indent +.It Control operators: +.Bl -column "XXX" "XXX" "XXX" "XXX" "XXX" -offset center -compact +.It Li & Ta Li && Ta Li \&( Ta Li \&) Ta Li \en +.It Li ;; Ta Li ;& Ta Li \&; Ta Li \&| Ta Li || +.El +. +.It Redirection operators: +.Bl -column "XXX" "XXX" "XXX" "XXX" "XXX" -offset center -compact +.It Li < Ta Li > Ta Li << Ta Li >> Ta Li <> +.It Li <& Ta Li >& Ta Li <<- Ta Li >| Ta \& +.El +.El +. +.Pp +The character +.Ql # +introduces a comment if used at the beginning of a word. +The word starting with +.Ql # +and the rest of the line are ignored. +. +.Pp +ASCII +.Dv NUL +characters (character code 0) are not allowed in shell input. +. +.Ss Quoting +Quoting is used to remove the special meaning of certain characters +or words to the shell, such as operators, whitespace, keywords, +or alias names. +. +.Pp +There are four types of quoting: matched single quotes, +dollar-single quotes, +matched double quotes, and backslash. +. +.Bl -tag -width indent +.It Single Quotes +Enclosing characters in single quotes preserves the literal +meaning of all the characters (except single quotes, making +it impossible to put single-quotes in a single-quoted string). +. +.It Dollar-Single Quotes +Enclosing characters between +.Li $' +and +.Li ' +preserves the literal meaning of all characters +except backslashes and single quotes. +A backslash introduces a C-style escape sequence: +. +.Bl -tag -width xUnnnnnnnn +.It \ea +Alert (ring the terminal bell) +.It \eb +Backspace +.It \ec Ns Ar c +The control character denoted by +.Li ^ Ns Ar c +in +.Xr stty 1 . +If +.Ar c +is a backslash, it must be doubled. +.It \ee +The ESC character (ASCII 0x1b) +.It \ef +Formfeed +.It \en +Newline +.It \er +Carriage return +.It \et +Horizontal tab +.It \ev +Vertical tab +.It \e\e +Literal backslash +.It \e\&' +Literal single-quote +.It \e\&" +Literal double-quote +.It \e Ns Ar nnn +The byte whose octal value is +.Ar nnn +(one to three digits) +.It \ex Ns Ar nn +The byte whose hexadecimal value is +.Ar nn +(one or more digits only the last two of which are used) +.It \eu Ns Ar nnnn +The Unicode code point +.Ar nnnn +(four hexadecimal digits) +.It \eU Ns Ar nnnnnnnn +The Unicode code point +.Ar nnnnnnnn +(eight hexadecimal digits) +.El +. +.Pp +The sequences for Unicode code points are currently only useful with +UTF-8 locales. +They reject code point 0 and UTF-16 surrogates. +. +.Pp +If an escape sequence would produce a byte with value 0, +that byte and the rest of the string until the matching single-quote +are ignored. +. +.Pp +Any other string starting with a backslash is an error. +. +.It Double Quotes +Enclosing characters within double quotes preserves the literal +meaning of all characters except dollar sign +.Pq Ql $ , +backquote +.Pq Ql ` , +and backslash +.Pq Ql \e . +The backslash inside double quotes is historically weird. +It remains literal unless it precedes the following characters, +which it serves to quote: +. +.Pp +.Bl -column "XXX" "XXX" "XXX" "XXX" "XXX" -offset center -compact +.It Li $ Ta Li ` Ta Li \&" Ta Li \e Ta Li \en +.El +. +.It Backslash +A backslash preserves the literal meaning of the following +character, with the exception of the newline character +.Pq Ql \en . +A backslash preceding a newline is treated as a line continuation. +.El +. +.Ss Keywords +Keywords or reserved words are words that have special meaning to the +shell and are recognized at the beginning of a line and +after a control operator. +The following are keywords: +.Bl -column "doneXX" "elifXX" "elseXX" "untilXX" "whileX" -offset center +.It Li \&! Ta { Ta } Ta Ic case Ta Ic do +.It Ic done Ta Ic elif Ta Ic else Ta Ic esac Ta Ic fi +.It Ic for Ta Ic if Ta Ic then Ta Ic until Ta Ic while +.El +. +.Ss Aliases +An alias is a name and corresponding value set using the +.Ic alias +built-in command. +Wherever the command word of a simple command may occur, +and after checking for keywords if a keyword may occur, the shell +checks the word to see if it matches an alias. +If it does, it replaces it in the input stream with its value. +For example, if there is an alias called +.Dq Li lf +with the value +.Dq Li "ls -F" , +then the input +.Pp +.Dl "lf foobar" +.Pp +would become +.Pp +.Dl "ls -F foobar" +. +.Pp +Aliases are also recognized after an alias +whose value ends with a space or tab. +For example, if there is also an alias called +.Dq Li nohup +with the value +.Dq Li "nohup " , +then the input +.Pp +.Dl "nohup lf foobar" +.Pp +would become +.Pp +.Dl "nohup ls -F foobar" +. +.Pp +Aliases provide a convenient way for naive users to +create shorthands for commands without having to learn how +to create functions with arguments. +Using aliases in scripts is discouraged +because the command that defines them must be executed +before the code that uses them is parsed. +This is fragile and not portable. +. +.Pp +An alias name may be escaped in a command line, so that it is not +replaced by its alias value, by using quoting characters within or +adjacent to the alias name. +This is most often done by prefixing +an alias name with a backslash to execute a function, built-in, or +normal program with the same name. +See the +.Sx Quoting +subsection. +. +.Ss Commands +The shell interprets the words it reads according to a +language, the specification of which is outside the scope +of this man page (refer to the BNF in the +.St -p1003.2 +document). +Essentially though, a line is read and if +the first word of the line (or after a control operator) +is not a keyword, then the shell has recognized a +simple command. +Otherwise, a complex command or some +other special construct may have been recognized. +. +.Ss Simple Commands +If a simple command has been recognized, the shell performs +the following actions: +. +.Bl -enum +.It +Leading words of the form +.Dq Li name=value +are stripped off and assigned to the environment of +the simple command +(they do not affect expansions). +Redirection operators and +their arguments (as described below) are stripped +off and saved for processing. +. +.It +The remaining words are expanded as described in +the section called +.Sx Word Expansions , +and the first remaining word is considered the command +name and the command is located. +The remaining +words are considered the arguments of the command. +If no command name resulted, then the +.Dq Li name=value +variable assignments recognized in 1) affect the +current shell. +. +.It +Redirections are performed as described in +the next section. +.El +. +.Ss Redirections +Redirections are used to change where a command reads its input +or sends its output. +In general, redirections open, close, or +duplicate an existing reference to a file. +The overall format +used for redirection is: +.Pp +.D1 Oo Ar n Oc Ar redir-op file +. +.Pp +The +.Ar redir-op +is one of the redirection operators mentioned +previously. +The following gives some examples of how these +operators can be used. +Note that stdin and stdout are commonly used abbreviations +for standard input and standard output respectively. +. +.Bl -tag -width "1234567890XX" -offset indent +.It Oo Ar n Oc Ns Li > Ar file +redirect stdout (or file descriptor +.Ar n ) +to +.Ar file +. +.It Oo Ar n Oc Ns Li >| Ar file +same as above, but override the +.Fl C +option +. +.It Oo Ar n Oc Ns Li >> Ar file +append stdout (or file descriptor +.Ar n ) +to +.Ar file +. +.It Oo Ar n Oc Ns Li < Ar file +redirect stdin (or file descriptor +.Ar n ) +from +.Ar file +. +.It Oo Ar n Oc Ns Li <> Ar file +redirect stdin (or file descriptor +.Ar n ) +to and from +.Ar file +. +.It Oo Ar n1 Oc Ns Li <& Ns Ar n2 +duplicate stdin (or file descriptor +.Ar n1 ) +from file descriptor +.Ar n2 +. +.It Oo Ar n Oc Ns Li <&- +close stdin (or file descriptor +.Ar n ) +. +.It Oo Ar n1 Oc Ns Li >& Ns Ar n2 +duplicate stdout (or file descriptor +.Ar n1 ) +to file descriptor +.Ar n2 +. +.It Oo Ar n Oc Ns Li >&- +close stdout (or file descriptor +.Ar n ) +.El +. +.Pp +The following redirection is often called a +.Dq here-document . +.Bd -unfilled -offset indent +.Oo Ar n Oc Ns Li << Ar delimiter +.Ar here-doc-text +.Ar ... +.Ar delimiter +.Ed +. +.Pp +All the text on successive lines up to the delimiter is +saved away and made available to the command on standard +input, or file descriptor +.Ar n +if it is specified. +If the +.Ar delimiter +as specified on the initial line is quoted, then the +.Ar here-doc-text +is treated literally, otherwise the text is subjected to +parameter expansion, command substitution, and arithmetic +expansion (as described in the section on +.Sx Word Expansions ) . +If the operator is +.Dq Li <<- +instead of +.Dq Li << , +then leading tabs +in the +.Ar here-doc-text +are stripped. +. +.Ss Search and Execution +There are three types of commands: shell functions, +built-in commands, and normal programs. +The command is searched for (by name) in that order. +The three types of commands are all executed in a different way. +. +.Pp +When a shell function is executed, all of the shell positional +parameters (except +.Li $0 , +which remains unchanged) are +set to the arguments of the shell function. +The variables which are explicitly placed in the environment of +the command (by placing assignments to them before the +function name) are made local to the function and are set +to the values given. +Then the command given in the function definition is executed. +The positional parameters are restored to their original values +when the command completes. +This all occurs within the current shell. +. +.Pp +Shell built-in commands are executed internally to the shell, without +spawning a new process. +There are two kinds of built-in commands: regular and special. +Assignments before special builtins persist after they finish +executing and assignment errors, redirection errors and certain +operand errors cause a script to be aborted. +Special builtins cannot be overridden with a function. +Both regular and special builtins can affect the shell in ways +normal programs cannot. +. +.Pp +Otherwise, if the command name does not match a function +or built-in command, the command is searched for as a normal +program in the file system (as described in the next section). +When a normal program is executed, the shell runs the program, +passing the arguments and the environment to the program. +If the program is not a normal executable file +(i.e., if it does not begin with the +.Dq "magic number" +whose ASCII representation is +.Dq Li #! , +resulting in an +.Er ENOEXEC +return value from +.Xr execve 2 ) +but appears to be a text file, +the shell will run a new instance of +.Nm +to interpret it. +. +.Pp +Note that previous versions of this document +and the source code itself misleadingly and sporadically +refer to a shell script without a magic number +as a +.Dq "shell procedure" . +. +.Ss Path Search +When locating a command, the shell first looks to see if +it has a shell function by that name. +Then it looks for a +built-in command by that name. +If a built-in command is not found, +one of two things happen: +. +.Bl -enum +.It +Command names containing a slash are simply executed without +performing any searches. +. +.It +The shell searches each entry in the +.Va PATH +variable +in turn for the command. +The value of the +.Va PATH +variable should be a series of +entries separated by colons. +Each entry consists of a +directory name. +The current directory +may be indicated implicitly by an empty directory name, +or explicitly by a single period. +.El +. +.Ss Command Exit Status +Each command has an exit status that can influence the behavior +of other shell commands. +The paradigm is that a command exits +with zero for normal or success, and non-zero for failure, +error, or a false indication. +The man page for each command +should indicate the various exit codes and what they mean. +Additionally, the built-in commands return exit codes, as does +an executed shell function. +. +.Pp +If a command is terminated by a signal, its exit status is greater than 128. +The signal name can be found by passing the exit status to +.Li kill -l . +. +.Pp +If there is no command word, +the exit status is the exit status of the last command substitution executed, +or zero if the command does not contain any command substitutions. +. +.Ss Complex Commands +Complex commands are combinations of simple commands +with control operators or keywords, together creating a larger complex +command. +More generally, a command is one of the following: +. +.Bl -item -offset indent +.It +simple command +.It +pipeline +.It +list or compound-list +.It +compound command +.It +function definition +.El +. +.Pp +Unless otherwise stated, the exit status of a command is +that of the last simple command executed by the command, +or zero if no simple command was executed. +. +.Ss Pipelines +A pipeline is a sequence of one or more commands separated +by the control operator +.Ql \&| . +The standard output of all but +the last command is connected to the standard input +of the next command. +The standard output of the last +command is inherited from the shell, as usual. +. +.Pp +The format for a pipeline is: +.Pp +.D1 Oo Li \&! Oc Ar command1 Op Li \&| Ar command2 ... +. +.Pp +The standard output of +.Ar command1 +is connected to the standard input of +.Ar command2 . +The standard input, standard output, or +both of a command is considered to be assigned by the +pipeline before any redirection specified by redirection +operators that are part of the command. +. +.Pp +Note that unlike some other shells, +.Nm +executes each process in a pipeline with more than one command +in a subshell environment and as a child of the +.Nm +process. +. +.Pp +If the pipeline is not in the background (discussed later), +the shell waits for all commands to complete. +. +.Pp +If the keyword +.Ic !\& +does not precede the pipeline, the +exit status is the exit status of the last command specified +in the pipeline. +Otherwise, the exit status is the logical +NOT of the exit status of the last command. +That is, if +the last command returns zero, the exit status is 1; if +the last command returns greater than zero, the exit status +is zero. +. +.Pp +Because pipeline assignment of standard input or standard +output or both takes place before redirection, it can be +modified by redirection. +For example: +.Pp +.Dl "command1 2>&1 | command2" +. +.Pp +sends both the standard output and standard error of +.Ar command1 +to the standard input of +.Ar command2 . +. +.Pp +A +.Ql \&; +or newline terminator causes the preceding +AND-OR-list +(described below in the section called +.Sx Short-Circuit List Operators ) +to be executed sequentially; +an +.Ql & +causes asynchronous execution of the preceding AND-OR-list. +. +.Ss Background Commands (&) +If a command is terminated by the control operator ampersand +.Pq Ql & , +the shell executes the command in a subshell environment (see +.Sx Grouping Commands Together +below) and asynchronously; +the shell does not wait for the command to finish +before executing the next command. +. +.Pp +The format for running a command in background is: +.Pp +.D1 Ar command1 Li & Op Ar command2 Li & Ar ... +. +.Pp +If the shell is not interactive, the standard input of an +asynchronous command is set to +.Pa /dev/null . +. +.Pp +The exit status is zero. +. +.Ss Lists (Generally Speaking) +A list is a sequence of zero or more commands separated by +newlines, semicolons, or ampersands, +and optionally terminated by one of these three characters. +The commands in a +list are executed in the order they are written. +If command is followed by an ampersand, the shell starts the +command and immediately proceeds onto the next command; +otherwise it waits for the command to terminate before +proceeding to the next one. +. +.Ss Short-Circuit List Operators +.Dq Li && +and +.Dq Li || +are AND-OR list operators. +.Dq Li && +executes the first command, and then executes the second command +if the exit status of the first command is zero. +.Dq Li || +is similar, but executes the second command if the exit +status of the first command is nonzero. +.Dq Li && +and +.Dq Li || +both have the same priority. +. +.Ss Flow-Control Constructs (if, while, for, case) +The syntax of the +.Ic if +command is: +.Bd -unfilled -offset indent -compact +.Ic if Ar list +.Ic then Ar list +.Oo Ic elif Ar list +.Ic then Ar list Oc Ar ... +.Op Ic else Ar list +.Ic fi +.Ed +. +.Pp +The exit status is that of selected +.Ic then +or +.Ic else +list, +or zero if no list was selected. +. +.Pp +The syntax of the +.Ic while +command is: +.Bd -unfilled -offset indent -compact +.Ic while Ar list +.Ic do Ar list +.Ic done +.Ed +. +.Pp +The two lists are executed repeatedly while the exit status of the +first list is zero. +The +.Ic until +command is similar, but has the word +.Ic until +in place of +.Ic while , +which causes it to +repeat until the exit status of the first list is zero. +. +.Pp +The exit status is that of the last execution of the second list, +or zero if it was never executed. +. +.Pp +The syntax of the +.Ic for +command is: +.Bd -unfilled -offset indent -compact +.Ic for Ar variable Op Ic in Ar word ... +.Ic do Ar list +.Ic done +.Ed +. +.Pp +If +.Ic in +and the following words are omitted, +.Ic in Li \&"$@\&" +is used instead. +The words are expanded, and then the list is executed +repeatedly with the variable set to each word in turn. +The +.Ic do +and +.Ic done +commands may be replaced with +.Ql { +and +.Ql } . +. +.Pp +The syntax of the +.Ic break +and +.Ic continue +commands is: +.D1 Ic break Op Ar num +.D1 Ic continue Op Ar num +. +.Pp +The +.Ic break +command terminates the +.Ar num +innermost +.Ic for +or +.Ic while +loops. +The +.Ic continue +command continues with the next iteration of the innermost loop. +These are implemented as special built-in commands. +. +.Pp +The syntax of the +.Ic case +command is: +.Bd -unfilled -offset indent -compact +.Ic case Ar word Ic in +.Ar pattern ) Ar list Li ;; +.Ar ... +.Ic esac +.Ed +. +.Pp +The pattern can actually be one or more patterns +(see +.Sx Shell Patterns +described later), +separated by +.Ql \&| +characters. +Tilde expansion, parameter expansion, command substitution, +arithmetic expansion and quote removal are applied to the word. +Then, each pattern is expanded in turn using tilde expansion, +parameter expansion, command substitution and arithmetic expansion and +the expanded form of the word is checked against it. +If a match is found, the corresponding list is executed. +If the selected list is terminated by the control operator +.Ql ;& +instead of +.Ql ;; , +execution continues with the next list, +continuing until a list terminated with +.Ql ;; +or the end of the +.Ic case +command. +. +.Ss Grouping Commands Together +Commands may be grouped by writing either +.Pp +.Sm off +.Bd -literal -offset -ident +.Po Ar list Pc +.Ed +.Sm on +.Pp +or +.Bd -literal -offset -ident +.No { Ar list ; } +.Ed +. +.Pp +The first form executes the commands in a subshell environment. +A subshell environment has its own copy of: +.Bl -enum +.It +The current working directory as set by +.Ic cd . +.It +The file creation mask as set by +.Ic umask . +.It +Resource limits as set by +.Ic ulimit . +.It +References to open files. +.It +Traps as set by +.Ic trap . +.It +Known jobs. +.It +Positional parameters and variables. +.It +Shell options. +.It +Shell functions. +.It +Shell aliases. +.El +. +.Pp +These are copied from the parent shell environment, +except that trapped (but not ignored) signals are reset to the default action +and known jobs are cleared. +Any changes do not affect the parent shell environment. +. +.Pp +A subshell environment may be implemented as a child process or differently. +If job control is enabled in an interactive shell, +commands grouped in parentheses can be suspended and continued as a unit. +. +.Pp +For compatibility with other shells, +two open parentheses in sequence should be separated by whitespace. +. +.Pp +The second form never forks another shell, +so it is slightly more efficient. +Grouping commands together this way allows the user to +redirect their output as though they were one program: +.Bd -literal -offset indent +{ echo -n "hello"; echo " world"; } > greeting +.Ed +. +.Ss Functions +The syntax of a function definition is +.Pp +.D1 Ar name Li \&( \&) Ar command +. +.Pp +A function definition is an executable statement; when +executed it installs a function named +.Ar name +and returns an +exit status of zero. +The +.Ar command +is normally a list +enclosed between +.Ql { +and +.Ql } . +. +.Pp +Variables may be declared to be local to a function by +using the +.Ic local +command. +This should appear as the first statement of a function, +and the syntax is: +.Pp +.D1 Ic local Oo Ar variable ... Oc Op Fl +. +.Pp +The +.Ic local +command is implemented as a built-in command. +The exit status is zero +unless the command is not in a function or a variable name is invalid. +. +.Pp +When a variable is made local, it inherits the initial +value and exported and readonly flags from the variable +with the same name in the surrounding scope, if there is +one. +Otherwise, the variable is initially unset. +The shell +uses dynamic scoping, so that if the variable +.Va x +is made local to function +.Em f , +which then calls function +.Em g , +references to the variable +.Va x +made inside +.Em g +will refer to the variable +.Va x +declared inside +.Em f , +not to the global variable named +.Va x . +. +.Pp +The only special parameter that can be made local is +.Ql - . +Making +.Ql - +local causes any shell options +(including those that only have long names) +that are +changed via the +.Ic set +command inside the function to be +restored to their original values when the function +returns. +. +.Pp +The syntax of the +.Ic return +command is +.Pp +.D1 Ic return Op Ar exitstatus +. +.Pp +It terminates the current executional scope, returning from the closest +nested function or sourced script; +if no function or sourced script is being executed, +it exits the shell instance. +The +.Ic return +command is implemented as a special built-in command. +. +.Ss Variables and Parameters +The shell maintains a set of parameters. +A parameter +denoted by a name +(consisting solely +of alphabetics, numerics, and underscores, +and starting with an alphabetic or an underscore) +is called a variable. +When starting up, +the shell turns all environment variables with valid names into shell +variables. +New variables can be set using the form +.Pp +.D1 Ar name Ns = Ns Ar value +. +.Pp +A parameter can also be denoted by a number +or a special character as explained below. +. +.Pp +Assignments are expanded differently from other words: +tilde expansion is also performed after the equals sign and after any colon +and usernames are also terminated by colons, +and field splitting and pathname expansion are not performed. +. +.Pp +This special expansion applies not only to assignments that form a simple +command by themselves or precede a command word, +but also to words passed to the +.Ic export , +.Ic local +or +.Ic readonly +built-in commands that have this form. +For this, the builtin's name must be literal +(not the result of an expansion) +and may optionally be preceded by one or more literal instances of +.Ic command +without options. +. +.Ss Positional Parameters +A positional parameter is a parameter denoted by a number greater than zero. +The shell sets these initially to the values of its command line +arguments that follow the name of the shell script. +The +.Ic set +built-in command can also be used to set or reset them. +. +.Ss Special Parameters +Special parameters are parameters denoted by a single special character +or the digit zero. +They are shown in the following list, exactly as they would appear in input +typed by the user or in the source of a shell script. +. +.Bl -hang +.It Li $* +Expands to the positional parameters, starting from one. +When +the expansion occurs within a double-quoted string +it expands to a single field with the value of each parameter +separated by the first character of the +.Va IFS +variable, +or by a space if +.Va IFS +is unset. +. +.It Li $@ +Expands to the positional parameters, starting from one. +When +the expansion occurs within double-quotes, each positional +parameter expands as a separate argument. +If there are no positional parameters, the +expansion of +.Li @ +generates zero arguments, even when +.Li @ +is double-quoted. +What this basically means, for example, is +if +.Li $1 +is +.Dq Li abc +and +.Li $2 +is +.Dq Li "def ghi" , +then +.Li \&"$@\&" +expands to +the two arguments: +.Bd -literal -offset indent +"abc" "def ghi" +.Ed +. +.It Li $# +Expands to the number of positional parameters. +. +.It Li $? +Expands to the exit status of the most recent pipeline. +. +.It Li $- +(hyphen) Expands to the current option flags (the single-letter +option names concatenated into a string) as specified on +invocation, by the +.Ic set +built-in command, or implicitly +by the shell. +. +.It Li $$ +Expands to the process ID of the invoked shell. +A subshell +retains the same value of +.Va $ +as its parent. +. +.It Li $! +Expands to the process ID of the most recent background +command executed from the current shell. +For a +pipeline, the process ID is that of the last command in the +pipeline. +If this parameter is referenced, the shell will remember +the process ID and its exit status until the +.Ic wait +built-in command reports completion of the process. +. +.It Li $0 +(zero) Expands to the name of the shell script if passed on the command line, +the +.Ar name +operand if given (with +.Fl c ) +or otherwise argument 0 passed to the shell. +.El +. +.Ss Special Variables +The following variables are set by the shell or +have special meaning to it: +. +.Bl -tag -width ".Va HISTSIZE" +.It Va CDPATH +The search path used with the +.Ic cd +built-in. +. +.It Va EDITOR +The fallback editor used with the +.Ic fc +built-in. +If not set, the default editor is +.Xr ed 1 . +. +.It Va FCEDIT +The default editor used with the +.Ic fc +built-in. +. +.It Va HISTSIZE +The number of previous commands that are accessible. +. +.It Va HOME +The user's home directory, +used in tilde expansion and as a default directory for the +.Ic cd +built-in. +. +.It Va IFS +Input Field Separators. +This is initialized at startup to +.Aq space , +.Aq tab , +and +.Aq newline +in that order. +This value also applies if +.Va IFS +is unset, but not if it is set to the empty string. +See the +.Sx White Space Splitting +section for more details. +. +.It Va LINENO +The current line number in the script or function. +. +.It Va MAIL +The name of a mail file, that will be checked for the arrival of new +mail. +Overridden by +.Va MAILPATH . +. +.It Va MAILPATH +A colon +.Pq Ql \&: +separated list of file names, for the shell to check for incoming +mail. +This variable overrides the +.Va MAIL +setting. +There is a maximum of 10 mailboxes that can be monitored at once. +. +.It Va OPTIND +The index of the next argument to be processed by +.Ic getopts . +This is initialized to 1 at startup. +. +.It Va PATH +The default search path for executables. +See the +.Sx Path Search +section for details. +. +.It Va PPID +The parent process ID of the invoked shell. +This is set at startup +unless this variable is in the environment. +A later change of parent process ID is not reflected. +A subshell retains the same value of +.Va PPID . +. +.It Va PS0 +The pre-prompt string, +which is printed before each new prompt. +.Va PS0 +may include any of the formatting sequences from +.Va PS1 . +. +.It Va PS1 +The primary prompt string, which defaults to +.Dq Li "$ " , +unless you are the superuser, in which case it defaults to +.Dq Li "# " . +.Va PS1 +may include any of the following formatting sequences, +which are replaced by the given information: +.Bl -tag -width indent +.It Li \eH +This system's fully-qualified hostname (FQDN). +.It Li \eh +This system's hostname. +.It Li \eW +The final component of the current working directory. +.It Li \ew +The entire path of the current working directory. +.It Li \e$ +Superuser status. +.Dq Li "$ " +for normal users and +.Dq Li "# " +for superusers. +.It Li \e\e +A literal backslash. +.El +. +.It Va PS2 +The secondary prompt string, which defaults to +.Dq Li "> " . +.Va PS2 +may include any of the formatting sequences from +.Va PS1 . +. +.It Va PS4 +The prefix for the trace output (if +.Fl x +is active). +The default is +.Dq Li "+ " . +. +.It Va PSlit +Defines the character which may be embedded in pairs, in +.Va PS1 , +.Va PS2 , +.Va RPS1 +or +.Va RPS2 +to indicate to +.Xr editline 7 +that the characters between each pair of occurrences of the +.Va PSlit +character will not appear in the visible prompt, and will not +cause the terminal's cursor to change position, but rather set terminal +attributes for the following prompt character(s) at least one of +which must be present. +. +.It Va RPS1 +The primary right prompt string. +.Va RPS1 +may include any of the formatting sequences from +.Va PS1 . +. +.It Va RPS2 +The secondary right prompt string. +.Va RPS2 +may include any of the formatting sequences from +.Va PS1 . +.El +. +.Ss Word Expansions +This clause describes the various expansions that are +performed on words. +Not all expansions are performed on +every word, as explained later. +. +.Pp +Tilde expansions, parameter expansions, command substitutions, +arithmetic expansions, and quote removals that occur within +a single word expand to a single field. +It is only field +splitting or pathname expansion that can create multiple +fields from a single word. +The single exception to this rule is +the expansion of the special parameter +.Va @ +within double-quotes, +as was described above. +. +.Pp +The order of word expansion is: +.Bl -enum +.It +Tilde Expansion, Parameter Expansion, Command Substitution, +Arithmetic Expansion (these all occur at the same time). +.It +Field Splitting is performed on fields generated by step (1) +unless the +.Va IFS +variable is null. +.It +Pathname Expansion (unless the +.Fl f +option is in effect). +.It +Quote Removal. +.El +. +.Pp +The +.Ql $ +character is used to introduce parameter expansion, command +substitution, or arithmetic expansion. +. +.Ss Tilde Expansion (substituting a user's home directory) +A word beginning with an unquoted tilde character +.Pq Ql ~ +is +subjected to tilde expansion. +All the characters up to a slash +.Pq Ql / +or the end of the word are treated as a username +and are replaced with the user's home directory. +If the +username is missing (as in +.Pa ~/foobar ) , +the tilde is replaced with the value of the +.Va HOME +variable (the current user's home directory). +. +.Ss Parameter Expansion +The format for parameter expansion is as follows: +.Pp +.D1 Li ${ Ns Ar expression Ns Li } +.Pp +where +.Ar expression +consists of all characters until the matching +.Ql } . +Any +.Ql } +escaped by a backslash or within a single-quoted or double-quoted +string, and characters in +embedded arithmetic expansions, command substitutions, and variable +expansions, are not examined in determining the matching +.Ql } . +If the variants with +.Ql + , +.Ql - , +.Ql = +or +.Ql ?\& +occur within a double-quoted string, +as an extension there may be unquoted parts +(via double-quotes inside the expansion); +.Ql } +within such parts are also not examined in determining the matching +.Ql } . +. +.Pp +The simplest form for parameter expansion is: +.Pp +.D1 Li ${ Ns Ar parameter Ns Li } +.Pp +The value, if any, of +.Ar parameter +is substituted. +. +.Pp +The parameter name or symbol can be enclosed in braces, which are +optional except for positional parameters with more than one digit or +when parameter is followed by a character that could be interpreted as +part of the name. +If a parameter expansion occurs inside double-quotes: +.Bl -enum +.It +Field splitting is not performed on the results of the +expansion, with the exception of the special parameter +.Va @ . +.It +Pathname expansion is not performed on the results of the +expansion. +.El +. +.Pp +In addition, a parameter expansion can be modified by using one of the +following formats. +. +.Bl -tag -width indent +.It Li ${ Ns Ar parameter Ns Li :- Ns Ar word Ns Li } +Use Default Values. +If +.Ar parameter +is unset or null, the expansion of +.Ar word +is substituted; otherwise, the value of +.Ar parameter +is substituted. +. +.It Li ${ Ns Ar parameter Ns Li := Ns Ar word Ns Li } +Assign Default Values. +If +.Ar parameter +is unset or null, the expansion of +.Ar word +is assigned to +.Ar parameter . +In all cases, the +final value of +.Ar parameter +is substituted. +Quoting inside +.Ar word +does not prevent field splitting or pathname expansion. +Only variables, not positional +parameters or special parameters, can be +assigned in this way. +. +.It Li ${ Ns Ar parameter Ns Li :? Ns Oo Ar word Oc Ns Li } +Indicate Error if Null or Unset. +If +.Ar parameter +is unset or null, the expansion of +.Ar word +(or a message indicating it is unset if +.Ar word +is omitted) is written to standard +error and the shell exits with a nonzero +exit status. +Otherwise, the value of +.Ar parameter +is substituted. +An +interactive shell need not exit. +. +.It Li ${ Ns Ar parameter Ns Li :+ Ns Ar word Ns Li } +Use Alternate Value. +If +.Ar parameter +is unset or null, null is substituted; +otherwise, the expansion of +.Ar word +is substituted. +.El +. +.Pp +In the parameter expansions shown previously, use of the colon in the +format results in a test for a parameter that is unset or null; omission +of the colon results in a test for a parameter that is only unset. +. +.Pp +The +.Ar word +inherits the type of quoting +(unquoted, double-quoted or here-document) +from the surroundings, +with the exception that a backslash that quotes a closing brace is removed +during quote removal. +.Bl -tag -width indent +.It Li ${# Ns Ar parameter Ns Li } +String Length. +The length in characters of +the value of +.Ar parameter . +.El +. +.Pp +The following four varieties of parameter expansion provide for substring +processing. +In each case, pattern matching notation +(see +.Sx Shell Patterns ) , +rather than regular expression notation, +is used to evaluate the patterns. +If parameter is one of the special parameters +.Va * +or +.Va @ , +the result of the expansion is unspecified. +Enclosing the full parameter expansion string in double-quotes does not +cause the following four varieties of pattern characters to be quoted, +whereas quoting characters within the braces has this effect. +. +.Bl -tag -width indent +.It Li ${ Ns Ar parameter Ns Li % Ns Ar word Ns Li } +Remove Smallest Suffix Pattern. +The +.Ar word +is expanded to produce a pattern. +The +parameter expansion then results in +.Ar parameter , +with the smallest portion of the +suffix matched by the pattern deleted. +. +.It Li ${ Ns Ar parameter Ns Li %% Ns Ar word Ns Li } +Remove Largest Suffix Pattern. +The +.Ar word +is expanded to produce a pattern. +The +parameter expansion then results in +.Ar parameter , +with the largest portion of the +suffix matched by the pattern deleted. +. +.It Li ${ Ns Ar parameter Ns Li # Ns Ar word Ns Li } +Remove Smallest Prefix Pattern. +The +.Ar word +is expanded to produce a pattern. +The +parameter expansion then results in +.Ar parameter , +with the smallest portion of the +prefix matched by the pattern deleted. +. +.It Li ${ Ns Ar parameter Ns Li ## Ns Ar word Ns Li } +Remove Largest Prefix Pattern. +The +.Ar word +is expanded to produce a pattern. +The +parameter expansion then results in +.Ar parameter , +with the largest portion of the +prefix matched by the pattern deleted. +.El +. +.Ss Command Substitution +Command substitution allows the output of a command to be substituted in +place of the command name itself. +Command substitution occurs when +the command is enclosed as follows: +.Pp +.D1 Li $( Ns Ar command Ns Li )\& +.Pp +or the backquoted version: +.Pp +.D1 Li ` Ns Ar command Ns Li ` +. +.Pp +The shell expands the command substitution by executing command +and replacing the command substitution +with the standard output of the command, +removing sequences of one or more newlines at the end of the substitution. +Embedded newlines before the end of the output are not removed; +however, during field splitting, they may be translated into spaces +depending on the value of +.Va IFS +and the quoting that is in effect. +The command is executed in a subshell environment, +except that the built-in commands +.Ic jobid , +.Ic jobs , +and +.Ic trap +return information about the parent shell environment +and +.Ic times +returns information about the same process +if they are the only command in a command substitution. +. +.Pp +If a command substitution of the +.Li $( +form begins with a subshell, +the +.Li $( +and +.Li (\& +must be separated by whitespace +to avoid ambiguity with arithmetic expansion. +. +.Ss Arithmetic Expansion +Arithmetic expansion provides a mechanism for evaluating an arithmetic +expression and substituting its value. +The format for arithmetic expansion is as follows: +.Pp +.D1 Li $(( Ns Ar expression Ns Li )) +. +.Pp +The +.Ar expression +is treated as if it were in double-quotes, except +that a double-quote inside the expression is not treated specially. +The +shell expands all tokens in the +.Ar expression +for parameter expansion, +command substitution, +arithmetic expansion +and quote removal. +. +.Pp +The allowed expressions are a subset of C expressions, +summarized below. +. +.Bl -tag -width "Variables" -offset indent +.It Values +All values are of type +.Ft intmax_t . +. +.It Constants +Decimal, octal (starting with +.Li 0 ) +and hexadecimal (starting with +.Li 0x ) +integer constants. +. +.It Variables +Shell variables can be read and written +and contain integer constants. +. +.It Unary operators +.Li "! ~ + -" +. +.It Binary operators +.Li "* / % + - << >> < <= > >= == != & ^ | && ||"\& +. +.It Assignment operators +.Li "= += -= *= /= %= <<= >>= &= ^= |=" +. +.It Conditional operator +.Li "? :"\& +.El +. +.Pp +The result of the expression is substituted in decimal. +. +.Ss White Space Splitting (Field Splitting) +In certain contexts, +after parameter expansion, command substitution, and +arithmetic expansion the shell scans the results of +expansions and substitutions that did not occur in double-quotes for +field splitting and multiple fields can result. +. +.Pp +Characters in +.Va IFS +that are whitespace +.Po +.Aq space , +.Aq tab , +and +.Aq newline +.Pc +are treated differently from other characters in +.Va IFS . +. +.Pp +Whitespace in +.Va IFS +at the beginning or end of a word is discarded. +. +.Pp +Subsequently, a field is delimited by either +.Bl -enum +.It +a non-whitespace character in +.Va IFS +with any whitespace in +.Va IFS +surrounding it, or +.It +one or more whitespace characters in +.Va IFS . +.El +. +.Pp +If a word ends with a non-whitespace character in +.Va IFS , +there is no empty field after this character. +. +.Pp +If no field is delimited, the word is discarded. +In particular, if a word consists solely of an unquoted substitution +and the result of the substitution is null, +it is removed by field splitting even if +.Va IFS +is null. +. +.Ss Pathname Expansion (File Name Generation) +Unless the +.Fl f +option is set, +file name generation is performed +after word splitting is complete. +Each word is +viewed as a series of patterns, separated by slashes. +The +process of expansion replaces the word with the names of +all existing files whose names can be formed by replacing +each pattern with a string that matches the specified pattern. +There are two restrictions on this: first, a pattern cannot match +a string containing a slash, and second, +a pattern cannot match a string starting with a period +unless the first character of the pattern is a period. +The next section describes the patterns used for +Pathname Expansion, +the four varieties of parameter expansion for substring processing and the +.Ic case +command. +. +.Ss Shell Patterns +A pattern consists of normal characters, which match themselves, +and meta-characters. +The meta-characters are +.Ql * , +.Ql \&? , +and +.Ql \&[ . +These characters lose their special meanings if they are quoted. +When command or variable substitution is performed and the dollar sign +or back quotes are not double-quoted, the value of the +variable or the output of the command is scanned for these +characters and they are turned into meta-characters. +. +.Pp +An asterisk +.Pq Ql * +matches any string of characters. +A question mark +.Pq Ql \&? +matches any single character. +A left bracket +.Pq Ql \&[ +introduces a character class. +The end of the character class is indicated by a +.Ql \&] ; +if the +.Ql \&] +is missing then the +.Ql \&[ +matches a +.Ql \&[ +rather than introducing a character class. +A character class matches any of the characters between the square brackets. +A locale-dependent range of characters may be specified using a minus sign. +A named class of characters (see +.Xr wctype 3 ) +may be specified by surrounding the name with +.Ql \&[:\& +and +.Ql :\&] . +For example, +.Ql \&[\&[:alpha:\&]\&] +is a shell pattern that matches a single letter. +The character class may be complemented by making an exclamation point +.Pq Ql !\& +the first character of the character class. +A caret +.Pq Ql ^ +has the same effect but is non-standard. +. +.Pp +To include a +.Ql \&] +in a character class, make it the first character listed +(after the +.Ql \&! +or +.Ql ^ , +if any). +To include a +.Ql - , +make it the first or last character listed. +. +.Ss Built-in Commands +This section lists the built-in commands. +.Bl -tag -width indent +.It Ic \&: +A null command that returns a 0 (true) exit value. +. +.It Ic \&. Ar file +The commands in the specified file are read and executed by the shell. +The +.Ic return +command may be used to return to the +.Ic \&. +command's caller. +If +.Ar file +contains any +.Ql / +characters, it is used as is. +Otherwise, the shell searches the +.Va PATH +for the file. +If it is not found in the +.Va PATH , +it is sought in the current working directory. +. +.It Ic \&[ +A built-in equivalent of +.Xr test 1 . +This version is documented in +.Xr catsh-test 1 . +. +.It Ic alias Oo Ar name Ns Oo = Ns Ar string Oc ... Oc +If +.Ar name Ns = Ns Ar string +is specified, the shell defines the alias +.Ar name +with value +.Ar string . +If just +.Ar name +is specified, the value of the alias +.Ar name +is printed. +With no arguments, the +.Ic alias +built-in command prints the names and values of all defined aliases +(see +.Ic unalias ) . +Alias values are written with appropriate quoting so that they are +suitable for re-input to the shell. +Also see the +.Sx Aliases +subsection. +. +.It Ic bg Op Ar job ... +Continue the specified jobs +(or the current job if no jobs are given) +in the background. +. +.It Ic bind Oo Fl aeklrsv Oc Oo Ar key Oo Ar command Oc Oc +List or alter key bindings for the line editor. +This command is documented in +.Xr editrc 5 . +. +.It Ic break Op Ar num +See the +.Sx Flow-Control Constructs +subsection. +. +.It Ic builtin Ar cmd Op Ar arg ... +Execute the specified built-in command, +.Ar cmd . +This is useful when the user wishes to override a shell function +with the same name as a built-in command. +. +.It Ic cd Oo Fl L | P Oc Oo Fl e Oc Op Ar directory +.It Ic cd Fl +Switch to the specified +.Ar directory , +to the directory specified in the +.Va HOME +environment variable if no +.Ar directory +is specified or +to the directory specified in the +.Va OLDPWD +environment variable if +.Ar directory +is +.Fl . +If +.Ar directory +does not begin with +.Pa / , \&. , +or +.Pa .. , +then the directories listed in the +.Va CDPATH +variable will be +searched for the specified +.Ar directory . +If +.Va CDPATH +is unset, the current directory is searched. +The format of +.Va CDPATH +is the same as that of +.Va PATH . +In an interactive shell, +the +.Ic cd +command will print out the name of the directory +that it actually switched to +if the +.Va CDPATH +mechanism was used or if +.Ar directory +was +.Fl . +. +.Pp +If the +.Fl P +option is specified, +.Pa .. +is handled physically and symbolic links are resolved before +.Pa .. +components are processed. +If the +.Fl L +option is specified, +.Pa .. +is handled logically. +This is the default. +. +.Pp +The +.Fl e +option causes +.Ic cd +to return exit status 1 if the full pathname of the new directory +cannot be determined reliably or at all. +Normally this is not considered an error, +although a warning is printed. +. +.Pp +If changing the directory fails, the exit status is greater than 1. +If the directory is changed, the exit status is 0, or also 1 if +.Fl e +was given. +. +.It Ic chdir +A synonym for the +.Ic cd +built-in command. +. +.It Ic command Oo Fl p Oc Op Ar utility Op Ar argument ... +.It Ic command Oo Fl p Oc Fl v Ar utility +.It Ic command Oo Fl p Oc Fl V Ar utility +The first form of invocation executes the specified +.Ar utility , +ignoring shell functions in the search. +If +.Ar utility +is a special builtin, +it is executed as if it were a regular builtin. +. +.Pp +If the +.Fl p +option is specified, the command search is performed using a +default value of +.Va PATH +that is guaranteed to find all of the standard utilities. +. +.Pp +If the +.Fl v +option is specified, +.Ar utility +is not executed but a description of its interpretation by the shell is +printed. +For ordinary commands the output is the path name; for shell built-in +commands, shell functions and keywords only the name is written. +Aliases are printed as +.Dq Ic alias Ar name Ns = Ns Ar value . +. +.Pp +The +.Fl V +option is identical to +.Fl v +except for the output. +It prints +.Dq Ar utility Ic is Ar description +where +.Ar description +is either +the path name to +.Ar utility , +a special shell builtin, +a shell builtin, +a shell function, +a shell keyword +or +an alias for +.Ar value . +. +.It Ic continue Op Ar num +See the +.Sx Flow-Control Constructs +subsection. +. +.It Ic echo Oo Fl e | n Oc Op Ar string ... +Print a space-separated list of the arguments to the standard output +and append a newline character. +.Bl -tag -width indent +.It Fl n +Suppress the output of the trailing newline. +.It Fl e +Process C-style backslash escape sequences. +The +.Ic echo +command understands the following character escapes: +.Bl -tag -width indent +.It \ea +Alert (ring the terminal bell) +.It \eb +Backspace +.It \ec +Suppress the trailing newline (this has the side-effect of truncating the +line if it is not the last character) +.It \ee +The ESC character (ASCII 0x1b) +.It \ef +Formfeed +.It \en +Newline +.It \er +Carriage return +.It \et +Horizontal tab +.It \ev +Vertical tab +.It \e\e +Literal backslash +.It \e0nnn +(Zero) The character whose octal value is +.Ar nnn +.El +. +.Pp +If +.Ar string +is not enclosed in quotes then the backslash itself must be escaped +with a backslash to protect it from the shell. +For example +.Bd -literal -offset indent +$ echo -e "a\evb" +a + b +$ echo -e a\e\evb +a + b +$ echo -e "a\e\eb" +a\eb +$ echo -e a\e\e\e\eb +a\eb +.Ed +.El +. +.Pp +Only one of the +.Fl e +and +.Fl n +options may be specified. +. +.It Ic eval Ar string ... +Concatenate all the arguments with spaces. +Then re-parse and execute the command. +. +.It Ic exec Op Ar command Op arg ... +Unless +.Ar command +is omitted, +the shell process is replaced with the specified program +(which must be a real program, not a shell built-in command or function). +Any redirections on the +.Ic exec +command are marked as permanent, +so that they are not undone when the +.Ic exec +command finishes. +. +.It Ic exit Op Ar exitstatus +Terminate the shell process. +If +.Ar exitstatus +is given +it is used as the exit status of the shell. +Otherwise, if the shell is executing an +.Cm EXIT +trap, the exit status of the last command before the trap is used; +if the shell is executing a trap for a signal, +the shell exits by resending the signal to itself. +Otherwise, the exit status of the preceding command is used. +The exit status should be an integer between 0 and 255. +. +.It Ic export Ar name ... +.It Ic export Op Fl p +The specified names are exported so that they will +appear in the environment of subsequent commands. +The only way to un-export a variable is to +.Ic unset +it. +The shell allows the value of a variable to be set +at the same time as it is exported by writing +.Pp +.D1 Ic export Ar name Ns = Ns Ar value +.Pp +With no arguments the +.Ic export +command lists the names +of all exported variables. +If the +.Fl p +option is specified, the exported variables are printed as +.Dq Ic export Ar name Ns = Ns Ar value +lines, suitable for re-input to the shell. +. +.It Ic false +A null command that returns a non-zero (false) exit value. +. +.It Ic fc Oo Fl e Ar editor Oc Op Ar first Op Ar last +.It Ic fc Fl l Oo Fl nr Oc Op Ar first Op Ar last +.It Ic fc Fl s Oo Ar old Ns = Ns Ar new Oc Op Ar first +The +.Ic fc +built-in command lists, or edits and re-executes, +commands previously entered to an interactive shell. +. +.Bl -tag -width indent +.It Fl e Ar editor +Use the editor named by +.Ar editor +to edit the commands. +The +.Ar editor +string is a command name, +subject to search via the +.Va PATH +variable. +The value in the +.Va FCEDIT +variable is used as a default when +.Fl e +is not specified. +If +.Va FCEDIT +is null or unset, the value of the +.Va EDITOR +variable is used. +If +.Va EDITOR +is null or unset, +.Xr ed 1 +is used as the editor. +. +.It Fl l No (ell) +List the commands rather than invoking +an editor on them. +The commands are written in the +sequence indicated by the +.Ar first +and +.Ar last +operands, as affected by +.Fl r , +with each command preceded by the command number. +. +.It Fl n +Suppress command numbers when listing with +.Fl l . +. +.It Fl r +Reverse the order of the commands listed +(with +.Fl l ) +or edited +(with neither +.Fl l +nor +.Fl s ) . +. +.It Fl s +Re-execute the command without invoking an editor. +. +.It Ar old Ns = Ns Ar new +Replace the first occurrence of +.Ar old +in the command with +.Ar new . +If +.Ar old +is the empty string, +the command is prefixed with +.Ar new . +. +.It Ar first +.It Ar last +Select the commands to list or edit. +The number of previous commands that can be accessed +are determined by the value of the +.Va HISTSIZE +variable. +The value of +.Ar first +or +.Ar last +or both are one of the following: +. +.Bl -tag -width indent +.It Oo Cm + Oc Ns Ar num +A positive number representing a command number; +command numbers can be displayed with the +.Fl l +option. +. +.It Fl Ar num +A negative decimal number representing the +command that was executed +.Ar num +of +commands previously. +For example, \-1 is the immediately previous command. +. +.It Ar string +A string indicating the most recently entered command +that begins with that string. +If the +.Ar old Ns = Ns Ar new +operand is not also specified with +.Fl s , +the string form of the first operand cannot contain an embedded equal sign. +.El +.El +. +.Pp +The following variables affect the execution of +.Ic fc : +.Bl -tag -width ".Va HISTSIZE" +.It Va FCEDIT +Name of the editor to use for history editing. +.It Va HISTSIZE +The number of previous commands that are accessible. +.El +. +.It Ic fg Op Ar job +Move the specified +.Ar job +or the current job to the foreground. +. +.It Ic getopts Ar optstring var +The POSIX +.Ic getopts +command. +The +.Ic getopts +command deprecates the older +.Xr getopt 1 +command. +The first argument should be a series of letters, each possibly +followed by a colon which indicates that the option takes an argument. +The specified variable is set to the parsed option. +The index of +the next argument is placed into the shell variable +.Va OPTIND . +If an option takes an argument, it is placed into the shell variable +.Va OPTARG . +If an invalid option is encountered, +.Ar var +is set to +.Ql \&? . +It returns a false value (1) when it encounters the end of the options. +A new set of arguments may be parsed by assigning +.Li OPTIND=1 . +. +.It Ic hash Oo Fl rv Oc Op Ar command ... +The shell maintains a hash table which remembers the locations of commands. +With no arguments whatsoever, the +.Ic hash +command prints out the contents of this table. +. +.Pp +With arguments, the +.Ic hash +command removes each specified +.Ar command +from the hash table (unless they are functions) and then locates it. +With the +.Fl v +option, +.Ic hash +prints the locations of the commands as it finds them. +The +.Fl r +option causes the +.Ic hash +command to delete all the entries in the hash table except for functions. +. +.It Ic jobid Op Ar job +Print the process IDs of the processes in the specified +.Ar job . +If the +.Ar job +argument is omitted, use the current job. +. +.It Ic jobs Oo Fl lps Oc Op Ar job ... +Print information about the specified jobs, or all jobs if no +.Ar job +argument is given. +The information printed includes job ID, status and command name. +. +.Pp +If the +.Fl l +option is specified, the PID of each job is also printed. +If the +.Fl p +option is specified, only the process IDs for the process group leaders +are printed, one per line. +If the +.Fl s +option is specified, only the PIDs of the job commands are printed, one per +line. +. +.It Ic kill +A built-in equivalent of +.Xr kill 1 +that additionally supports sending signals to jobs. +This version is documented in +.Xr catsh-kill 1 . +. +.It Ic local Oo Ar variable ... Oc Op Fl +See the +.Sx Functions +subsection. +. +.It Ic printf +A built-in equivalent of +.Xr printf 1 . +This version is documented in +.Xr catsh-printf 1 . +. +.It Ic pwd Op Fl L | P +Print the path of the current directory. +The built-in command may +differ from the program of the same name because the +built-in command remembers what the current directory +is rather than recomputing it each time. +This makes +it faster. +However, if the current directory is +renamed, +the built-in version of +.Xr pwd 1 +will continue to print the old name for the directory. +. +.Pp +If the +.Fl P +option is specified, symbolic links are resolved. +If the +.Fl L +option is specified, the shell's notion of the current directory +is printed (symbolic links are not resolved). +This is the default. +. +.It Ic read Oo Fl p Ar prompt Oc Oo +.Fl t Ar timeout Oc Oo Fl er Oc Ar variable ... +The +.Ar prompt +is printed if the +.Fl p +option is specified +and the standard input is a terminal. +Then a line is +read from the standard input. +The trailing newline +is deleted from the line and the line is split as +described in the section on +.Sx White Space Splitting (Field Splitting)\& +above, and +the pieces are assigned to the variables in order. +If there are more pieces than variables, the remaining +pieces (along with the characters in +.Va IFS +that separated them) +are assigned to the last variable. +If there are more variables than pieces, the remaining +variables are assigned the null string. +. +.Pp +Backslashes are treated specially, unless the +.Fl r +option is +specified. +If a backslash is followed by +a newline, the backslash and the newline will be +deleted. +If a backslash is followed by any other +character, the backslash will be deleted and the following +character will be treated as though it were not in +.Va IFS , +even if it is. +. +.Pp +If the +.Fl t +option is specified and the +.Ar timeout +elapses before a complete line of input is supplied, +the +.Ic read +command will return an exit status as if terminated by +.Dv SIGALRM +without assigning any values. +The +.Ar timeout +value may optionally be followed by one of +.Ql s , +.Ql m +or +.Ql h +to explicitly specify seconds, minutes or hours. +If none is supplied, +.Ql s +is assumed. +. +.Pp +The +.Fl e +option exists only for backward compatibility with older scripts. +. +.Pp +The exit status is 0 on success, 1 on end of file, +between 2 and 128 if an error occurs +and greater than 128 if a trapped signal interrupts +.Ic read . +. +.It Ic readonly Oo Fl p Oc Op Ar name ... +Each specified +.Ar name +is marked as read only, +so that it cannot be subsequently modified or unset. +The shell allows the value of a variable to be set +at the same time as it is marked read only +by using the following form: +.Pp +.D1 Ic readonly Ar name Ns = Ns Ar value +.Pp +With no arguments the +.Ic readonly +command lists the names of all read only variables. +If the +.Fl p +option is specified, the read-only variables are printed as +.Dq Ic readonly Ar name Ns = Ns Ar value +lines, suitable for re-input to the shell. +. +.It Ic return Op Ar exitstatus +See the +.Sx Functions +subsection. +. +.It Ic set Oo Fl /+abCEefIimnpTuVvx Oc Oo Fl /+o Ar longname Oc Oo +.Fl c Ar string Oc Op Fl - Ar arg ... +The +.Ic set +command performs three different functions: +. +.Bl -item +.It +With no arguments, it lists the values of all shell variables. +. +.It +If options are given, +either in short form or using the long +.Dq Fl /+o Ar longname +form, +it sets or clears the specified options as described in the section called +.Sx Argument List Processing . +. +.It +If the +.Dq Fl - +option is specified, +.Ic set +will replace the shell's positional parameters with the subsequent +arguments. +If no arguments follow the +.Dq Fl - +option, +all the positional parameters will be cleared, +which is equivalent to executing the command +.Dq Li "shift $#" . +The +.Dq Fl - +flag may be omitted when specifying arguments to be used +as positional replacement parameters. +This is not recommended, +because the first argument may begin with a dash +.Pq Ql - +or a plus +.Pq Ql + , +which the +.Ic set +command will interpret as a request to enable or disable options. +.El +. +.It Ic setvar Ar variable value +Assigns the specified +.Ar value +to the specified +.Ar variable . +The +.Ic setvar +command is intended to be used in functions that +assign values to variables whose names are passed as parameters. +In general it is better to write +.Dq Ar variable Ns = Ns Ar value +rather than using +.Ic setvar . +. +.It Ic shift Op Ar n +Shift the positional parameters +.Ar n +times, or once if +.Ar n +is not specified. +A shift sets the value of +.Li $1 +to the value of +.Li $2 , +the value of +.Li $2 +to the value of +.Li $3 , +and so on, +decreasing the value of +.Li $# +by one. +For portability, shifting if there are zero positional parameters +should be avoided, since the shell may abort. +. +.It Ic test +A built-in equivalent of +.Xr test 1 . +This version is documented in +.Xr catsh-test 1 . +. +.It Ic times +Print the amount of time spent executing the shell process and its children. +The first output line shows the user and system times for the shell process +itself, the second one contains the user and system times for the +children. +. +.It Ic trap Oo Ar action Oc Ar signal ... +.It Ic trap Fl l +Cause the shell to parse and execute +.Ar action +when any specified +.Ar signal +is received. +The signals are specified by name or number. +In addition, the pseudo-signal +.Cm EXIT +may be used to specify an +.Ar action +that is performed when the shell terminates. +The +.Ar action +may be an empty string or a dash +.Pq Ql - ; +the former causes the specified signal to be ignored +and the latter causes the default action to be taken. +Omitting the +.Ar action +and using only signal numbers is another way to request the default action. +In a subshell or utility environment, +the shell resets trapped (but not ignored) signals to the default action. +The +.Ic trap +command has no effect on signals that were ignored on entry to the shell. +. +.Pp +Option +.Fl l +causes the +.Ic trap +command to display a list of valid signal names. +. +.It Ic true +A null command that returns a 0 (true) exit value. +. +.It Ic type Op Ar name ... +Interpret each +.Ar name +as a command and print the resolution of the command search. +Possible resolutions are: +shell keyword, alias, special shell builtin, shell builtin, command, +tracked alias +and not found. +For aliases the alias expansion is printed; +for commands and tracked aliases +the complete pathname of the command is printed. +. +.It Ic ulimit Oo Fl HSabcdfklmnopstuvw Oc Op Ar limit +Set or display resource limits (see +.Xr getrlimit 2 ) . +If +.Ar limit +is specified, the named resource will be set; +otherwise the current resource value will be displayed. +. +.Pp +If +.Fl H +is specified, the hard limits will be set or displayed. +While everybody is allowed to reduce a hard limit, +only the superuser can increase it. +The +.Fl S +option +specifies the soft limits instead. +When displaying limits, +only one of +.Fl S +or +.Fl H +can be given. +The default is to display the soft limits, +and to set both the hard and the soft limits. +. +.Pp +Option +.Fl a +causes the +.Ic ulimit +command to display all resources. +The parameter +.Ar limit +is not acceptable in this mode. +. +.Pp +The remaining options specify which resource value is to be +displayed or modified. +They are mutually exclusive. +.Bl -tag -width indent +.It Fl b Ar sbsize +The maximum size of socket buffer usage, in bytes. +.It Fl c Ar coredumpsize +The maximal size of core dump files, in 512-byte blocks. +Setting +.Ar coredumpsize +to 0 prevents core dump files from being created. +.It Fl d Ar datasize +The maximal size of the data segment of a process, in kilobytes. +.It Fl f Ar filesize +The maximal size of a file, in 512-byte blocks. +.It Fl k Ar kqueues +The maximal number of kqueues +(see +.Xr kqueue 2 ) +for this user ID. +.It Fl l Ar lockedmem +The maximal size of memory that can be locked by a process, in +kilobytes. +.It Fl m Ar memoryuse +The maximal resident set size of a process, in kilobytes. +.It Fl n Ar nofiles +The maximal number of descriptors that could be opened by a process. +.It Fl o Ar umtxp +The maximal number of process-shared locks +(see +.Xr pthread 3 ) +for this user ID. +.It Fl p Ar pseudoterminals +The maximal number of pseudo-terminals for this user ID. +.It Fl s Ar stacksize +The maximal size of the stack segment, in kilobytes. +.It Fl t Ar time +The maximal amount of CPU time to be used by each process, in seconds. +.It Fl u Ar userproc +The maximal number of simultaneous processes for this user ID. +.It Fl v Ar virtualmem +The maximal virtual size of a process, in kilobytes. +.It Fl w Ar swapuse +The maximum amount of swap space reserved or used for this user ID, +in kilobytes. +.El +. +.It Ic umask Oo Fl S Oc Op Ar mask +Set the file creation mask (see +.Xr umask 2 ) +to the octal or symbolic (see +.Xr chmod 1 ) +value specified by +.Ar mask . +If the argument is omitted, the current mask value is printed. +If the +.Fl S +option is specified, the output is symbolic, otherwise the output is octal. +. +.It Ic unalias Oo Fl a Oc Op Ar name ... +The specified alias names are removed. +If +.Fl a +is specified, all aliases are removed. +. +.It Ic unset Oo Fl fv Oc Ar name ... +The specified variables or functions are unset and unexported. +If the +.Fl v +option is specified or no options are given, the +.Ar name +arguments are treated as variable names. +If the +.Fl f +option is specified, the +.Ar name +arguments are treated as function names. +. +.It Ic wait Op Ar job ... +Wait for each specified +.Ar job +to complete and return the exit status of the last process in the +last specified +.Ar job . +If any +.Ar job +specified is unknown to the shell, it is treated as if it +were a known job that exited with exit status 127. +If no operands are given, wait for all jobs to complete +and return an exit status of zero. +.El +. +.Ss Command Line Editing +When +.Nm +is being used interactively from a terminal, the current command +and the command history +(see +.Ic fc +in +.Sx Built-in Commands ) +can be edited using +.Nm vi Ns -mode +command line editing. +This mode uses commands similar +to a subset of those described in the +.Xr vi 1 +man page. +The command +.Dq Li "set -o vi" +(or +.Dq Li "set -V" ) +enables +.Nm vi Ns -mode +editing and places +.Nm +into +.Nm vi +insert mode. +With +.Nm vi Ns -mode +enabled, +.Nm +can be switched between insert mode and command mode by typing +.Aq ESC . +Hitting +.Aq return +while in command mode will pass the line to the shell. +. +.Pp +Similarly, the +.Dq Li "set -o emacs" +(or +.Dq Li "set -E" ) +command can be used to enable a subset of +.Nm emacs Ns -style +command line editing features. +. +.Sh ENVIRONMENT +The following environment variables affect the execution of +.Nm : +. +.Bl -tag -width ".Ev LANGXXXXXX" +.It Ev ENV +Initialization file for interactive shells. +. +.It Ev LANG , Ev LC_* +Locale settings. +These are inherited by children of the shell, +and is used in a limited manner by the shell itself. +. +.It Ev OLDPWD +The previous current directory. +This is used and updated by +.Ic cd . +. +.It Ev PWD +An absolute pathname for the current directory, +possibly containing symbolic links. +This is used and updated by the shell. +. +.It Ev TERM +The default terminal setting for the shell. +This is inherited by children of the shell, and is used in the history +editing modes. +.El +. +.Pp +Additionally, environment variables are turned into shell variables +at startup, +which may affect the shell as described under +.Sx Special Variables . +. +.Sh FILES +.Bl -tag -width "/etc/suid_profileXX" -compact +.It Pa ~/.profile +User's login profile. +.It Pa /etc/profile +System login profile. +.It Pa /etc/shells +Shell database. +.It Pa /etc/suid_profile +Privileged shell profile. +.El +. +.Sh EXIT STATUS +Errors that are detected by the shell, such as a syntax error, will +cause the shell to exit with a non-zero exit status. +If the shell is not an interactive shell, the execution of the shell +file will be aborted. +Otherwise the shell will return the exit status of the last command +executed, or if the +.Ic exit +builtin is used with a numeric argument, it +will return the argument. +. +.Sh SEE ALSO +.Xr builtin 1 , +.Xr catsh-kill 1 , +.Xr catsh-printf 1 , +.Xr catsh-test 1 , +.Xr chsh 1 , +.Xr echo 1 , +.Xr ed 1 , +.Xr emacs 1 , +.Xr pwd 1 , +.Xr vi 1 , +.Xr execve 2 , +.Xr getrlimit 2 , +.Xr umask 2 , +.Xr wctype 3 , +.Xr editrc 5 , +.Xr shells 5 +. +.Sh HISTORY +A +.Xr sh 1 +command, the Thompson shell, appeared in +.At v1 . +It was superseded in +.At v7 +by the Bourne shell, which inherited the name +.Xr sh 1 . +. +.Pp +The +.Fx +version of +.Xr sh 1 +was rewritten in 1989 under the +.Bx +license after the Bourne shell from +.At V.4 . +. +.Pp +The +.Nm +utility is based on +.Xr sh 1 +from +.Fx 12.0 . +. +.Sh AUTHORS +.An -nosplit +The +.Fx +version of +.Xr sh 1 +was originally written by +.An Kenneth Almquist . +. +.Pp +The +.Nm +utility is developed by +.An June McEnroe Aq Mt june@causal.agency . +. +.Sh BUGS +The +.Nm +utility does not recognize multibyte characters other than UTF-8. +Splitting using +.Va IFS +does not recognize multibyte characters. diff --git a/bin/catsh/catsh.7 b/bin/catsh/catsh.7 new file mode 100644 index 00000000..e41544fd --- /dev/null +++ b/bin/catsh/catsh.7 @@ -0,0 +1,63 @@ +.Dd January 14, 2019 +.Dt CATSH 7 +.Os "Causal Agency" +. +.Sh NAME +.Nm catsh +.Nd a shell +. +.Sh DESCRIPTION +.Nm +is a shell derived from +.Fx +.Xr sh 1 , +which is in turn derived from Almquist shell. +It includes +.Xr editline 3 +from +.Nx . +. +.Ss Differences from Xr sh 1 +.Bl -bullet +.It +.Va ENV +defaults to +.Ql ${XDG_CONFIG_HOME:-${HOME}/.config}/catsh/env.sh . +. +.It +.Va PS0 +is printed before each prompt, +allowing multi-line prompts. +. +.It +Right-aligned prompts can be set with +.Va RPS1 +and +.Va RPS2 . +. +.It +.Va PSlit +can be used to embed terminal escape sequences in prompts, +as in +.Nx +.Xr sh 1 . +. +.It +.Va HOME +is shortened to +.Sq ~ +in prompt expansion. +. +.It +.Ic fc Fl s Ar = Ns Ar new +allows prefixing commands with +.Ar new . +.El +. +.Sh HISTORY +.Xr sh 1 +sources were imported from +.Fx 12.0 . +.Xr editline 3 +sources were imported from +.Nx 8.0 . diff --git a/bin/catsh/cd.c b/bin/catsh/cd.c new file mode 100644 index 00000000..d1780b64 --- /dev/null +++ b/bin/catsh/cd.c @@ -0,0 +1,430 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)cd.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/cd.c 336320 2018-07-15 21:55:17Z jilles $"); + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <limits.h> + +/* + * The cd and pwd commands. + */ + +#include "shell.h" +#include "var.h" +#include "nodes.h" /* for jobs.h */ +#include "jobs.h" +#include "options.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "exec.h" +#include "redir.h" +#include "mystring.h" +#include "show.h" +#include "cd.h" +#include "builtins.h" + +static int cdlogical(char *); +static int cdphysical(char *); +static int docd(char *, int, int); +static char *getcomponent(char **); +static char *findcwd(char *); +static void updatepwd(char *); +static char *getpwd(void); +static char *getpwd2(void); + +static char *curdir = NULL; /* current working directory */ + +int +cdcmd(int argc __unused, char **argv __unused) +{ + const char *dest; + const char *path; + char *p; + struct stat statb; + int ch, phys, print = 0, getcwderr = 0; + int rc; + int errno1 = ENOENT; + + phys = Pflag; + while ((ch = nextopt("eLP")) != '\0') { + switch (ch) { + case 'e': + getcwderr = 1; + break; + case 'L': + phys = 0; + break; + case 'P': + phys = 1; + break; + } + } + + if (*argptr != NULL && argptr[1] != NULL) + error("too many arguments"); + + if ((dest = *argptr) == NULL && (dest = bltinlookup("HOME", 1)) == NULL) + error("HOME not set"); + if (*dest == '\0') + dest = "."; + if (dest[0] == '-' && dest[1] == '\0') { + dest = bltinlookup("OLDPWD", 1); + if (dest == NULL) + error("OLDPWD not set"); + print = 1; + } + if (dest[0] == '/' || + (dest[0] == '.' && (dest[1] == '/' || dest[1] == '\0')) || + (dest[0] == '.' && dest[1] == '.' && (dest[2] == '/' || dest[2] == '\0')) || + (path = bltinlookup("CDPATH", 1)) == NULL) + path = ""; + while ((p = padvance(&path, NULL, dest)) != NULL) { + if (stat(p, &statb) < 0) { + if (errno != ENOENT) + errno1 = errno; + } else if (!S_ISDIR(statb.st_mode)) + errno1 = ENOTDIR; + else { + if (!print) { + /* + * XXX - rethink + */ + if (p[0] == '.' && p[1] == '/' && p[2] != '\0') + print = strcmp(p + 2, dest); + else + print = strcmp(p, dest); + } + rc = docd(p, print, phys); + if (rc >= 0) + return getcwderr ? rc : 0; + if (errno != ENOENT) + errno1 = errno; + } + } + error("%s: %s", dest, strerror(errno1)); + /*NOTREACHED*/ + return 0; +} + + +/* + * Actually change the directory. In an interactive shell, print the + * directory name if "print" is nonzero. + */ +static int +docd(char *dest, int print, int phys) +{ + int rc; + + TRACE(("docd(\"%s\", %d, %d) called\n", dest, print, phys)); + + /* If logical cd fails, fall back to physical. */ + if ((phys || (rc = cdlogical(dest)) < 0) && (rc = cdphysical(dest)) < 0) + return (-1); + + if (print && iflag && curdir) { + out1fmt("%s\n", curdir); + /* + * Ignore write errors to preserve the invariant that the + * current directory is changed iff the exit status is 0 + * (or 1 if -e was given and the full pathname could not be + * determined). + */ + flushout(out1); + outclearerror(out1); + } + + return (rc); +} + +static int +cdlogical(char *dest) +{ + char *p; + char *q; + char *component; + char *path; + struct stat statb; + int first; + int badstat; + + /* + * Check each component of the path. If we find a symlink or + * something we can't stat, clear curdir to force a getcwd() + * next time we get the value of the current directory. + */ + badstat = 0; + path = stsavestr(dest); + STARTSTACKSTR(p); + if (*dest == '/') { + STPUTC('/', p); + path++; + } + first = 1; + while ((q = getcomponent(&path)) != NULL) { + if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0')) + continue; + if (! first) + STPUTC('/', p); + first = 0; + component = q; + STPUTS(q, p); + if (equal(component, "..")) + continue; + STACKSTRNUL(p); + if (lstat(stackblock(), &statb) < 0) { + badstat = 1; + break; + } + } + + INTOFF; + if ((p = findcwd(badstat ? NULL : dest)) == NULL || chdir(p) < 0) { + INTON; + return (-1); + } + updatepwd(p); + INTON; + return (0); +} + +static int +cdphysical(char *dest) +{ + char *p; + int rc = 0; + + INTOFF; + if (chdir(dest) < 0) { + INTON; + return (-1); + } + p = findcwd(NULL); + if (p == NULL) { + warning("warning: failed to get name of current directory"); + rc = 1; + } + updatepwd(p); + INTON; + return (rc); +} + +/* + * Get the next component of the path name pointed to by *path. + * This routine overwrites *path and the string pointed to by it. + */ +static char * +getcomponent(char **path) +{ + char *p; + char *start; + + if ((p = *path) == NULL) + return NULL; + start = *path; + while (*p != '/' && *p != '\0') + p++; + if (*p == '\0') { + *path = NULL; + } else { + *p++ = '\0'; + *path = p; + } + return start; +} + + +static char * +findcwd(char *dir) +{ + char *new; + char *p; + char *path; + + /* + * If our argument is NULL, we don't know the current directory + * any more because we traversed a symbolic link or something + * we couldn't stat(). + */ + if (dir == NULL || curdir == NULL) + return getpwd2(); + path = stsavestr(dir); + STARTSTACKSTR(new); + if (*dir != '/') { + STPUTS(curdir, new); + if (STTOPC(new) == '/') + STUNPUTC(new); + } + while ((p = getcomponent(&path)) != NULL) { + if (equal(p, "..")) { + while (new > stackblock() && (STUNPUTC(new), *new) != '/'); + } else if (*p != '\0' && ! equal(p, ".")) { + STPUTC('/', new); + STPUTS(p, new); + } + } + if (new == stackblock()) + STPUTC('/', new); + STACKSTRNUL(new); + return stackblock(); +} + +/* + * Update curdir (the name of the current directory) in response to a + * cd command. We also call hashcd to let the routines in exec.c know + * that the current directory has changed. + */ +static void +updatepwd(char *dir) +{ + char *prevdir; + + hashcd(); /* update command hash table */ + + setvar("PWD", dir, VEXPORT); + setvar("OLDPWD", curdir, VEXPORT); + prevdir = curdir; + curdir = dir ? savestr(dir) : NULL; + ckfree(prevdir); +} + +int +pwdcmd(int argc __unused, char **argv __unused) +{ + char *p; + int ch, phys; + + phys = Pflag; + while ((ch = nextopt("LP")) != '\0') { + switch (ch) { + case 'L': + phys = 0; + break; + case 'P': + phys = 1; + break; + } + } + + if (*argptr != NULL) + error("too many arguments"); + + if (!phys && getpwd()) { + out1str(curdir); + out1c('\n'); + } else { + if ((p = getpwd2()) == NULL) + error(".: %s", strerror(errno)); + out1str(p); + out1c('\n'); + } + + return 0; +} + +/* + * Get the current directory and cache the result in curdir. + */ +static char * +getpwd(void) +{ + char *p; + + if (curdir) + return curdir; + + p = getpwd2(); + if (p != NULL) + curdir = savestr(p); + + return curdir; +} + +#define MAXPWD 256 + +/* + * Return the current directory. + */ +static char * +getpwd2(void) +{ + char *pwd; + int i; + + for (i = MAXPWD;; i *= 2) { + pwd = stalloc(i); + if (getcwd(pwd, i) != NULL) + return pwd; + stunalloc(pwd); + if (errno != ERANGE) + break; + } + + return NULL; +} + +/* + * Initialize PWD in a new shell. + * If the shell is interactive, we need to warn if this fails. + */ +void +pwd_init(int warn) +{ + char *pwd; + struct stat stdot, stpwd; + + pwd = lookupvar("PWD"); + if (pwd && *pwd == '/' && stat(".", &stdot) != -1 && + stat(pwd, &stpwd) != -1 && + stdot.st_dev == stpwd.st_dev && + stdot.st_ino == stpwd.st_ino) { + if (curdir) + ckfree(curdir); + curdir = savestr(pwd); + } + if (getpwd() == NULL && warn) + out2fmt_flush("sh: cannot determine working directory\n"); + setvar("PWD", curdir, VEXPORT); +} diff --git a/bin/catsh/cd.h b/bin/catsh/cd.h new file mode 100644 index 00000000..11de2228 --- /dev/null +++ b/bin/catsh/cd.h @@ -0,0 +1,32 @@ +/*- + * Copyright (c) 1995 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: releng/12.0/bin/sh/cd.h 314436 2017-02-28 23:42:47Z imp $ + */ + +void pwd_init(int); diff --git a/bin/catsh/echo.c b/bin/catsh/echo.c new file mode 100644 index 00000000..78c146e9 --- /dev/null +++ b/bin/catsh/echo.c @@ -0,0 +1,109 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)echo.c 8.2 (Berkeley) 5/4/95 + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/bltin/echo.c 326025 2017-11-20 19:49:47Z pfg $"); + +/* + * Echo command. + */ + +#define main echocmd + +#include "bltin.h" + +/* #define eflag 1 */ + +int +main(int argc, char *argv[]) +{ + char **ap; + char *p; + char c; + int count; + int nflag = 0; +#ifndef eflag + int eflag = 0; +#endif + + ap = argv; + if (argc) + ap++; + if ((p = *ap) != NULL) { + if (equal(p, "-n")) { + nflag++; + ap++; + } else if (equal(p, "-e")) { +#ifndef eflag + eflag++; +#endif + ap++; + } + } + while ((p = *ap++) != NULL) { + while ((c = *p++) != '\0') { + if (c == '\\' && eflag) { + switch (*p++) { + case 'a': c = '\a'; break; + case 'b': c = '\b'; break; + case 'c': return 0; /* exit */ + case 'e': c = '\033'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case '\\': break; /* c = '\\' */ + case '0': + c = 0; + count = 3; + while (--count >= 0 && (unsigned)(*p - '0') < 8) + c = (c << 3) + (*p++ - '0'); + break; + default: + p--; + break; + } + } + putchar(c); + } + if (*ap) + putchar(' '); + } + if (! nflag) + putchar('\n'); + return 0; +} diff --git a/bin/catsh/error.c b/bin/catsh/error.c new file mode 100644 index 00000000..02680718 --- /dev/null +++ b/bin/catsh/error.c @@ -0,0 +1,199 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)error.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/error.c 314436 2017-02-28 23:42:47Z imp $"); + +/* + * Errors and exceptions. + */ + +#include "shell.h" +#include "eval.h" +#include "main.h" +#include "options.h" +#include "output.h" +#include "error.h" +#include "nodes.h" /* show.h needs nodes.h */ +#include "show.h" +#include "trap.h" +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> + + +/* + * Code to handle exceptions in C. + */ + +struct jmploc *handler; +volatile sig_atomic_t exception; +volatile sig_atomic_t suppressint; +volatile sig_atomic_t intpending; + + +static void exverror(int, const char *, va_list) __printf0like(2, 0) __dead2; + +/* + * Called to raise an exception. Since C doesn't include exceptions, we + * just do a longjmp to the exception handler. The type of exception is + * stored in the global variable "exception". + * + * Interrupts are disabled; they should be reenabled when the exception is + * caught. + */ + +void +exraise(int e) +{ + INTOFF; + if (handler == NULL) + abort(); + exception = e; + longjmp(handler->loc, 1); +} + + +/* + * Called from trap.c when a SIGINT is received and not suppressed, or when + * an interrupt is pending and interrupts are re-enabled using INTON. + * (If the user specifies that SIGINT is to be trapped or ignored using the + * trap builtin, then this routine is not called.) Suppressint is nonzero + * when interrupts are held using the INTOFF macro. If SIGINTs are not + * suppressed and the shell is not a root shell, then we want to be + * terminated if we get here, as if we were terminated directly by a SIGINT. + * Arrange for this here. + */ + +void +onint(void) +{ + sigset_t sigs; + + intpending = 0; + sigemptyset(&sigs); + sigprocmask(SIG_SETMASK, &sigs, NULL); + + /* + * This doesn't seem to be needed, since main() emits a newline. + */ +#if 0 + if (tcgetpgrp(0) == getpid()) + write(STDERR_FILENO, "\n", 1); +#endif + if (rootshell && iflag) + exraise(EXINT); + else { + signal(SIGINT, SIG_DFL); + kill(getpid(), SIGINT); + _exit(128 + SIGINT); + } +} + + +static void +vwarning(const char *msg, va_list ap) +{ + if (commandname) + outfmt(out2, "%s: ", commandname); + else if (arg0) + outfmt(out2, "%s: ", arg0); + doformat(out2, msg, ap); + out2fmt_flush("\n"); +} + + +void +warning(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + vwarning(msg, ap); + va_end(ap); +} + + +/* + * Exverror is called to raise the error exception. If the first argument + * is not NULL then error prints an error message using printf style + * formatting. It then raises the error exception. + */ +static void +exverror(int cond, const char *msg, va_list ap) +{ + /* + * An interrupt trumps an error. Certain places catch error + * exceptions or transform them to a plain nonzero exit code + * in child processes, and if an error exception can be handled, + * an interrupt can be handled as well. + * + * exraise() will disable interrupts for the exception handler. + */ + FORCEINTON; + +#ifdef DEBUG + if (msg) + TRACE(("exverror(%d, \"%s\") pid=%d\n", cond, msg, getpid())); + else + TRACE(("exverror(%d, NULL) pid=%d\n", cond, getpid())); +#endif + if (msg) + vwarning(msg, ap); + flushall(); + exraise(cond); +} + + +void +error(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + exverror(EXERROR, msg, ap); + va_end(ap); +} + + +void +exerror(int cond, const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + exverror(cond, msg, ap); + va_end(ap); +} diff --git a/bin/catsh/error.h b/bin/catsh/error.h new file mode 100644 index 00000000..8efd5b38 --- /dev/null +++ b/bin/catsh/error.h @@ -0,0 +1,95 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)error.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/error.h 319591 2017-06-04 21:58:02Z jilles $ + */ + +/* + * We enclose jmp_buf in a structure so that we can declare pointers to + * jump locations. The global variable handler contains the location to + * jump to when an exception occurs, and the global variable exception + * contains a code identifying the exception. To implement nested + * exception handlers, the user should save the value of handler on entry + * to an inner scope, set handler to point to a jmploc structure for the + * inner scope, and restore handler on exit from the scope. + */ + +#include <setjmp.h> +#include <signal.h> + +struct jmploc { + jmp_buf loc; +}; + +extern struct jmploc *handler; +extern volatile sig_atomic_t exception; + +/* exceptions */ +#define EXINT 0 /* SIGINT received */ +#define EXERROR 1 /* a generic error */ +#define EXEXEC 2 /* command execution failed */ +#define EXEXIT 3 /* call exitshell(exitstatus) */ + + +/* + * These macros allow the user to suspend the handling of interrupt signals + * over a period of time. This is similar to SIGHOLD to or sigblock, but + * much more efficient and portable. (But hacking the kernel is so much + * more fun than worrying about efficiency and portability. :-)) + */ + +extern volatile sig_atomic_t suppressint; +extern volatile sig_atomic_t intpending; + +#define INTOFF suppressint++ +#define INTON { if (--suppressint == 0 && intpending) onint(); } +#define is_int_on() suppressint +#define SETINTON(s) do { suppressint = (s); if (suppressint == 0 && intpending) onint(); } while (0) +#define FORCEINTON {suppressint = 0; if (intpending) onint();} +#define SET_PENDING_INT intpending = 1 +#define CLEAR_PENDING_INT intpending = 0 +#define int_pending() intpending + +void exraise(int) __dead2; +void onint(void) __dead2; +void warning(const char *, ...) __printflike(1, 2); +void error(const char *, ...) __printf0like(1, 2) __dead2; +void exerror(int, const char *, ...) __printf0like(2, 3) __dead2; + + +/* + * BSD setjmp saves the signal mask, which violates ANSI C and takes time, + * so we use _setjmp instead. + */ + +#define setjmp(jmploc) _setjmp(jmploc) +#define longjmp(jmploc, val) _longjmp(jmploc, val) diff --git a/bin/catsh/eval.c b/bin/catsh/eval.c new file mode 100644 index 00000000..5a5289e1 --- /dev/null +++ b/bin/catsh/eval.c @@ -0,0 +1,1381 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)eval.c 8.9 (Berkeley) 6/8/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/eval.c 327212 2017-12-26 16:23:18Z jilles $"); + +#include <paths.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/resource.h> +#include <errno.h> + +/* + * Evaluate a command. + */ + +#include "shell.h" +#include "nodes.h" +#include "syntax.h" +#include "expand.h" +#include "parser.h" +#include "jobs.h" +#include "eval.h" +#include "builtins.h" +#include "options.h" +#include "exec.h" +#include "redir.h" +#include "input.h" +#include "output.h" +#include "trap.h" +#include "var.h" +#include "memalloc.h" +#include "error.h" +#include "show.h" +#include "mystring.h" +#ifndef NO_HISTORY +#include "myhistedit.h" +#endif + + +int evalskip; /* set if we are skipping commands */ +int skipcount; /* number of levels to skip */ +static int loopnest; /* current loop nesting level */ +int funcnest; /* depth of function calls */ +static int builtin_flags; /* evalcommand flags for builtins */ + + +char *commandname; +struct arglist *cmdenviron; +int exitstatus; /* exit status of last command */ +int oexitstatus; /* saved exit status */ + + +static void evalloop(union node *, int); +static void evalfor(union node *, int); +static union node *evalcase(union node *); +static void evalsubshell(union node *, int); +static void evalredir(union node *, int); +static void exphere(union node *, struct arglist *); +static void expredir(union node *); +static void evalpipe(union node *); +static int is_valid_fast_cmdsubst(union node *n); +static void evalcommand(union node *, int, struct backcmd *); +static void prehash(union node *); + + +/* + * Called to reset things after an exception. + */ + +void +reseteval(void) +{ + evalskip = 0; + loopnest = 0; +} + + +/* + * The eval command. + */ + +int +evalcmd(int argc, char **argv) +{ + char *p; + char *concat; + char **ap; + + if (argc > 1) { + p = argv[1]; + if (argc > 2) { + STARTSTACKSTR(concat); + ap = argv + 2; + for (;;) { + STPUTS(p, concat); + if ((p = *ap++) == NULL) + break; + STPUTC(' ', concat); + } + STPUTC('\0', concat); + p = grabstackstr(concat); + } + evalstring(p, builtin_flags); + } else + exitstatus = 0; + return exitstatus; +} + + +/* + * Execute a command or commands contained in a string. + */ + +void +evalstring(const char *s, int flags) +{ + union node *n; + struct stackmark smark; + int flags_exit; + int any; + + flags_exit = flags & EV_EXIT; + flags &= ~EV_EXIT; + any = 0; + setstackmark(&smark); + setinputstring(s, 1); + while ((n = parsecmd(0)) != NEOF) { + if (n != NULL && !nflag) { + if (flags_exit && preadateof()) + evaltree(n, flags | EV_EXIT); + else + evaltree(n, flags); + any = 1; + if (evalskip) + break; + } + popstackmark(&smark); + setstackmark(&smark); + } + popfile(); + popstackmark(&smark); + if (!any) + exitstatus = 0; + if (flags_exit) + exraise(EXEXIT); +} + + +/* + * Evaluate a parse tree. The value is left in the global variable + * exitstatus. + */ + +void +evaltree(union node *n, int flags) +{ + int do_etest; + union node *next; + struct stackmark smark; + + setstackmark(&smark); + do_etest = 0; + if (n == NULL) { + TRACE(("evaltree(NULL) called\n")); + exitstatus = 0; + goto out; + } + do { + next = NULL; +#ifndef NO_HISTORY + displayhist = 1; /* show history substitutions done with fc */ +#endif + TRACE(("evaltree(%p: %d) called\n", (void *)n, n->type)); + switch (n->type) { + case NSEMI: + evaltree(n->nbinary.ch1, flags & ~EV_EXIT); + if (evalskip) + goto out; + next = n->nbinary.ch2; + break; + case NAND: + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip || exitstatus != 0) { + goto out; + } + next = n->nbinary.ch2; + break; + case NOR: + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip || exitstatus == 0) + goto out; + next = n->nbinary.ch2; + break; + case NREDIR: + evalredir(n, flags); + break; + case NSUBSHELL: + evalsubshell(n, flags); + do_etest = !(flags & EV_TESTED); + break; + case NBACKGND: + evalsubshell(n, flags); + break; + case NIF: { + evaltree(n->nif.test, EV_TESTED); + if (evalskip) + goto out; + if (exitstatus == 0) + next = n->nif.ifpart; + else if (n->nif.elsepart) + next = n->nif.elsepart; + else + exitstatus = 0; + break; + } + case NWHILE: + case NUNTIL: + evalloop(n, flags & ~EV_EXIT); + break; + case NFOR: + evalfor(n, flags & ~EV_EXIT); + break; + case NCASE: + next = evalcase(n); + break; + case NCLIST: + next = n->nclist.body; + break; + case NCLISTFALLTHRU: + if (n->nclist.body) { + evaltree(n->nclist.body, flags & ~EV_EXIT); + if (evalskip) + goto out; + } + next = n->nclist.next; + break; + case NDEFUN: + defun(n->narg.text, n->narg.next); + exitstatus = 0; + break; + case NNOT: + evaltree(n->nnot.com, EV_TESTED); + if (evalskip) + goto out; + exitstatus = !exitstatus; + break; + + case NPIPE: + evalpipe(n); + do_etest = !(flags & EV_TESTED); + break; + case NCMD: + evalcommand(n, flags, (struct backcmd *)NULL); + do_etest = !(flags & EV_TESTED); + break; + default: + out1fmt("Node type = %d\n", n->type); + flushout(&output); + break; + } + n = next; + popstackmark(&smark); + setstackmark(&smark); + } while (n != NULL); +out: + popstackmark(&smark); + if (pendingsig) + dotrap(); + if (eflag && exitstatus != 0 && do_etest) + exitshell(exitstatus); + if (flags & EV_EXIT) + exraise(EXEXIT); +} + + +static void +evalloop(union node *n, int flags) +{ + int status; + + loopnest++; + status = 0; + for (;;) { + if (!evalskip) + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip) { + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; + if (evalskip == SKIPRETURN) + status = exitstatus; + break; + } + if (n->type == NWHILE) { + if (exitstatus != 0) + break; + } else { + if (exitstatus == 0) + break; + } + evaltree(n->nbinary.ch2, flags); + status = exitstatus; + } + loopnest--; + exitstatus = status; +} + + + +static void +evalfor(union node *n, int flags) +{ + struct arglist arglist; + union node *argp; + int i; + int status; + + emptyarglist(&arglist); + for (argp = n->nfor.args ; argp ; argp = argp->narg.next) { + oexitstatus = exitstatus; + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + } + + loopnest++; + status = 0; + for (i = 0; i < arglist.count; i++) { + setvar(n->nfor.var, arglist.args[i], 0); + evaltree(n->nfor.body, flags); + status = exitstatus; + if (evalskip) { + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; + break; + } + } + loopnest--; + exitstatus = status; +} + + +/* + * Evaluate a case statement, returning the selected tree. + * + * The exit status needs care to get right. + */ + +static union node * +evalcase(union node *n) +{ + union node *cp; + union node *patp; + struct arglist arglist; + + emptyarglist(&arglist); + oexitstatus = exitstatus; + expandarg(n->ncase.expr, &arglist, EXP_TILDE); + for (cp = n->ncase.cases ; cp ; cp = cp->nclist.next) { + for (patp = cp->nclist.pattern ; patp ; patp = patp->narg.next) { + if (casematch(patp, arglist.args[0])) { + while (cp->nclist.next && + cp->type == NCLISTFALLTHRU && + cp->nclist.body == NULL) + cp = cp->nclist.next; + if (cp->nclist.next && + cp->type == NCLISTFALLTHRU) + return (cp); + if (cp->nclist.body == NULL) + exitstatus = 0; + return (cp->nclist.body); + } + } + } + exitstatus = 0; + return (NULL); +} + + + +/* + * Kick off a subshell to evaluate a tree. + */ + +static void +evalsubshell(union node *n, int flags) +{ + struct job *jp; + int backgnd = (n->type == NBACKGND); + + oexitstatus = exitstatus; + expredir(n->nredir.redirect); + if ((!backgnd && flags & EV_EXIT && !have_traps()) || + forkshell(jp = makejob(n, 1), n, backgnd) == 0) { + if (backgnd) + flags &=~ EV_TESTED; + redirect(n->nredir.redirect, 0); + evaltree(n->nredir.n, flags | EV_EXIT); /* never returns */ + } else if (! backgnd) { + INTOFF; + exitstatus = waitforjob(jp, (int *)NULL); + INTON; + } else + exitstatus = 0; +} + + +/* + * Evaluate a redirected compound command. + */ + +static void +evalredir(union node *n, int flags) +{ + struct jmploc jmploc; + struct jmploc *savehandler; + volatile int in_redirect = 1; + + oexitstatus = exitstatus; + expredir(n->nredir.redirect); + savehandler = handler; + if (setjmp(jmploc.loc)) { + int e; + + handler = savehandler; + e = exception; + popredir(); + if (e == EXERROR || e == EXEXEC) { + if (in_redirect) { + exitstatus = 2; + FORCEINTON; + return; + } + } + longjmp(handler->loc, 1); + } else { + INTOFF; + handler = &jmploc; + redirect(n->nredir.redirect, REDIR_PUSH); + in_redirect = 0; + INTON; + evaltree(n->nredir.n, flags); + } + INTOFF; + handler = savehandler; + popredir(); + INTON; +} + + +static void +exphere(union node *redir, struct arglist *fn) +{ + struct jmploc jmploc; + struct jmploc *savehandler; + struct localvar *savelocalvars; + int need_longjmp = 0; + unsigned char saveoptreset; + + redir->nhere.expdoc = ""; + savelocalvars = localvars; + localvars = NULL; + saveoptreset = shellparam.reset; + forcelocal++; + savehandler = handler; + if (setjmp(jmploc.loc)) + need_longjmp = exception != EXERROR && exception != EXEXEC; + else { + handler = &jmploc; + expandarg(redir->nhere.doc, fn, 0); + redir->nhere.expdoc = fn->args[0]; + INTOFF; + } + handler = savehandler; + forcelocal--; + poplocalvars(); + localvars = savelocalvars; + shellparam.reset = saveoptreset; + if (need_longjmp) + longjmp(handler->loc, 1); + INTON; +} + + +/* + * Compute the names of the files in a redirection list. + */ + +static void +expredir(union node *n) +{ + union node *redir; + + for (redir = n ; redir ; redir = redir->nfile.next) { + struct arglist fn; + emptyarglist(&fn); + switch (redir->type) { + case NFROM: + case NTO: + case NFROMTO: + case NAPPEND: + case NCLOBBER: + expandarg(redir->nfile.fname, &fn, EXP_TILDE); + redir->nfile.expfname = fn.args[0]; + break; + case NFROMFD: + case NTOFD: + if (redir->ndup.vname) { + expandarg(redir->ndup.vname, &fn, EXP_TILDE); + fixredir(redir, fn.args[0], 1); + } + break; + case NXHERE: + exphere(redir, &fn); + break; + } + } +} + + + +/* + * Evaluate a pipeline. All the processes in the pipeline are children + * of the process creating the pipeline. (This differs from some versions + * of the shell, which make the last process in a pipeline the parent + * of all the rest.) + */ + +static void +evalpipe(union node *n) +{ + struct job *jp; + struct nodelist *lp; + int pipelen; + int prevfd; + int pip[2]; + + TRACE(("evalpipe(%p) called\n", (void *)n)); + pipelen = 0; + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) + pipelen++; + INTOFF; + jp = makejob(n, pipelen); + prevfd = -1; + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + prehash(lp->n); + pip[1] = -1; + if (lp->next) { + if (pipe(pip) < 0) { + if (prevfd >= 0) + close(prevfd); + error("Pipe call failed: %s", strerror(errno)); + } + } + if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) { + INTON; + if (prevfd > 0) { + dup2(prevfd, 0); + close(prevfd); + } + if (pip[1] >= 0) { + if (!(prevfd >= 0 && pip[0] == 0)) + close(pip[0]); + if (pip[1] != 1) { + dup2(pip[1], 1); + close(pip[1]); + } + } + evaltree(lp->n, EV_EXIT); + } + if (prevfd >= 0) + close(prevfd); + prevfd = pip[0]; + if (pip[1] != -1) + close(pip[1]); + } + INTON; + if (n->npipe.backgnd == 0) { + INTOFF; + exitstatus = waitforjob(jp, (int *)NULL); + TRACE(("evalpipe: job done exit status %d\n", exitstatus)); + INTON; + } else + exitstatus = 0; +} + + + +static int +is_valid_fast_cmdsubst(union node *n) +{ + + return (n->type == NCMD); +} + +/* + * Execute a command inside back quotes. If it's a builtin command, we + * want to save its output in a block obtained from malloc. Otherwise + * we fork off a subprocess and get the output of the command via a pipe. + * Should be called with interrupts off. + */ + +void +evalbackcmd(union node *n, struct backcmd *result) +{ + int pip[2]; + struct job *jp; + struct stackmark smark; + struct jmploc jmploc; + struct jmploc *savehandler; + struct localvar *savelocalvars; + unsigned char saveoptreset; + + result->fd = -1; + result->buf = NULL; + result->nleft = 0; + result->jp = NULL; + if (n == NULL) { + exitstatus = 0; + return; + } + setstackmark(&smark); + exitstatus = oexitstatus; + if (is_valid_fast_cmdsubst(n)) { + savelocalvars = localvars; + localvars = NULL; + saveoptreset = shellparam.reset; + forcelocal++; + savehandler = handler; + if (setjmp(jmploc.loc)) { + if (exception == EXERROR || exception == EXEXEC) + exitstatus = 2; + else if (exception != 0) { + handler = savehandler; + forcelocal--; + poplocalvars(); + localvars = savelocalvars; + shellparam.reset = saveoptreset; + longjmp(handler->loc, 1); + } + } else { + handler = &jmploc; + evalcommand(n, EV_BACKCMD, result); + } + handler = savehandler; + forcelocal--; + poplocalvars(); + localvars = savelocalvars; + shellparam.reset = saveoptreset; + } else { + if (pipe(pip) < 0) + error("Pipe call failed: %s", strerror(errno)); + jp = makejob(n, 1); + if (forkshell(jp, n, FORK_NOJOB) == 0) { + FORCEINTON; + close(pip[0]); + if (pip[1] != 1) { + dup2(pip[1], 1); + close(pip[1]); + } + evaltree(n, EV_EXIT); + } + close(pip[1]); + result->fd = pip[0]; + result->jp = jp; + } + popstackmark(&smark); + TRACE(("evalbackcmd done: fd=%d buf=%p nleft=%d jp=%p\n", + result->fd, result->buf, result->nleft, result->jp)); +} + +static int +mustexpandto(const char *argtext, const char *mask) +{ + for (;;) { + if (*argtext == CTLQUOTEMARK || *argtext == CTLQUOTEEND) { + argtext++; + continue; + } + if (*argtext == CTLESC) + argtext++; + else if (BASESYNTAX[(int)*argtext] == CCTL) + return (0); + if (*argtext != *mask) + return (0); + if (*argtext == '\0') + return (1); + argtext++; + mask++; + } +} + +static int +isdeclarationcmd(struct narg *arg) +{ + int have_command = 0; + + if (arg == NULL) + return (0); + while (mustexpandto(arg->text, "command")) { + have_command = 1; + arg = &arg->next->narg; + if (arg == NULL) + return (0); + /* + * To also allow "command -p" and "command --" as part of + * a declaration command, add code here. + * We do not do this, as ksh does not do it either and it + * is not required by POSIX. + */ + } + return (mustexpandto(arg->text, "export") || + mustexpandto(arg->text, "readonly") || + (mustexpandto(arg->text, "local") && + (have_command || !isfunc("local")))); +} + +static void +xtracecommand(struct arglist *varlist, int argc, char **argv) +{ + char sep = 0; + const char *text, *p, *ps4; + int i; + + ps4 = expandstr(ps4val()); + out2str(ps4 != NULL ? ps4 : ps4val()); + for (i = 0; i < varlist->count; i++) { + text = varlist->args[i]; + if (sep != 0) + out2c(' '); + p = strchr(text, '='); + if (p != NULL) { + p++; + outbin(text, p - text, out2); + out2qstr(p); + } else + out2qstr(text); + sep = ' '; + } + for (i = 0; i < argc; i++) { + text = argv[i]; + if (sep != 0) + out2c(' '); + out2qstr(text); + sep = ' '; + } + out2c('\n'); + flushout(&errout); +} + +/* + * Check if a builtin can safely be executed in the same process, + * even though it should be in a subshell (command substitution). + * Note that jobid, jobs, times and trap can show information not + * available in a child process; this is deliberate. + * The arguments should already have been expanded. + */ +static int +safe_builtin(int idx, int argc, char **argv) +{ + /* Generated from builtins.def. */ + if (safe_builtin_always(idx)) + return (1); + if (idx == EXPORTCMD || idx == TRAPCMD || idx == ULIMITCMD || + idx == UMASKCMD) + return (argc <= 1 || (argc == 2 && argv[1][0] == '-')); + if (idx == SETCMD) + return (argc <= 1 || (argc == 2 && (argv[1][0] == '-' || + argv[1][0] == '+') && argv[1][1] == 'o' && + argv[1][2] == '\0')); + return (0); +} + +/* + * Execute a simple command. + * Note: This may or may not return if (flags & EV_EXIT). + */ + +static void +evalcommand(union node *cmd, int flags, struct backcmd *backcmd) +{ + union node *argp; + struct arglist arglist; + struct arglist varlist; + char **argv; + int argc; + char **envp; + int varflag; + int mode; + int pip[2]; + struct cmdentry cmdentry; + struct job *jp; + struct jmploc jmploc; + struct jmploc *savehandler; + char *savecmdname; + struct shparam saveparam; + struct localvar *savelocalvars; + struct parsefile *savetopfile; + volatile int e; + char *lastarg; + int signaled; + int do_clearcmdentry; + const char *path = pathval(); + int i; + + /* First expand the arguments. */ + TRACE(("evalcommand(%p, %d) called\n", (void *)cmd, flags)); + emptyarglist(&arglist); + emptyarglist(&varlist); + varflag = 1; + jp = NULL; + do_clearcmdentry = 0; + oexitstatus = exitstatus; + exitstatus = 0; + /* Add one slot at the beginning for tryexec(). */ + appendarglist(&arglist, nullstr); + for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) { + if (varflag && isassignment(argp->narg.text)) { + expandarg(argp, varflag == 1 ? &varlist : &arglist, + EXP_VARTILDE); + continue; + } else if (varflag == 1) + varflag = isdeclarationcmd(&argp->narg) ? 2 : 0; + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + } + appendarglist(&arglist, nullstr); + expredir(cmd->ncmd.redirect); + argc = arglist.count - 2; + argv = &arglist.args[1]; + + argv[argc] = NULL; + lastarg = NULL; + if (iflag && funcnest == 0 && argc > 0) + lastarg = argv[argc - 1]; + + /* Print the command if xflag is set. */ + if (xflag) + xtracecommand(&varlist, argc, argv); + + /* Now locate the command. */ + if (argc == 0) { + /* Variable assignment(s) without command */ + cmdentry.cmdtype = CMDBUILTIN; + cmdentry.u.index = BLTINCMD; + cmdentry.special = 0; + } else { + static const char PATH[] = "PATH="; + int cmd_flags = 0, bltinonly = 0; + + /* + * Modify the command lookup path, if a PATH= assignment + * is present + */ + for (i = 0; i < varlist.count; i++) + if (strncmp(varlist.args[i], PATH, sizeof(PATH) - 1) == 0) { + path = varlist.args[i] + sizeof(PATH) - 1; + /* + * On `PATH=... command`, we need to make + * sure that the command isn't using the + * non-updated hash table of the outer PATH + * setting and we need to make sure that + * the hash table isn't filled with items + * from the temporary setting. + * + * It would be better to forbit using and + * updating the table while this command + * runs, by the command finding mechanism + * is heavily integrated with hash handling, + * so we just delete the hash before and after + * the command runs. Partly deleting like + * changepatch() does doesn't seem worth the + * bookinging effort, since most such runs add + * directories in front of the new PATH. + */ + clearcmdentry(); + do_clearcmdentry = 1; + } + + for (;;) { + if (bltinonly) { + cmdentry.u.index = find_builtin(*argv, &cmdentry.special); + if (cmdentry.u.index < 0) { + cmdentry.u.index = BLTINCMD; + argv--; + argc++; + break; + } + } else + find_command(argv[0], &cmdentry, cmd_flags, path); + /* implement the bltin and command builtins here */ + if (cmdentry.cmdtype != CMDBUILTIN) + break; + if (cmdentry.u.index == BLTINCMD) { + if (argc == 1) + break; + argv++; + argc--; + bltinonly = 1; + } else if (cmdentry.u.index == COMMANDCMD) { + if (argc == 1) + break; + if (!strcmp(argv[1], "-p")) { + if (argc == 2) + break; + if (argv[2][0] == '-') { + if (strcmp(argv[2], "--")) + break; + if (argc == 3) + break; + argv += 3; + argc -= 3; + } else { + argv += 2; + argc -= 2; + } + path = _PATH_STDPATH; + clearcmdentry(); + do_clearcmdentry = 1; + } else if (!strcmp(argv[1], "--")) { + if (argc == 2) + break; + argv += 2; + argc -= 2; + } else if (argv[1][0] == '-') + break; + else { + argv++; + argc--; + } + cmd_flags |= DO_NOFUNC; + bltinonly = 0; + } else + break; + } + /* + * Special builtins lose their special properties when + * called via 'command'. + */ + if (cmd_flags & DO_NOFUNC) + cmdentry.special = 0; + } + + /* Fork off a child process if necessary. */ + if (((cmdentry.cmdtype == CMDNORMAL || cmdentry.cmdtype == CMDUNKNOWN) + && ((flags & EV_EXIT) == 0 || have_traps())) + || ((flags & EV_BACKCMD) != 0 + && (cmdentry.cmdtype != CMDBUILTIN || + !safe_builtin(cmdentry.u.index, argc, argv)))) { + jp = makejob(cmd, 1); + mode = FORK_FG; + if (flags & EV_BACKCMD) { + mode = FORK_NOJOB; + if (pipe(pip) < 0) + error("Pipe call failed: %s", strerror(errno)); + } + if (cmdentry.cmdtype == CMDNORMAL && + cmd->ncmd.redirect == NULL && + varlist.count == 0 && + (mode == FORK_FG || mode == FORK_NOJOB) && + !disvforkset() && !iflag && !mflag) { + vforkexecshell(jp, argv, environment(), path, + cmdentry.u.index, flags & EV_BACKCMD ? pip : NULL); + goto parent; + } + if (forkshell(jp, cmd, mode) != 0) + goto parent; /* at end of routine */ + if (flags & EV_BACKCMD) { + FORCEINTON; + close(pip[0]); + if (pip[1] != 1) { + dup2(pip[1], 1); + close(pip[1]); + } + flags &= ~EV_BACKCMD; + } + flags |= EV_EXIT; + } + + /* This is the child process if a fork occurred. */ + /* Execute the command. */ + if (cmdentry.cmdtype == CMDFUNCTION) { +#ifdef DEBUG + trputs("Shell function: "); trargs(argv); +#endif + saveparam = shellparam; + shellparam.malloc = 0; + shellparam.reset = 1; + shellparam.nparam = argc - 1; + shellparam.p = argv + 1; + shellparam.optp = NULL; + shellparam.optnext = NULL; + INTOFF; + savelocalvars = localvars; + localvars = NULL; + reffunc(cmdentry.u.func); + savehandler = handler; + if (setjmp(jmploc.loc)) { + popredir(); + unreffunc(cmdentry.u.func); + poplocalvars(); + localvars = savelocalvars; + freeparam(&shellparam); + shellparam = saveparam; + funcnest--; + handler = savehandler; + longjmp(handler->loc, 1); + } + handler = &jmploc; + funcnest++; + redirect(cmd->ncmd.redirect, REDIR_PUSH); + INTON; + for (i = 0; i < varlist.count; i++) + mklocal(varlist.args[i]); + exitstatus = oexitstatus; + evaltree(getfuncnode(cmdentry.u.func), + flags & (EV_TESTED | EV_EXIT)); + INTOFF; + unreffunc(cmdentry.u.func); + poplocalvars(); + localvars = savelocalvars; + freeparam(&shellparam); + shellparam = saveparam; + handler = savehandler; + funcnest--; + popredir(); + INTON; + if (evalskip == SKIPRETURN) { + evalskip = 0; + skipcount = 0; + } + if (jp) + exitshell(exitstatus); + } else if (cmdentry.cmdtype == CMDBUILTIN) { +#ifdef DEBUG + trputs("builtin command: "); trargs(argv); +#endif + mode = (cmdentry.u.index == EXECCMD)? 0 : REDIR_PUSH; + if (flags == EV_BACKCMD) { + memout.nextc = memout.buf; + mode |= REDIR_BACKQ; + } + savecmdname = commandname; + savetopfile = getcurrentfile(); + cmdenviron = &varlist; + e = -1; + savehandler = handler; + if (setjmp(jmploc.loc)) { + e = exception; + if (e == EXINT) + exitstatus = SIGINT+128; + else if (e != EXEXIT) + exitstatus = 2; + goto cmddone; + } + handler = &jmploc; + redirect(cmd->ncmd.redirect, mode); + outclearerror(out1); + /* + * If there is no command word, redirection errors should + * not be fatal but assignment errors should. + */ + if (argc == 0) + cmdentry.special = 1; + listsetvar(cmdenviron, cmdentry.special ? 0 : VNOSET); + if (argc > 0) + bltinsetlocale(); + commandname = argv[0]; + argptr = argv + 1; + nextopt_optptr = NULL; /* initialize nextopt */ + builtin_flags = flags; + exitstatus = (*builtinfunc[cmdentry.u.index])(argc, argv); + flushall(); + if (outiserror(out1)) { + warning("write error on stdout"); + if (exitstatus == 0 || exitstatus == 1) + exitstatus = 2; + } +cmddone: + if (argc > 0) + bltinunsetlocale(); + cmdenviron = NULL; + out1 = &output; + out2 = &errout; + freestdout(); + handler = savehandler; + commandname = savecmdname; + if (jp) + exitshell(exitstatus); + if (flags == EV_BACKCMD) { + backcmd->buf = memout.buf; + backcmd->nleft = memout.buf != NULL ? + memout.nextc - memout.buf : 0; + memout.buf = NULL; + memout.nextc = NULL; + memout.bufend = NULL; + memout.bufsize = 64; + } + if (cmdentry.u.index != EXECCMD) + popredir(); + if (e != -1) { + if ((e != EXERROR && e != EXEXEC) + || cmdentry.special) + exraise(e); + popfilesupto(savetopfile); + if (flags != EV_BACKCMD) + FORCEINTON; + } + } else { +#ifdef DEBUG + trputs("normal command: "); trargs(argv); +#endif + redirect(cmd->ncmd.redirect, 0); + for (i = 0; i < varlist.count; i++) + setvareq(varlist.args[i], VEXPORT|VSTACK); + envp = environment(); + shellexec(argv, envp, path, cmdentry.u.index); + /*NOTREACHED*/ + } + goto out; + +parent: /* parent process gets here (if we forked) */ + if (mode == FORK_FG) { /* argument to fork */ + INTOFF; + exitstatus = waitforjob(jp, &signaled); + INTON; + if (iflag && loopnest > 0 && signaled) { + evalskip = SKIPBREAK; + skipcount = loopnest; + } + } else if (mode == FORK_NOJOB) { + backcmd->fd = pip[0]; + close(pip[1]); + backcmd->jp = jp; + } + +out: + if (lastarg) + setvar("_", lastarg, 0); + if (do_clearcmdentry) + clearcmdentry(); +} + + + +/* + * Search for a command. This is called before we fork so that the + * location of the command will be available in the parent as well as + * the child. The check for "goodname" is an overly conservative + * check that the name will not be subject to expansion. + */ + +static void +prehash(union node *n) +{ + struct cmdentry entry; + + if (n && n->type == NCMD && n->ncmd.args) + if (goodname(n->ncmd.args->narg.text)) + find_command(n->ncmd.args->narg.text, &entry, 0, + pathval()); +} + + + +/* + * Builtin commands. Builtin commands whose functions are closely + * tied to evaluation are implemented here. + */ + +/* + * No command given, a bltin command with no arguments, or a bltin command + * with an invalid name. + */ + +int +bltincmd(int argc, char **argv) +{ + if (argc > 1) { + out2fmt_flush("%s: not found\n", argv[1]); + return 127; + } + /* + * Preserve exitstatus of a previous possible command substitution + * as POSIX mandates + */ + return exitstatus; +} + + +/* + * Handle break and continue commands. Break, continue, and return are + * all handled by setting the evalskip flag. The evaluation routines + * above all check this flag, and if it is set they start skipping + * commands rather than executing them. The variable skipcount is + * the number of loops to break/continue, or the number of function + * levels to return. (The latter is always 1.) It should probably + * be an error to break out of more loops than exist, but it isn't + * in the standard shell so we don't make it one here. + */ + +int +breakcmd(int argc, char **argv) +{ + long n; + char *end; + + if (argc > 1) { + /* Allow arbitrarily large numbers. */ + n = strtol(argv[1], &end, 10); + if (!is_digit(argv[1][0]) || *end != '\0') + error("Illegal number: %s", argv[1]); + } else + n = 1; + if (n > loopnest) + n = loopnest; + if (n > 0) { + evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK; + skipcount = n; + } + return 0; +} + +/* + * The `command' command. + */ +int +commandcmd(int argc __unused, char **argv __unused) +{ + const char *path; + int ch; + int cmd = -1; + + path = bltinlookup("PATH", 1); + + while ((ch = nextopt("pvV")) != '\0') { + switch (ch) { + case 'p': + path = _PATH_STDPATH; + break; + case 'v': + cmd = TYPECMD_SMALLV; + break; + case 'V': + cmd = TYPECMD_BIGV; + break; + } + } + + if (cmd != -1) { + if (*argptr == NULL || argptr[1] != NULL) + error("wrong number of arguments"); + return typecmd_impl(2, argptr - 1, cmd, path); + } + if (*argptr != NULL) + error("commandcmd bad call"); + + /* + * Do nothing successfully if no command was specified; + * ksh also does this. + */ + return 0; +} + + +/* + * The return command. + */ + +int +returncmd(int argc, char **argv) +{ + int ret = argc > 1 ? number(argv[1]) : oexitstatus; + + evalskip = SKIPRETURN; + skipcount = 1; + return ret; +} + + +int +falsecmd(int argc __unused, char **argv __unused) +{ + return 1; +} + + +int +truecmd(int argc __unused, char **argv __unused) +{ + return 0; +} + + +int +execcmd(int argc, char **argv) +{ + int i; + + /* + * Because we have historically not supported any options, + * only treat "--" specially. + */ + if (argc > 1 && strcmp(argv[1], "--") == 0) + argc--, argv++; + if (argc > 1) { + iflag = 0; /* exit on error */ + mflag = 0; + optschanged(); + for (i = 0; i < cmdenviron->count; i++) + setvareq(cmdenviron->args[i], VEXPORT|VSTACK); + shellexec(argv + 1, environment(), pathval(), 0); + + } + return 0; +} + + +int +timescmd(int argc __unused, char **argv __unused) +{ + struct rusage ru; + long shumins, shsmins, chumins, chsmins; + double shusecs, shssecs, chusecs, chssecs; + + if (getrusage(RUSAGE_SELF, &ru) < 0) + return 1; + shumins = ru.ru_utime.tv_sec / 60; + shusecs = ru.ru_utime.tv_sec % 60 + ru.ru_utime.tv_usec / 1000000.; + shsmins = ru.ru_stime.tv_sec / 60; + shssecs = ru.ru_stime.tv_sec % 60 + ru.ru_stime.tv_usec / 1000000.; + if (getrusage(RUSAGE_CHILDREN, &ru) < 0) + return 1; + chumins = ru.ru_utime.tv_sec / 60; + chusecs = ru.ru_utime.tv_sec % 60 + ru.ru_utime.tv_usec / 1000000.; + chsmins = ru.ru_stime.tv_sec / 60; + chssecs = ru.ru_stime.tv_sec % 60 + ru.ru_stime.tv_usec / 1000000.; + out1fmt("%ldm%.3fs %ldm%.3fs\n%ldm%.3fs %ldm%.3fs\n", shumins, + shusecs, shsmins, shssecs, chumins, chusecs, chsmins, chssecs); + return 0; +} diff --git a/bin/catsh/eval.h b/bin/catsh/eval.h new file mode 100644 index 00000000..7bfa0216 --- /dev/null +++ b/bin/catsh/eval.h @@ -0,0 +1,70 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)eval.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/eval.h 314436 2017-02-28 23:42:47Z imp $ + */ + +extern char *commandname; /* currently executing command */ +extern int exitstatus; /* exit status of last command */ +extern int oexitstatus; /* saved exit status */ +extern struct arglist *cmdenviron; /* environment for builtin command */ + + +struct backcmd { /* result of evalbackcmd */ + int fd; /* file descriptor to read from */ + char *buf; /* buffer */ + int nleft; /* number of chars in buffer */ + struct job *jp; /* job structure for command */ +}; + +void reseteval(void); + +/* flags in argument to evaltree/evalstring */ +#define EV_EXIT 01 /* exit after evaluating tree */ +#define EV_TESTED 02 /* exit status is checked; ignore -e flag */ +#define EV_BACKCMD 04 /* command executing within back quotes */ + +void evalstring(const char *, int); +union node; /* BLETCH for ansi C */ +void evaltree(union node *, int); +void evalbackcmd(union node *, struct backcmd *); + +/* in_function returns nonzero if we are currently evaluating a function */ +#define in_function() funcnest +extern int funcnest; +extern int evalskip; +extern int skipcount; + +/* reasons for skipping commands (see comment on breakcmd routine) */ +#define SKIPBREAK 1 +#define SKIPCONT 2 +#define SKIPRETURN 3 diff --git a/bin/catsh/exec.c b/bin/catsh/exec.c new file mode 100644 index 00000000..5026e644 --- /dev/null +++ b/bin/catsh/exec.c @@ -0,0 +1,784 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)exec.c 8.4 (Berkeley) 6/8/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/exec.c 336320 2018-07-15 21:55:17Z jilles $"); + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <paths.h> +#include <stdlib.h> + +/* + * When commands are first encountered, they are entered in a hash table. + * This ensures that a full path search will not have to be done for them + * on each invocation. + * + * We should investigate converting to a linear search, even though that + * would make the command name "hash" a misnomer. + */ + +#include "shell.h" +#include "main.h" +#include "nodes.h" +#include "parser.h" +#include "redir.h" +#include "eval.h" +#include "exec.h" +#include "builtins.h" +#include "var.h" +#include "options.h" +#include "input.h" +#include "output.h" +#include "syntax.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "show.h" +#include "jobs.h" +#include "alias.h" + + +#define CMDTABLESIZE 31 /* should be prime */ + + + +struct tblentry { + struct tblentry *next; /* next entry in hash chain */ + union param param; /* definition of builtin function */ + int special; /* flag for special builtin commands */ + signed char cmdtype; /* index identifying command */ + char cmdname[]; /* name of command */ +}; + + +static struct tblentry *cmdtable[CMDTABLESIZE]; +static int cmdtable_cd = 0; /* cmdtable contains cd-dependent entries */ +int exerrno = 0; /* Last exec error */ + + +static void tryexec(char *, char **, char **); +static void printentry(struct tblentry *, int); +static struct tblentry *cmdlookup(const char *, int); +static void delete_cmd_entry(void); +static void addcmdentry(const char *, struct cmdentry *); + + + +/* + * Exec a program. Never returns. If you change this routine, you may + * have to change the find_command routine as well. + * + * The argv array may be changed and element argv[-1] should be writable. + */ + +void +shellexec(char **argv, char **envp, const char *path, int idx) +{ + char *cmdname; + const char *opt; + int e; + + if (strchr(argv[0], '/') != NULL) { + tryexec(argv[0], argv, envp); + e = errno; + } else { + e = ENOENT; + while ((cmdname = padvance(&path, &opt, argv[0])) != NULL) { + if (--idx < 0 && opt == NULL) { + tryexec(cmdname, argv, envp); + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + if (e == ENOEXEC) + break; + } + stunalloc(cmdname); + } + } + + /* Map to POSIX errors */ + if (e == ENOENT || e == ENOTDIR) { + exerrno = 127; + exerror(EXEXEC, "%s: not found", argv[0]); + } else { + exerrno = 126; + exerror(EXEXEC, "%s: %s", argv[0], strerror(e)); + } +} + + +static void +tryexec(char *cmd, char **argv, char **envp) +{ + int e, in; + ssize_t n; + char buf[256]; + + execve(cmd, argv, envp); + e = errno; + if (e == ENOEXEC) { + INTOFF; + in = open(cmd, O_RDONLY | O_NONBLOCK); + if (in != -1) { + n = pread(in, buf, sizeof buf, 0); + close(in); + if (n > 0 && memchr(buf, '\0', n) != NULL) { + errno = ENOEXEC; + return; + } + } + *argv = cmd; + *--argv = __DECONST(char *, _PATH_BSHELL); + execve(_PATH_BSHELL, argv, envp); + } + errno = e; +} + +/* + * Do a path search. The variable path (passed by reference) should be + * set to the start of the path before the first call; padvance will update + * this value as it proceeds. Successive calls to padvance will return + * the possible path expansions in sequence. If popt is not NULL, options + * are processed: if an option (indicated by a percent sign) appears in + * the path entry then *popt will be set to point to it; else *popt will be + * set to NULL. If popt is NULL, percent signs are not special. + */ + +char * +padvance(const char **path, const char **popt, const char *name) +{ + const char *p, *start; + char *q; + size_t len, namelen; + + if (*path == NULL) + return NULL; + start = *path; + if (popt != NULL) + for (p = start; *p && *p != ':' && *p != '%'; p++) + ; /* nothing */ + else + for (p = start; *p && *p != ':'; p++) + ; /* nothing */ + namelen = strlen(name); + len = p - start + namelen + 2; /* "2" is for '/' and '\0' */ + STARTSTACKSTR(q); + CHECKSTRSPACE(len, q); + if (p != start) { + memcpy(q, start, p - start); + q += p - start; + *q++ = '/'; + } + memcpy(q, name, namelen + 1); + if (popt != NULL) { + if (*p == '%') { + *popt = ++p; + while (*p && *p != ':') p++; + } else + *popt = NULL; + } + if (*p == ':') + *path = p + 1; + else + *path = NULL; + return stalloc(len); +} + + + +/*** Command hashing code ***/ + + +int +hashcmd(int argc __unused, char **argv __unused) +{ + struct tblentry **pp; + struct tblentry *cmdp; + int c; + int verbose; + struct cmdentry entry; + char *name; + int errors; + + errors = 0; + verbose = 0; + while ((c = nextopt("rv")) != '\0') { + if (c == 'r') { + clearcmdentry(); + } else if (c == 'v') { + verbose++; + } + } + if (*argptr == NULL) { + for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (cmdp->cmdtype == CMDNORMAL) + printentry(cmdp, verbose); + } + } + return 0; + } + while ((name = *argptr) != NULL) { + if ((cmdp = cmdlookup(name, 0)) != NULL + && cmdp->cmdtype == CMDNORMAL) + delete_cmd_entry(); + find_command(name, &entry, DO_ERR, pathval()); + if (entry.cmdtype == CMDUNKNOWN) + errors = 1; + else if (verbose) { + cmdp = cmdlookup(name, 0); + if (cmdp != NULL) + printentry(cmdp, verbose); + else { + outfmt(out2, "%s: not found\n", name); + errors = 1; + } + flushall(); + } + argptr++; + } + return errors; +} + + +static void +printentry(struct tblentry *cmdp, int verbose) +{ + int idx; + const char *path, *opt; + char *name; + + if (cmdp->cmdtype == CMDNORMAL) { + idx = cmdp->param.index; + path = pathval(); + do { + name = padvance(&path, &opt, cmdp->cmdname); + stunalloc(name); + } while (--idx >= 0); + out1str(name); + } else if (cmdp->cmdtype == CMDBUILTIN) { + out1fmt("builtin %s", cmdp->cmdname); + } else if (cmdp->cmdtype == CMDFUNCTION) { + out1fmt("function %s", cmdp->cmdname); + if (verbose) { + INTOFF; + name = commandtext(getfuncnode(cmdp->param.func)); + out1c(' '); + out1str(name); + ckfree(name); + INTON; + } +#ifdef DEBUG + } else { + error("internal error: cmdtype %d", cmdp->cmdtype); +#endif + } + out1c('\n'); +} + + + +/* + * Resolve a command name. If you change this routine, you may have to + * change the shellexec routine as well. + */ + +void +find_command(const char *name, struct cmdentry *entry, int act, + const char *path) +{ + struct tblentry *cmdp, loc_cmd; + int idx; + const char *opt; + char *fullname; + struct stat statb; + int e; + int i; + int spec; + int cd; + + /* If name contains a slash, don't use the hash table */ + if (strchr(name, '/') != NULL) { + entry->cmdtype = CMDNORMAL; + entry->u.index = 0; + entry->special = 0; + return; + } + + cd = 0; + + /* If name is in the table, we're done */ + if ((cmdp = cmdlookup(name, 0)) != NULL) { + if (cmdp->cmdtype == CMDFUNCTION && act & DO_NOFUNC) + cmdp = NULL; + else + goto success; + } + + /* Check for builtin next */ + if ((i = find_builtin(name, &spec)) >= 0) { + INTOFF; + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) + cmdp = &loc_cmd; + cmdp->cmdtype = CMDBUILTIN; + cmdp->param.index = i; + cmdp->special = spec; + INTON; + goto success; + } + + /* We have to search path. */ + + e = ENOENT; + idx = -1; + for (;(fullname = padvance(&path, &opt, name)) != NULL; + stunalloc(fullname)) { + idx++; + if (opt) { + if (strncmp(opt, "func", 4) == 0) { + /* handled below */ + } else { + continue; /* ignore unimplemented options */ + } + } + if (fullname[0] != '/') + cd = 1; + if (stat(fullname, &statb) < 0) { + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + continue; + } + e = EACCES; /* if we fail, this will be the error */ + if (!S_ISREG(statb.st_mode)) + continue; + if (opt) { /* this is a %func directory */ + readcmdfile(fullname); + if ((cmdp = cmdlookup(name, 0)) == NULL || cmdp->cmdtype != CMDFUNCTION) + error("%s not defined in %s", name, fullname); + stunalloc(fullname); + goto success; + } +#ifdef notdef + if (statb.st_uid == geteuid()) { + if ((statb.st_mode & 0100) == 0) + goto loop; + } else if (statb.st_gid == getegid()) { + if ((statb.st_mode & 010) == 0) + goto loop; + } else { + if ((statb.st_mode & 01) == 0) + goto loop; + } +#endif + TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname)); + INTOFF; + stunalloc(fullname); + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) + cmdp = &loc_cmd; + cmdp->cmdtype = CMDNORMAL; + cmdp->param.index = idx; + cmdp->special = 0; + INTON; + goto success; + } + + if (act & DO_ERR) { + if (e == ENOENT || e == ENOTDIR) + outfmt(out2, "%s: not found\n", name); + else + outfmt(out2, "%s: %s\n", name, strerror(e)); + } + entry->cmdtype = CMDUNKNOWN; + entry->u.index = 0; + entry->special = 0; + return; + +success: + if (cd) + cmdtable_cd = 1; + entry->cmdtype = cmdp->cmdtype; + entry->u = cmdp->param; + entry->special = cmdp->special; +} + + + +/* + * Search the table of builtin commands. + */ + +int +find_builtin(const char *name, int *special) +{ + const unsigned char *bp; + size_t len; + + len = strlen(name); + for (bp = builtincmd ; *bp ; bp += 2 + bp[0]) { + if (bp[0] == len && memcmp(bp + 2, name, len) == 0) { + *special = (bp[1] & BUILTIN_SPECIAL) != 0; + return bp[1] & ~BUILTIN_SPECIAL; + } + } + return -1; +} + + + +/* + * Called when a cd is done. If any entry in cmdtable depends on the current + * directory, simply clear cmdtable completely. + */ + +void +hashcd(void) +{ + if (cmdtable_cd) + clearcmdentry(); +} + + + +/* + * Called before PATH is changed. The argument is the new value of PATH; + * pathval() still returns the old value at this point. Called with + * interrupts off. + */ + +void +changepath(const char *newval __unused) +{ + clearcmdentry(); +} + + +/* + * Clear out cached utility locations. + */ + +void +clearcmdentry(void) +{ + struct tblentry **tblp; + struct tblentry **pp; + struct tblentry *cmdp; + + INTOFF; + for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) { + pp = tblp; + while ((cmdp = *pp) != NULL) { + if (cmdp->cmdtype == CMDNORMAL) { + *pp = cmdp->next; + ckfree(cmdp); + } else { + pp = &cmdp->next; + } + } + } + cmdtable_cd = 0; + INTON; +} + + +/* + * Locate a command in the command hash table. If "add" is nonzero, + * add the command to the table if it is not already present. The + * variable "lastcmdentry" is set to point to the address of the link + * pointing to the entry, so that delete_cmd_entry can delete the + * entry. + */ + +static struct tblentry **lastcmdentry; + + +static struct tblentry * +cmdlookup(const char *name, int add) +{ + unsigned int hashval; + const char *p; + struct tblentry *cmdp; + struct tblentry **pp; + size_t len; + + p = name; + hashval = (unsigned char)*p << 4; + while (*p) + hashval += *p++; + pp = &cmdtable[hashval % CMDTABLESIZE]; + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (equal(cmdp->cmdname, name)) + break; + pp = &cmdp->next; + } + if (add && cmdp == NULL) { + INTOFF; + len = strlen(name); + cmdp = *pp = ckmalloc(sizeof (struct tblentry) + len + 1); + cmdp->next = NULL; + cmdp->cmdtype = CMDUNKNOWN; + memcpy(cmdp->cmdname, name, len + 1); + INTON; + } + lastcmdentry = pp; + return cmdp; +} + +/* + * Delete the command entry returned on the last lookup. + */ + +static void +delete_cmd_entry(void) +{ + struct tblentry *cmdp; + + INTOFF; + cmdp = *lastcmdentry; + *lastcmdentry = cmdp->next; + ckfree(cmdp); + INTON; +} + + + +/* + * Add a new command entry, replacing any existing command entry for + * the same name. + */ + +static void +addcmdentry(const char *name, struct cmdentry *entry) +{ + struct tblentry *cmdp; + + INTOFF; + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) { + unreffunc(cmdp->param.func); + } + cmdp->cmdtype = entry->cmdtype; + cmdp->param = entry->u; + cmdp->special = entry->special; + INTON; +} + + +/* + * Define a shell function. + */ + +void +defun(const char *name, union node *func) +{ + struct cmdentry entry; + + INTOFF; + entry.cmdtype = CMDFUNCTION; + entry.u.func = copyfunc(func); + entry.special = 0; + addcmdentry(name, &entry); + INTON; +} + + +/* + * Delete a function if it exists. + * Called with interrupts off. + */ + +int +unsetfunc(const char *name) +{ + struct tblentry *cmdp; + + if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDFUNCTION) { + unreffunc(cmdp->param.func); + delete_cmd_entry(); + return (0); + } + return (0); +} + + +/* + * Check if a function by a certain name exists. + */ +int +isfunc(const char *name) +{ + struct tblentry *cmdp; + cmdp = cmdlookup(name, 0); + return (cmdp != NULL && cmdp->cmdtype == CMDFUNCTION); +} + + +/* + * Shared code for the following builtin commands: + * type, command -v, command -V + */ + +int +typecmd_impl(int argc, char **argv, int cmd, const char *path) +{ + struct cmdentry entry; + struct tblentry *cmdp; + const char *const *pp; + struct alias *ap; + int i; + int error1 = 0; + + if (path != pathval()) + clearcmdentry(); + + for (i = 1; i < argc; i++) { + /* First look at the keywords */ + for (pp = parsekwd; *pp; pp++) + if (**pp == *argv[i] && equal(*pp, argv[i])) + break; + + if (*pp) { + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else + out1fmt("%s is a shell keyword\n", argv[i]); + continue; + } + + /* Then look at the aliases */ + if ((ap = lookupalias(argv[i], 1)) != NULL) { + if (cmd == TYPECMD_SMALLV) { + out1fmt("alias %s=", argv[i]); + out1qstr(ap->val); + outcslow('\n', out1); + } else + out1fmt("%s is an alias for %s\n", argv[i], + ap->val); + continue; + } + + /* Then check if it is a tracked alias */ + if ((cmdp = cmdlookup(argv[i], 0)) != NULL) { + entry.cmdtype = cmdp->cmdtype; + entry.u = cmdp->param; + entry.special = cmdp->special; + } + else { + /* Finally use brute force */ + find_command(argv[i], &entry, 0, path); + } + + switch (entry.cmdtype) { + case CMDNORMAL: { + if (strchr(argv[i], '/') == NULL) { + const char *path2 = path; + const char *opt2; + char *name; + int j = entry.u.index; + do { + name = padvance(&path2, &opt2, argv[i]); + stunalloc(name); + } while (--j >= 0); + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", name); + else + out1fmt("%s is%s %s\n", argv[i], + (cmdp && cmd == TYPECMD_TYPE) ? + " a tracked alias for" : "", + name); + } else { + if (faccessat(AT_FDCWD, argv[i], X_OK, AT_EACCESS) == 0) { + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else + out1fmt("%s is %s\n", argv[i], + argv[i]); + } else { + if (cmd != TYPECMD_SMALLV) + outfmt(out2, "%s: %s\n", + argv[i], strerror(errno)); + error1 |= 127; + } + } + break; + } + case CMDFUNCTION: + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else + out1fmt("%s is a shell function\n", argv[i]); + break; + + case CMDBUILTIN: + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else if (entry.special) + out1fmt("%s is a special shell builtin\n", + argv[i]); + else + out1fmt("%s is a shell builtin\n", argv[i]); + break; + + default: + if (cmd != TYPECMD_SMALLV) + outfmt(out2, "%s: not found\n", argv[i]); + error1 |= 127; + break; + } + } + + if (path != pathval()) + clearcmdentry(); + + return error1; +} + +/* + * Locate and print what a word is... + */ + +int +typecmd(int argc, char **argv) +{ + if (argc > 2 && strcmp(argv[1], "--") == 0) + argc--, argv++; + return typecmd_impl(argc, argv, TYPECMD_TYPE, bltinlookup("PATH", 1)); +} diff --git a/bin/catsh/exec.h b/bin/catsh/exec.h new file mode 100644 index 00000000..3b3a5b77 --- /dev/null +++ b/bin/catsh/exec.h @@ -0,0 +1,76 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)exec.h 8.3 (Berkeley) 6/8/95 + * $FreeBSD: releng/12.0/bin/sh/exec.h 336320 2018-07-15 21:55:17Z jilles $ + */ + +/* values of cmdtype */ +#define CMDUNKNOWN -1 /* no entry in table for command */ +#define CMDNORMAL 0 /* command is an executable program */ +#define CMDBUILTIN 1 /* command is a shell builtin */ +#define CMDFUNCTION 2 /* command is a shell function */ + +/* values for typecmd_impl's third parameter */ +enum { + TYPECMD_SMALLV, /* command -v */ + TYPECMD_BIGV, /* command -V */ + TYPECMD_TYPE /* type */ +}; + +union node; +struct cmdentry { + int cmdtype; + union param { + int index; + struct funcdef *func; + } u; + int special; +}; + + +/* action to find_command() */ +#define DO_ERR 0x01 /* prints errors */ +#define DO_NOFUNC 0x02 /* don't return shell functions, for command */ + +extern int exerrno; /* last exec error */ + +void shellexec(char **, char **, const char *, int) __dead2; +char *padvance(const char **, const char **, const char *); +void find_command(const char *, struct cmdentry *, int, const char *); +int find_builtin(const char *, int *); +void hashcd(void); +void changepath(const char *); +void defun(const char *, union node *); +int unsetfunc(const char *); +int isfunc(const char *); +int typecmd_impl(int, char **, int, const char *); +void clearcmdentry(void); diff --git a/bin/catsh/expand.c b/bin/catsh/expand.c new file mode 100644 index 00000000..7c419b80 --- /dev/null +++ b/bin/catsh/expand.c @@ -0,0 +1,1552 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1997-2005 + * Herbert Xu <herbert@gondor.apana.org.au>. All rights reserved. + * Copyright (c) 2010-2015 + * Jilles Tjoelker <jilles@stack.nl>. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)expand.c 8.5 (Berkeley) 5/15/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/expand.c 338473 2018-09-05 19:16:09Z jilles $"); + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <dirent.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wchar.h> +#include <wctype.h> + +/* + * Routines to expand arguments to commands. We have to deal with + * backquotes, shell variables, and file metacharacters. + */ + +#include "shell.h" +#include "main.h" +#include "nodes.h" +#include "eval.h" +#include "expand.h" +#include "syntax.h" +#include "parser.h" +#include "jobs.h" +#include "options.h" +#include "var.h" +#include "input.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "arith.h" +#include "show.h" +#include "builtins.h" + +enum wordstate { WORD_IDLE, WORD_WS_DELIMITED, WORD_QUOTEMARK }; + +struct worddest { + struct arglist *list; + enum wordstate state; +}; + +static char *expdest; /* output of current string */ + +static const char *argstr(const char *, struct nodelist **restrict, int, + struct worddest *); +static const char *exptilde(const char *, int); +static const char *expari(const char *, struct nodelist **restrict, int, + struct worddest *); +static void expbackq(union node *, int, int, struct worddest *); +static const char *subevalvar_trim(const char *, struct nodelist **restrict, + int, int, int); +static const char *subevalvar_misc(const char *, struct nodelist **restrict, + const char *, int, int, int); +static const char *evalvar(const char *, struct nodelist **restrict, int, + struct worddest *); +static int varisset(const char *, int); +static void strtodest(const char *, int, int, int, struct worddest *); +static void reprocess(int, int, int, int, struct worddest *); +static void varvalue(const char *, int, int, int, struct worddest *); +static void expandmeta(char *, struct arglist *); +static void expmeta(char *, char *, struct arglist *); +static int expsortcmp(const void *, const void *); +static int patmatch(const char *, const char *); +static void cvtnum(int, char *); +static int collate_range_cmp(wchar_t, wchar_t); + +void +emptyarglist(struct arglist *list) +{ + + list->args = list->smallarg; + list->count = 0; + list->capacity = sizeof(list->smallarg) / sizeof(list->smallarg[0]); +} + +void +appendarglist(struct arglist *list, char *str) +{ + char **newargs; + int newcapacity; + + if (list->count >= list->capacity) { + newcapacity = list->capacity * 2; + if (newcapacity < 16) + newcapacity = 16; + if (newcapacity > INT_MAX / (int)sizeof(newargs[0])) + error("Too many entries in arglist"); + newargs = stalloc(newcapacity * sizeof(newargs[0])); + memcpy(newargs, list->args, list->count * sizeof(newargs[0])); + list->args = newargs; + list->capacity = newcapacity; + } + list->args[list->count++] = str; +} + +static int +collate_range_cmp(wchar_t c1, wchar_t c2) +{ + wchar_t s1[2], s2[2]; + + s1[0] = c1; + s1[1] = L'\0'; + s2[0] = c2; + s2[1] = L'\0'; + return (wcscoll(s1, s2)); +} + +static char * +stputs_quotes(const char *data, const char *syntax, char *p) +{ + while (*data) { + CHECKSTRSPACE(2, p); + if (syntax[(int)*data] == CCTL) + USTPUTC(CTLESC, p); + USTPUTC(*data++, p); + } + return (p); +} +#define STPUTS_QUOTES(data, syntax, p) p = stputs_quotes((data), syntax, p) + +static char * +nextword(char c, int flag, char *p, struct worddest *dst) +{ + int is_ws; + + is_ws = c == '\t' || c == '\n' || c == ' '; + if (p != stackblock() || (is_ws ? dst->state == WORD_QUOTEMARK : + dst->state != WORD_WS_DELIMITED) || c == '\0') { + STPUTC('\0', p); + if (flag & EXP_GLOB) + expandmeta(grabstackstr(p), dst->list); + else + appendarglist(dst->list, grabstackstr(p)); + dst->state = is_ws ? WORD_WS_DELIMITED : WORD_IDLE; + } else if (!is_ws && dst->state == WORD_WS_DELIMITED) + dst->state = WORD_IDLE; + /* Reserve space while the stack string is empty. */ + appendarglist(dst->list, NULL); + dst->list->count--; + STARTSTACKSTR(p); + return p; +} +#define NEXTWORD(c, flag, p, dstlist) p = nextword(c, flag, p, dstlist) + +static char * +stputs_split(const char *data, const char *syntax, int flag, char *p, + struct worddest *dst) +{ + const char *ifs; + char c; + + ifs = ifsset() ? ifsval() : " \t\n"; + while (*data) { + CHECKSTRSPACE(2, p); + c = *data++; + if (strchr(ifs, c) != NULL) { + NEXTWORD(c, flag, p, dst); + continue; + } + if (flag & EXP_GLOB && syntax[(int)c] == CCTL) + USTPUTC(CTLESC, p); + USTPUTC(c, p); + } + return (p); +} +#define STPUTS_SPLIT(data, syntax, flag, p, dst) p = stputs_split((data), syntax, flag, p, dst) + +/* + * Perform expansions on an argument, placing the resulting list of arguments + * in arglist. Parameter expansion, command substitution and arithmetic + * expansion are always performed; additional expansions can be requested + * via flag (EXP_*). + * The result is left in the stack string. + * When arglist is NULL, perform here document expansion. + * + * When doing something that may cause this to be re-entered, make sure + * the stack string is empty via grabstackstr() and do not assume expdest + * remains valid. + */ +void +expandarg(union node *arg, struct arglist *arglist, int flag) +{ + struct worddest exparg; + struct nodelist *argbackq; + + if (fflag) + flag &= ~EXP_GLOB; + argbackq = arg->narg.backquote; + exparg.list = arglist; + exparg.state = WORD_IDLE; + STARTSTACKSTR(expdest); + argstr(arg->narg.text, &argbackq, flag, &exparg); + if (arglist == NULL) { + STACKSTRNUL(expdest); + return; /* here document expanded */ + } + if ((flag & EXP_SPLIT) == 0 || expdest != stackblock() || + exparg.state == WORD_QUOTEMARK) { + STPUTC('\0', expdest); + if (flag & EXP_SPLIT) { + if (flag & EXP_GLOB) + expandmeta(grabstackstr(expdest), exparg.list); + else + appendarglist(exparg.list, grabstackstr(expdest)); + } + } + if ((flag & EXP_SPLIT) == 0) + appendarglist(arglist, grabstackstr(expdest)); +} + + + +/* + * Perform parameter expansion, command substitution and arithmetic + * expansion, and tilde expansion if requested via EXP_TILDE/EXP_VARTILDE. + * Processing ends at a CTLENDVAR or CTLENDARI character as well as '\0'. + * This is used to expand word in ${var+word} etc. + * If EXP_GLOB or EXP_CASE are set, keep and/or generate CTLESC + * characters to allow for further processing. + * + * If EXP_SPLIT is set, dst receives any complete words produced. + */ +static const char * +argstr(const char *p, struct nodelist **restrict argbackq, int flag, + struct worddest *dst) +{ + char c; + int quotes = flag & (EXP_GLOB | EXP_CASE); /* do CTLESC */ + int firsteq = 1; + int split_lit; + int lit_quoted; + + split_lit = flag & EXP_SPLIT_LIT; + lit_quoted = flag & EXP_LIT_QUOTED; + flag &= ~(EXP_SPLIT_LIT | EXP_LIT_QUOTED); + if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE))) + p = exptilde(p, flag); + for (;;) { + CHECKSTRSPACE(2, expdest); + switch (c = *p++) { + case '\0': + return (p - 1); + case CTLENDVAR: + case CTLENDARI: + return (p); + case CTLQUOTEMARK: + lit_quoted = 1; + /* "$@" syntax adherence hack */ + if (p[0] == CTLVAR && (p[1] & VSQUOTE) != 0 && + p[2] == '@' && p[3] == '=') + break; + if ((flag & EXP_SPLIT) != 0 && expdest == stackblock()) + dst->state = WORD_QUOTEMARK; + break; + case CTLQUOTEEND: + lit_quoted = 0; + break; + case CTLESC: + c = *p++; + if (split_lit && !lit_quoted && + strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) { + NEXTWORD(c, flag, expdest, dst); + break; + } + if (quotes) + USTPUTC(CTLESC, expdest); + USTPUTC(c, expdest); + break; + case CTLVAR: + p = evalvar(p, argbackq, flag, dst); + break; + case CTLBACKQ: + case CTLBACKQ|CTLQUOTE: + expbackq((*argbackq)->n, c & CTLQUOTE, flag, dst); + *argbackq = (*argbackq)->next; + break; + case CTLARI: + p = expari(p, argbackq, flag, dst); + break; + case ':': + case '=': + /* + * sort of a hack - expand tildes in variable + * assignments (after the first '=' and after ':'s). + */ + if (split_lit && !lit_quoted && + strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) { + NEXTWORD(c, flag, expdest, dst); + break; + } + USTPUTC(c, expdest); + if (flag & EXP_VARTILDE && *p == '~' && + (c != '=' || firsteq)) { + if (c == '=') + firsteq = 0; + p = exptilde(p, flag); + } + break; + default: + if (split_lit && !lit_quoted && + strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) { + NEXTWORD(c, flag, expdest, dst); + break; + } + USTPUTC(c, expdest); + } + } +} + +/* + * Perform tilde expansion, placing the result in the stack string and + * returning the next position in the input string to process. + */ +static const char * +exptilde(const char *p, int flag) +{ + char c; + const char *startp = p; + const char *user; + struct passwd *pw; + char *home; + int len; + + for (;;) { + c = *p; + switch(c) { + case CTLESC: /* This means CTL* are always considered quoted. */ + case CTLVAR: + case CTLBACKQ: + case CTLBACKQ | CTLQUOTE: + case CTLARI: + case CTLENDARI: + case CTLQUOTEMARK: + return (startp); + case ':': + if ((flag & EXP_VARTILDE) == 0) + break; + /* FALLTHROUGH */ + case '\0': + case '/': + case CTLENDVAR: + len = p - startp - 1; + STPUTBIN(startp + 1, len, expdest); + STACKSTRNUL(expdest); + user = expdest - len; + if (*user == '\0') { + home = lookupvar("HOME"); + } else { + pw = getpwnam(user); + home = pw != NULL ? pw->pw_dir : NULL; + } + STADJUST(-len, expdest); + if (home == NULL || *home == '\0') + return (startp); + strtodest(home, flag, VSNORMAL, 1, NULL); + return (p); + } + p++; + } +} + + +/* + * Expand arithmetic expression. + */ +static const char * +expari(const char *p, struct nodelist **restrict argbackq, int flag, + struct worddest *dst) +{ + char *q, *start; + arith_t result; + int begoff; + int quoted; + int adj; + + quoted = *p++ == '"'; + begoff = expdest - stackblock(); + p = argstr(p, argbackq, 0, NULL); + STPUTC('\0', expdest); + start = stackblock() + begoff; + + q = grabstackstr(expdest); + result = arith(start); + ungrabstackstr(q, expdest); + + start = stackblock() + begoff; + adj = start - expdest; + STADJUST(adj, expdest); + + CHECKSTRSPACE((int)(DIGITS(result) + 1), expdest); + fmtstr(expdest, DIGITS(result), ARITH_FORMAT_STR, result); + adj = strlen(expdest); + STADJUST(adj, expdest); + /* + * If this is quoted, a '-' must not indicate a range in [...]. + * If this is not quoted, splitting may occur. + */ + if (quoted ? + result < 0 && begoff > 1 && flag & (EXP_GLOB | EXP_CASE) : + flag & EXP_SPLIT) + reprocess(expdest - adj - stackblock(), flag, VSNORMAL, quoted, + dst); + return p; +} + + +/* + * Perform command substitution. + */ +static void +expbackq(union node *cmd, int quoted, int flag, struct worddest *dst) +{ + struct backcmd in; + int i; + char buf[128]; + char *p; + char *dest = expdest; + char lastc; + char const *syntax = quoted? DQSYNTAX : BASESYNTAX; + int quotes = flag & (EXP_GLOB | EXP_CASE); + size_t nnl; + const char *ifs; + int startloc; + + INTOFF; + p = grabstackstr(dest); + evalbackcmd(cmd, &in); + ungrabstackstr(p, dest); + + p = in.buf; + startloc = dest - stackblock(); + nnl = 0; + if (!quoted && flag & EXP_SPLIT) + ifs = ifsset() ? ifsval() : " \t\n"; + else + ifs = ""; + /* Remove trailing newlines */ + for (;;) { + if (--in.nleft < 0) { + if (in.fd < 0) + break; + while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR) + ; + TRACE(("expbackq: read returns %d\n", i)); + if (i <= 0) + break; + p = buf; + in.nleft = i - 1; + } + lastc = *p++; + if (lastc == '\0') + continue; + if (nnl > 0 && lastc != '\n') { + NEXTWORD('\n', flag, dest, dst); + nnl = 0; + } + if (strchr(ifs, lastc) != NULL) { + if (lastc == '\n') + nnl++; + else + NEXTWORD(lastc, flag, dest, dst); + } else { + CHECKSTRSPACE(2, dest); + if (quotes && syntax[(int)lastc] == CCTL) + USTPUTC(CTLESC, dest); + USTPUTC(lastc, dest); + } + } + while (dest > stackblock() + startloc && STTOPC(dest) == '\n') + STUNPUTC(dest); + + if (in.fd >= 0) + close(in.fd); + if (in.buf) + ckfree(in.buf); + if (in.jp) { + p = grabstackstr(dest); + exitstatus = waitforjob(in.jp, (int *)NULL); + ungrabstackstr(p, dest); + } + TRACE(("expbackq: done\n")); + expdest = dest; + INTON; +} + + + +static void +recordleft(const char *str, const char *loc, char *startp) +{ + int amount; + + amount = ((str - 1) - (loc - startp)) - expdest; + STADJUST(amount, expdest); + while (loc != str - 1) + *startp++ = *loc++; +} + +static const char * +subevalvar_trim(const char *p, struct nodelist **restrict argbackq, int strloc, + int subtype, int startloc) +{ + char *startp; + char *loc = NULL; + char *str; + int c = 0; + int amount; + + p = argstr(p, argbackq, EXP_CASE | EXP_TILDE, NULL); + STACKSTRNUL(expdest); + startp = stackblock() + startloc; + str = stackblock() + strloc; + + switch (subtype) { + case VSTRIMLEFT: + for (loc = startp; loc < str; loc++) { + c = *loc; + *loc = '\0'; + if (patmatch(str, startp)) { + *loc = c; + recordleft(str, loc, startp); + return p; + } + *loc = c; + } + break; + + case VSTRIMLEFTMAX: + for (loc = str - 1; loc >= startp;) { + c = *loc; + *loc = '\0'; + if (patmatch(str, startp)) { + *loc = c; + recordleft(str, loc, startp); + return p; + } + *loc = c; + loc--; + } + break; + + case VSTRIMRIGHT: + for (loc = str - 1; loc >= startp;) { + if (patmatch(str, loc)) { + amount = loc - expdest; + STADJUST(amount, expdest); + return p; + } + loc--; + } + break; + + case VSTRIMRIGHTMAX: + for (loc = startp; loc < str - 1; loc++) { + if (patmatch(str, loc)) { + amount = loc - expdest; + STADJUST(amount, expdest); + return p; + } + } + break; + + + default: + abort(); + } + amount = (expdest - stackblock() - strloc) + 1; + STADJUST(-amount, expdest); + return p; +} + + +static const char * +subevalvar_misc(const char *p, struct nodelist **restrict argbackq, + const char *var, int subtype, int startloc, int varflags) +{ + char *startp; + int amount; + + p = argstr(p, argbackq, EXP_TILDE, NULL); + STACKSTRNUL(expdest); + startp = stackblock() + startloc; + + switch (subtype) { + case VSASSIGN: + setvar(var, startp, 0); + amount = startp - expdest; + STADJUST(amount, expdest); + return p; + + case VSQUESTION: + if (*p != CTLENDVAR) { + outfmt(out2, "%s\n", startp); + error((char *)NULL); + } + error("%.*s: parameter %snot set", (int)(p - var - 1), + var, (varflags & VSNUL) ? "null or " : ""); + + default: + abort(); + } +} + + +/* + * Expand a variable, and return a pointer to the next character in the + * input string. + */ + +static const char * +evalvar(const char *p, struct nodelist **restrict argbackq, int flag, + struct worddest *dst) +{ + int subtype; + int varflags; + const char *var; + const char *val; + int patloc; + int c; + int set; + int special; + int startloc; + int varlen; + int varlenb; + char buf[21]; + + varflags = (unsigned char)*p++; + subtype = varflags & VSTYPE; + var = p; + special = 0; + if (! is_name(*p)) + special = 1; + p = strchr(p, '=') + 1; + if (varflags & VSLINENO) { + set = 1; + special = 1; + val = NULL; + } else if (special) { + set = varisset(var, varflags & VSNUL); + val = NULL; + } else { + val = bltinlookup(var, 1); + if (val == NULL || ((varflags & VSNUL) && val[0] == '\0')) { + val = NULL; + set = 0; + } else + set = 1; + } + varlen = 0; + startloc = expdest - stackblock(); + if (!set && uflag && *var != '@' && *var != '*') { + switch (subtype) { + case VSNORMAL: + case VSTRIMLEFT: + case VSTRIMLEFTMAX: + case VSTRIMRIGHT: + case VSTRIMRIGHTMAX: + case VSLENGTH: + error("%.*s: parameter not set", (int)(p - var - 1), + var); + } + } + if (set && subtype != VSPLUS) { + /* insert the value of the variable */ + if (special) { + if (varflags & VSLINENO) { + if (p - var > (ptrdiff_t)sizeof(buf)) + abort(); + memcpy(buf, var, p - var - 1); + buf[p - var - 1] = '\0'; + strtodest(buf, flag, subtype, + varflags & VSQUOTE, dst); + } else + varvalue(var, varflags & VSQUOTE, subtype, flag, + dst); + if (subtype == VSLENGTH) { + varlenb = expdest - stackblock() - startloc; + varlen = varlenb; + if (localeisutf8) { + val = stackblock() + startloc; + for (;val != expdest; val++) + if ((*val & 0xC0) == 0x80) + varlen--; + } + STADJUST(-varlenb, expdest); + } + } else { + if (subtype == VSLENGTH) { + for (;*val; val++) + if (!localeisutf8 || + (*val & 0xC0) != 0x80) + varlen++; + } + else + strtodest(val, flag, subtype, + varflags & VSQUOTE, dst); + } + } + + if (subtype == VSPLUS) + set = ! set; + + switch (subtype) { + case VSLENGTH: + cvtnum(varlen, buf); + strtodest(buf, flag, VSNORMAL, varflags & VSQUOTE, dst); + break; + + case VSNORMAL: + return p; + + case VSPLUS: + case VSMINUS: + if (!set) { + return argstr(p, argbackq, + flag | (flag & EXP_SPLIT ? EXP_SPLIT_LIT : 0) | + (varflags & VSQUOTE ? EXP_LIT_QUOTED : 0), dst); + } + break; + + case VSTRIMLEFT: + case VSTRIMLEFTMAX: + case VSTRIMRIGHT: + case VSTRIMRIGHTMAX: + if (!set) + break; + /* + * Terminate the string and start recording the pattern + * right after it + */ + STPUTC('\0', expdest); + patloc = expdest - stackblock(); + p = subevalvar_trim(p, argbackq, patloc, subtype, startloc); + reprocess(startloc, flag, VSNORMAL, varflags & VSQUOTE, dst); + if (flag & EXP_SPLIT && *var == '@' && varflags & VSQUOTE) + dst->state = WORD_QUOTEMARK; + return p; + + case VSASSIGN: + case VSQUESTION: + if (!set) { + p = subevalvar_misc(p, argbackq, var, subtype, + startloc, varflags); + /* assert(subtype == VSASSIGN); */ + val = lookupvar(var); + strtodest(val, flag, subtype, varflags & VSQUOTE, dst); + return p; + } + break; + + case VSERROR: + c = p - var - 1; + error("${%.*s%s}: Bad substitution", c, var, + (c > 0 && *p != CTLENDVAR) ? "..." : ""); + + default: + abort(); + } + + { /* skip to end of alternative */ + int nesting = 1; + for (;;) { + if ((c = *p++) == CTLESC) + p++; + else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) + *argbackq = (*argbackq)->next; + else if (c == CTLVAR) { + if ((*p++ & VSTYPE) != VSNORMAL) + nesting++; + } else if (c == CTLENDVAR) { + if (--nesting == 0) + break; + } + } + } + return p; +} + + + +/* + * Test whether a special or positional parameter is set. + */ + +static int +varisset(const char *name, int nulok) +{ + + if (*name == '!') + return backgndpidset(); + else if (*name == '@' || *name == '*') { + if (*shellparam.p == NULL) + return 0; + + if (nulok) { + char **av; + + for (av = shellparam.p; *av; av++) + if (**av != '\0') + return 1; + return 0; + } + } else if (is_digit(*name)) { + char *ap; + long num; + + errno = 0; + num = strtol(name, NULL, 10); + if (errno != 0 || num > shellparam.nparam) + return 0; + + if (num == 0) + ap = arg0; + else + ap = shellparam.p[num - 1]; + + if (nulok && (ap == NULL || *ap == '\0')) + return 0; + } + return 1; +} + +static void +strtodest(const char *p, int flag, int subtype, int quoted, + struct worddest *dst) +{ + if (subtype == VSLENGTH || subtype == VSTRIMLEFT || + subtype == VSTRIMLEFTMAX || subtype == VSTRIMRIGHT || + subtype == VSTRIMRIGHTMAX) + STPUTS(p, expdest); + else if (flag & EXP_SPLIT && !quoted && dst != NULL) + STPUTS_SPLIT(p, BASESYNTAX, flag, expdest, dst); + else if (flag & (EXP_GLOB | EXP_CASE)) + STPUTS_QUOTES(p, quoted ? DQSYNTAX : BASESYNTAX, expdest); + else + STPUTS(p, expdest); +} + +static void +reprocess(int startloc, int flag, int subtype, int quoted, + struct worddest *dst) +{ + static char *buf = NULL; + static size_t buflen = 0; + char *startp; + size_t len, zpos, zlen; + + startp = stackblock() + startloc; + len = expdest - startp; + if (len >= SIZE_MAX / 2 || len > PTRDIFF_MAX) + abort(); + INTOFF; + if (len >= buflen) { + ckfree(buf); + buf = NULL; + } + if (buflen < 128) + buflen = 128; + while (len >= buflen) + buflen <<= 1; + if (buf == NULL) + buf = ckmalloc(buflen); + INTON; + memcpy(buf, startp, len); + buf[len] = '\0'; + STADJUST(-(ptrdiff_t)len, expdest); + for (zpos = 0;;) { + zlen = strlen(buf + zpos); + strtodest(buf + zpos, flag, subtype, quoted, dst); + zpos += zlen + 1; + if (zpos == len + 1) + break; + if (flag & EXP_SPLIT && (quoted || (zlen > 0 && zpos < len))) + NEXTWORD('\0', flag, expdest, dst); + } +} + +/* + * Add the value of a special or positional parameter to the stack string. + */ + +static void +varvalue(const char *name, int quoted, int subtype, int flag, + struct worddest *dst) +{ + int num; + char *p; + int i; + int splitlater; + char sep[2]; + char **ap; + char buf[(NSHORTOPTS > 10 ? NSHORTOPTS : 10) + 1]; + + if (subtype == VSLENGTH) + flag &= ~EXP_FULL; + splitlater = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX || + subtype == VSTRIMRIGHT || subtype == VSTRIMRIGHTMAX; + + switch (*name) { + case '$': + num = rootpid; + break; + case '?': + num = oexitstatus; + break; + case '#': + num = shellparam.nparam; + break; + case '!': + num = backgndpidval(); + break; + case '-': + p = buf; + for (i = 0 ; i < NSHORTOPTS ; i++) { + if (optval[i]) + *p++ = optletter[i]; + } + *p = '\0'; + strtodest(buf, flag, subtype, quoted, dst); + return; + case '@': + if (flag & EXP_SPLIT && quoted) { + for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { + strtodest(p, flag, subtype, quoted, dst); + if (*ap) { + if (splitlater) + STPUTC('\0', expdest); + else + NEXTWORD('\0', flag, expdest, + dst); + } + } + if (shellparam.nparam > 0) + dst->state = WORD_QUOTEMARK; + return; + } + /* FALLTHROUGH */ + case '*': + if (ifsset()) + sep[0] = ifsval()[0]; + else + sep[0] = ' '; + sep[1] = '\0'; + for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { + strtodest(p, flag, subtype, quoted, dst); + if (!*ap) + break; + if (sep[0]) + strtodest(sep, flag, subtype, quoted, dst); + else if (flag & EXP_SPLIT && !quoted && **ap != '\0') { + if (splitlater) + STPUTC('\0', expdest); + else + NEXTWORD('\0', flag, expdest, dst); + } + } + return; + default: + if (is_digit(*name)) { + num = atoi(name); + if (num == 0) + p = arg0; + else if (num > 0 && num <= shellparam.nparam) + p = shellparam.p[num - 1]; + else + return; + strtodest(p, flag, subtype, quoted, dst); + } + return; + } + cvtnum(num, buf); + strtodest(buf, flag, subtype, quoted, dst); +} + + + +static char expdir[PATH_MAX]; +#define expdir_end (expdir + sizeof(expdir)) + +/* + * Perform pathname generation and remove control characters. + * At this point, the only control characters should be CTLESC. + * The results are stored in the list dstlist. + */ +static void +expandmeta(char *pattern, struct arglist *dstlist) +{ + char *p; + int firstmatch; + char c; + + firstmatch = dstlist->count; + p = pattern; + for (; (c = *p) != '\0'; p++) { + /* fast check for meta chars */ + if (c == '*' || c == '?' || c == '[') { + INTOFF; + expmeta(expdir, pattern, dstlist); + INTON; + break; + } + } + if (dstlist->count == firstmatch) { + /* + * no matches + */ + rmescapes(pattern); + appendarglist(dstlist, pattern); + } else { + qsort(&dstlist->args[firstmatch], + dstlist->count - firstmatch, + sizeof(dstlist->args[0]), expsortcmp); + } +} + + +/* + * Do metacharacter (i.e. *, ?, [...]) expansion. + */ + +static void +expmeta(char *enddir, char *name, struct arglist *arglist) +{ + const char *p; + const char *q; + const char *start; + char *endname; + int metaflag; + struct stat statb; + DIR *dirp; + struct dirent *dp; + int atend; + int matchdot; + int esc; + int namlen; + + metaflag = 0; + start = name; + for (p = name; esc = 0, *p; p += esc + 1) { + if (*p == '*' || *p == '?') + metaflag = 1; + else if (*p == '[') { + q = p + 1; + if (*q == '!' || *q == '^') + q++; + for (;;) { + if (*q == CTLESC) + q++; + if (*q == '/' || *q == '\0') + break; + if (*++q == ']') { + metaflag = 1; + break; + } + } + } else if (*p == '\0') + break; + else { + if (*p == CTLESC) + esc++; + if (p[esc] == '/') { + if (metaflag) + break; + start = p + esc + 1; + } + } + } + if (metaflag == 0) { /* we've reached the end of the file name */ + if (enddir != expdir) + metaflag++; + for (p = name ; ; p++) { + if (*p == CTLESC) + p++; + *enddir++ = *p; + if (*p == '\0') + break; + if (enddir == expdir_end) + return; + } + if (metaflag == 0 || lstat(expdir, &statb) >= 0) + appendarglist(arglist, stsavestr(expdir)); + return; + } + endname = name + (p - name); + if (start != name) { + p = name; + while (p < start) { + if (*p == CTLESC) + p++; + *enddir++ = *p++; + if (enddir == expdir_end) + return; + } + } + if (enddir == expdir) { + p = "."; + } else if (enddir == expdir + 1 && *expdir == '/') { + p = "/"; + } else { + p = expdir; + enddir[-1] = '\0'; + } + if ((dirp = opendir(p)) == NULL) + return; + if (enddir != expdir) + enddir[-1] = '/'; + if (*endname == 0) { + atend = 1; + } else { + atend = 0; + *endname = '\0'; + endname += esc + 1; + } + matchdot = 0; + p = start; + if (*p == CTLESC) + p++; + if (*p == '.') + matchdot++; + while (! int_pending() && (dp = readdir(dirp)) != NULL) { + if (dp->d_name[0] == '.' && ! matchdot) + continue; + if (patmatch(start, dp->d_name)) { + namlen = dp->d_namlen; + if (enddir + namlen + 1 > expdir_end) + continue; + memcpy(enddir, dp->d_name, namlen + 1); + if (atend) + appendarglist(arglist, stsavestr(expdir)); + else { + if (dp->d_type != DT_UNKNOWN && + dp->d_type != DT_DIR && + dp->d_type != DT_LNK) + continue; + if (enddir + namlen + 2 > expdir_end) + continue; + enddir[namlen] = '/'; + enddir[namlen + 1] = '\0'; + expmeta(enddir + namlen + 1, endname, arglist); + } + } + } + closedir(dirp); + if (! atend) + endname[-esc - 1] = esc ? CTLESC : '/'; +} + + +static int +expsortcmp(const void *p1, const void *p2) +{ + const char *s1 = *(const char * const *)p1; + const char *s2 = *(const char * const *)p2; + + return (strcoll(s1, s2)); +} + + + +static wchar_t +get_wc(const char **p) +{ + wchar_t c; + int chrlen; + + chrlen = mbtowc(&c, *p, 4); + if (chrlen == 0) + return 0; + else if (chrlen == -1) + c = 0; + else + *p += chrlen; + return c; +} + + +/* + * See if a character matches a character class, starting at the first colon + * of "[:class:]". + * If a valid character class is recognized, a pointer to the next character + * after the final closing bracket is stored into *end, otherwise a null + * pointer is stored into *end. + */ +static int +match_charclass(const char *p, wchar_t chr, const char **end) +{ + char name[20]; + const char *nameend; + wctype_t cclass; + + *end = NULL; + p++; + nameend = strstr(p, ":]"); + if (nameend == NULL || (size_t)(nameend - p) >= sizeof(name) || + nameend == p) + return 0; + memcpy(name, p, nameend - p); + name[nameend - p] = '\0'; + *end = nameend + 2; + cclass = wctype(name); + /* An unknown class matches nothing but is valid nevertheless. */ + if (cclass == 0) + return 0; + return iswctype(chr, cclass); +} + + +/* + * Returns true if the pattern matches the string. + */ + +static int +patmatch(const char *pattern, const char *string) +{ + const char *p, *q, *end; + const char *bt_p, *bt_q; + char c; + wchar_t wc, wc2; + + p = pattern; + q = string; + bt_p = NULL; + bt_q = NULL; + for (;;) { + switch (c = *p++) { + case '\0': + if (*q != '\0') + goto backtrack; + return 1; + case CTLESC: + if (*q++ != *p++) + goto backtrack; + break; + case '?': + if (*q == '\0') + return 0; + if (localeisutf8) { + wc = get_wc(&q); + /* + * A '?' does not match invalid UTF-8 but a + * '*' does, so backtrack. + */ + if (wc == 0) + goto backtrack; + } else + q++; + break; + case '*': + c = *p; + while (c == '*') + c = *++p; + /* + * If the pattern ends here, we know the string + * matches without needing to look at the rest of it. + */ + if (c == '\0') + return 1; + /* + * First try the shortest match for the '*' that + * could work. We can forget any earlier '*' since + * there is no way having it match more characters + * can help us, given that we are already here. + */ + bt_p = p; + bt_q = q; + break; + case '[': { + const char *savep, *saveq; + int invert, found; + wchar_t chr; + + savep = p, saveq = q; + invert = 0; + if (*p == '!' || *p == '^') { + invert++; + p++; + } + found = 0; + if (*q == '\0') + return 0; + if (localeisutf8) { + chr = get_wc(&q); + if (chr == 0) + goto backtrack; + } else + chr = (unsigned char)*q++; + c = *p++; + do { + if (c == '\0') { + p = savep, q = saveq; + c = '['; + goto dft; + } + if (c == '[' && *p == ':') { + found |= match_charclass(p, chr, &end); + if (end != NULL) { + p = end; + continue; + } + } + if (c == CTLESC) + c = *p++; + if (localeisutf8 && c & 0x80) { + p--; + wc = get_wc(&p); + if (wc == 0) /* bad utf-8 */ + return 0; + } else + wc = (unsigned char)c; + if (*p == '-' && p[1] != ']') { + p++; + if (*p == CTLESC) + p++; + if (localeisutf8) { + wc2 = get_wc(&p); + if (wc2 == 0) /* bad utf-8 */ + return 0; + } else + wc2 = (unsigned char)*p++; + if ( collate_range_cmp(chr, wc) >= 0 + && collate_range_cmp(chr, wc2) <= 0 + ) + found = 1; + } else { + if (chr == wc) + found = 1; + } + } while ((c = *p++) != ']'); + if (found == invert) + goto backtrack; + break; + } +dft: default: + if (*q == '\0') + return 0; + if (*q++ == c) + break; +backtrack: + /* + * If we have a mismatch (other than hitting the end + * of the string), go back to the last '*' seen and + * have it match one additional character. + */ + if (bt_p == NULL) + return 0; + if (*bt_q == '\0') + return 0; + bt_q++; + p = bt_p; + q = bt_q; + break; + } + } +} + + + +/* + * Remove any CTLESC and CTLQUOTEMARK characters from a string. + */ + +void +rmescapes(char *str) +{ + char *p, *q; + + p = str; + while (*p != CTLESC && *p != CTLQUOTEMARK && *p != CTLQUOTEEND) { + if (*p++ == '\0') + return; + } + q = p; + while (*p) { + if (*p == CTLQUOTEMARK || *p == CTLQUOTEEND) { + p++; + continue; + } + if (*p == CTLESC) + p++; + *q++ = *p++; + } + *q = '\0'; +} + + + +/* + * See if a pattern matches in a case statement. + */ + +int +casematch(union node *pattern, const char *val) +{ + struct stackmark smark; + struct nodelist *argbackq; + int result; + char *p; + + setstackmark(&smark); + argbackq = pattern->narg.backquote; + STARTSTACKSTR(expdest); + argstr(pattern->narg.text, &argbackq, EXP_TILDE | EXP_CASE, NULL); + STPUTC('\0', expdest); + p = grabstackstr(expdest); + result = patmatch(p, val); + popstackmark(&smark); + return result; +} + +/* + * Our own itoa(). + */ + +static void +cvtnum(int num, char *buf) +{ + char temp[32]; + int neg = num < 0; + char *p = temp + 31; + + temp[31] = '\0'; + + do { + *--p = num % 10 + '0'; + } while ((num /= 10) != 0); + + if (neg) + *--p = '-'; + + memcpy(buf, p, temp + 32 - p); +} + +/* + * Do most of the work for wordexp(3). + */ + +int +wordexpcmd(int argc, char **argv) +{ + size_t len; + int i; + + out1fmt("%08x", argc - 1); + for (i = 1, len = 0; i < argc; i++) + len += strlen(argv[i]); + out1fmt("%08x", (int)len); + for (i = 1; i < argc; i++) + outbin(argv[i], strlen(argv[i]) + 1, out1); + return (0); +} + +/* + * Do most of the work for wordexp(3), new version. + */ + +int +freebsd_wordexpcmd(int argc __unused, char **argv __unused) +{ + struct arglist arglist; + union node *args, *n; + size_t len; + int ch; + int protected = 0; + int fd = -1; + int i; + + while ((ch = nextopt("f:p")) != '\0') { + switch (ch) { + case 'f': + fd = number(shoptarg); + break; + case 'p': + protected = 1; + break; + } + } + if (*argptr != NULL) + error("wrong number of arguments"); + if (fd < 0) + error("missing fd"); + INTOFF; + setinputfd(fd, 1); + INTON; + args = parsewordexp(); + popfile(); /* will also close fd */ + if (protected) + for (n = args; n != NULL; n = n->narg.next) { + if (n->narg.backquote != NULL) { + outcslow('C', out1); + error("command substitution disabled"); + } + } + outcslow(' ', out1); + emptyarglist(&arglist); + for (n = args; n != NULL; n = n->narg.next) + expandarg(n, &arglist, EXP_FULL | EXP_TILDE); + for (i = 0, len = 0; i < arglist.count; i++) + len += strlen(arglist.args[i]); + out1fmt("%016x %016zx", arglist.count, len); + for (i = 0; i < arglist.count; i++) + outbin(arglist.args[i], strlen(arglist.args[i]) + 1, out1); + return (0); +} diff --git a/bin/catsh/expand.h b/bin/catsh/expand.h new file mode 100644 index 00000000..92e344d9 --- /dev/null +++ b/bin/catsh/expand.h @@ -0,0 +1,62 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)expand.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/expand.h 314436 2017-02-28 23:42:47Z imp $ + */ + +struct arglist { + char **args; + int count; + int capacity; + char *smallarg[1]; +}; + +/* + * expandarg() flags + */ +#define EXP_SPLIT 0x1 /* perform word splitting */ +#define EXP_TILDE 0x2 /* do normal tilde expansion */ +#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */ +#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ +#define EXP_SPLIT_LIT 0x20 /* IFS split literal text ${v+-a b c} */ +#define EXP_LIT_QUOTED 0x40 /* for EXP_SPLIT_LIT, start off quoted */ +#define EXP_GLOB 0x80 /* perform file globbing */ + +#define EXP_FULL (EXP_SPLIT | EXP_GLOB) + + +void emptyarglist(struct arglist *); +void appendarglist(struct arglist *, char *); +union node; +void expandarg(union node *, struct arglist *, int); +void rmescapes(char *); +int casematch(union node *, const char *); diff --git a/bin/catsh/histedit.c b/bin/catsh/histedit.c new file mode 100644 index 00000000..08339a00 --- /dev/null +++ b/bin/catsh/histedit.c @@ -0,0 +1,575 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)histedit.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/histedit.c 319635 2017-06-06 21:08:05Z jilles $"); + +#include <sys/param.h> +#include <limits.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +/* + * Editline and history functions (and glue). + */ +#include "shell.h" +#include "parser.h" +#include "var.h" +#include "options.h" +#include "main.h" +#include "output.h" +#include "mystring.h" +#ifndef NO_HISTORY +#include "myhistedit.h" +#include "error.h" +#include "eval.h" +#include "memalloc.h" +#include "builtins.h" + +#define MAXHISTLOOPS 4 /* max recursions through fc */ +#define DEFEDITOR "ed" /* default editor *should* be $EDITOR */ + +History *hist; /* history cookie */ +EditLine *el; /* editline cookie */ +int displayhist; +static FILE *el_in, *el_out, *el_err; +static HistEvent he_saved; + +static void history_load(const char *hf); +static void history_save(const char *hf); + +static char *fc_replace(const char *, char *, char *); +static int not_fcnumber(const char *); +static int str_to_event(const char *, int); + +/* + * Set history and editing status. Called whenever the status may + * have changed (figures out what to do). + */ +void +histedit(void) +{ + +#define editing (Eflag || Vflag) + + if (iflag) { + if (!hist) { + /* + * turn history on + */ + INTOFF; + hist = history_init(); + INTON; + + if (hist != NULL) { + sethistsize(histsizeval()); + sethistfile(histfileval()); + } else + out2fmt_flush("sh: can't initialize history\n"); + } + if (editing && !el && isatty(0)) { /* && isatty(2) ??? */ + /* + * turn editing on + */ + char *term; + + INTOFF; + if (el_in == NULL) + el_in = fdopen(0, "r"); + if (el_err == NULL) + el_err = fdopen(1, "w"); + if (el_out == NULL) + el_out = fdopen(2, "w"); + if (el_in == NULL || el_err == NULL || el_out == NULL) + goto bad; + term = lookupvar("TERM"); + if (term) + setenv("TERM", term, 1); + else + unsetenv("TERM"); + el = el_init(arg0, el_in, el_out, el_err); + if (el != NULL) { + if (hist) + el_set(el, EL_HIST, history, hist); + el_set(el, EL_PROMPT, getprompt); + el_set(el, EL_RPROMPT, getrprompt); + el_set(el, EL_ADDFN, "sh-complete", + "Filename completion", + _el_fn_complete); + } else { +bad: + out2fmt_flush("sh: can't initialize editing\n"); + } + INTON; + } else if (!editing && el) { + INTOFF; + el_end(el); + el = NULL; + INTON; + } + if (el) { + if (Vflag) + el_set(el, EL_EDITOR, "vi"); + else if (Eflag) + el_set(el, EL_EDITOR, "emacs"); + el_set(el, EL_BIND, "^I", "sh-complete", NULL); + el_source(el, NULL); + } + } else { + INTOFF; + if (el) { /* no editing if not interactive */ + el_end(el); + el = NULL; + } + if (hist) { + if (*histfileval() != '\0') + history_save(histfileval()); + history_end(hist); + hist = NULL; + } + INTON; + } +} + + +void +sethistfile(const char *hf) +{ + if (hist != NULL && hf != NULL && *hf != '\0') + history_load(hf); +} + + +static void +history_load(const char *hf) { + HistEvent he; + const char *ehf; + + ehf = expandstr(hf); + if (ehf == NULL) + return; + if (history(hist, &he, H_LOAD, ehf) == -1) + warning("%s: %s", he.str, ehf); + else + history(hist, &he_saved, H_FIRST); +} + + +static void +history_save(const char *hf) { + HistEvent he; + const char *ehf; + + ehf = expandstr(hf); + if (ehf == NULL) + return; + if (he_saved.num == 0) + history(hist, &he_saved, H_LAST); + else + history(hist, &he_saved, H_NEXT_EVENT, he_saved.num + 1); + if (history(hist, &he, H_SAVE_INCR, ehf, he_saved.num) == -1) + warning("%s: %s", he.str, ehf); +} + + +void +sethistsize(const char *hs) +{ + int histsize; + HistEvent he; + + if (hist != NULL) { + if (hs == NULL || !is_number(hs)) + histsize = 100; + else + histsize = atoi(hs); + history(hist, &he, H_SETSIZE, histsize); + history(hist, &he, H_SETUNIQUE, 1); + } +} + +void +setpslit(const char *lit_ch) { + wchar_t wc; + + if (!(iflag && editing && el)) + return; + + if (lit_ch == NULL) { + el_set(el, EL_PROMPT, getprompt); + el_set(el, EL_RPROMPT, getrprompt); + return; + } + + mbtowc(&wc, NULL, 1); /* state init */ + + if (mbtowc(&wc, lit_ch, strlen(lit_ch)) <= 0) { + el_set(el, EL_PROMPT, getprompt); + el_set(el, EL_RPROMPT, getrprompt); + } else { + el_set(el, EL_PROMPT_ESC, getprompt, (int)wc); + el_set(el, EL_RPROMPT_ESC, getrprompt, (int)wc); + } +} + +void +setterm(const char *term) +{ + if (rootshell && el != NULL && term != NULL) + el_set(el, EL_TERMINAL, term); +} + +int +histcmd(int argc, char **argv __unused) +{ + int ch; + const char *editor = NULL; + HistEvent he; + int lflg = 0, nflg = 0, rflg = 0, sflg = 0; + int i, retval; + const char *firststr, *laststr; + int first, last, direction; + char *pat = NULL, *repl = NULL; + static int active = 0; + struct jmploc jmploc; + struct jmploc *savehandler; + char editfilestr[PATH_MAX]; + char *volatile editfile; + FILE *efp = NULL; + int oldhistnum; + + if (hist == NULL) + error("history not active"); + + if (argc == 1) + error("missing history argument"); + + while (not_fcnumber(*argptr) && (ch = nextopt("e:lnrs")) != '\0') + switch ((char)ch) { + case 'e': + editor = shoptarg; + break; + case 'l': + lflg = 1; + break; + case 'n': + nflg = 1; + break; + case 'r': + rflg = 1; + break; + case 's': + sflg = 1; + break; + } + + savehandler = handler; + /* + * If executing... + */ + if (lflg == 0 || editor || sflg) { + lflg = 0; /* ignore */ + editfile = NULL; + /* + * Catch interrupts to reset active counter and + * cleanup temp files. + */ + if (setjmp(jmploc.loc)) { + active = 0; + if (editfile) + unlink(editfile); + handler = savehandler; + longjmp(handler->loc, 1); + } + handler = &jmploc; + if (++active > MAXHISTLOOPS) { + active = 0; + displayhist = 0; + error("called recursively too many times"); + } + /* + * Set editor. + */ + if (sflg == 0) { + if (editor == NULL && + (editor = bltinlookup("FCEDIT", 1)) == NULL && + (editor = bltinlookup("EDITOR", 1)) == NULL) + editor = DEFEDITOR; + if (editor[0] == '-' && editor[1] == '\0') { + sflg = 1; /* no edit */ + editor = NULL; + } + } + } + + /* + * If executing, parse [old=new] now + */ + if (lflg == 0 && *argptr != NULL && + ((repl = strchr(*argptr, '=')) != NULL)) { + pat = *argptr; + *repl++ = '\0'; + argptr++; + } + /* + * determine [first] and [last] + */ + if (*argptr == NULL) { + firststr = lflg ? "-16" : "-1"; + laststr = "-1"; + } else if (argptr[1] == NULL) { + firststr = argptr[0]; + laststr = lflg ? "-1" : argptr[0]; + } else if (argptr[2] == NULL) { + firststr = argptr[0]; + laststr = argptr[1]; + } else + error("too many arguments"); + /* + * Turn into event numbers. + */ + first = str_to_event(firststr, 0); + last = str_to_event(laststr, 1); + + if (rflg) { + i = last; + last = first; + first = i; + } + /* + * XXX - this should not depend on the event numbers + * always increasing. Add sequence numbers or offset + * to the history element in next (diskbased) release. + */ + direction = first < last ? H_PREV : H_NEXT; + + /* + * If editing, grab a temp file. + */ + if (editor) { + int fd; + INTOFF; /* easier */ + sprintf(editfilestr, "%s/_shXXXXXX", _PATH_TMP); + if ((fd = mkstemp(editfilestr)) < 0) + error("can't create temporary file %s", editfile); + editfile = editfilestr; + if ((efp = fdopen(fd, "w")) == NULL) { + close(fd); + error("Out of space"); + } + } + + /* + * Loop through selected history events. If listing or executing, + * do it now. Otherwise, put into temp file and call the editor + * after. + * + * The history interface needs rethinking, as the following + * convolutions will demonstrate. + */ + history(hist, &he, H_FIRST); + retval = history(hist, &he, H_NEXT_EVENT, first); + for (;retval != -1; retval = history(hist, &he, direction)) { + if (lflg) { + if (!nflg) + out1fmt("%5d ", he.num); + out1str(he.str); + } else { + const char *s = pat ? + fc_replace(he.str, pat, repl) : he.str; + + if (sflg) { + if (displayhist) { + out2str(s); + flushout(out2); + } + evalstring(s, 0); + if (displayhist && hist) { + /* + * XXX what about recursive and + * relative histnums. + */ + oldhistnum = he.num; + history(hist, &he, H_ENTER, s); + /* + * XXX H_ENTER moves the internal + * cursor, set it back to the current + * entry. + */ + history(hist, &he, + H_NEXT_EVENT, oldhistnum); + } + } else + fputs(s, efp); + } + /* + * At end? (if we were to lose last, we'd sure be + * messed up). + */ + if (he.num == last) + break; + } + if (editor) { + char *editcmd; + + fclose(efp); + INTON; + editcmd = stalloc(strlen(editor) + strlen(editfile) + 2); + sprintf(editcmd, "%s %s", editor, editfile); + evalstring(editcmd, 0); /* XXX - should use no JC command */ + readcmdfile(editfile); /* XXX - should read back - quick tst */ + unlink(editfile); + } + + if (lflg == 0 && active > 0) + --active; + if (displayhist) + displayhist = 0; + handler = savehandler; + return 0; +} + +static char * +fc_replace(const char *s, char *p, char *r) +{ + char *dest; + int plen = strlen(p); + + STARTSTACKSTR(dest); + while (*s) { + if (strncmp(s, p, plen) == 0) { + STPUTS(r, dest); + s += plen; + break; + } + STPUTC(*s++, dest); + } + STPUTS(s, dest); + STPUTC('\0', dest); + dest = grabstackstr(dest); + + return (dest); +} + +static int +not_fcnumber(const char *s) +{ + if (s == NULL) + return (0); + if (*s == '-') + s++; + return (!is_number(s)); +} + +static int +str_to_event(const char *str, int last) +{ + HistEvent he; + const char *s = str; + int relative = 0; + int i, retval; + + retval = history(hist, &he, H_FIRST); + switch (*s) { + case '-': + relative = 1; + /*FALLTHROUGH*/ + case '+': + s++; + } + if (is_number(s)) { + i = atoi(s); + if (relative) { + while (retval != -1 && i--) { + retval = history(hist, &he, H_NEXT); + } + if (retval == -1) + retval = history(hist, &he, H_LAST); + } else { + retval = history(hist, &he, H_NEXT_EVENT, i); + if (retval == -1) { + /* + * the notion of first and last is + * backwards to that of the history package + */ + retval = history(hist, &he, last ? H_FIRST : H_LAST); + } + } + if (retval == -1) + error("history number %s not found (internal error)", + str); + } else { + /* + * pattern + */ + retval = history(hist, &he, H_PREV_STR, str); + if (retval == -1) + error("history pattern not found: %s", str); + } + return (he.num); +} + +int +bindcmd(int argc, char **argv) +{ + + if (el == NULL) + error("line editing is disabled"); + return (el_parse(el, argc, __DECONST(const char **, argv))); +} + +#else +#include "error.h" + +int +histcmd(int argc, char **argv) +{ + + error("not compiled with history support"); + /*NOTREACHED*/ + return (0); +} + +int +bindcmd(int argc, char **argv) +{ + + error("not compiled with line editing support"); + return (0); +} +#endif diff --git a/bin/catsh/input.c b/bin/catsh/input.c new file mode 100644 index 00000000..24ba73b5 --- /dev/null +++ b/bin/catsh/input.c @@ -0,0 +1,520 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)input.c 8.3 (Berkeley) 6/9/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/input.c 314436 2017-02-28 23:42:47Z imp $"); + +#include <stdio.h> /* defines BUFSIZ */ +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +/* + * This file implements the input routines used by the parser. + */ + +#include "shell.h" +#include "redir.h" +#include "syntax.h" +#include "input.h" +#include "output.h" +#include "options.h" +#include "memalloc.h" +#include "error.h" +#include "alias.h" +#include "parser.h" +#include "myhistedit.h" +#include "trap.h" + +#define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */ + +struct strpush { + struct strpush *prev; /* preceding string on stack */ + const char *prevstring; + int prevnleft; + int prevlleft; + struct alias *ap; /* if push was associated with an alias */ +}; + +/* + * The parsefile structure pointed to by the global variable parsefile + * contains information about the current file being read. + */ + +struct parsefile { + struct parsefile *prev; /* preceding file on stack */ + int linno; /* current line */ + int fd; /* file descriptor (or -1 if string) */ + int nleft; /* number of chars left in this line */ + int lleft; /* number of lines left in this buffer */ + const char *nextc; /* next char in buffer */ + char *buf; /* input buffer */ + struct strpush *strpush; /* for pushing strings at this level */ + struct strpush basestrpush; /* so pushing one is fast */ +}; + + +int plinno = 1; /* input line number */ +int parsenleft; /* copy of parsefile->nleft */ +static int parselleft; /* copy of parsefile->lleft */ +const char *parsenextc; /* copy of parsefile->nextc */ +static char basebuf[BUFSIZ + 1];/* buffer for top level input file */ +static struct parsefile basepf = { /* top level input file */ + .nextc = basebuf, + .buf = basebuf +}; +static struct parsefile *parsefile = &basepf; /* current input file */ +int whichprompt; /* 1 == PS1, 2 == PS2 */ + +EditLine *el; /* cookie for editline package */ + +static void pushfile(void); +static int preadfd(void); +static void popstring(void); + +void +resetinput(void) +{ + popallfiles(); + parselleft = parsenleft = 0; /* clear input buffer */ +} + + + +/* + * Read a character from the script, returning PEOF on end of file. + * Nul characters in the input are silently discarded. + */ + +int +pgetc(void) +{ + return pgetc_macro(); +} + + +static int +preadfd(void) +{ + int nr; + parsenextc = parsefile->buf; + +retry: +#ifndef NO_HISTORY + if (parsefile->fd == 0 && el) { + static const char *rl_cp; + static int el_len; + + if (rl_cp == NULL) { + el_resize(el); + rl_cp = el_gets(el, &el_len); + } + if (rl_cp == NULL) + nr = el_len == 0 ? 0 : -1; + else { + nr = el_len; + if (nr > BUFSIZ) + nr = BUFSIZ; + memcpy(parsefile->buf, rl_cp, nr); + if (nr != el_len) { + el_len -= nr; + rl_cp += nr; + } else + rl_cp = NULL; + } + } else +#endif + nr = read(parsefile->fd, parsefile->buf, BUFSIZ); + + if (nr <= 0) { + if (nr < 0) { + if (errno == EINTR) + goto retry; + if (parsefile->fd == 0 && errno == EWOULDBLOCK) { + int flags = fcntl(0, F_GETFL, 0); + if (flags >= 0 && flags & O_NONBLOCK) { + flags &=~ O_NONBLOCK; + if (fcntl(0, F_SETFL, flags) >= 0) { + out2fmt_flush("sh: turning off NDELAY mode\n"); + goto retry; + } + } + } + } + nr = -1; + } + return nr; +} + +/* + * Refill the input buffer and return the next input character: + * + * 1) If a string was pushed back on the input, pop it; + * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading + * from a string so we can't refill the buffer, return EOF. + * 3) If there is more in this buffer, use it else call read to fill it. + * 4) Process input up to the next newline, deleting nul characters. + */ + +int +preadbuffer(void) +{ + char *p, *q, *r, *end; + char savec; + + while (parsefile->strpush) { + /* + * Add a space to the end of an alias to ensure that the + * alias remains in use while parsing its last word. + * This avoids alias recursions. + */ + if (parsenleft == -1 && parsefile->strpush->ap != NULL) + return ' '; + popstring(); + if (--parsenleft >= 0) + return (*parsenextc++); + } + if (parsenleft == EOF_NLEFT || parsefile->buf == NULL) + return PEOF; + +again: + if (parselleft <= 0) { + if ((parselleft = preadfd()) == -1) { + parselleft = parsenleft = EOF_NLEFT; + return PEOF; + } + } + + p = parsefile->buf + (parsenextc - parsefile->buf); + end = p + parselleft; + *end = '\0'; + q = strchr(p, '\n'); + if (!q) q = strchr(p, '\0'); + if (q != end && *q == '\0') { + /* delete nul characters */ + for (r = q; q != end; q++) { + if (*q != '\0') + *r++ = *q; + } + parselleft -= end - r; + if (parselleft == 0) + goto again; + end = p + parselleft; + *end = '\0'; + q = strchr(p, '\n'); + if (!q) q = strchr(p, '\0'); + } + if (q == end) { + parsenleft = parselleft; + parselleft = 0; + } else /* *q == '\n' */ { + q++; + parsenleft = q - parsenextc; + parselleft -= parsenleft; + } + parsenleft--; + + savec = *q; + *q = '\0'; + +#ifndef NO_HISTORY + if (parsefile->fd == 0 && hist && + parsenextc[strspn(parsenextc, " \t\n")] != '\0') { + HistEvent he; + INTOFF; + history(hist, &he, whichprompt == 1 ? H_ENTER : H_ADD, + parsenextc); + INTON; + } +#endif + + if (vflag) { + out2str(parsenextc); + flushout(out2); + } + + *q = savec; + + return *parsenextc++; +} + +/* + * Returns if we are certain we are at EOF. Does not cause any more input + * to be read from the outside world. + */ + +int +preadateof(void) +{ + if (parsenleft > 0) + return 0; + if (parsefile->strpush) + return 0; + if (parsenleft == EOF_NLEFT || parsefile->buf == NULL) + return 1; + return 0; +} + +/* + * Undo the last call to pgetc. Only one character may be pushed back. + * PEOF may be pushed back. + */ + +void +pungetc(void) +{ + parsenleft++; + parsenextc--; +} + +/* + * Push a string back onto the input at this current parsefile level. + * We handle aliases this way. + */ +void +pushstring(const char *s, int len, struct alias *ap) +{ + struct strpush *sp; + + INTOFF; +/*out2fmt_flush("*** calling pushstring: %s, %d\n", s, len);*/ + if (parsefile->strpush) { + sp = ckmalloc(sizeof (struct strpush)); + sp->prev = parsefile->strpush; + parsefile->strpush = sp; + } else + sp = parsefile->strpush = &(parsefile->basestrpush); + sp->prevstring = parsenextc; + sp->prevnleft = parsenleft; + sp->prevlleft = parselleft; + sp->ap = ap; + if (ap) + ap->flag |= ALIASINUSE; + parsenextc = s; + parsenleft = len; + INTON; +} + +static void +popstring(void) +{ + struct strpush *sp = parsefile->strpush; + + INTOFF; + if (sp->ap) { + if (parsenextc != sp->ap->val && + (parsenextc[-1] == ' ' || parsenextc[-1] == '\t')) + forcealias(); + sp->ap->flag &= ~ALIASINUSE; + } + parsenextc = sp->prevstring; + parsenleft = sp->prevnleft; + parselleft = sp->prevlleft; +/*out2fmt_flush("*** calling popstring: restoring to '%s'\n", parsenextc);*/ + parsefile->strpush = sp->prev; + if (sp != &(parsefile->basestrpush)) + ckfree(sp); + INTON; +} + +/* + * Set the input to take input from a file. If push is set, push the + * old input onto the stack first. + */ + +void +setinputfile(const char *fname, int push) +{ + int fd; + int fd2; + + INTOFF; + if ((fd = open(fname, O_RDONLY | O_CLOEXEC)) < 0) + error("cannot open %s: %s", fname, strerror(errno)); + if (fd < 10) { + fd2 = fcntl(fd, F_DUPFD_CLOEXEC, 10); + close(fd); + if (fd2 < 0) + error("Out of file descriptors"); + fd = fd2; + } + setinputfd(fd, push); + INTON; +} + + +/* + * Like setinputfile, but takes an open file descriptor (which should have + * its FD_CLOEXEC flag already set). Call this with interrupts off. + */ + +void +setinputfd(int fd, int push) +{ + if (push) { + pushfile(); + parsefile->buf = ckmalloc(BUFSIZ + 1); + } + if (parsefile->fd > 0) + close(parsefile->fd); + parsefile->fd = fd; + if (parsefile->buf == NULL) + parsefile->buf = ckmalloc(BUFSIZ + 1); + parselleft = parsenleft = 0; + plinno = 1; +} + + +/* + * Like setinputfile, but takes input from a string. + */ + +void +setinputstring(const char *string, int push) +{ + INTOFF; + if (push) + pushfile(); + parsenextc = string; + parselleft = parsenleft = strlen(string); + parsefile->buf = NULL; + plinno = 1; + INTON; +} + + + +/* + * To handle the "." command, a stack of input files is used. Pushfile + * adds a new entry to the stack and popfile restores the previous level. + */ + +static void +pushfile(void) +{ + struct parsefile *pf; + + parsefile->nleft = parsenleft; + parsefile->lleft = parselleft; + parsefile->nextc = parsenextc; + parsefile->linno = plinno; + pf = (struct parsefile *)ckmalloc(sizeof (struct parsefile)); + pf->prev = parsefile; + pf->fd = -1; + pf->strpush = NULL; + pf->basestrpush.prev = NULL; + parsefile = pf; +} + + +void +popfile(void) +{ + struct parsefile *pf = parsefile; + + INTOFF; + if (pf->fd >= 0) + close(pf->fd); + if (pf->buf) + ckfree(pf->buf); + while (pf->strpush) + popstring(); + parsefile = pf->prev; + ckfree(pf); + parsenleft = parsefile->nleft; + parselleft = parsefile->lleft; + parsenextc = parsefile->nextc; + plinno = parsefile->linno; + INTON; +} + + +/* + * Return current file (to go back to it later using popfilesupto()). + */ + +struct parsefile * +getcurrentfile(void) +{ + return parsefile; +} + + +/* + * Pop files until the given file is on top again. Useful for regular + * builtins that read shell commands from files or strings. + * If the given file is not an active file, an error is raised. + */ + +void +popfilesupto(struct parsefile *file) +{ + while (parsefile != file && parsefile != &basepf) + popfile(); + if (parsefile != file) + error("popfilesupto() misused"); +} + +/* + * Return to top level. + */ + +void +popallfiles(void) +{ + while (parsefile != &basepf) + popfile(); +} + + + +/* + * Close the file(s) that the shell is reading commands from. Called + * after a fork is done. + */ + +void +closescript(void) +{ + popallfiles(); + if (parsefile->fd > 0) { + close(parsefile->fd); + parsefile->fd = 0; + } +} diff --git a/bin/catsh/input.h b/bin/catsh/input.h new file mode 100644 index 00000000..2a04921d --- /dev/null +++ b/bin/catsh/input.h @@ -0,0 +1,65 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)input.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/input.h 314436 2017-02-28 23:42:47Z imp $ + */ + +/* PEOF (the end of file marker) is defined in syntax.h */ + +/* + * The input line number. Input.c just defines this variable, and saves + * and restores it when files are pushed and popped. The user of this + * package must set its value. + */ +extern int plinno; +extern int parsenleft; /* number of characters left in input buffer */ +extern const char *parsenextc; /* next character in input buffer */ + +struct alias; +struct parsefile; + +void resetinput(void); +int pgetc(void); +int preadbuffer(void); +int preadateof(void); +void pungetc(void); +void pushstring(const char *, int, struct alias *); +void setinputfile(const char *, int); +void setinputfd(int, int); +void setinputstring(const char *, int); +void popfile(void); +struct parsefile *getcurrentfile(void); +void popfilesupto(struct parsefile *); +void popallfiles(void); +void closescript(void); + +#define pgetc_macro() (--parsenleft >= 0? *parsenextc++ : preadbuffer()) diff --git a/bin/catsh/jobs.c b/bin/catsh/jobs.c new file mode 100644 index 00000000..88703edd --- /dev/null +++ b/bin/catsh/jobs.c @@ -0,0 +1,1552 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)jobs.c 8.5 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/jobs.c 328818 2018-02-02 22:53:58Z jilles $"); + +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <signal.h> +#include <stddef.h> +#include <stdlib.h> +#include <unistd.h> + +#include "shell.h" +#if JOBS +#include <termios.h> +#undef CEOF /* syntax.h redefines this */ +#endif +#include "redir.h" +#include "exec.h" +#include "show.h" +#include "main.h" +#include "parser.h" +#include "nodes.h" +#include "jobs.h" +#include "options.h" +#include "trap.h" +#include "syntax.h" +#include "input.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "var.h" +#include "builtins.h" + + +/* + * A job structure contains information about a job. A job is either a + * single process or a set of processes contained in a pipeline. In the + * latter case, pidlist will be non-NULL, and will point to a -1 terminated + * array of pids. + */ + +struct procstat { + pid_t pid; /* process id */ + int status; /* status flags (defined above) */ + char *cmd; /* text of command being run */ +}; + + +/* states */ +#define JOBSTOPPED 1 /* all procs are stopped */ +#define JOBDONE 2 /* all procs are completed */ + + +struct job { + struct procstat ps0; /* status of process */ + struct procstat *ps; /* status or processes when more than one */ + short nprocs; /* number of processes */ + pid_t pgrp; /* process group of this job */ + char state; /* true if job is finished */ + char used; /* true if this entry is in used */ + char changed; /* true if status has changed */ + char foreground; /* true if running in the foreground */ + char remembered; /* true if $! referenced */ +#if JOBS + char jobctl; /* job running under job control */ + struct job *next; /* job used after this one */ +#endif +}; + + +static struct job *jobtab; /* array of jobs */ +static int njobs; /* size of array */ +static pid_t backgndpid = -1; /* pid of last background process */ +static struct job *bgjob = NULL; /* last background process */ +#if JOBS +static struct job *jobmru; /* most recently used job list */ +static pid_t initialpgrp; /* pgrp of shell on invocation */ +#endif +static int ttyfd = -1; + +/* mode flags for dowait */ +#define DOWAIT_BLOCK 0x1 /* wait until a child exits */ +#define DOWAIT_SIG 0x2 /* if DOWAIT_BLOCK, abort on signal */ +#define DOWAIT_SIG_TRAP 0x4 /* if DOWAIT_SIG, abort on trapped signal only */ + +#if JOBS +static void restartjob(struct job *); +#endif +static void freejob(struct job *); +static int waitcmdloop(struct job *); +static struct job *getjob_nonotfound(const char *); +static struct job *getjob(const char *); +pid_t killjob(const char *, int); +static pid_t dowait(int, struct job *); +static void checkzombies(void); +static void cmdtxt(union node *); +static void cmdputs(const char *); +#if JOBS +static void setcurjob(struct job *); +static void deljob(struct job *); +static struct job *getcurjob(struct job *); +#endif +static void printjobcmd(struct job *); +static void showjob(struct job *, int); + + +/* + * Turn job control on and off. + */ + +static int jobctl; + +#if JOBS +static void +jobctl_notty(void) +{ + if (ttyfd >= 0) { + close(ttyfd); + ttyfd = -1; + } + if (!iflag) { + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + jobctl = 1; + return; + } + out2fmt_flush("sh: can't access tty; job control turned off\n"); + mflag = 0; +} + +void +setjobctl(int on) +{ + int i; + + if (on == jobctl || rootshell == 0) + return; + if (on) { + if (ttyfd != -1) + close(ttyfd); + if ((ttyfd = open(_PATH_TTY, O_RDWR | O_CLOEXEC)) < 0) { + i = 0; + while (i <= 2 && !isatty(i)) + i++; + if (i > 2 || + (ttyfd = fcntl(i, F_DUPFD_CLOEXEC, 10)) < 0) { + jobctl_notty(); + return; + } + } + if (ttyfd < 10) { + /* + * Keep our TTY file descriptor out of the way of + * the user's redirections. + */ + if ((i = fcntl(ttyfd, F_DUPFD_CLOEXEC, 10)) < 0) { + jobctl_notty(); + return; + } + close(ttyfd); + ttyfd = i; + } + do { /* while we are in the background */ + initialpgrp = tcgetpgrp(ttyfd); + if (initialpgrp < 0) { + jobctl_notty(); + return; + } + if (initialpgrp != getpgrp()) { + if (!iflag) { + initialpgrp = -1; + jobctl_notty(); + return; + } + kill(0, SIGTTIN); + continue; + } + } while (0); + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + setpgid(0, rootpid); + tcsetpgrp(ttyfd, rootpid); + } else { /* turning job control off */ + setpgid(0, initialpgrp); + if (ttyfd >= 0) { + tcsetpgrp(ttyfd, initialpgrp); + close(ttyfd); + ttyfd = -1; + } + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + } + jobctl = on; +} +#endif + + +#if JOBS +int +fgcmd(int argc __unused, char **argv __unused) +{ + struct job *jp; + pid_t pgrp; + int status; + + nextopt(""); + jp = getjob(*argptr); + if (jp->jobctl == 0) + error("job not created under job control"); + printjobcmd(jp); + flushout(&output); + pgrp = jp->ps[0].pid; + if (ttyfd >= 0) + tcsetpgrp(ttyfd, pgrp); + restartjob(jp); + jp->foreground = 1; + INTOFF; + status = waitforjob(jp, (int *)NULL); + INTON; + return status; +} + + +int +bgcmd(int argc __unused, char **argv __unused) +{ + struct job *jp; + + nextopt(""); + do { + jp = getjob(*argptr); + if (jp->jobctl == 0) + error("job not created under job control"); + if (jp->state == JOBDONE) + continue; + restartjob(jp); + jp->foreground = 0; + out1fmt("[%td] ", jp - jobtab + 1); + printjobcmd(jp); + } while (*argptr != NULL && *++argptr != NULL); + return 0; +} + + +static void +restartjob(struct job *jp) +{ + struct procstat *ps; + int i; + + if (jp->state == JOBDONE) + return; + setcurjob(jp); + INTOFF; + kill(-jp->ps[0].pid, SIGCONT); + for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) { + if (WIFSTOPPED(ps->status)) { + ps->status = -1; + jp->state = 0; + } + } + INTON; +} +#endif + + +int +jobscmd(int argc __unused, char *argv[] __unused) +{ + char *id; + int ch, mode; + + mode = SHOWJOBS_DEFAULT; + while ((ch = nextopt("lps")) != '\0') { + switch (ch) { + case 'l': + mode = SHOWJOBS_VERBOSE; + break; + case 'p': + mode = SHOWJOBS_PGIDS; + break; + case 's': + mode = SHOWJOBS_PIDS; + break; + } + } + + if (*argptr == NULL) + showjobs(0, mode); + else + while ((id = *argptr++) != NULL) + showjob(getjob(id), mode); + + return (0); +} + +static void +printjobcmd(struct job *jp) +{ + struct procstat *ps; + int i; + + for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) { + out1str(ps->cmd); + if (i > 0) + out1str(" | "); + } + out1c('\n'); +} + +static void +showjob(struct job *jp, int mode) +{ + char s[64]; + char statebuf[16]; + const char *statestr, *coredump; + struct procstat *ps; + struct job *j; + int col, curr, i, jobno, prev, procno, status; + char c; + + procno = (mode == SHOWJOBS_PGIDS) ? 1 : jp->nprocs; + jobno = jp - jobtab + 1; + curr = prev = 0; +#if JOBS + if ((j = getcurjob(NULL)) != NULL) { + curr = j - jobtab + 1; + if ((j = getcurjob(j)) != NULL) + prev = j - jobtab + 1; + } +#endif + coredump = ""; + status = jp->ps[jp->nprocs - 1].status; + if (jp->state == 0) { + statestr = "Running"; +#if JOBS + } else if (jp->state == JOBSTOPPED) { + ps = jp->ps + jp->nprocs - 1; + while (!WIFSTOPPED(ps->status) && ps > jp->ps) + ps--; + if (WIFSTOPPED(ps->status)) + i = WSTOPSIG(ps->status); + else + i = -1; + statestr = strsignal(i); + if (statestr == NULL) + statestr = "Suspended"; +#endif + } else if (WIFEXITED(status)) { + if (WEXITSTATUS(status) == 0) + statestr = "Done"; + else { + fmtstr(statebuf, sizeof(statebuf), "Done(%d)", + WEXITSTATUS(status)); + statestr = statebuf; + } + } else { + i = WTERMSIG(status); + statestr = strsignal(i); + if (statestr == NULL) + statestr = "Unknown signal"; + if (WCOREDUMP(status)) + coredump = " (core dumped)"; + } + + for (ps = jp->ps ; procno > 0 ; ps++, procno--) { /* for each process */ + if (mode == SHOWJOBS_PIDS || mode == SHOWJOBS_PGIDS) { + out1fmt("%d\n", (int)ps->pid); + continue; + } + if (mode != SHOWJOBS_VERBOSE && ps != jp->ps) + continue; + if (jobno == curr && ps == jp->ps) + c = '+'; + else if (jobno == prev && ps == jp->ps) + c = '-'; + else + c = ' '; + if (ps == jp->ps) + fmtstr(s, 64, "[%d] %c ", jobno, c); + else + fmtstr(s, 64, " %c ", c); + out1str(s); + col = strlen(s); + if (mode == SHOWJOBS_VERBOSE) { + fmtstr(s, 64, "%d ", (int)ps->pid); + out1str(s); + col += strlen(s); + } + if (ps == jp->ps) { + out1str(statestr); + out1str(coredump); + col += strlen(statestr) + strlen(coredump); + } + do { + out1c(' '); + col++; + } while (col < 30); + if (mode == SHOWJOBS_VERBOSE) { + out1str(ps->cmd); + out1c('\n'); + } else + printjobcmd(jp); + } +} + +/* + * Print a list of jobs. If "change" is nonzero, only print jobs whose + * statuses have changed since the last call to showjobs. + * + * If the shell is interrupted in the process of creating a job, the + * result may be a job structure containing zero processes. Such structures + * will be freed here. + */ + +void +showjobs(int change, int mode) +{ + int jobno; + struct job *jp; + + TRACE(("showjobs(%d) called\n", change)); + checkzombies(); + for (jobno = 1, jp = jobtab ; jobno <= njobs ; jobno++, jp++) { + if (! jp->used) + continue; + if (jp->nprocs == 0) { + freejob(jp); + continue; + } + if (change && ! jp->changed) + continue; + showjob(jp, mode); + if (mode == SHOWJOBS_DEFAULT || mode == SHOWJOBS_VERBOSE) { + jp->changed = 0; + /* Hack: discard jobs for which $! has not been + * referenced in interactive mode when they terminate. + */ + if (jp->state == JOBDONE && !jp->remembered && + (iflag || jp != bgjob)) { + freejob(jp); + } + } + } +} + + +/* + * Mark a job structure as unused. + */ + +static void +freejob(struct job *jp) +{ + struct procstat *ps; + int i; + + INTOFF; + if (bgjob == jp) + bgjob = NULL; + for (i = jp->nprocs, ps = jp->ps ; --i >= 0 ; ps++) { + if (ps->cmd != nullstr) + ckfree(ps->cmd); + } + if (jp->ps != &jp->ps0) + ckfree(jp->ps); + jp->used = 0; +#if JOBS + deljob(jp); +#endif + INTON; +} + + + +int +waitcmd(int argc __unused, char **argv __unused) +{ + struct job *job; + int retval; + + nextopt(""); + if (*argptr == NULL) + return (waitcmdloop(NULL)); + + do { + job = getjob_nonotfound(*argptr); + if (job == NULL) + retval = 127; + else + retval = waitcmdloop(job); + argptr++; + } while (*argptr != NULL); + + return (retval); +} + +static int +waitcmdloop(struct job *job) +{ + int status, retval, sig; + struct job *jp; + + /* + * Loop until a process is terminated or stopped, or a SIGINT is + * received. + */ + + do { + if (job != NULL) { + if (job->state == JOBDONE) { + status = job->ps[job->nprocs - 1].status; + if (WIFEXITED(status)) + retval = WEXITSTATUS(status); + else + retval = WTERMSIG(status) + 128; + if (! iflag || ! job->changed) + freejob(job); + else { + job->remembered = 0; + if (job == bgjob) + bgjob = NULL; + } + return retval; + } + } else { + for (jp = jobtab ; jp < jobtab + njobs; jp++) + if (jp->used && jp->state == JOBDONE) { + if (! iflag || ! jp->changed) + freejob(jp); + else { + jp->remembered = 0; + if (jp == bgjob) + bgjob = NULL; + } + } + for (jp = jobtab ; ; jp++) { + if (jp >= jobtab + njobs) { /* no running procs */ + return 0; + } + if (jp->used && jp->state == 0) + break; + } + } + } while (dowait(DOWAIT_BLOCK | DOWAIT_SIG, (struct job *)NULL) != -1); + + sig = pendingsig_waitcmd; + pendingsig_waitcmd = 0; + return sig + 128; +} + + + +int +jobidcmd(int argc __unused, char **argv __unused) +{ + struct job *jp; + int i; + + nextopt(""); + jp = getjob(*argptr); + for (i = 0 ; i < jp->nprocs ; ) { + out1fmt("%d", (int)jp->ps[i].pid); + out1c(++i < jp->nprocs? ' ' : '\n'); + } + return 0; +} + + + +/* + * Convert a job name to a job structure. + */ + +static struct job * +getjob_nonotfound(const char *name) +{ + int jobno; + struct job *found, *jp; + size_t namelen; + pid_t pid; + int i; + + if (name == NULL) { +#if JOBS + name = "%+"; +#else + error("No current job"); +#endif + } + if (name[0] == '%') { + if (is_digit(name[1])) { + jobno = number(name + 1); + if (jobno > 0 && jobno <= njobs + && jobtab[jobno - 1].used != 0) + return &jobtab[jobno - 1]; +#if JOBS + } else if ((name[1] == '%' || name[1] == '+') && + name[2] == '\0') { + if ((jp = getcurjob(NULL)) == NULL) + error("No current job"); + return (jp); + } else if (name[1] == '-' && name[2] == '\0') { + if ((jp = getcurjob(NULL)) == NULL || + (jp = getcurjob(jp)) == NULL) + error("No previous job"); + return (jp); +#endif + } else if (name[1] == '?') { + found = NULL; + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (jp->used && jp->nprocs > 0 + && strstr(jp->ps[0].cmd, name + 2) != NULL) { + if (found) + error("%s: ambiguous", name); + found = jp; + } + } + if (found != NULL) + return (found); + } else { + namelen = strlen(name); + found = NULL; + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (jp->used && jp->nprocs > 0 + && strncmp(jp->ps[0].cmd, name + 1, + namelen - 1) == 0) { + if (found) + error("%s: ambiguous", name); + found = jp; + } + } + if (found) + return found; + } + } else if (is_number(name)) { + pid = (pid_t)number(name); + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (jp->used && jp->nprocs > 0 + && jp->ps[jp->nprocs - 1].pid == pid) + return jp; + } + } + return NULL; +} + + +static struct job * +getjob(const char *name) +{ + struct job *jp; + + jp = getjob_nonotfound(name); + if (jp == NULL) + error("No such job: %s", name); + return (jp); +} + + +int +killjob(const char *name, int sig) +{ + struct job *jp; + int i, ret; + + jp = getjob(name); + if (jp->state == JOBDONE) + return 0; + if (jp->jobctl) + return kill(-jp->ps[0].pid, sig); + ret = -1; + errno = ESRCH; + for (i = 0; i < jp->nprocs; i++) + if (jp->ps[i].status == -1 || WIFSTOPPED(jp->ps[i].status)) { + if (kill(jp->ps[i].pid, sig) == 0) + ret = 0; + } else + ret = 0; + return ret; +} + +/* + * Return a new job structure, + */ + +struct job * +makejob(union node *node __unused, int nprocs) +{ + int i; + struct job *jp; + + for (i = njobs, jp = jobtab ; ; jp++) { + if (--i < 0) { + INTOFF; + if (njobs == 0) { + jobtab = ckmalloc(4 * sizeof jobtab[0]); +#if JOBS + jobmru = NULL; +#endif + } else { + jp = ckmalloc((njobs + 4) * sizeof jobtab[0]); + memcpy(jp, jobtab, njobs * sizeof jp[0]); +#if JOBS + /* Relocate `next' pointers and list head */ + if (jobmru != NULL) + jobmru = &jp[jobmru - jobtab]; + for (i = 0; i < njobs; i++) + if (jp[i].next != NULL) + jp[i].next = &jp[jp[i].next - + jobtab]; +#endif + if (bgjob != NULL) + bgjob = &jp[bgjob - jobtab]; + /* Relocate `ps' pointers */ + for (i = 0; i < njobs; i++) + if (jp[i].ps == &jobtab[i].ps0) + jp[i].ps = &jp[i].ps0; + ckfree(jobtab); + jobtab = jp; + } + jp = jobtab + njobs; + for (i = 4 ; --i >= 0 ; jobtab[njobs++].used = 0) + ; + INTON; + break; + } + if (jp->used == 0) + break; + } + INTOFF; + jp->state = 0; + jp->used = 1; + jp->changed = 0; + jp->nprocs = 0; + jp->foreground = 0; + jp->remembered = 0; +#if JOBS + jp->jobctl = jobctl; + jp->next = NULL; +#endif + if (nprocs > 1) { + jp->ps = ckmalloc(nprocs * sizeof (struct procstat)); + } else { + jp->ps = &jp->ps0; + } + INTON; + TRACE(("makejob(%p, %d) returns %%%td\n", (void *)node, nprocs, + jp - jobtab + 1)); + return jp; +} + +#if JOBS +static void +setcurjob(struct job *cj) +{ + struct job *jp, *prev; + + for (prev = NULL, jp = jobmru; jp != NULL; prev = jp, jp = jp->next) { + if (jp == cj) { + if (prev != NULL) + prev->next = jp->next; + else + jobmru = jp->next; + jp->next = jobmru; + jobmru = cj; + return; + } + } + cj->next = jobmru; + jobmru = cj; +} + +static void +deljob(struct job *j) +{ + struct job *jp, *prev; + + for (prev = NULL, jp = jobmru; jp != NULL; prev = jp, jp = jp->next) { + if (jp == j) { + if (prev != NULL) + prev->next = jp->next; + else + jobmru = jp->next; + return; + } + } +} + +/* + * Return the most recently used job that isn't `nj', and preferably one + * that is stopped. + */ +static struct job * +getcurjob(struct job *nj) +{ + struct job *jp; + + /* Try to find a stopped one.. */ + for (jp = jobmru; jp != NULL; jp = jp->next) + if (jp->used && jp != nj && jp->state == JOBSTOPPED) + return (jp); + /* Otherwise the most recently used job that isn't `nj' */ + for (jp = jobmru; jp != NULL; jp = jp->next) + if (jp->used && jp != nj) + return (jp); + + return (NULL); +} + +#endif + +/* + * Fork of a subshell. If we are doing job control, give the subshell its + * own process group. Jp is a job structure that the job is to be added to. + * N is the command that will be evaluated by the child. Both jp and n may + * be NULL. The mode parameter can be one of the following: + * FORK_FG - Fork off a foreground process. + * FORK_BG - Fork off a background process. + * FORK_NOJOB - Like FORK_FG, but don't give the process its own + * process group even if job control is on. + * + * When job control is turned off, background processes have their standard + * input redirected to /dev/null (except for the second and later processes + * in a pipeline). + */ + +pid_t +forkshell(struct job *jp, union node *n, int mode) +{ + pid_t pid; + pid_t pgrp; + + TRACE(("forkshell(%%%td, %p, %d) called\n", jp - jobtab, (void *)n, + mode)); + INTOFF; + if (mode == FORK_BG && (jp == NULL || jp->nprocs == 0)) + checkzombies(); + flushall(); + pid = fork(); + if (pid == -1) { + TRACE(("Fork failed, errno=%d\n", errno)); + INTON; + error("Cannot fork: %s", strerror(errno)); + } + if (pid == 0) { + struct job *p; + int wasroot; + int i; + + TRACE(("Child shell %d\n", (int)getpid())); + wasroot = rootshell; + rootshell = 0; + handler = &main_handler; + closescript(); + INTON; + forcelocal = 0; + clear_traps(); +#if JOBS + jobctl = 0; /* do job control only in root shell */ + if (wasroot && mode != FORK_NOJOB && mflag) { + if (jp == NULL || jp->nprocs == 0) + pgrp = getpid(); + else + pgrp = jp->ps[0].pid; + if (setpgid(0, pgrp) == 0 && mode == FORK_FG && + ttyfd >= 0) { + /*** this causes superfluous TIOCSPGRPS ***/ + if (tcsetpgrp(ttyfd, pgrp) < 0) + error("tcsetpgrp failed, errno=%d", errno); + } + setsignal(SIGTSTP); + setsignal(SIGTTOU); + } else if (mode == FORK_BG) { + ignoresig(SIGINT); + ignoresig(SIGQUIT); + if ((jp == NULL || jp->nprocs == 0) && + ! fd0_redirected_p ()) { + close(0); + if (open(_PATH_DEVNULL, O_RDONLY) != 0) + error("cannot open %s: %s", + _PATH_DEVNULL, strerror(errno)); + } + } +#else + if (mode == FORK_BG) { + ignoresig(SIGINT); + ignoresig(SIGQUIT); + if ((jp == NULL || jp->nprocs == 0) && + ! fd0_redirected_p ()) { + close(0); + if (open(_PATH_DEVNULL, O_RDONLY) != 0) + error("cannot open %s: %s", + _PATH_DEVNULL, strerror(errno)); + } + } +#endif + INTOFF; + for (i = njobs, p = jobtab ; --i >= 0 ; p++) + if (p->used) + freejob(p); + INTON; + if (wasroot && iflag) { + setsignal(SIGINT); + setsignal(SIGQUIT); + setsignal(SIGTERM); + } + return pid; + } + if (rootshell && mode != FORK_NOJOB && mflag) { + if (jp == NULL || jp->nprocs == 0) + pgrp = pid; + else + pgrp = jp->ps[0].pid; + setpgid(pid, pgrp); + } + if (mode == FORK_BG) { + if (bgjob != NULL && bgjob->state == JOBDONE && + !bgjob->remembered && !iflag) + freejob(bgjob); + backgndpid = pid; /* set $! */ + bgjob = jp; + } + if (jp) { + struct procstat *ps = &jp->ps[jp->nprocs++]; + ps->pid = pid; + ps->status = -1; + ps->cmd = nullstr; + if (iflag && rootshell && n) + ps->cmd = commandtext(n); + jp->foreground = mode == FORK_FG; +#if JOBS + setcurjob(jp); +#endif + } + INTON; + TRACE(("In parent shell: child = %d\n", (int)pid)); + return pid; +} + + +pid_t +vforkexecshell(struct job *jp, char **argv, char **envp, const char *path, int idx, int pip[2]) +{ + pid_t pid; + struct jmploc jmploc; + struct jmploc *savehandler; + + TRACE(("vforkexecshell(%%%td, %s, %p) called\n", jp - jobtab, argv[0], + (void *)pip)); + INTOFF; + flushall(); + savehandler = handler; + pid = vfork(); + if (pid == -1) { + TRACE(("Vfork failed, errno=%d\n", errno)); + INTON; + error("Cannot fork: %s", strerror(errno)); + } + if (pid == 0) { + TRACE(("Child shell %d\n", (int)getpid())); + if (setjmp(jmploc.loc)) + _exit(exception == EXEXEC ? exerrno : 2); + if (pip != NULL) { + close(pip[0]); + if (pip[1] != 1) { + dup2(pip[1], 1); + close(pip[1]); + } + } + handler = &jmploc; + shellexec(argv, envp, path, idx); + } + handler = savehandler; + if (jp) { + struct procstat *ps = &jp->ps[jp->nprocs++]; + ps->pid = pid; + ps->status = -1; + ps->cmd = nullstr; + jp->foreground = 1; +#if JOBS + setcurjob(jp); +#endif + } + INTON; + TRACE(("In parent shell: child = %d\n", (int)pid)); + return pid; +} + + +/* + * Wait for job to finish. + * + * Under job control we have the problem that while a child process is + * running interrupts generated by the user are sent to the child but not + * to the shell. This means that an infinite loop started by an inter- + * active user may be hard to kill. With job control turned off, an + * interactive user may place an interactive program inside a loop. If + * the interactive program catches interrupts, the user doesn't want + * these interrupts to also abort the loop. The approach we take here + * is to have the shell ignore interrupt signals while waiting for a + * foreground process to terminate, and then send itself an interrupt + * signal if the child process was terminated by an interrupt signal. + * Unfortunately, some programs want to do a bit of cleanup and then + * exit on interrupt; unless these processes terminate themselves by + * sending a signal to themselves (instead of calling exit) they will + * confuse this approach. + */ + +int +waitforjob(struct job *jp, int *signaled) +{ +#if JOBS + int propagate_int = jp->jobctl && jp->foreground; +#endif + int status; + int st; + + INTOFF; + TRACE(("waitforjob(%%%td) called\n", jp - jobtab + 1)); + while (jp->state == 0) + if (dowait(DOWAIT_BLOCK | (Tflag ? DOWAIT_SIG | + DOWAIT_SIG_TRAP : 0), jp) == -1) + dotrap(); +#if JOBS + if (jp->jobctl) { + if (ttyfd >= 0 && tcsetpgrp(ttyfd, rootpid) < 0) + error("tcsetpgrp failed, errno=%d\n", errno); + } + if (jp->state == JOBSTOPPED) + setcurjob(jp); +#endif + status = jp->ps[jp->nprocs - 1].status; + if (signaled != NULL) + *signaled = WIFSIGNALED(status); + /* convert to 8 bits */ + if (WIFEXITED(status)) + st = WEXITSTATUS(status); +#if JOBS + else if (WIFSTOPPED(status)) + st = WSTOPSIG(status) + 128; +#endif + else + st = WTERMSIG(status) + 128; + if (! JOBS || jp->state == JOBDONE) + freejob(jp); + if (int_pending()) { + if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGINT) + CLEAR_PENDING_INT; + } +#if JOBS + else if (rootshell && propagate_int && + WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) + kill(getpid(), SIGINT); +#endif + INTON; + return st; +} + + +static void +dummy_handler(int sig __unused) +{ +} + +/* + * Wait for a process to terminate. + */ + +static pid_t +dowait(int mode, struct job *job) +{ + struct sigaction sa, osa; + sigset_t mask, omask; + pid_t pid; + int status; + struct procstat *sp; + struct job *jp; + struct job *thisjob; + const char *sigstr; + int done; + int stopped; + int sig; + int coredump; + int wflags; + int restore_sigchld; + + TRACE(("dowait(%d, %p) called\n", mode, job)); + restore_sigchld = 0; + if ((mode & DOWAIT_SIG) != 0) { + sigfillset(&mask); + sigprocmask(SIG_BLOCK, &mask, &omask); + INTOFF; + if (!issigchldtrapped()) { + restore_sigchld = 1; + sa.sa_handler = dummy_handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(SIGCHLD, &sa, &osa); + } + } + do { +#if JOBS + if (iflag) + wflags = WUNTRACED | WCONTINUED; + else +#endif + wflags = 0; + if ((mode & (DOWAIT_BLOCK | DOWAIT_SIG)) != DOWAIT_BLOCK) + wflags |= WNOHANG; + pid = wait3(&status, wflags, (struct rusage *)NULL); + TRACE(("wait returns %d, status=%d\n", (int)pid, status)); + if (pid == 0 && (mode & DOWAIT_SIG) != 0) { + pid = -1; + if (((mode & DOWAIT_SIG_TRAP) != 0 ? + pendingsig : pendingsig_waitcmd) != 0) { + errno = EINTR; + break; + } + sigsuspend(&omask); + if (int_pending()) + break; + } + } while (pid == -1 && errno == EINTR); + if (pid == -1 && errno == ECHILD && job != NULL) + job->state = JOBDONE; + if ((mode & DOWAIT_SIG) != 0) { + if (restore_sigchld) + sigaction(SIGCHLD, &osa, NULL); + sigprocmask(SIG_SETMASK, &omask, NULL); + INTON; + } + if (pid <= 0) + return pid; + INTOFF; + thisjob = NULL; + for (jp = jobtab ; jp < jobtab + njobs ; jp++) { + if (jp->used && jp->nprocs > 0) { + done = 1; + stopped = 1; + for (sp = jp->ps ; sp < jp->ps + jp->nprocs ; sp++) { + if (sp->pid == -1) + continue; + if (sp->pid == pid && (sp->status == -1 || + WIFSTOPPED(sp->status))) { + TRACE(("Changing status of proc %d from 0x%x to 0x%x\n", + (int)pid, sp->status, + status)); + if (WIFCONTINUED(status)) { + sp->status = -1; + jp->state = 0; + } else + sp->status = status; + thisjob = jp; + } + if (sp->status == -1) + stopped = 0; + else if (WIFSTOPPED(sp->status)) + done = 0; + } + if (stopped) { /* stopped or done */ + int state = done? JOBDONE : JOBSTOPPED; + if (jp->state != state) { + TRACE(("Job %td: changing state from %d to %d\n", jp - jobtab + 1, jp->state, state)); + jp->state = state; + if (jp != job) { + if (done && !jp->remembered && + !iflag && jp != bgjob) + freejob(jp); +#if JOBS + else if (done) + deljob(jp); +#endif + } + } + } + } + } + INTON; + if (!thisjob || thisjob->state == 0) + ; + else if ((!rootshell || !iflag || thisjob == job) && + thisjob->foreground && thisjob->state != JOBSTOPPED) { + sig = 0; + coredump = 0; + for (sp = thisjob->ps; sp < thisjob->ps + thisjob->nprocs; sp++) + if (WIFSIGNALED(sp->status)) { + sig = WTERMSIG(sp->status); + coredump = WCOREDUMP(sp->status); + } + if (sig > 0 && sig != SIGINT && sig != SIGPIPE) { + sigstr = strsignal(sig); + if (sigstr != NULL) + out2str(sigstr); + else + out2str("Unknown signal"); + if (coredump) + out2str(" (core dumped)"); + out2c('\n'); + flushout(out2); + } + } else { + TRACE(("Not printing status, rootshell=%d, job=%p\n", rootshell, job)); + thisjob->changed = 1; + } + return pid; +} + + + +/* + * return 1 if there are stopped jobs, otherwise 0 + */ +int job_warning = 0; +int +stoppedjobs(void) +{ + int jobno; + struct job *jp; + + if (job_warning) + return (0); + for (jobno = 1, jp = jobtab; jobno <= njobs; jobno++, jp++) { + if (jp->used == 0) + continue; + if (jp->state == JOBSTOPPED) { + out2fmt_flush("You have stopped jobs.\n"); + job_warning = 2; + return (1); + } + } + + return (0); +} + + +static void +checkzombies(void) +{ + while (njobs > 0 && dowait(0, NULL) > 0) + ; +} + + +int +backgndpidset(void) +{ + return backgndpid != -1; +} + + +pid_t +backgndpidval(void) +{ + if (bgjob != NULL && !forcelocal) + bgjob->remembered = 1; + return backgndpid; +} + +/* + * Return a string identifying a command (to be printed by the + * jobs command. + */ + +static char *cmdnextc; +static int cmdnleft; +#define MAXCMDTEXT 200 + +char * +commandtext(union node *n) +{ + char *name; + + cmdnextc = name = ckmalloc(MAXCMDTEXT); + cmdnleft = MAXCMDTEXT - 4; + cmdtxt(n); + *cmdnextc = '\0'; + return name; +} + + +static void +cmdtxtdogroup(union node *n) +{ + cmdputs("; do "); + cmdtxt(n); + cmdputs("; done"); +} + + +static void +cmdtxtredir(union node *n, const char *op, int deffd) +{ + char s[2]; + + if (n->nfile.fd != deffd) { + s[0] = n->nfile.fd + '0'; + s[1] = '\0'; + cmdputs(s); + } + cmdputs(op); + if (n->type == NTOFD || n->type == NFROMFD) { + if (n->ndup.dupfd >= 0) + s[0] = n->ndup.dupfd + '0'; + else + s[0] = '-'; + s[1] = '\0'; + cmdputs(s); + } else { + cmdtxt(n->nfile.fname); + } +} + + +static void +cmdtxt(union node *n) +{ + union node *np; + struct nodelist *lp; + + if (n == NULL) + return; + switch (n->type) { + case NSEMI: + cmdtxt(n->nbinary.ch1); + cmdputs("; "); + cmdtxt(n->nbinary.ch2); + break; + case NAND: + cmdtxt(n->nbinary.ch1); + cmdputs(" && "); + cmdtxt(n->nbinary.ch2); + break; + case NOR: + cmdtxt(n->nbinary.ch1); + cmdputs(" || "); + cmdtxt(n->nbinary.ch2); + break; + case NPIPE: + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + cmdtxt(lp->n); + if (lp->next) + cmdputs(" | "); + } + break; + case NSUBSHELL: + cmdputs("("); + cmdtxt(n->nredir.n); + cmdputs(")"); + break; + case NREDIR: + case NBACKGND: + cmdtxt(n->nredir.n); + break; + case NIF: + cmdputs("if "); + cmdtxt(n->nif.test); + cmdputs("; then "); + cmdtxt(n->nif.ifpart); + cmdputs("..."); + break; + case NWHILE: + cmdputs("while "); + cmdtxt(n->nbinary.ch1); + cmdtxtdogroup(n->nbinary.ch2); + break; + case NUNTIL: + cmdputs("until "); + cmdtxt(n->nbinary.ch1); + cmdtxtdogroup(n->nbinary.ch2); + break; + case NFOR: + cmdputs("for "); + cmdputs(n->nfor.var); + cmdputs(" in ..."); + break; + case NCASE: + cmdputs("case "); + cmdputs(n->ncase.expr->narg.text); + cmdputs(" in ..."); + break; + case NDEFUN: + cmdputs(n->narg.text); + cmdputs("() ..."); + break; + case NNOT: + cmdputs("! "); + cmdtxt(n->nnot.com); + break; + case NCMD: + for (np = n->ncmd.args ; np ; np = np->narg.next) { + cmdtxt(np); + if (np->narg.next) + cmdputs(" "); + } + for (np = n->ncmd.redirect ; np ; np = np->nfile.next) { + cmdputs(" "); + cmdtxt(np); + } + break; + case NARG: + cmdputs(n->narg.text); + break; + case NTO: + cmdtxtredir(n, ">", 1); + break; + case NAPPEND: + cmdtxtredir(n, ">>", 1); + break; + case NTOFD: + cmdtxtredir(n, ">&", 1); + break; + case NCLOBBER: + cmdtxtredir(n, ">|", 1); + break; + case NFROM: + cmdtxtredir(n, "<", 0); + break; + case NFROMTO: + cmdtxtredir(n, "<>", 0); + break; + case NFROMFD: + cmdtxtredir(n, "<&", 0); + break; + case NHERE: + case NXHERE: + cmdputs("<<..."); + break; + default: + cmdputs("???"); + break; + } +} + + + +static void +cmdputs(const char *s) +{ + const char *p; + char *q; + char c; + int subtype = 0; + + if (cmdnleft <= 0) + return; + p = s; + q = cmdnextc; + while ((c = *p++) != '\0') { + if (c == CTLESC) + *q++ = *p++; + else if (c == CTLVAR) { + *q++ = '$'; + if (--cmdnleft > 0) + *q++ = '{'; + subtype = *p++; + if ((subtype & VSTYPE) == VSLENGTH && --cmdnleft > 0) + *q++ = '#'; + } else if (c == '=' && subtype != 0) { + *q = "}-+?=##%%\0X"[(subtype & VSTYPE) - VSNORMAL]; + if (*q) + q++; + else + cmdnleft++; + if (((subtype & VSTYPE) == VSTRIMLEFTMAX || + (subtype & VSTYPE) == VSTRIMRIGHTMAX) && + --cmdnleft > 0) + *q = q[-1], q++; + subtype = 0; + } else if (c == CTLENDVAR) { + *q++ = '}'; + } else if (c == CTLBACKQ || c == CTLBACKQ+CTLQUOTE) { + cmdnleft -= 5; + if (cmdnleft > 0) { + *q++ = '$'; + *q++ = '('; + *q++ = '.'; + *q++ = '.'; + *q++ = '.'; + *q++ = ')'; + } + } else if (c == CTLARI) { + cmdnleft -= 2; + if (cmdnleft > 0) { + *q++ = '$'; + *q++ = '('; + *q++ = '('; + } + p++; + } else if (c == CTLENDARI) { + if (--cmdnleft > 0) { + *q++ = ')'; + *q++ = ')'; + } + } else if (c == CTLQUOTEMARK || c == CTLQUOTEEND) + cmdnleft++; /* ignore */ + else + *q++ = c; + if (--cmdnleft <= 0) { + *q++ = '.'; + *q++ = '.'; + *q++ = '.'; + break; + } + } + cmdnextc = q; +} diff --git a/bin/catsh/jobs.h b/bin/catsh/jobs.h new file mode 100644 index 00000000..52c3abe1 --- /dev/null +++ b/bin/catsh/jobs.h @@ -0,0 +1,67 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)jobs.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/jobs.h 327475 2018-01-01 22:31:52Z jilles $ + */ + +/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */ +#define FORK_FG 0 +#define FORK_BG 1 +#define FORK_NOJOB 2 + +#include <signal.h> /* for sig_atomic_t */ + +struct job; + +enum { + SHOWJOBS_DEFAULT, /* job number, status, command */ + SHOWJOBS_VERBOSE, /* job number, PID, status, command */ + SHOWJOBS_PIDS, /* PID only */ + SHOWJOBS_PGIDS /* PID of the group leader only */ +}; + +extern int job_warning; /* user was warned about stopped jobs */ + +void setjobctl(int); +void showjobs(int, int); +struct job *makejob(union node *, int); +pid_t forkshell(struct job *, union node *, int); +pid_t vforkexecshell(struct job *, char **, char **, const char *, int, int []); +int waitforjob(struct job *, int *); +int stoppedjobs(void); +int backgndpidset(void); +pid_t backgndpidval(void); +char *commandtext(union node *); + +#if ! JOBS +#define setjobctl(on) /* do nothing */ +#endif diff --git a/bin/catsh/kill.c b/bin/catsh/kill.c new file mode 100644 index 00000000..f58987cc --- /dev/null +++ b/bin/catsh/kill.c @@ -0,0 +1,215 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/* + * Important: This file is used both as a standalone program /bin/kill and + * as a builtin for /bin/sh (#define SHELL). + */ + +#if 0 +#ifndef lint +static char const copyright[] = +"@(#) Copyright (c) 1988, 1993, 1994\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +static char sccsid[] = "@(#)kill.c 8.4 (Berkeley) 4/28/95"; +#endif /* not lint */ +#endif +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/kill/kill.c 326025 2017-11-20 19:49:47Z pfg $"); + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef SHELL +#define main killcmd +#include "bltin.h" +#endif + +static void nosig(const char *); +static void printsignals(FILE *); +static int signame_to_signum(const char *); +static void usage(void); + +int +main(int argc, char *argv[]) +{ + long pidl; + pid_t pid; + int errors, numsig, ret; + char *ep; + + if (argc < 2) + usage(); + + numsig = SIGTERM; + + argc--, argv++; + if (!strcmp(*argv, "-l")) { + argc--, argv++; + if (argc > 1) + usage(); + if (argc == 1) { + if (!isdigit(**argv)) + usage(); + numsig = strtol(*argv, &ep, 10); + if (!**argv || *ep) + errx(2, "illegal signal number: %s", *argv); + if (numsig >= 128) + numsig -= 128; + if (numsig <= 0 || numsig >= NSIG) + nosig(*argv); + printf("%s\n", sys_signame[numsig]); + return (0); + } + printsignals(stdout); + return (0); + } + + if (!strcmp(*argv, "-s")) { + argc--, argv++; + if (argc < 1) { + warnx("option requires an argument -- s"); + usage(); + } + if (strcmp(*argv, "0")) { + if ((numsig = signame_to_signum(*argv)) < 0) + nosig(*argv); + } else + numsig = 0; + argc--, argv++; + } else if (**argv == '-' && *(*argv + 1) != '-') { + ++*argv; + if (isalpha(**argv)) { + if ((numsig = signame_to_signum(*argv)) < 0) + nosig(*argv); + } else if (isdigit(**argv)) { + numsig = strtol(*argv, &ep, 10); + if (!**argv || *ep) + errx(2, "illegal signal number: %s", *argv); + if (numsig < 0) + nosig(*argv); + } else + nosig(*argv); + argc--, argv++; + } + + if (argc > 0 && strncmp(*argv, "--", 2) == 0) + argc--, argv++; + + if (argc == 0) + usage(); + + for (errors = 0; argc; argc--, argv++) { +#ifdef SHELL + if (**argv == '%') + ret = killjob(*argv, numsig); + else +#endif + { + pidl = strtol(*argv, &ep, 10); + /* Check for overflow of pid_t. */ + pid = (pid_t)pidl; + if (!**argv || *ep || pid != pidl) + errx(2, "illegal process id: %s", *argv); + ret = kill(pid, numsig); + } + if (ret == -1) { + warn("%s", *argv); + errors = 1; + } + } + + return (errors); +} + +static int +signame_to_signum(const char *sig) +{ + int n; + + if (strncasecmp(sig, "SIG", 3) == 0) + sig += 3; + for (n = 1; n < NSIG; n++) { + if (!strcasecmp(sys_signame[n], sig)) + return (n); + } + return (-1); +} + +static void +nosig(const char *name) +{ + + warnx("unknown signal %s; valid signals:", name); + printsignals(stderr); +#ifdef SHELL + error(NULL); +#else + exit(2); +#endif +} + +static void +printsignals(FILE *fp) +{ + int n; + + for (n = 1; n < NSIG; n++) { + (void)fprintf(fp, "%s", sys_signame[n]); + if (n == (NSIG / 2) || n == (NSIG - 1)) + (void)fprintf(fp, "\n"); + else + (void)fprintf(fp, " "); + } +} + +static void +usage(void) +{ + + (void)fprintf(stderr, "%s\n%s\n%s\n%s\n", + "usage: kill [-s signal_name] pid ...", + " kill -l [exit_status]", + " kill -signal_name pid ...", + " kill -signal_number pid ..."); +#ifdef SHELL + error(NULL); +#else + exit(2); +#endif +} diff --git a/bin/catsh/libedit/.gitignore b/bin/catsh/libedit/.gitignore new file mode 100644 index 00000000..eeae3b81 --- /dev/null +++ b/bin/catsh/libedit/.gitignore @@ -0,0 +1,9 @@ +*.o +common.h +config.mk +emacs.h +fcns.h +func.h +help.h +libedit.a +vi.h diff --git a/bin/catsh/libedit/Makefile b/bin/catsh/libedit/Makefile new file mode 100644 index 00000000..9b13d59d --- /dev/null +++ b/bin/catsh/libedit/Makefile @@ -0,0 +1,58 @@ +CFLAGS += -std=c99 -Wall -Wextra + +-include config.mk + +OBJS += chared.o +OBJS += chartype.o +OBJS += common.o +OBJS += el.o +OBJS += eln.o +OBJS += emacs.o +OBJS += filecomplete.o +OBJS += hist.o +OBJS += history.o +OBJS += historyn.o +OBJS += keymacro.o +OBJS += literal.o +OBJS += map.o +OBJS += parse.o +OBJS += prompt.o +OBJS += read.o +OBJS += readline.o +OBJS += refresh.o +OBJS += search.o +OBJS += sig.o +OBJS += terminal.o +OBJS += tokenizer.o +OBJS += tokenizern.o +OBJS += tty.o +OBJS += vi.o + +AHDR = common.h emacs.h vi.h +ASRC = common.c emacs.c vi.c + +libedit.a: $(OBJS) + $(AR) $(ARFLAGS) $@ $(OBJS) + +$(OBJS): $(AHDR) fcns.h func.h help.h + +common.h: makelist common.c + sh makelist -h common.c > common.h + +emacs.h: makelist emacs.c + sh makelist -h emacs.c > emacs.h + +vi.h: makelist vi.c + sh makelist -h vi.c > vi.h + +fcns.h: makelist $(AHDR) + sh makelist -fh $(AHDR) > fcns.h + +func.h: makelist $(AHDR) + sh makelist -fc $(AHDR) > func.h + +help.h: makelist $(ASRC) + sh makelist -bh $(ASRC) > help.h + +clean: + rm -f libedit.a $(OBJS) $(AHDR) fcns.h func.h help.h diff --git a/bin/catsh/libedit/chared.c b/bin/catsh/libedit/chared.c new file mode 100644 index 00000000..9d46ba68 --- /dev/null +++ b/bin/catsh/libedit/chared.c @@ -0,0 +1,753 @@ +/* $NetBSD: chared.c,v 1.56 2016/05/22 19:44:26 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)chared.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: chared.c,v 1.56 2016/05/22 19:44:26 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * chared.c: Character editor utilities + */ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include "el.h" +#include "common.h" +#include "fcns.h" + +/* value to leave unused in line buffer */ +#define EL_LEAVE 2 + +/* cv_undo(): + * Handle state for the vi undo command + */ +libedit_private void +cv_undo(EditLine *el) +{ + c_undo_t *vu = &el->el_chared.c_undo; + c_redo_t *r = &el->el_chared.c_redo; + size_t size; + + /* Save entire line for undo */ + size = (size_t)(el->el_line.lastchar - el->el_line.buffer); + vu->len = (ssize_t)size; + vu->cursor = (int)(el->el_line.cursor - el->el_line.buffer); + (void)memcpy(vu->buf, el->el_line.buffer, size * sizeof(*vu->buf)); + + /* save command info for redo */ + r->count = el->el_state.doingarg ? el->el_state.argument : 0; + r->action = el->el_chared.c_vcmd.action; + r->pos = r->buf; + r->cmd = el->el_state.thiscmd; + r->ch = el->el_state.thisch; +} + +/* cv_yank(): + * Save yank/delete data for paste + */ +libedit_private void +cv_yank(EditLine *el, const wchar_t *ptr, int size) +{ + c_kill_t *k = &el->el_chared.c_kill; + + (void)memcpy(k->buf, ptr, (size_t)size * sizeof(*k->buf)); + k->last = k->buf + size; +} + + +/* c_insert(): + * Insert num characters + */ +libedit_private void +c_insert(EditLine *el, int num) +{ + wchar_t *cp; + + if (el->el_line.lastchar + num >= el->el_line.limit) { + if (!ch_enlargebufs(el, (size_t)num)) + return; /* can't go past end of buffer */ + } + + if (el->el_line.cursor < el->el_line.lastchar) { + /* if I must move chars */ + for (cp = el->el_line.lastchar; cp >= el->el_line.cursor; cp--) + cp[num] = *cp; + } + el->el_line.lastchar += num; +} + + +/* c_delafter(): + * Delete num characters after the cursor + */ +libedit_private void +c_delafter(EditLine *el, int num) +{ + + if (el->el_line.cursor + num > el->el_line.lastchar) + num = (int)(el->el_line.lastchar - el->el_line.cursor); + + if (el->el_map.current != el->el_map.emacs) { + cv_undo(el); + cv_yank(el, el->el_line.cursor, num); + } + + if (num > 0) { + wchar_t *cp; + + for (cp = el->el_line.cursor; cp <= el->el_line.lastchar; cp++) + *cp = cp[num]; + + el->el_line.lastchar -= num; + } +} + + +/* c_delafter1(): + * Delete the character after the cursor, do not yank + */ +libedit_private void +c_delafter1(EditLine *el) +{ + wchar_t *cp; + + for (cp = el->el_line.cursor; cp <= el->el_line.lastchar; cp++) + *cp = cp[1]; + + el->el_line.lastchar--; +} + + +/* c_delbefore(): + * Delete num characters before the cursor + */ +libedit_private void +c_delbefore(EditLine *el, int num) +{ + + if (el->el_line.cursor - num < el->el_line.buffer) + num = (int)(el->el_line.cursor - el->el_line.buffer); + + if (el->el_map.current != el->el_map.emacs) { + cv_undo(el); + cv_yank(el, el->el_line.cursor - num, num); + } + + if (num > 0) { + wchar_t *cp; + + for (cp = el->el_line.cursor - num; + cp <= el->el_line.lastchar; + cp++) + *cp = cp[num]; + + el->el_line.lastchar -= num; + } +} + + +/* c_delbefore1(): + * Delete the character before the cursor, do not yank + */ +libedit_private void +c_delbefore1(EditLine *el) +{ + wchar_t *cp; + + for (cp = el->el_line.cursor - 1; cp <= el->el_line.lastchar; cp++) + *cp = cp[1]; + + el->el_line.lastchar--; +} + + +/* ce__isword(): + * Return if p is part of a word according to emacs + */ +libedit_private int +ce__isword(wint_t p) +{ + return iswalnum(p) || wcschr(L"*?_-.[]~=", p) != NULL; +} + + +/* cv__isword(): + * Return if p is part of a word according to vi + */ +libedit_private int +cv__isword(wint_t p) +{ + if (iswalnum(p) || p == L'_') + return 1; + if (iswgraph(p)) + return 2; + return 0; +} + + +/* cv__isWord(): + * Return if p is part of a big word according to vi + */ +libedit_private int +cv__isWord(wint_t p) +{ + return !iswspace(p); +} + + +/* c__prev_word(): + * Find the previous word + */ +libedit_private wchar_t * +c__prev_word(wchar_t *p, wchar_t *low, int n, int (*wtest)(wint_t)) +{ + p--; + + while (n--) { + while ((p >= low) && !(*wtest)(*p)) + p--; + while ((p >= low) && (*wtest)(*p)) + p--; + } + + /* cp now points to one character before the word */ + p++; + if (p < low) + p = low; + /* cp now points where we want it */ + return p; +} + + +/* c__next_word(): + * Find the next word + */ +libedit_private wchar_t * +c__next_word(wchar_t *p, wchar_t *high, int n, int (*wtest)(wint_t)) +{ + while (n--) { + while ((p < high) && !(*wtest)(*p)) + p++; + while ((p < high) && (*wtest)(*p)) + p++; + } + if (p > high) + p = high; + /* p now points where we want it */ + return p; +} + +/* cv_next_word(): + * Find the next word vi style + */ +libedit_private wchar_t * +cv_next_word(EditLine *el, wchar_t *p, wchar_t *high, int n, + int (*wtest)(wint_t)) +{ + int test; + + while (n--) { + test = (*wtest)(*p); + while ((p < high) && (*wtest)(*p) == test) + p++; + /* + * vi historically deletes with cw only the word preserving the + * trailing whitespace! This is not what 'w' does.. + */ + if (n || el->el_chared.c_vcmd.action != (DELETE|INSERT)) + while ((p < high) && iswspace(*p)) + p++; + } + + /* p now points where we want it */ + if (p > high) + return high; + else + return p; +} + + +/* cv_prev_word(): + * Find the previous word vi style + */ +libedit_private wchar_t * +cv_prev_word(wchar_t *p, wchar_t *low, int n, int (*wtest)(wint_t)) +{ + int test; + + p--; + while (n--) { + while ((p > low) && iswspace(*p)) + p--; + test = (*wtest)(*p); + while ((p >= low) && (*wtest)(*p) == test) + p--; + } + p++; + + /* p now points where we want it */ + if (p < low) + return low; + else + return p; +} + + +/* cv_delfini(): + * Finish vi delete action + */ +libedit_private void +cv_delfini(EditLine *el) +{ + int size; + int action = el->el_chared.c_vcmd.action; + + if (action & INSERT) + el->el_map.current = el->el_map.key; + + if (el->el_chared.c_vcmd.pos == 0) + /* sanity */ + return; + + size = (int)(el->el_line.cursor - el->el_chared.c_vcmd.pos); + if (size == 0) + size = 1; + el->el_line.cursor = el->el_chared.c_vcmd.pos; + if (action & YANK) { + if (size > 0) + cv_yank(el, el->el_line.cursor, size); + else + cv_yank(el, el->el_line.cursor + size, -size); + } else { + if (size > 0) { + c_delafter(el, size); + re_refresh_cursor(el); + } else { + c_delbefore(el, -size); + el->el_line.cursor += size; + } + } + el->el_chared.c_vcmd.action = NOP; +} + + +/* cv__endword(): + * Go to the end of this word according to vi + */ +libedit_private wchar_t * +cv__endword(wchar_t *p, wchar_t *high, int n, int (*wtest)(wint_t)) +{ + int test; + + p++; + + while (n--) { + while ((p < high) && iswspace(*p)) + p++; + + test = (*wtest)(*p); + while ((p < high) && (*wtest)(*p) == test) + p++; + } + p--; + return p; +} + +/* ch_init(): + * Initialize the character editor + */ +libedit_private int +ch_init(EditLine *el) +{ + el->el_line.buffer = el_malloc(EL_BUFSIZ * + sizeof(*el->el_line.buffer)); + if (el->el_line.buffer == NULL) + return -1; + + (void) memset(el->el_line.buffer, 0, EL_BUFSIZ * + sizeof(*el->el_line.buffer)); + el->el_line.cursor = el->el_line.buffer; + el->el_line.lastchar = el->el_line.buffer; + el->el_line.limit = &el->el_line.buffer[EL_BUFSIZ - EL_LEAVE]; + + el->el_chared.c_undo.buf = el_malloc(EL_BUFSIZ * + sizeof(*el->el_chared.c_undo.buf)); + if (el->el_chared.c_undo.buf == NULL) + return -1; + (void) memset(el->el_chared.c_undo.buf, 0, EL_BUFSIZ * + sizeof(*el->el_chared.c_undo.buf)); + el->el_chared.c_undo.len = -1; + el->el_chared.c_undo.cursor = 0; + el->el_chared.c_redo.buf = el_malloc(EL_BUFSIZ * + sizeof(*el->el_chared.c_redo.buf)); + if (el->el_chared.c_redo.buf == NULL) + return -1; + el->el_chared.c_redo.pos = el->el_chared.c_redo.buf; + el->el_chared.c_redo.lim = el->el_chared.c_redo.buf + EL_BUFSIZ; + el->el_chared.c_redo.cmd = ED_UNASSIGNED; + + el->el_chared.c_vcmd.action = NOP; + el->el_chared.c_vcmd.pos = el->el_line.buffer; + + el->el_chared.c_kill.buf = el_malloc(EL_BUFSIZ * + sizeof(*el->el_chared.c_kill.buf)); + if (el->el_chared.c_kill.buf == NULL) + return -1; + (void) memset(el->el_chared.c_kill.buf, 0, EL_BUFSIZ * + sizeof(*el->el_chared.c_kill.buf)); + el->el_chared.c_kill.mark = el->el_line.buffer; + el->el_chared.c_kill.last = el->el_chared.c_kill.buf; + el->el_chared.c_resizefun = NULL; + el->el_chared.c_resizearg = NULL; + el->el_chared.c_aliasfun = NULL; + el->el_chared.c_aliasarg = NULL; + + el->el_map.current = el->el_map.key; + + el->el_state.inputmode = MODE_INSERT; /* XXX: save a default */ + el->el_state.doingarg = 0; + el->el_state.metanext = 0; + el->el_state.argument = 1; + el->el_state.lastcmd = ED_UNASSIGNED; + + return 0; +} + +/* ch_reset(): + * Reset the character editor + */ +libedit_private void +ch_reset(EditLine *el) +{ + el->el_line.cursor = el->el_line.buffer; + el->el_line.lastchar = el->el_line.buffer; + + el->el_chared.c_undo.len = -1; + el->el_chared.c_undo.cursor = 0; + + el->el_chared.c_vcmd.action = NOP; + el->el_chared.c_vcmd.pos = el->el_line.buffer; + + el->el_chared.c_kill.mark = el->el_line.buffer; + + el->el_map.current = el->el_map.key; + + el->el_state.inputmode = MODE_INSERT; /* XXX: save a default */ + el->el_state.doingarg = 0; + el->el_state.metanext = 0; + el->el_state.argument = 1; + el->el_state.lastcmd = ED_UNASSIGNED; + + el->el_history.eventno = 0; +} + +/* ch_enlargebufs(): + * Enlarge line buffer to be able to hold twice as much characters. + * Returns 1 if successful, 0 if not. + */ +libedit_private int +ch_enlargebufs(EditLine *el, size_t addlen) +{ + size_t sz, newsz; + wchar_t *newbuffer, *oldbuf, *oldkbuf; + + sz = (size_t)(el->el_line.limit - el->el_line.buffer + EL_LEAVE); + newsz = sz * 2; + /* + * If newly required length is longer than current buffer, we need + * to make the buffer big enough to hold both old and new stuff. + */ + if (addlen > sz) { + while(newsz - sz < addlen) + newsz *= 2; + } + + /* + * Reallocate line buffer. + */ + newbuffer = el_realloc(el->el_line.buffer, newsz * sizeof(*newbuffer)); + if (!newbuffer) + return 0; + + /* zero the newly added memory, leave old data in */ + (void) memset(&newbuffer[sz], 0, (newsz - sz) * sizeof(*newbuffer)); + + oldbuf = el->el_line.buffer; + + el->el_line.buffer = newbuffer; + el->el_line.cursor = newbuffer + (el->el_line.cursor - oldbuf); + el->el_line.lastchar = newbuffer + (el->el_line.lastchar - oldbuf); + /* don't set new size until all buffers are enlarged */ + el->el_line.limit = &newbuffer[sz - EL_LEAVE]; + + /* + * Reallocate kill buffer. + */ + newbuffer = el_realloc(el->el_chared.c_kill.buf, newsz * + sizeof(*newbuffer)); + if (!newbuffer) + return 0; + + /* zero the newly added memory, leave old data in */ + (void) memset(&newbuffer[sz], 0, (newsz - sz) * sizeof(*newbuffer)); + + oldkbuf = el->el_chared.c_kill.buf; + + el->el_chared.c_kill.buf = newbuffer; + el->el_chared.c_kill.last = newbuffer + + (el->el_chared.c_kill.last - oldkbuf); + el->el_chared.c_kill.mark = el->el_line.buffer + + (el->el_chared.c_kill.mark - oldbuf); + + /* + * Reallocate undo buffer. + */ + newbuffer = el_realloc(el->el_chared.c_undo.buf, + newsz * sizeof(*newbuffer)); + if (!newbuffer) + return 0; + + /* zero the newly added memory, leave old data in */ + (void) memset(&newbuffer[sz], 0, (newsz - sz) * sizeof(*newbuffer)); + el->el_chared.c_undo.buf = newbuffer; + + newbuffer = el_realloc(el->el_chared.c_redo.buf, + newsz * sizeof(*newbuffer)); + if (!newbuffer) + return 0; + el->el_chared.c_redo.pos = newbuffer + + (el->el_chared.c_redo.pos - el->el_chared.c_redo.buf); + el->el_chared.c_redo.lim = newbuffer + + (el->el_chared.c_redo.lim - el->el_chared.c_redo.buf); + el->el_chared.c_redo.buf = newbuffer; + + if (!hist_enlargebuf(el, sz, newsz)) + return 0; + + /* Safe to set enlarged buffer size */ + el->el_line.limit = &el->el_line.buffer[newsz - EL_LEAVE]; + if (el->el_chared.c_resizefun) + (*el->el_chared.c_resizefun)(el, el->el_chared.c_resizearg); + return 1; +} + +/* ch_end(): + * Free the data structures used by the editor + */ +libedit_private void +ch_end(EditLine *el) +{ + el_free(el->el_line.buffer); + el->el_line.buffer = NULL; + el->el_line.limit = NULL; + el_free(el->el_chared.c_undo.buf); + el->el_chared.c_undo.buf = NULL; + el_free(el->el_chared.c_redo.buf); + el->el_chared.c_redo.buf = NULL; + el->el_chared.c_redo.pos = NULL; + el->el_chared.c_redo.lim = NULL; + el->el_chared.c_redo.cmd = ED_UNASSIGNED; + el_free(el->el_chared.c_kill.buf); + el->el_chared.c_kill.buf = NULL; + ch_reset(el); +} + + +/* el_insertstr(): + * Insert string at cursorI + */ +int +el_winsertstr(EditLine *el, const wchar_t *s) +{ + size_t len; + + if (s == NULL || (len = wcslen(s)) == 0) + return -1; + if (el->el_line.lastchar + len >= el->el_line.limit) { + if (!ch_enlargebufs(el, len)) + return -1; + } + + c_insert(el, (int)len); + while (*s) + *el->el_line.cursor++ = *s++; + return 0; +} + + +/* el_deletestr(): + * Delete num characters before the cursor + */ +void +el_deletestr(EditLine *el, int n) +{ + if (n <= 0) + return; + + if (el->el_line.cursor < &el->el_line.buffer[n]) + return; + + c_delbefore(el, n); /* delete before dot */ + el->el_line.cursor -= n; + if (el->el_line.cursor < el->el_line.buffer) + el->el_line.cursor = el->el_line.buffer; +} + +/* el_cursor(): + * Move the cursor to the left or the right of the current position + */ +int +el_cursor(EditLine *el, int n) +{ + if (n == 0) + goto out; + + el->el_line.cursor += n; + + if (el->el_line.cursor < el->el_line.buffer) + el->el_line.cursor = el->el_line.buffer; + if (el->el_line.cursor > el->el_line.lastchar) + el->el_line.cursor = el->el_line.lastchar; +out: + return (int)(el->el_line.cursor - el->el_line.buffer); +} + +/* c_gets(): + * Get a string + */ +libedit_private int +c_gets(EditLine *el, wchar_t *buf, const wchar_t *prompt) +{ + ssize_t len; + wchar_t *cp = el->el_line.buffer, ch; + + if (prompt) { + len = (ssize_t)wcslen(prompt); + (void)memcpy(cp, prompt, (size_t)len * sizeof(*cp)); + cp += len; + } + len = 0; + + for (;;) { + el->el_line.cursor = cp; + *cp = ' '; + el->el_line.lastchar = cp + 1; + re_refresh(el); + + if (el_wgetc(el, &ch) != 1) { + ed_end_of_file(el, 0); + len = -1; + break; + } + + switch (ch) { + + case L'\b': /* Delete and backspace */ + case 0177: + if (len == 0) { + len = -1; + break; + } + len--; + cp--; + continue; + + case 0033: /* ESC */ + case L'\r': /* Newline */ + case L'\n': + buf[len] = ch; + break; + + default: + if (len >= (ssize_t)(EL_BUFSIZ - 16)) + terminal_beep(el); + else { + buf[len++] = ch; + *cp++ = ch; + } + continue; + } + break; + } + + el->el_line.buffer[0] = '\0'; + el->el_line.lastchar = el->el_line.buffer; + el->el_line.cursor = el->el_line.buffer; + return (int)len; +} + + +/* c_hpos(): + * Return the current horizontal position of the cursor + */ +libedit_private int +c_hpos(EditLine *el) +{ + wchar_t *ptr; + + /* + * Find how many characters till the beginning of this line. + */ + if (el->el_line.cursor == el->el_line.buffer) + return 0; + else { + for (ptr = el->el_line.cursor - 1; + ptr >= el->el_line.buffer && *ptr != '\n'; + ptr--) + continue; + return (int)(el->el_line.cursor - ptr - 1); + } +} + +libedit_private int +ch_resizefun(EditLine *el, el_zfunc_t f, void *a) +{ + el->el_chared.c_resizefun = f; + el->el_chared.c_resizearg = a; + return 0; +} + +libedit_private int +ch_aliasfun(EditLine *el, el_afunc_t f, void *a) +{ + el->el_chared.c_aliasfun = f; + el->el_chared.c_aliasarg = a; + return 0; +} diff --git a/bin/catsh/libedit/chared.h b/bin/catsh/libedit/chared.h new file mode 100644 index 00000000..39f7d517 --- /dev/null +++ b/bin/catsh/libedit/chared.h @@ -0,0 +1,155 @@ +/* $NetBSD: chared.h,v 1.30 2016/05/22 19:44:26 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)chared.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.chared.h: Character editor interface + */ +#ifndef _h_el_chared +#define _h_el_chared + +/* + * This is an issue of basic "vi" look-and-feel. Defining VI_MOVE works + * like real vi: i.e. the transition from command<->insert modes moves + * the cursor. + * + * On the other hand we really don't want to move the cursor, because + * all the editing commands don't include the character under the cursor. + * Probably the best fix is to make all the editing commands aware of + * this fact. + */ +#define VI_MOVE + +/* + * Undo information for vi - no undo in emacs (yet) + */ +typedef struct c_undo_t { + ssize_t len; /* length of saved line */ + int cursor; /* position of saved cursor */ + wchar_t *buf; /* full saved text */ +} c_undo_t; + +/* redo for vi */ +typedef struct c_redo_t { + wchar_t *buf; /* redo insert key sequence */ + wchar_t *pos; + wchar_t *lim; + el_action_t cmd; /* command to redo */ + wchar_t ch; /* char that invoked it */ + int count; + int action; /* from cv_action() */ +} c_redo_t; + +/* + * Current action information for vi + */ +typedef struct c_vcmd_t { + int action; + wchar_t *pos; +} c_vcmd_t; + +/* + * Kill buffer for emacs + */ +typedef struct c_kill_t { + wchar_t *buf; + wchar_t *last; + wchar_t *mark; +} c_kill_t; + +typedef void (*el_zfunc_t)(EditLine *, void *); +typedef const char *(*el_afunc_t)(void *, const char *); + +/* + * Note that we use both data structures because the user can bind + * commands from both editors! + */ +typedef struct el_chared_t { + c_undo_t c_undo; + c_kill_t c_kill; + c_redo_t c_redo; + c_vcmd_t c_vcmd; + el_zfunc_t c_resizefun; + el_afunc_t c_aliasfun; + void * c_resizearg; + void * c_aliasarg; +} el_chared_t; + + +#define STRQQ "\"\"" + +#define isglob(a) (strchr("*[]?", (a)) != NULL) + +#define NOP 0x00 +#define DELETE 0x01 +#define INSERT 0x02 +#define YANK 0x04 + +#define CHAR_FWD (+1) +#define CHAR_BACK (-1) + +#define MODE_INSERT 0 +#define MODE_REPLACE 1 +#define MODE_REPLACE_1 2 + + +libedit_private int cv__isword(wint_t); +libedit_private int cv__isWord(wint_t); +libedit_private void cv_delfini(EditLine *); +libedit_private wchar_t *cv__endword(wchar_t *, wchar_t *, int, int (*)(wint_t)); +libedit_private int ce__isword(wint_t); +libedit_private void cv_undo(EditLine *); +libedit_private void cv_yank(EditLine *, const wchar_t *, int); +libedit_private wchar_t *cv_next_word(EditLine*, wchar_t *, wchar_t *, int, + int (*)(wint_t)); +libedit_private wchar_t *cv_prev_word(wchar_t *, wchar_t *, int, int (*)(wint_t)); +libedit_private wchar_t *c__next_word(wchar_t *, wchar_t *, int, int (*)(wint_t)); +libedit_private wchar_t *c__prev_word(wchar_t *, wchar_t *, int, int (*)(wint_t)); +libedit_private void c_insert(EditLine *, int); +libedit_private void c_delbefore(EditLine *, int); +libedit_private void c_delbefore1(EditLine *); +libedit_private void c_delafter(EditLine *, int); +libedit_private void c_delafter1(EditLine *); +libedit_private int c_gets(EditLine *, wchar_t *, const wchar_t *); +libedit_private int c_hpos(EditLine *); + +libedit_private int ch_init(EditLine *); +libedit_private void ch_reset(EditLine *); +libedit_private int ch_resizefun(EditLine *, el_zfunc_t, void *); +libedit_private int ch_aliasfun(EditLine *, el_afunc_t, void *); +libedit_private int ch_enlargebufs(EditLine *, size_t); +libedit_private void ch_end(EditLine *); + +#endif /* _h_el_chared */ diff --git a/bin/catsh/libedit/chartype.c b/bin/catsh/libedit/chartype.c new file mode 100644 index 00000000..9288e6b7 --- /dev/null +++ b/bin/catsh/libedit/chartype.c @@ -0,0 +1,341 @@ +/* $NetBSD: chartype.c,v 1.31 2017/01/09 02:54:18 christos Exp $ */ + +/*- + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * chartype.c: character classification and meta information + */ +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +__RCSID("$NetBSD: chartype.c,v 1.31 2017/01/09 02:54:18 christos Exp $"); +#endif /* not lint && not SCCSID */ + +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include "el.h" + +#define CT_BUFSIZ ((size_t)1024) + +static int ct_conv_cbuff_resize(ct_buffer_t *, size_t); +static int ct_conv_wbuff_resize(ct_buffer_t *, size_t); + +static int +ct_conv_cbuff_resize(ct_buffer_t *conv, size_t csize) +{ + void *p; + + if (csize <= conv->csize) + return 0; + + conv->csize = csize; + + p = el_realloc(conv->cbuff, conv->csize * sizeof(*conv->cbuff)); + if (p == NULL) { + conv->csize = 0; + el_free(conv->cbuff); + conv->cbuff = NULL; + return -1; + } + conv->cbuff = p; + return 0; +} + +static int +ct_conv_wbuff_resize(ct_buffer_t *conv, size_t wsize) +{ + void *p; + + if (wsize <= conv->wsize) + return 0; + + conv->wsize = wsize; + + p = el_realloc(conv->wbuff, conv->wsize * sizeof(*conv->wbuff)); + if (p == NULL) { + conv->wsize = 0; + el_free(conv->wbuff); + conv->wbuff = NULL; + return -1; + } + conv->wbuff = p; + return 0; +} + + +char * +ct_encode_string(const wchar_t *s, ct_buffer_t *conv) +{ + char *dst; + ssize_t used; + + if (!s) + return NULL; + + dst = conv->cbuff; + for (;;) { + used = (ssize_t)(dst - conv->cbuff); + if ((conv->csize - (size_t)used) < 5) { + if (ct_conv_cbuff_resize(conv, + conv->csize + CT_BUFSIZ) == -1) + return NULL; + dst = conv->cbuff + used; + } + if (!*s) + break; + used = ct_encode_char(dst, (size_t)5, *s); + if (used == -1) /* failed to encode, need more buffer space */ + abort(); + ++s; + dst += used; + } + *dst = '\0'; + return conv->cbuff; +} + +wchar_t * +ct_decode_string(const char *s, ct_buffer_t *conv) +{ + size_t len; + + if (!s) + return NULL; + + len = mbstowcs(NULL, s, (size_t)0); + if (len == (size_t)-1) + return NULL; + + if (conv->wsize < ++len) + if (ct_conv_wbuff_resize(conv, len + CT_BUFSIZ) == -1) + return NULL; + + mbstowcs(conv->wbuff, s, conv->wsize); + return conv->wbuff; +} + + +libedit_private wchar_t ** +ct_decode_argv(int argc, const char *argv[], ct_buffer_t *conv) +{ + size_t bufspace; + int i; + wchar_t *p; + wchar_t **wargv; + ssize_t bytes; + + /* Make sure we have enough space in the conversion buffer to store all + * the argv strings. */ + for (i = 0, bufspace = 0; i < argc; ++i) + bufspace += argv[i] ? strlen(argv[i]) + 1 : 0; + if (conv->wsize < ++bufspace) + if (ct_conv_wbuff_resize(conv, bufspace + CT_BUFSIZ) == -1) + return NULL; + + wargv = el_malloc((size_t)(argc + 1) * sizeof(*wargv)); + + for (i = 0, p = conv->wbuff; i < argc; ++i) { + if (!argv[i]) { /* don't pass null pointers to mbstowcs */ + wargv[i] = NULL; + continue; + } else { + wargv[i] = p; + bytes = (ssize_t)mbstowcs(p, argv[i], bufspace); + } + if (bytes == -1) { + el_free(wargv); + return NULL; + } else + bytes++; /* include '\0' in the count */ + bufspace -= (size_t)bytes; + p += bytes; + } + wargv[i] = NULL; + + return wargv; +} + + +libedit_private size_t +ct_enc_width(wchar_t c) +{ + /* UTF-8 encoding specific values */ + if (c < 0x80) + return 1; + else if (c < 0x0800) + return 2; + else if (c < 0x10000) + return 3; + else if (c < 0x110000) + return 4; + else + return 0; /* not a valid codepoint */ +} + +libedit_private ssize_t +ct_encode_char(char *dst, size_t len, wchar_t c) +{ + ssize_t l = 0; + if (len < ct_enc_width(c)) + return -1; + l = wctomb(dst, c); + + if (l < 0) { + wctomb(NULL, L'\0'); + l = 0; + } + return l; +} + +libedit_private const wchar_t * +ct_visual_string(const wchar_t *s, ct_buffer_t *conv) +{ + wchar_t *dst; + ssize_t used; + + if (!s) + return NULL; + + if (ct_conv_wbuff_resize(conv, CT_BUFSIZ) == -1) + return NULL; + + used = 0; + dst = conv->wbuff; + while (*s) { + used = ct_visual_char(dst, + conv->wsize - (size_t)(dst - conv->wbuff), *s); + if (used != -1) { + ++s; + dst += used; + continue; + } + + /* failed to encode, need more buffer space */ + used = dst - conv->wbuff; + if (ct_conv_wbuff_resize(conv, conv->wsize + CT_BUFSIZ) == -1) + return NULL; + dst = conv->wbuff + used; + } + + if (dst >= (conv->wbuff + conv->wsize)) { /* sigh */ + used = dst - conv->wbuff; + if (ct_conv_wbuff_resize(conv, conv->wsize + CT_BUFSIZ) == -1) + return NULL; + dst = conv->wbuff + used; + } + + *dst = L'\0'; + return conv->wbuff; +} + + + +libedit_private int +ct_visual_width(wchar_t c) +{ + int t = ct_chr_class(c); + switch (t) { + case CHTYPE_ASCIICTL: + return 2; /* ^@ ^? etc. */ + case CHTYPE_TAB: + return 1; /* Hmm, this really need to be handled outside! */ + case CHTYPE_NL: + return 0; /* Should this be 1 instead? */ + case CHTYPE_PRINT: + return wcwidth(c); + case CHTYPE_NONPRINT: + if (c > 0xffff) /* prefer standard 4-byte display over 5-byte */ + return 8; /* \U+12345 */ + else + return 7; /* \U+1234 */ + default: + return 0; /* should not happen */ + } +} + + +libedit_private ssize_t +ct_visual_char(wchar_t *dst, size_t len, wchar_t c) +{ + int t = ct_chr_class(c); + switch (t) { + case CHTYPE_TAB: + case CHTYPE_NL: + case CHTYPE_ASCIICTL: + if (len < 2) + return -1; /* insufficient space */ + *dst++ = '^'; + if (c == '\177') + *dst = '?'; /* DEL -> ^? */ + else + *dst = c | 0100; /* uncontrolify it */ + return 2; + case CHTYPE_PRINT: + if (len < 1) + return -1; /* insufficient space */ + *dst = c; + return 1; + case CHTYPE_NONPRINT: + /* we only use single-width glyphs for display, + * so this is right */ + if ((ssize_t)len < ct_visual_width(c)) + return -1; /* insufficient space */ + *dst++ = '\\'; + *dst++ = 'U'; + *dst++ = '+'; +#define tohexdigit(v) "0123456789ABCDEF"[v] + if (c > 0xffff) /* prefer standard 4-byte display over 5-byte */ + *dst++ = tohexdigit(((unsigned int) c >> 16) & 0xf); + *dst++ = tohexdigit(((unsigned int) c >> 12) & 0xf); + *dst++ = tohexdigit(((unsigned int) c >> 8) & 0xf); + *dst++ = tohexdigit(((unsigned int) c >> 4) & 0xf); + *dst = tohexdigit(((unsigned int) c ) & 0xf); + return c > 0xffff ? 8 : 7; + /*FALLTHROUGH*/ + /* these two should be handled outside this function */ + default: /* we should never hit the default */ + return 0; + } +} + + + + +libedit_private int +ct_chr_class(wchar_t c) +{ + if (c == '\t') + return CHTYPE_TAB; + else if (c == '\n') + return CHTYPE_NL; + else if (c < 0x100 && iswcntrl(c)) + return CHTYPE_ASCIICTL; + else if (iswprint(c)) + return CHTYPE_PRINT; + else + return CHTYPE_NONPRINT; +} diff --git a/bin/catsh/libedit/chartype.h b/bin/catsh/libedit/chartype.h new file mode 100644 index 00000000..4cdd981d --- /dev/null +++ b/bin/catsh/libedit/chartype.h @@ -0,0 +1,119 @@ +/* $NetBSD: chartype.h,v 1.35 2017/05/22 19:16:25 christos Exp $ */ + +/*- + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _h_chartype_f +#define _h_chartype_f + +/* Ideally we should also test the value of the define to see if it + * supports non-BMP code points without requiring UTF-16, but nothing + * seems to actually advertise this properly, despite Unicode 3.1 having + * been around since 2001... */ +#if !defined(__NetBSD__) && \ + !defined(__sun) && \ + !(defined(__APPLE__) && defined(__MACH__)) && \ + !defined(__OpenBSD__) && \ + !defined(__FreeBSD__) && \ + !defined(__DragonFly__) +#ifndef __STDC_ISO_10646__ +/* In many places it is assumed that the first 127 code points are ASCII + * compatible, so ensure wchar_t indeed does ISO 10646 and not some other + * funky encoding that could break us in weird and wonderful ways. */ + #error wchar_t must store ISO 10646 characters +#endif +#endif + +/* Oh for a <uchar.h> with char32_t and __STDC_UTF_32__ in it... + * ref: ISO/IEC DTR 19769 + */ +#if WCHAR_MAX < INT32_MAX +#warning Build environment does not support non-BMP characters +#endif + +/* + * Conversion buffer + */ +typedef struct ct_buffer_t { + char *cbuff; + size_t csize; + wchar_t *wbuff; + size_t wsize; +} ct_buffer_t; + +/* Encode a wide-character string and return the UTF-8 encoded result. */ +char *ct_encode_string(const wchar_t *, ct_buffer_t *); + +/* Decode a (multi)?byte string and return the wide-character string result. */ +wchar_t *ct_decode_string(const char *, ct_buffer_t *); + +/* Decode a (multi)?byte argv string array. + * The pointer returned must be free()d when done. */ +libedit_private wchar_t **ct_decode_argv(int, const char *[], ct_buffer_t *); + +/* Encode a character into the destination buffer, provided there is sufficient + * buffer space available. Returns the number of bytes used up (zero if the + * character cannot be encoded, -1 if there was not enough space available). */ +libedit_private ssize_t ct_encode_char(char *, size_t, wchar_t); +libedit_private size_t ct_enc_width(wchar_t); + +/* The maximum buffer size to hold the most unwieldy visual representation, + * in this case \U+nnnnn. */ +#define VISUAL_WIDTH_MAX ((size_t)8) + +/* The terminal is thought of in terms of X columns by Y lines. In the cases + * where a wide character takes up more than one column, the adjacent + * occupied column entries will contain this faux character. */ +#define MB_FILL_CHAR ((wchar_t)-1) + +/* Visual width of character c, taking into account ^? , \0177 and \U+nnnnn + * style visual expansions. */ +libedit_private int ct_visual_width(wchar_t); + +/* Turn the given character into the appropriate visual format, matching + * the width given by ct_visual_width(). Returns the number of characters used + * up, or -1 if insufficient space. Buffer length is in count of wchar_t's. */ +libedit_private ssize_t ct_visual_char(wchar_t *, size_t, wchar_t); + +/* Convert the given string into visual format, using the ct_visual_char() + * function. Uses a static buffer, so not threadsafe. */ +libedit_private const wchar_t *ct_visual_string(const wchar_t *, ct_buffer_t *); + + +/* printable character, use ct_visual_width() to find out display width */ +#define CHTYPE_PRINT ( 0) +/* control character found inside the ASCII portion of the charset */ +#define CHTYPE_ASCIICTL (-1) +/* a \t */ +#define CHTYPE_TAB (-2) +/* a \n */ +#define CHTYPE_NL (-3) +/* non-printable character */ +#define CHTYPE_NONPRINT (-4) +/* classification of character c, as one of the above defines */ +libedit_private int ct_chr_class(wchar_t c); + +#endif /* _chartype_f */ diff --git a/bin/catsh/libedit/common.c b/bin/catsh/libedit/common.c new file mode 100644 index 00000000..27086051 --- /dev/null +++ b/bin/catsh/libedit/common.c @@ -0,0 +1,835 @@ +/* $NetBSD: common.c,v 1.47 2016/05/22 19:44:26 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)common.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: common.c,v 1.47 2016/05/22 19:44:26 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * common.c: Common Editor functions + */ +#include <ctype.h> +#include <string.h> + +#include "el.h" +#include "common.h" +#include "fcns.h" +#include "parse.h" +#include "vi.h" + +/* ed_end_of_file(): + * Indicate end of file + * [^D] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_end_of_file(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + re_goto_bottom(el); + *el->el_line.lastchar = '\0'; + return CC_EOF; +} + + +/* ed_insert(): + * Add character to the line + * Insert a character [bound to all insert keys] + */ +libedit_private el_action_t +ed_insert(EditLine *el, wint_t c) +{ + int count = el->el_state.argument; + + if (c == '\0') + return CC_ERROR; + + if (el->el_line.lastchar + el->el_state.argument >= + el->el_line.limit) { + /* end of buffer space, try to allocate more */ + if (!ch_enlargebufs(el, (size_t) count)) + return CC_ERROR; /* error allocating more */ + } + + if (count == 1) { + if (el->el_state.inputmode == MODE_INSERT + || el->el_line.cursor >= el->el_line.lastchar) + c_insert(el, 1); + + *el->el_line.cursor++ = c; + re_fastaddc(el); /* fast refresh for one char. */ + } else { + if (el->el_state.inputmode != MODE_REPLACE_1) + c_insert(el, el->el_state.argument); + + while (count-- && el->el_line.cursor < el->el_line.lastchar) + *el->el_line.cursor++ = c; + re_refresh(el); + } + + if (el->el_state.inputmode == MODE_REPLACE_1) + return vi_command_mode(el, 0); + + return CC_NORM; +} + + +/* ed_delete_prev_word(): + * Delete from beginning of current word to cursor + * [M-^?] [^W] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_delete_prev_word(EditLine *el, wint_t c __attribute__((__unused__))) +{ + wchar_t *cp, *p, *kp; + + if (el->el_line.cursor == el->el_line.buffer) + return CC_ERROR; + + cp = c__prev_word(el->el_line.cursor, el->el_line.buffer, + el->el_state.argument, ce__isword); + + for (p = cp, kp = el->el_chared.c_kill.buf; p < el->el_line.cursor; p++) + *kp++ = *p; + el->el_chared.c_kill.last = kp; + + c_delbefore(el, (int)(el->el_line.cursor - cp));/* delete before dot */ + el->el_line.cursor = cp; + if (el->el_line.cursor < el->el_line.buffer) + el->el_line.cursor = el->el_line.buffer; /* bounds check */ + return CC_REFRESH; +} + + +/* ed_delete_next_char(): + * Delete character under cursor + * [^D] [x] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_delete_next_char(EditLine *el, wint_t c __attribute__((__unused__))) +{ +#ifdef DEBUG_EDIT +#define EL el->el_line + (void) fprintf(el->el_errfile, + "\nD(b: %p(%ls) c: %p(%ls) last: %p(%ls) limit: %p(%ls)\n", + EL.buffer, EL.buffer, EL.cursor, EL.cursor, EL.lastchar, + EL.lastchar, EL.limit, EL.limit); +#endif + if (el->el_line.cursor == el->el_line.lastchar) { + /* if I'm at the end */ + if (el->el_map.type == MAP_VI) { + if (el->el_line.cursor == el->el_line.buffer) { + /* if I'm also at the beginning */ +#ifdef KSHVI + return CC_ERROR; +#else + /* then do an EOF */ + terminal_writec(el, c); + return CC_EOF; +#endif + } else { +#ifdef KSHVI + el->el_line.cursor--; +#else + return CC_ERROR; +#endif + } + } else + return CC_ERROR; + } + c_delafter(el, el->el_state.argument); /* delete after dot */ + if (el->el_map.type == MAP_VI && + el->el_line.cursor >= el->el_line.lastchar && + el->el_line.cursor > el->el_line.buffer) + /* bounds check */ + el->el_line.cursor = el->el_line.lastchar - 1; + return CC_REFRESH; +} + + +/* ed_kill_line(): + * Cut to the end of line + * [^K] [^K] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_kill_line(EditLine *el, wint_t c __attribute__((__unused__))) +{ + wchar_t *kp, *cp; + + cp = el->el_line.cursor; + kp = el->el_chared.c_kill.buf; + while (cp < el->el_line.lastchar) + *kp++ = *cp++; /* copy it */ + el->el_chared.c_kill.last = kp; + /* zap! -- delete to end */ + el->el_line.lastchar = el->el_line.cursor; + return CC_REFRESH; +} + + +/* ed_move_to_end(): + * Move cursor to the end of line + * [^E] [^E] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_move_to_end(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + el->el_line.cursor = el->el_line.lastchar; + if (el->el_map.type == MAP_VI) { + if (el->el_chared.c_vcmd.action != NOP) { + cv_delfini(el); + return CC_REFRESH; + } +#ifdef VI_MOVE + el->el_line.cursor--; +#endif + } + return CC_CURSOR; +} + + +/* ed_move_to_beg(): + * Move cursor to the beginning of line + * [^A] [^A] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_move_to_beg(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + el->el_line.cursor = el->el_line.buffer; + + if (el->el_map.type == MAP_VI) { + /* We want FIRST non space character */ + while (iswspace(*el->el_line.cursor)) + el->el_line.cursor++; + if (el->el_chared.c_vcmd.action != NOP) { + cv_delfini(el); + return CC_REFRESH; + } + } + return CC_CURSOR; +} + + +/* ed_transpose_chars(): + * Exchange the character to the left of the cursor with the one under it + * [^T] [^T] + */ +libedit_private el_action_t +ed_transpose_chars(EditLine *el, wint_t c) +{ + + if (el->el_line.cursor < el->el_line.lastchar) { + if (el->el_line.lastchar <= &el->el_line.buffer[1]) + return CC_ERROR; + else + el->el_line.cursor++; + } + if (el->el_line.cursor > &el->el_line.buffer[1]) { + /* must have at least two chars entered */ + c = el->el_line.cursor[-2]; + el->el_line.cursor[-2] = el->el_line.cursor[-1]; + el->el_line.cursor[-1] = c; + return CC_REFRESH; + } else + return CC_ERROR; +} + + +/* ed_next_char(): + * Move to the right one character + * [^F] [^F] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_next_char(EditLine *el, wint_t c __attribute__((__unused__))) +{ + wchar_t *lim = el->el_line.lastchar; + + if (el->el_line.cursor >= lim || + (el->el_line.cursor == lim - 1 && + el->el_map.type == MAP_VI && + el->el_chared.c_vcmd.action == NOP)) + return CC_ERROR; + + el->el_line.cursor += el->el_state.argument; + if (el->el_line.cursor > lim) + el->el_line.cursor = lim; + + if (el->el_map.type == MAP_VI) + if (el->el_chared.c_vcmd.action != NOP) { + cv_delfini(el); + return CC_REFRESH; + } + return CC_CURSOR; +} + + +/* ed_prev_word(): + * Move to the beginning of the current word + * [M-b] [b] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_prev_word(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + if (el->el_line.cursor == el->el_line.buffer) + return CC_ERROR; + + el->el_line.cursor = c__prev_word(el->el_line.cursor, + el->el_line.buffer, + el->el_state.argument, + ce__isword); + + if (el->el_map.type == MAP_VI) + if (el->el_chared.c_vcmd.action != NOP) { + cv_delfini(el); + return CC_REFRESH; + } + return CC_CURSOR; +} + + +/* ed_prev_char(): + * Move to the left one character + * [^B] [^B] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_prev_char(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + if (el->el_line.cursor > el->el_line.buffer) { + el->el_line.cursor -= el->el_state.argument; + if (el->el_line.cursor < el->el_line.buffer) + el->el_line.cursor = el->el_line.buffer; + + if (el->el_map.type == MAP_VI) + if (el->el_chared.c_vcmd.action != NOP) { + cv_delfini(el); + return CC_REFRESH; + } + return CC_CURSOR; + } else + return CC_ERROR; +} + + +/* ed_quoted_insert(): + * Add the next character typed verbatim + * [^V] [^V] + */ +libedit_private el_action_t +ed_quoted_insert(EditLine *el, wint_t c) +{ + int num; + + tty_quotemode(el); + num = el_wgetc(el, &c); + tty_noquotemode(el); + if (num == 1) + return ed_insert(el, c); + else + return ed_end_of_file(el, 0); +} + + +/* ed_digit(): + * Adds to argument or enters a digit + */ +libedit_private el_action_t +ed_digit(EditLine *el, wint_t c) +{ + + if (!iswdigit(c)) + return CC_ERROR; + + if (el->el_state.doingarg) { + /* if doing an arg, add this in... */ + if (el->el_state.lastcmd == EM_UNIVERSAL_ARGUMENT) + el->el_state.argument = c - '0'; + else { + if (el->el_state.argument > 1000000) + return CC_ERROR; + el->el_state.argument = + (el->el_state.argument * 10) + (c - '0'); + } + return CC_ARGHACK; + } + + return ed_insert(el, c); +} + + +/* ed_argument_digit(): + * Digit that starts argument + * For ESC-n + */ +libedit_private el_action_t +ed_argument_digit(EditLine *el, wint_t c) +{ + + if (!iswdigit(c)) + return CC_ERROR; + + if (el->el_state.doingarg) { + if (el->el_state.argument > 1000000) + return CC_ERROR; + el->el_state.argument = (el->el_state.argument * 10) + + (c - '0'); + } else { /* else starting an argument */ + el->el_state.argument = c - '0'; + el->el_state.doingarg = 1; + } + return CC_ARGHACK; +} + + +/* ed_unassigned(): + * Indicates unbound character + * Bound to keys that are not assigned + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_unassigned(EditLine *el __attribute__((__unused__)), + wint_t c __attribute__((__unused__))) +{ + + return CC_ERROR; +} + + +/* ed_ignore(): + * Input characters that have no effect + * [^C ^O ^Q ^S ^Z ^\ ^]] [^C ^O ^Q ^S ^\] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_ignore(EditLine *el __attribute__((__unused__)), + wint_t c __attribute__((__unused__))) +{ + + return CC_NORM; +} + + +/* ed_newline(): + * Execute command + * [^J] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_newline(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + re_goto_bottom(el); + *el->el_line.lastchar++ = '\n'; + *el->el_line.lastchar = '\0'; + return CC_NEWLINE; +} + + +/* ed_delete_prev_char(): + * Delete the character to the left of the cursor + * [^?] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_delete_prev_char(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + if (el->el_line.cursor <= el->el_line.buffer) + return CC_ERROR; + + c_delbefore(el, el->el_state.argument); + el->el_line.cursor -= el->el_state.argument; + if (el->el_line.cursor < el->el_line.buffer) + el->el_line.cursor = el->el_line.buffer; + return CC_REFRESH; +} + + +/* ed_clear_screen(): + * Clear screen leaving current line at the top + * [^L] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_clear_screen(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + terminal_clear_screen(el); /* clear the whole real screen */ + re_clear_display(el); /* reset everything */ + return CC_REFRESH; +} + + +/* ed_redisplay(): + * Redisplay everything + * ^R + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_redisplay(EditLine *el __attribute__((__unused__)), + wint_t c __attribute__((__unused__))) +{ + + return CC_REDISPLAY; +} + + +/* ed_start_over(): + * Erase current line and start from scratch + * [^G] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_start_over(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + ch_reset(el); + return CC_REFRESH; +} + + +/* ed_sequence_lead_in(): + * First character in a bound sequence + * Placeholder for external keys + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_sequence_lead_in(EditLine *el __attribute__((__unused__)), + wint_t c __attribute__((__unused__))) +{ + + return CC_NORM; +} + + +/* ed_prev_history(): + * Move to the previous history line + * [^P] [k] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_prev_history(EditLine *el, wint_t c __attribute__((__unused__))) +{ + char beep = 0; + int sv_event = el->el_history.eventno; + + el->el_chared.c_undo.len = -1; + *el->el_line.lastchar = '\0'; /* just in case */ + + if (el->el_history.eventno == 0) { /* save the current buffer + * away */ + (void) wcsncpy(el->el_history.buf, el->el_line.buffer, + EL_BUFSIZ); + el->el_history.last = el->el_history.buf + + (el->el_line.lastchar - el->el_line.buffer); + } + el->el_history.eventno += el->el_state.argument; + + if (hist_get(el) == CC_ERROR) { + if (el->el_map.type == MAP_VI) { + el->el_history.eventno = sv_event; + } + beep = 1; + /* el->el_history.eventno was fixed by first call */ + (void) hist_get(el); + } + if (beep) + return CC_REFRESH_BEEP; + return CC_REFRESH; +} + + +/* ed_next_history(): + * Move to the next history line + * [^N] [j] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_next_history(EditLine *el, wint_t c __attribute__((__unused__))) +{ + el_action_t beep = CC_REFRESH, rval; + + el->el_chared.c_undo.len = -1; + *el->el_line.lastchar = '\0'; /* just in case */ + + el->el_history.eventno -= el->el_state.argument; + + if (el->el_history.eventno < 0) { + el->el_history.eventno = 0; + beep = CC_REFRESH_BEEP; + } + rval = hist_get(el); + if (rval == CC_REFRESH) + return beep; + return rval; + +} + + +/* ed_search_prev_history(): + * Search previous in history for a line matching the current + * next search history [M-P] [K] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_search_prev_history(EditLine *el, wint_t c __attribute__((__unused__))) +{ + const wchar_t *hp; + int h; + int found = 0; + + el->el_chared.c_vcmd.action = NOP; + el->el_chared.c_undo.len = -1; + *el->el_line.lastchar = '\0'; /* just in case */ + if (el->el_history.eventno < 0) { +#ifdef DEBUG_EDIT + (void) fprintf(el->el_errfile, + "e_prev_search_hist(): eventno < 0;\n"); +#endif + el->el_history.eventno = 0; + return CC_ERROR; + } + if (el->el_history.eventno == 0) { + (void) wcsncpy(el->el_history.buf, el->el_line.buffer, + EL_BUFSIZ); + el->el_history.last = el->el_history.buf + + (el->el_line.lastchar - el->el_line.buffer); + } + if (el->el_history.ref == NULL) + return CC_ERROR; + + hp = HIST_FIRST(el); + if (hp == NULL) + return CC_ERROR; + + c_setpat(el); /* Set search pattern !! */ + + for (h = 1; h <= el->el_history.eventno; h++) + hp = HIST_NEXT(el); + + while (hp != NULL) { +#ifdef SDEBUG + (void) fprintf(el->el_errfile, "Comparing with \"%s\"\n", hp); +#endif + if ((wcsncmp(hp, el->el_line.buffer, (size_t) + (el->el_line.lastchar - el->el_line.buffer)) || + hp[el->el_line.lastchar - el->el_line.buffer]) && + c_hmatch(el, hp)) { + found = 1; + break; + } + h++; + hp = HIST_NEXT(el); + } + + if (!found) { +#ifdef SDEBUG + (void) fprintf(el->el_errfile, "not found\n"); +#endif + return CC_ERROR; + } + el->el_history.eventno = h; + + return hist_get(el); +} + + +/* ed_search_next_history(): + * Search next in history for a line matching the current + * [M-N] [J] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_search_next_history(EditLine *el, wint_t c __attribute__((__unused__))) +{ + const wchar_t *hp; + int h; + int found = 0; + + el->el_chared.c_vcmd.action = NOP; + el->el_chared.c_undo.len = -1; + *el->el_line.lastchar = '\0'; /* just in case */ + + if (el->el_history.eventno == 0) + return CC_ERROR; + + if (el->el_history.ref == NULL) + return CC_ERROR; + + hp = HIST_FIRST(el); + if (hp == NULL) + return CC_ERROR; + + c_setpat(el); /* Set search pattern !! */ + + for (h = 1; h < el->el_history.eventno && hp; h++) { +#ifdef SDEBUG + (void) fprintf(el->el_errfile, "Comparing with \"%s\"\n", hp); +#endif + if ((wcsncmp(hp, el->el_line.buffer, (size_t) + (el->el_line.lastchar - el->el_line.buffer)) || + hp[el->el_line.lastchar - el->el_line.buffer]) && + c_hmatch(el, hp)) + found = h; + hp = HIST_NEXT(el); + } + + if (!found) { /* is it the current history number? */ + if (!c_hmatch(el, el->el_history.buf)) { +#ifdef SDEBUG + (void) fprintf(el->el_errfile, "not found\n"); +#endif + return CC_ERROR; + } + } + el->el_history.eventno = found; + + return hist_get(el); +} + + +/* ed_prev_line(): + * Move up one line + * Could be [k] [^p] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_prev_line(EditLine *el, wint_t c __attribute__((__unused__))) +{ + wchar_t *ptr; + int nchars = c_hpos(el); + + /* + * Move to the line requested + */ + if (*(ptr = el->el_line.cursor) == '\n') + ptr--; + + for (; ptr >= el->el_line.buffer; ptr--) + if (*ptr == '\n' && --el->el_state.argument <= 0) + break; + + if (el->el_state.argument > 0) + return CC_ERROR; + + /* + * Move to the beginning of the line + */ + for (ptr--; ptr >= el->el_line.buffer && *ptr != '\n'; ptr--) + continue; + + /* + * Move to the character requested + */ + for (ptr++; + nchars-- > 0 && ptr < el->el_line.lastchar && *ptr != '\n'; + ptr++) + continue; + + el->el_line.cursor = ptr; + return CC_CURSOR; +} + + +/* ed_next_line(): + * Move down one line + * Could be [j] [^n] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_next_line(EditLine *el, wint_t c __attribute__((__unused__))) +{ + wchar_t *ptr; + int nchars = c_hpos(el); + + /* + * Move to the line requested + */ + for (ptr = el->el_line.cursor; ptr < el->el_line.lastchar; ptr++) + if (*ptr == '\n' && --el->el_state.argument <= 0) + break; + + if (el->el_state.argument > 0) + return CC_ERROR; + + /* + * Move to the character requested + */ + for (ptr++; + nchars-- > 0 && ptr < el->el_line.lastchar && *ptr != '\n'; + ptr++) + continue; + + el->el_line.cursor = ptr; + return CC_CURSOR; +} + + +/* ed_command(): + * Editline extended command + * [M-X] [:] + */ +libedit_private el_action_t +/*ARGSUSED*/ +ed_command(EditLine *el, wint_t c __attribute__((__unused__))) +{ + wchar_t tmpbuf[EL_BUFSIZ]; + int tmplen; + + tmplen = c_gets(el, tmpbuf, L"\n: "); + terminal__putc(el, '\n'); + + if (tmplen < 0 || (tmpbuf[tmplen] = 0, parse_line(el, tmpbuf)) == -1) + terminal_beep(el); + + el->el_map.current = el->el_map.key; + re_clear_display(el); + return CC_REFRESH; +} diff --git a/bin/catsh/libedit/config.h b/bin/catsh/libedit/config.h new file mode 100644 index 00000000..3eb35d17 --- /dev/null +++ b/bin/catsh/libedit/config.h @@ -0,0 +1,286 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Define to 1 if the `closedir' function returns void instead of `int'. */ +/* #undef CLOSEDIR_VOID */ + +/* Define to 1 if you have the <curses.h> header file. */ +#define HAVE_CURSES_H 1 + +/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'. + */ +#define HAVE_DIRENT_H 1 + +/* Define to 1 if you have the <dlfcn.h> header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the `endpwent' function. */ +#define HAVE_ENDPWENT 1 + +/* Define to 1 if you have the <fcntl.h> header file. */ +#define HAVE_FCNTL_H 1 + +/* Define to 1 if you have the `getline' function. */ +#define HAVE_GETLINE 1 + +/* Define to 1 if you have the `fork' function. */ +#define HAVE_FORK 1 + +/* Define to 1 if you have getpwnam_r and getpwuid_r that are draft POSIX.1 + versions. */ +/* #undef HAVE_GETPW_R_DRAFT */ + +/* Define to 1 if you have getpwnam_r and getpwuid_r that are POSIX.1 + compatible. */ +#define HAVE_GETPW_R_POSIX 1 + +/* Define to 1 if you have the <inttypes.h> header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `isascii' function. */ +#define HAVE_ISASCII 1 + +/* Define to 1 if you have the `issetugid' function. */ +#define HAVE_ISSETUGID 1 + +/* Define to 1 if you have the `curses' library (-lcurses). */ +/* #undef HAVE_LIBCURSES */ + +/* Define to 1 if you have the `ncurses' library (-lncurses). */ +/* #undef HAVE_LIBNCURSES */ + +/* Define to 1 if you have the `termcap' library (-ltermcap). */ +/* #undef HAVE_LIBTERMCAP */ + +/* Define to 1 if you have the `terminfo' library (-lterminfo). */ +#define HAVE_LIBTERMINFO 1 + +/* Define to 1 if you have the `termlib' library (-ltermlib). */ +/* #undef HAVE_LIBTERMLIB */ + +/* Define to 1 if you have the <limits.h> header file. */ +#define HAVE_LIMITS_H 1 + +/* Define to 1 if you have the <malloc.h> header file. */ +#define HAVE_MALLOC_H 1 + +/* Define to 1 if you have the `memchr' function. */ +#define HAVE_MEMCHR 1 + +/* Define to 1 if you have the <memory.h> header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `memset' function. */ +#define HAVE_MEMSET 1 + +/* Define to 1 if you have the <ncurses.h> header file. */ +/* #undef HAVE_NCURSES_H */ + +/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */ +/* #undef HAVE_NDIR_H */ + +/* Define to 1 if you have the `regcomp' function. */ +#define HAVE_REGCOMP 1 + +/* Define to 1 if you have the `re_comp' function. */ +/* #undef HAVE_RE_COMP */ + +/* Define to 1 if `stat' has the bug that it succeeds when given the + zero-length file name argument. */ +/* #undef HAVE_STAT_EMPTY_STRING_BUG */ + +/* Define to 1 if you have the <stdint.h> header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the <stdlib.h> header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the `strcasecmp' function. */ +#define HAVE_STRCASECMP 1 + +/* Define to 1 if you have the `strchr' function. */ +#define HAVE_STRCHR 1 + +/* Define to 1 if you have the `strcspn' function. */ +#define HAVE_STRCSPN 1 + +/* Define to 1 if you have the `strdup' function. */ +#define HAVE_STRDUP 1 + +/* Define to 1 if you have the `strerror' function. */ +#define HAVE_STRERROR 1 + +/* Define to 1 if you have the <strings.h> header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the <string.h> header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the `strlcat' function. */ +#define HAVE_STRLCAT 1 + +/* Define to 1 if you have the `strlcpy' function. */ +#define HAVE_STRLCPY 1 + +/* Define to 1 if you have the `strrchr' function. */ +#define HAVE_STRRCHR 1 + +/* Define to 1 if you have the `strstr' function. */ +#define HAVE_STRSTR 1 + +/* Define to 1 if you have the `strtol' function. */ +#define HAVE_STRTOL 1 + +/* Define to 1 if struct dirent has member d_namlen */ +#define HAVE_STRUCT_DIRENT_D_NAMLEN 1 + +/* Define to 1 if you have the `strunvis' function. */ +#define HAVE_STRUNVIS 1 + +/* Define to 1 if you have the `strvis' function. */ +#define HAVE_STRVIS 1 + +/* Define to 1 if you have the <sys/cdefs.h> header file. */ +#define HAVE_SYS_CDEFS_H 1 + +/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'. + */ +/* #undef HAVE_SYS_DIR_H */ + +/* Define to 1 if you have the <sys/ioctl.h> header file. */ +#define HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'. + */ +/* #undef HAVE_SYS_NDIR_H */ + +/* Define to 1 if you have the <sys/param.h> header file. */ +#define HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the <sys/types.h> header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have <sys/wait.h> that is POSIX.1 compatible. */ +#define HAVE_SYS_WAIT_H 1 + +/* Define to 1 if you have the <termcap.h> header file. */ +#define HAVE_TERMCAP_H 1 + +/* Define to 1 if you have the <term.h> header file. */ +#define HAVE_TERM_H 1 + +/* Define to 1 if you have the <unistd.h> header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if the system has the type `u_int32_t'. */ +#define HAVE_U_INT32_T 1 + +/* Define to 1 if you have the `vfork' function. */ +#define HAVE_VFORK 1 + +/* Define to 1 if you have the <vfork.h> header file. */ +/* #undef HAVE_VFORK_H */ + +/* Define to 1 if you have the `vis' function. */ +#define HAVE_VIS 1 + +/* Define to 1 if `fork' works. */ +#define HAVE_WORKING_FORK 1 + +/* Define to 1 if `vfork' works. */ +#define HAVE_WORKING_VFORK 1 + +/* Define to 1 if `lstat' dereferences a symlink specified with a trailing + slash. */ +#define LSTAT_FOLLOWS_SLASHED_SYMLINK 1 + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#define LT_OBJDIR ".libs/" + +/* Name of package */ +#define PACKAGE "libedit-20110729" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "libedit" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "libedit 3.0" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "libedit-20110729" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "3.0" + +/* Define as the return type of signal handlers (`int' or `void'). */ +#define RETSIGTYPE void + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Enable extensions on AIX 3, Interix. */ +#ifndef _ALL_SOURCE +# define _ALL_SOURCE 1 +#endif +/* Enable GNU extensions on systems that have them. */ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE 1 +#endif +/* Enable threading extensions on Solaris. */ +#ifndef _POSIX_PTHREAD_SEMANTICS +# define _POSIX_PTHREAD_SEMANTICS 1 +#endif +/* Enable extensions on HP NonStop. */ +#ifndef _TANDEM_SOURCE +# define _TANDEM_SOURCE 1 +#endif +/* Enable general extensions on Solaris. */ +#ifndef __EXTENSIONS__ +# define __EXTENSIONS__ 1 +#endif + + +/* Version number of package */ +#define VERSION "3.0" + +/* Define to 1 if the system provides the SIZE_MAX constant */ +#define HAVE_SIZE_MAX 1 + +/* Define to 1 if on MINIX. */ +/* #undef _MINIX */ + +/* Define to 2 if the system does not provide POSIX.1 features except with + this defined. */ +/* #undef _POSIX_1_SOURCE */ + +/* Define to 1 if you need to in order for `stat' and other things to work. */ +/* #undef _POSIX_SOURCE */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to `int' if <sys/types.h> does not define. */ +/* #undef pid_t */ + +/* Define to `unsigned int' if <sys/types.h> does not define. */ +/* #undef size_t */ + +/* Define as `fork' if `vfork' does not work. */ +/* #undef vfork */ + + +#include "sys.h" +/* #undef SCCSID */ +/* #undef LIBC_SCCS */ +/* #undef lint */ + diff --git a/bin/catsh/libedit/editline.3 b/bin/catsh/libedit/editline.3 new file mode 100644 index 00000000..a27e24ce --- /dev/null +++ b/bin/catsh/libedit/editline.3 @@ -0,0 +1,1013 @@ +.\" $NetBSD: editline.3,v 1.93.4.1 2017/07/23 14:41:26 snj Exp $ +.\" +.\" Copyright (c) 1997-2014 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This file was contributed to The NetBSD Foundation by Luke Mewburn. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd June 27, 2017 +.Dt EDITLINE 3 +.Os +.Sh NAME +.Nm editline , +.Nm el_init , +.Nm el_init_fd , +.Nm el_end , +.Nm el_reset , +.Nm el_gets , +.Nm el_wgets , +.Nm el_getc , +.Nm el_wgetc , +.Nm el_push , +.Nm el_wpush , +.Nm el_parse , +.Nm el_wparse , +.Nm el_set , +.Nm el_wset , +.Nm el_get , +.Nm el_wget , +.Nm el_source , +.Nm el_resize , +.Nm el_cursor , +.Nm el_line , +.Nm el_wline , +.Nm el_insertstr , +.Nm el_winsertstr , +.Nm el_deletestr , +.Nm el_wdeletestr , +.Nm history_init , +.Nm history_winit , +.Nm history_end , +.Nm history_wend , +.Nm history , +.Nm history_w , +.Nm tok_init , +.Nm tok_winit , +.Nm tok_end , +.Nm tok_wend , +.Nm tok_reset , +.Nm tok_wreset , +.Nm tok_line , +.Nm tok_wline , +.Nm tok_str , +.Nm tok_wstr +.Nd line editor, history and tokenization functions +.Sh LIBRARY +.Lb libedit +.Sh SYNOPSIS +.In histedit.h +.Ft EditLine * +.Fn el_init "const char *prog" "FILE *fin" "FILE *fout" "FILE *ferr" +.Ft EditLine * +.Fn el_init_fd "const char *prog" "FILE *fin" "FILE *fout" "FILE *ferr" "int fdin" "int fdout" "int fderr" +.Ft void +.Fn el_end "EditLine *e" +.Ft void +.Fn el_reset "EditLine *e" +.Ft const char * +.Fn el_gets "EditLine *e" "int *count" +.Ft const wchar_t * +.Fn el_wgets "EditLine *e" "int *count" +.Ft int +.Fn el_getc "EditLine *e" "char *ch" +.Ft int +.Fn el_wgetc "EditLine *e" "wchar_t *wc" +.Ft void +.Fn el_push "EditLine *e" "const char *mbs" +.Ft void +.Fn el_wpush "EditLine *e" "const wchar_t *wcs" +.Ft int +.Fn el_parse "EditLine *e" "int argc" "const char *argv[]" +.Ft int +.Fn el_wparse "EditLine *e" "int argc" "const wchar_t *argv[]" +.Ft int +.Fn el_set "EditLine *e" "int op" "..." +.Ft int +.Fn el_wset "EditLine *e" "int op" "..." +.Ft int +.Fn el_get "EditLine *e" "int op" "..." +.Ft int +.Fn el_wget "EditLine *e" "int op" "..." +.Ft int +.Fn el_source "EditLine *e" "const char *file" +.Ft void +.Fn el_resize "EditLine *e" +.Ft int +.Fn el_cursor "EditLine *e" "int count" +.Ft const LineInfo * +.Fn el_line "EditLine *e" +.Ft const LineInfoW * +.Fn el_wline "EditLine *e" +.Ft int +.Fn el_insertstr "EditLine *e" "const char *str" +.Ft int +.Fn el_winsertstr "EditLine *e" "const wchar_t *str" +.Ft void +.Fn el_deletestr "EditLine *e" "int count" +.Ft void +.Fn el_wdeletestr "EditLine *e" "int count" +.Ft History * +.Fn history_init void +.Ft HistoryW * +.Fn history_winit void +.Ft void +.Fn history_end "History *h" +.Ft void +.Fn history_wend "HistoryW *h" +.Ft int +.Fn history "History *h" "HistEvent *ev" "int op" "..." +.Ft int +.Fn history_w "HistoryW *h" "HistEventW *ev" "int op" "..." +.Ft Tokenizer * +.Fn tok_init "const char *IFS" +.Ft TokenizerW * +.Fn tok_winit "const wchar_t *IFS" +.Ft void +.Fn tok_end "Tokenizer *t" +.Ft void +.Fn tok_wend "TokenizerW *t" +.Ft void +.Fn tok_reset "Tokenizer *t" +.Ft void +.Fn tok_wreset "TokenizerW *t" +.Ft int +.Fn tok_line "Tokenizer *t" "const LineInfo *li" "int *argc" "const char **argv[]" "int *cursorc" "int *cursoro" +.Ft int +.Fn tok_wline "TokenizerW *t" "const LineInfoW *li" "int *argc" "const wchar_t **argv[]" "int *cursorc" "int *cursoro" +.Ft int +.Fn tok_str "Tokenizer *t" "const char *str" "int *argc" "const char **argv[]" +.Ft int +.Fn tok_wstr "TokenizerW *t" "const wchar_t *str" "int *argc" "const wchar_t **argv[]" +.Sh DESCRIPTION +The +.Nm +library provides generic line editing, history and tokenization functions, +similar to those found in +.Xr sh 1 . +.Pp +These functions are available in the +.Nm libedit +library (which needs the +.Nm libtermcap +library). +Programs should be linked with +.Fl ledit ltermcap . +.Pp +The +.Nm +library respects the +.Ev LC_CTYPE +locale set by the application program and never uses +.Xr setlocale 3 +to change the locale. +The only locales supported are UTF-8 and the default C or POSIX locale. +If any other locale is set, behaviour is undefined. +.Sh LINE EDITING FUNCTIONS +The line editing functions use a common data structure, +.Fa EditLine , +which is created by +.Fn el_init +or +.Fn el_init_fd +and freed by +.Fn el_end . +.Pp +The wide-character functions behave the same way as their narrow +counterparts. +.Pp +The following functions are available: +.Bl -tag -width 4n +.It Fn el_init +Initialize the line editor, and return a data structure +to be used by all other line editing functions, or +.Dv NULL +on failure. +.Fa prog +is the name of the invoking program, used when reading the +.Xr editrc 5 +file to determine which settings to use. +.Fa fin , +.Fa fout +and +.Fa ferr +are the input, output, and error streams (respectively) to use. +In this documentation, references to +.Dq the tty +are actually to this input/output stream combination. +.It Fn el_init_fd +Like +.Fn el_init +but allows specifying file descriptors for the +.Xr stdio 3 +corresponding streams, in case those were created with +.Xr funopen 3 . +.It Fn el_end +Clean up and finish with +.Fa e , +assumed to have been created with +.Fn el_init +or +.Fn el_init_fd . +.It Fn el_reset +Reset the tty and the parser. +This should be called after an error which may have upset the tty's +state. +.It Fn el_gets +Read a line from the tty. +.Fa count +is modified to contain the number of characters read. +Returns the line read if successful, or +.Dv NULL +if no characters were read or if an error occurred. +If an error occurred, +.Fa count +is set to \-1 and +.Dv errno +contains the error code that caused it. +The return value may not remain valid across calls to +.Fn el_gets +and must be copied if the data is to be retained. +.It Fn el_wgetc +Read a wide character from the tty, respecting the current locale, +or from the input queue described in +.Xr editline 7 +if that is not empty, and store it in +.Fa wc . +If an invalid or incomplete character is found, it is discarded, +.Va errno +is set to +.Er EILSEQ , +and the next character is read and stored in +.Fa wc . +Returns 1 if a valid character was read, 0 on end of file, or \-1 on +.Xr read 2 +failure. +In the latter case, +.Va errno +is set to indicate the error. +.It Fn el_getc +Read a wide character as described for +.Fn el_wgetc +and return 0 on end of file or \-1 on failure. +If the wide character can be represented as a single-byte character, +convert it with +.Xr wctob 3 , +store the result in +.Fa ch , +and return 1; otherwise, set +.Va errno +to +.Er ERANGE +and return \-1. +In the C or POSIX locale, this simply reads a byte, but for any other +locale, including UTF-8, this is rarely useful. +.It Fn el_wpush +Push the wide character string +.Fa wcs +back onto the input queue described in +.Xr editline 7 . +If the queue overflows, for example due to a recursive macro, +or if an error occurs, for example because +.Fa wcs +is +.Dv NULL +or memory allocation fails, the function beeps at the user, +but does not report the problem to the caller. +.It Fn el_push +Use the current locale to convert the multibyte string +.Fa mbs +to a wide character string, and pass the result to +.Fn el_wpush . +.It Fn el_parse +Parses the +.Fa argv +array (which is +.Fa argc +elements in size) +to execute builtin +.Nm +commands. +If the command is prefixed with +.Dq prog : +then +.Fn el_parse +will only execute the command if +.Dq prog +matches the +.Fa prog +argument supplied to +.Fn el_init . +The return value is +\-1 if the command is unknown, +0 if there was no error or +.Dq prog +didn't match, or +1 if the command returned an error. +Refer to +.Xr editrc 5 +for more information. +.It Fn el_set +Set +.Nm +parameters. +.Fa op +determines which parameter to set, and each operation has its +own parameter list. +Returns 0 on success, \-1 on failure. +.Pp +The following values for +.Fa op +are supported, along with the required argument list: +.Bl -tag -width 4n +.It Dv EL_PROMPT , Fa "char *(*f)(EditLine *)" +Define prompt printing function as +.Fa f , +which is to return a string that contains the prompt. +.It Dv EL_PROMPT_ESC , Fa "char *(*f)(EditLine *)" , Fa "char c" +Same as +.Dv EL_PROMPT , +but the +.Fa c +argument indicates the start/stop literal prompt character. +.Pp +If a start/stop literal character is found in the prompt, the +character itself +is not printed, but characters after it are printed directly to the +terminal without affecting the state of the current line. +A subsequent second start/stop literal character ends this behavior. +This is typically used to embed literal escape sequences that change the +color/style of the terminal in the prompt. +Note that the literal escape character cannot be the last character in the +prompt, as the escape sequence is attached to the next character in the prompt. +.Dv 0 +unsets it. +.It Dv EL_REFRESH +Re-display the current line on the next terminal line. +.It Dv EL_RPROMPT , Fa "char *(*f)(EditLine *)" +Define right side prompt printing function as +.Fa f , +which is to return a string that contains the prompt. +.It Dv EL_RPROMPT_ESC , Fa "char *(*f)(EditLine *)" , Fa "char c" +Define the right prompt printing function but with a literal escape character. +.It Dv EL_TERMINAL , Fa "const char *type" +Define terminal type of the tty to be +.Fa type , +or to +.Ev TERM +if +.Fa type +is +.Dv NULL . +.It Dv EL_EDITOR , Fa "const char *mode" +Set editing mode to +.Fa mode , +which must be one of +.Dq emacs +or +.Dq vi . +.It Dv EL_SIGNAL , Fa "int flag" +If +.Fa flag +is non-zero, +.Nm +will install its own signal handler for the following signals when +reading command input: +.Dv SIGCONT , +.Dv SIGHUP , +.Dv SIGINT , +.Dv SIGQUIT , +.Dv SIGSTOP , +.Dv SIGTERM , +.Dv SIGTSTP , +and +.Dv SIGWINCH . +Otherwise, the current signal handlers will be used. +.It Dv EL_BIND , Fa "const char *" , Fa "..." , Dv NULL +Perform the +.Ic bind +builtin command. +Refer to +.Xr editrc 5 +for more information. +.It Dv EL_ECHOTC , Fa "const char *" , Fa "..." , Dv NULL +Perform the +.Ic echotc +builtin command. +Refer to +.Xr editrc 5 +for more information. +.It Dv EL_SETTC , Fa "const char *" , Fa "..." , Dv NULL +Perform the +.Ic settc +builtin command. +Refer to +.Xr editrc 5 +for more information. +.It Dv EL_SETTY , Fa "const char *" , Fa "..." , Dv NULL +Perform the +.Ic setty +builtin command. +Refer to +.Xr editrc 5 +for more information. +.It Dv EL_TELLTC , Fa "const char *" , Fa "..." , Dv NULL +Perform the +.Ic telltc +builtin command. +Refer to +.Xr editrc 5 +for more information. +.It Dv EL_ADDFN , Fa "const char *name" , Fa "const char *help" , \ +Fa "unsigned char (*func)(EditLine *e, int ch)" +Add a user defined function, +.Fn func , +referred to as +.Fa name +which is invoked when a key which is bound to +.Fa name +is entered. +.Fa help +is a description of +.Fa name . +At invocation time, +.Fa ch +is the key which caused the invocation. +The return value of +.Fn func +should be one of: +.Bl -tag -width "CC_REDISPLAY" +.It Dv CC_NORM +Add a normal character. +.It Dv CC_NEWLINE +End of line was entered. +.It Dv CC_EOF +EOF was entered. +.It Dv CC_ARGHACK +Expecting further command input as arguments, do nothing visually. +.It Dv CC_REFRESH +Refresh display. +.It Dv CC_REFRESH_BEEP +Refresh display, and beep. +.It Dv CC_CURSOR +Cursor moved, so update and perform +.Dv CC_REFRESH . +.It Dv CC_REDISPLAY +Redisplay entire input line. +This is useful if a key binding outputs extra information. +.It Dv CC_ERROR +An error occurred. +Beep, and flush tty. +.It Dv CC_FATAL +Fatal error, reset tty to known state. +.El +.It Dv EL_HIST , Fa "History *(*func)(History *, int op, ...)" , \ +Fa "const char *ptr" +Defines which history function to use, which is usually +.Fn history . +.Fa ptr +should be the value returned by +.Fn history_init . +.It Dv EL_EDITMODE , Fa "int flag" +If +.Fa flag +is non-zero, +editing is enabled (the default). +Note that this is only an indication, and does not +affect the operation of +.Nm . +At this time, it is the caller's responsibility to +check this +(using +.Fn el_get ) +to determine if editing should be enabled or not. +.It Dv EL_UNBUFFERED , Fa "int flag" +If +.Fa flag +is zero, +unbuffered mode is disabled (the default). +In unbuffered mode, +.Fn el_gets +will return immediately after processing a single character. +.It Dv EL_GETCFN , Fa "el_rfunc_t f" +Whenever reading a character, use the function +.Bd -ragged -offset indent -compact +.Ft int +.Fo f +.Fa "EditLine *e" +.Fa "wchar_t *wc" +.Fc +.Ed +which stores the character in +.Fa wc +and returns 1 on success, 0 on end of file, or \-1 on I/O or encoding +errors. +Functions internally using it include +.Fn el_wgets , +.Fn el_wgetc , +.Fn el_gets , +and +.Fn el_getc . +Initially, a builtin function is installed, and replacing it +is discouraged because writing such a function is very error prone. +The builtin function can be restored at any time by passing the +special value +.Dv EL_BUILTIN_GETCFN +instead of a function pointer. +.It Dv EL_CLIENTDATA , Fa "void *data" +Register +.Fa data +to be associated with this EditLine structure. +It can be retrieved with the corresponding +.Fn el_get +call. +.It Dv EL_SETFP , Fa "int fd" , Fa "FILE *fp" +Set the current +.Nm editline +file pointer for +.Dq input +.Fa fd += +.Dv 0 , +.Dq output +.Fa fd += +.Dv 1 , +or +.Dq error +.Fa fd += +.Dv 2 +from +.Fa fp . +.El +.It Fn el_get +Get +.Nm +parameters. +.Fa op +determines which parameter to retrieve into +.Fa result . +Returns 0 if successful, \-1 otherwise. +.Pp +The following values for +.Fa op +are supported, along with actual type of +.Fa result : +.Bl -tag -width 4n +.It Dv EL_PROMPT , Fa "char *(*f)(EditLine *)" , Fa "char *c" +Set +.Fa f +to a pointer to the function that displays the prompt. +If +.Fa c +is not +.Dv NULL , +set it to the start/stop literal prompt character. +.It Dv EL_RPROMPT , Fa "char *(*f)(EditLine *)" , Fa "char *c" +Set +.Fa f +to a pointer to the function that displays the prompt. +If +.Fa c +is not +.Dv NULL , +set it to the start/stop literal prompt character. +.It Dv EL_EDITOR , Fa "const char **n" +Set the name of the editor in +.Fa n , +which will be one of +.Dq emacs +or +.Dq vi . +.It Dv EL_GETTC , Fa "const char *name" , Fa "void *value" +If +.Fa name +is a valid +.Xr termcap 5 +capability set +.Fa value +to the current value of that capability. +.It Dv EL_SIGNAL , Fa "int *s" +Set +.Fa s +to non-zero if +.Nm +has installed private signal handlers (see +.Fn el_get +above). +.It Dv EL_EDITMODE , Fa "int *c" +Set +.Fa c +to non-zero if editing is enabled. +.It Dv EL_GETCFN , Fa "el_rfunc_t *f" +Set +.Fa f +to a pointer to the function that reads characters, or to +.Dv EL_BUILTIN_GETCFN +if the builtin function is in use. +.It Dv EL_CLIENTDATA , Fa "void **data" +Set +.Fa data +to the previously registered client data set by an +.Fn el_set +call. +.It Dv EL_UNBUFFERED , Fa "int *c" +Set +.Fa c +to non-zero if unbuffered mode is enabled. +.It Dv EL_GETFP , Fa "int fd", Fa "FILE **fp" +Set +.Fa fp +to the current +.Nm editline +file pointer for +.Dq input +.Fa fd += +.Dv 0 , +.Dq output +.Fa fd += +.Dv 1 , +or +.Dq error +.Fa fd += +.Dv 2 . +.El +.It Fn el_source +Initialize +.Nm +by reading the contents of +.Fa file . +.Fn el_parse +is called for each line in +.Fa file . +If +.Fa file +is +.Dv NULL , +try +.Pa $EDITRC +and if that is not set +.Pa $HOME/.editrc . +Refer to +.Xr editrc 5 +for details on the format of +.Fa file . +.Fn el_source +returns 0 on success and \-1 on error. +.It Fn el_resize +Must be called if the terminal size changes. +If +.Dv EL_SIGNAL +has been set with +.Fn el_set , +then this is done automatically. +Otherwise, it's the responsibility of the application to call +.Fn el_resize +on the appropriate occasions. +.It Fn el_cursor +Move the cursor to the right (if positive) or to the left (if negative) +.Fa count +characters. +Returns the resulting offset of the cursor from the beginning of the line. +.It Fn el_line +Return the editing information for the current line in a +.Fa LineInfo +structure, which is defined as follows: +.Bd -literal +typedef struct lineinfo { + const char *buffer; /* address of buffer */ + const char *cursor; /* address of cursor */ + const char *lastchar; /* address of last character */ +} LineInfo; +.Ed +.Pp +.Fa buffer +is not NUL terminated. +This function may be called after +.Fn el_gets +to obtain the +.Fa LineInfo +structure pertaining to line returned by that function, +and from within user defined functions added with +.Dv EL_ADDFN . +.It Fn el_insertstr +Insert +.Fa str +into the line at the cursor. +Returns \-1 if +.Fa str +is empty or won't fit, and 0 otherwise. +.It Fn el_deletestr +Delete +.Fa count +characters before the cursor. +.El +.Sh HISTORY LIST FUNCTIONS +The history functions use a common data structure, +.Fa History , +which is created by +.Fn history_init +and freed by +.Fn history_end . +.Pp +The following functions are available: +.Bl -tag -width 4n +.It Fn history_init +Initialize the history list, and return a data structure +to be used by all other history list functions, or +.Dv NULL +on failure. +.It Fn history_end +Clean up and finish with +.Fa h , +assumed to have been created with +.Fn history_init . +.It Fn history +Perform operation +.Fa op +on the history list, with optional arguments as needed by the +operation. +.Fa ev +is changed accordingly to operation. +The following values for +.Fa op +are supported, along with the required argument list: +.Bl -tag -width 4n +.It Dv H_SETSIZE , Fa "int size" +Set size of history to +.Fa size +elements. +.It Dv H_GETSIZE +Get number of events currently in history. +.It Dv H_END +Cleans up and finishes with +.Fa h , +assumed to be created with +.Fn history_init . +.It Dv H_CLEAR +Clear the history. +.It Dv H_FUNC , Fa "void *ptr" , Fa "history_gfun_t first" , \ +Fa "history_gfun_t next" , Fa "history_gfun_t last" , \ +Fa "history_gfun_t prev" , Fa "history_gfun_t curr" , \ +Fa "history_sfun_t set" , Fa "history_vfun_t clear" , \ +Fa "history_efun_t enter" , Fa "history_efun_t add" +Define functions to perform various history operations. +.Fa ptr +is the argument given to a function when it's invoked. +.It Dv H_FIRST +Return the first element in the history. +.It Dv H_LAST +Return the last element in the history. +.It Dv H_PREV +Return the previous element in the history. +It is newer than the current one. +.It Dv H_NEXT +Return the next element in the history. +It is older than the current one. +.It Dv H_CURR +Return the current element in the history. +.It Dv H_SET , Fa "int position" +Set the cursor to point to the requested element. +.It Dv H_ADD , Fa "const char *str" +Append +.Fa str +to the current element of the history, or perform the +.Dv H_ENTER +operation with argument +.Fa str +if there is no current element. +.It Dv H_APPEND , Fa "const char *str" +Append +.Fa str +to the last new element of the history. +.It Dv H_ENTER , Fa "const char *str" +Add +.Fa str +as a new element to the history and, if necessary, +removing the oldest entry to keep the list to the created size. +If +.Dv H_SETUNIQUE +has been called with a non-zero argument, the element +will not be entered into the history if its contents match +the ones of the current history element. +If the element is entered +.Fn history +returns 1; if it is ignored as a duplicate returns 0. +Finally +.Fn history +returns \-1 if an error occurred. +.It Dv H_PREV_STR , Fa "const char *str" +Return the closest previous event that starts with +.Fa str . +.It Dv H_NEXT_STR , Fa "const char *str" +Return the closest next event that starts with +.Fa str . +.It Dv H_PREV_EVENT , Fa "int e" +Return the previous event numbered +.Fa e . +.It Dv H_NEXT_EVENT , Fa "int e" +Return the next event numbered +.Fa e . +.It Dv H_LOAD , Fa "const char *file" +Load the history list stored in +.Fa file . +.It Dv H_SAVE , Fa "const char *file" +Save the history list to +.Fa file . +.It Dv H_SAVE_INCR , Fa "const char *file" , Fa "int e" +Append the history list since the event numbered +.Fa e +to +.Fa file . +.It Dv H_SAVE_FP , Fa "FILE *fp" +Save the history list to the opened +.Ft FILE +pointer +.Fa fp . +.It Dv H_SAVE_FP_INCR , Fa "FILE *fp" , Fa "int e" +Append the history list since the event numbered +.Fa e +to the opened +.Ft FILE +pointer +.Fa fp . +.It Dv H_SETUNIQUE , Fa "int unique" +Set flag that adjacent identical event strings should not be entered +into the history. +.It Dv H_GETUNIQUE +Retrieve the current setting if adjacent identical elements should +be entered into the history. +.It Dv H_DEL , Fa "int e" +Delete the event numbered +.Fa e . +This function is only provided for +.Xr readline 3 +compatibility. +The caller is responsible for free'ing the string in the returned +.Fa HistEvent . +.El +.Pp +.Fn history +returns >= 0 if the operation +.Fa op +succeeds. +Otherwise, \-1 is returned and +.Fa ev +is updated to contain more details about the error. +.El +.Sh TOKENIZATION FUNCTIONS +The tokenization functions use a common data structure, +.Fa Tokenizer , +which is created by +.Fn tok_init +and freed by +.Fn tok_end . +.Pp +The following functions are available: +.Bl -tag -width 4n +.It Fn tok_init +Initialize the tokenizer, and return a data structure +to be used by all other tokenizer functions. +.Fa IFS +contains the Input Field Separators, which defaults to +.Aq space , +.Aq tab , +and +.Aq newline +if +.Dv NULL . +.It Fn tok_end +Clean up and finish with +.Fa t , +assumed to have been created with +.Fn tok_init . +.It Fn tok_reset +Reset the tokenizer state. +Use after a line has been successfully tokenized +by +.Fn tok_line +or +.Fn tok_str +and before a new line is to be tokenized. +.It Fn tok_line +Tokenize +.Fa li , +If successful, modify: +.Fa argv +to contain the words, +.Fa argc +to contain the number of words, +.Fa cursorc +(if not +.Dv NULL ) +to contain the index of the word containing the cursor, +and +.Fa cursoro +(if not +.Dv NULL ) +to contain the offset within +.Fa argv[cursorc] +of the cursor. +.Pp +Returns +0 if successful, +\-1 for an internal error, +1 for an unmatched single quote, +2 for an unmatched double quote, +and +3 for a backslash quoted +.Aq newline . +A positive exit code indicates that another line should be read +and tokenization attempted again. +. +.It Fn tok_str +A simpler form of +.Fn tok_line ; +.Fa str +is a NUL terminated string to tokenize. +.El +. +.\"XXX.Sh EXAMPLES +.\"XXX: provide some examples +.Sh SEE ALSO +.Xr sh 1 , +.Xr signal 3 , +.Xr termcap 3 , +.Xr editrc 5 , +.Xr termcap 5 , +.Xr editline 7 +.Sh HISTORY +The +.Nm +library first appeared in +.Bx 4.4 . +.Dv CC_REDISPLAY +appeared in +.Nx 1.3 . +.Dv CC_REFRESH_BEEP , +.Dv EL_EDITMODE +and the readline emulation appeared in +.Nx 1.4 . +.Dv EL_RPROMPT +appeared in +.Nx 1.5 . +.Sh AUTHORS +.An -nosplit +The +.Nm +library was written by +.An Christos Zoulas . +.An Luke Mewburn +wrote this manual and implemented +.Dv CC_REDISPLAY , +.Dv CC_REFRESH_BEEP , +.Dv EL_EDITMODE , +and +.Dv EL_RPROMPT . +.An Jaromir Dolecek +implemented the readline emulation. +.An Johny Mattsson +implemented wide-character support. +.Sh BUGS +At this time, it is the responsibility of the caller to +check the result of the +.Dv EL_EDITMODE +operation of +.Fn el_get +(after an +.Fn el_source +or +.Fn el_parse ) +to determine if +.Nm +should be used for further input. +I.e., +.Dv EL_EDITMODE +is purely an indication of the result of the most recent +.Xr editrc 5 +.Ic edit +command. diff --git a/bin/catsh/libedit/editline.7 b/bin/catsh/libedit/editline.7 new file mode 100644 index 00000000..863bab96 --- /dev/null +++ b/bin/catsh/libedit/editline.7 @@ -0,0 +1,935 @@ +.\" $NetBSD: editline.7,v 1.5 2016/05/09 21:27:55 christos Exp $ +.\" $OpenBSD: editline.7,v 1.1 2016/04/20 01:11:45 schwarze Exp $ +.\" +.\" Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd May 7, 2016 +.Dt EDITLINE 7 +.Os +.Sh NAME +.Nm editline +.Nd line editing user interface +.Sh DESCRIPTION +When a program using the +.Xr editline 3 +library prompts for an input string using the function +.Xr el_wgets 3 , +it reads characters from the terminal. +Invalid input bytes that do not form characters are silently +discarded. +For each character read, one editor command is executed. +The mapping of input characters to editor commands depends on the +editing mode. +There are three editing modes: vi insert mode, vi command mode, +and emacs mode. +The default is vi insert mode. +The program can switch the default to emacs mode by using the +.Xr el_set 3 +or +.Xr el_parse 3 +functions, and the user can switch to emacs mode either in the +.Xr editrc 5 +configuration file or interactively with the +.Ic ed-command +editor command, in all three cases executing the +.Ic bind Fl e +builtin command. +.Pp +If trying to read from the terminal results in end of file or an +error, the library signals end of file to the program and does not +return a string. +.Ss Input character bindings +All default bindings described below can be overridden by individual +programs and can be changed with the +.Xr editrc 5 +.Ic bind +builtin command. +.Pp +In the following tables, +.Sq Ctrl- +indicates a character with the bit 0x40 flipped, and +.Sq Meta- +indicates a character with the bit 0x80 set. +In vi insert mode and in emacs mode, all Meta-characters considered +printable by the current +.Xr locale 1 +are bound to +.Ic ed-insert +instead of to the editor command listed below. +Consequently, in UTF-8 mode, most of the Meta-characters are not +directly accessible because their code points are occupied by +printable Unicode characters, and Meta-characters are usually input +using the +.Ic em-meta-next +editor command. +For example, to enter +.Sq Meta-B +in order to call the +.Ic ed-prev-word +editor command in emacs mode, call +.Ic em-meta-next +by pressing and releasing the escape key (or equivalently, Ctrl-[), +then press and release the +.Sq B +key. +If you have configured a Meta-key on your keyboard, for example +with +.Ql setxkbmap -option altwin:left_meta_win , +the Ctrl-Meta-characters are directly accessible. +For example, to enter +.Sq Ctrl-Meta-H +in order to call the +.Ic ed-delete-prev-word +editor command in emacs mode, hold down the keys +.Sq Ctrl , +.Sq Meta , +and +.Sq H +at the same time. +Alternatively, press and release the escape key, then press and +release +.Sq Ctrl-H . +.Pp +In vi input mode, input characters are bound to the following editor +commands by default: +.Bl -column -offset indent "Ctrl-Z, TSTP" "ed-search-next-history" +.It Ctrl-D, EOF Ta Ic vi-list-or-eof +.It Ctrl-H, BS Ta Ic vi-delete-prev-char +.It Ctrl-J, LF Ta Ic ed-newline +.It Ctrl-M, CR Ta Ic ed-newline +.It Ctrl-Q Ta Ic ed-tty-start-output +.It Ctrl-S Ta Ic ed-tty-stop-output +.It Ctrl-U Ta Ic vi-kill-line-prev +.It Ctrl-V Ta Ic ed-quoted-insert +.It Ctrl-W Ta Ic ed-delete-prev-word +.It Ctrl-[, ESC Ta Ic vi-command-mode +.It Ctrl-\e, QUIT Ta Ic ed-tty-sigquit +.It Ctrl-?, DEL Ta Ic vi-delete-prev-char +.El +.Pp +All other input characters except the NUL character (Ctrl-@) are +bound to +.Ic ed-insert . +.Pp +In vi command mode, input characters are bound to the following +editor commands by default: +.Bl -column -offset indent "Ctrl-Z, TSTP" "ed-search-next-history" +.It Ctrl-A Ta Ic ed-move-to-beg +.It Ctrl-C, INT Ta Ic ed-tty-sigint +.It Ctrl-E Ta Ic ed-move-to-end +.It Ctrl-H, BS Ta Ic ed-delete-prev-char +.It Ctrl-J, LF Ta Ic ed-newline +.It Ctrl-K Ta Ic ed-kill-line +.It Ctrl-L, FF Ta Ic ed-clear-screen +.It Ctrl-M, CR Ta Ic ed-newline +.It Ctrl-N Ta Ic ed-next-history +.It Ctrl-O Ta Ic ed-tty-flush-output +.It Ctrl-P Ta Ic ed-prev-history +.It Ctrl-Q Ta Ic ed-tty-start-output +.It Ctrl-R Ta Ic ed-redisplay +.It Ctrl-S Ta Ic ed-tty-stop-output +.It Ctrl-U Ta Ic vi-kill-line-prev +.It Ctrl-W Ta Ic ed-delete-prev-word +.It Ctrl-[, ESC Ta Ic em-meta-next +.It Ctrl-\e, QUIT Ta Ic ed-tty-sigquit +.It Space Ta Ic ed-next-char +.It # Ta Ic vi-comment-out +.It $ Ta Ic ed-move-to-end +.It % Ta Ic vi-match +.It + Ta Ic ed-next-history +.It \&, Ta Ic vi-repeat-prev-char +.It - Ta Ic ed-prev-history +.It \&. Ta Ic vi-redo +.It / Ta Ic vi-search-prev +.It 0 Ta Ic vi-zero +.It 1 to 9 Ta Ic ed-argument-digit +.It \&: Ta Ic ed-command +.It \&; Ta Ic vi-repeat-next-char +.It \&? Ta Ic vi-search-next +.It @ Ta Ic vi-alias +.It A Ta Ic vi-add-at-eol +.It B Ta Ic vi-prev-big-word +.It C Ta Ic vi-change-to-eol +.It D Ta Ic ed-kill-line +.It E Ta Ic vi-end-big-word +.It F Ta Ic vi-prev-char +.It G Ta Ic vi-to-history-line +.It I Ta Ic vi-insert-at-bol +.It J Ta Ic ed-search-next-history +.It K Ta Ic ed-search-prev-history +.It N Ta Ic vi-repeat-search-prev +.It O Ta Ic ed-sequence-lead-in +.It P Ta Ic vi-paste-prev +.It R Ta Ic vi-replace-mode +.It S Ta Ic vi-substitute-line +.It T Ta Ic vi-to-prev-char +.It U Ta Ic vi-undo-line +.It W Ta Ic vi-next-big-word +.It X Ta Ic ed-delete-prev-char +.It Y Ta Ic vi-yank-end +.It \&[ Ta Ic ed-sequence-lead-in +.It ^ Ta Ic ed-move-to-beg +.It _ Ta Ic vi-history-word +.It a Ta Ic vi-add +.It b Ta Ic vi-prev-word +.It c Ta Ic vi-change-meta +.It d Ta Ic vi-delete-meta +.It e Ta Ic vi-end-word +.It f Ta Ic vi-next-char +.It h Ta Ic ed-prev-char +.It i Ta Ic vi-insert +.It j Ta Ic ed-next-history +.It k Ta Ic ed-prev-history +.It l Ta Ic ed-next-char +.It n Ta Ic vi-repeat-search-next +.It p Ta Ic vi-paste-next +.It r Ta Ic vi-replace-char +.It s Ta Ic vi-substitute-char +.It t Ta Ic vi-to-next-char +.It u Ta Ic vi-undo +.It v Ta Ic vi-histedit +.It w Ta Ic vi-next-word +.It x Ta Ic ed-delete-next-char +.It y Ta Ic vi-yank +.It \&| Ta Ic vi-to-column +.It ~ Ta Ic vi-change-case +.It Ctrl-?, DEL Ta Ic ed-delete-prev-char +.It Meta-O Ta Ic ed-sequence-lead-in +.It Meta-[ Ta Ic ed-sequence-lead-in +.El +.Pp +In emacs mode, input characters are bound to the following editor +commands by default: +.Bl -column -offset indent "Ctrl-Z, TSTP" "ed-search-next-history" +.It 0 to 9 Ta Ic ed-digit +.It Ctrl-@, NUL Ta Ic em-set-mark +.It Ctrl-A Ta Ic ed-move-to-beg +.It Ctrl-B Ta Ic ed-prev-char +.It Ctrl-C, INT Ta Ic ed-tty-sigint +.It Ctrl-D, EOF Ta Ic em-delete-or-list +.It Ctrl-E Ta Ic ed-move-to-end +.It Ctrl-F Ta Ic ed-next-char +.It Ctrl-H, BS Ta Ic em-delete-prev-char +.It Ctrl-J, LF Ta Ic ed-newline +.It Ctrl-K Ta Ic ed-kill-line +.It Ctrl-L, FF Ta Ic ed-clear-screen +.It Ctrl-M, CR Ta Ic ed-newline +.It Ctrl-N Ta Ic ed-next-history +.It Ctrl-O Ta Ic ed-tty-flush-output +.It Ctrl-P Ta Ic ed-prev-history +.It Ctrl-Q Ta Ic ed-tty-start-output +.It Ctrl-R Ta Ic ed-redisplay +.It Ctrl-S Ta Ic ed-tty-stop-output +.It Ctrl-T Ta Ic ed-transpose-chars +.It Ctrl-U Ta Ic ed-kill-line +.It Ctrl-V Ta Ic ed-quoted-insert +.It Ctrl-W Ta Ic em-kill-region +.It Ctrl-X Ta Ic ed-sequence-lead-in +.It Ctrl-Y Ta Ic em-yank +.It Ctrl-Z, TSTP Ta Ic ed-tty-sigtstp +.It Ctrl-[, ESC Ta Ic em-meta-next +.It Ctrl-\e, QUIT Ta Ic ed-tty-sigquit +.It Ctrl-] Ta Ic ed-tty-dsusp +.It Ctrl-?, DEL Ta Ic em-delete-prev-char +.It Ctrl-Meta-H Ta Ic ed-delete-prev-word +.It Ctrl-Meta-L Ta Ic ed-clear-screen +.It Ctrl-Meta-_ Ta Ic em-copy-prev-word +.It Meta-0 to 9 Ta Ic ed-argument-digit +.It Meta-B Ta Ic ed-prev-word +.It Meta-C Ta Ic em-capitol-case +.It Meta-D Ta Ic em-delete-next-word +.It Meta-F Ta Ic em-next-word +.It Meta-L Ta Ic em-lower-case +.It Meta-N Ta Ic ed-search-next-history +.It Meta-O Ta Ic ed-sequence-lead-in +.It Meta-P Ta Ic ed-search-prev-history +.It Meta-U Ta Ic em-upper-case +.It Meta-W Ta Ic em-copy-region +.It Meta-X Ta Ic ed-command +.It Meta-[ Ta Ic ed-sequence-lead-in +.It Meta-b Ta Ic ed-prev-word +.It Meta-c Ta Ic em-capitol-case +.It Meta-d Ta Ic em-delete-next-word +.It Meta-f Ta Ic em-next-word +.It Meta-l Ta Ic em-lower-case +.It Meta-n Ta Ic ed-search-next-history +.It Meta-p Ta Ic ed-search-prev-history +.It Meta-u Ta Ic em-upper-case +.It Meta-w Ta Ic em-copy-region +.It Meta-x Ta Ic ed-command +.It Ctrl-Meta-? Ta Ic ed-delete-prev-word +.El +.Pp +The remaining +.Xr ascii 7 +characters in the range 0x20 to 0x7e are bound to +.Ic ed-insert . +.Pp +If standard output is not connected to a terminal device +or +.Xr el_set 3 +was used to set +.Dv EL_EDITMODE +to 0, all input character bindings are disabled and all characters +typed are appended to the edit buffer. +In that case, the edit buffer is returned to the program after a +newline or carriage return character is typed, or after the first +character typed if +.Xr el_set 3 +was used to set +.Dv EL_UNBUFFERED +to non-zero. +.Ss Editor commands +Most editor commands accept an optional argument. +The argument is entered by prefixing the editor command with one +or more of the editor commands +.Ic ed-argument-digit , +.Ic ed-digit , +.Ic em-universal-argument , +or +.Ic vi-zero . +When an argument is not provided, it defaults to 1. +For most editor commands, the effect of an argument is to repeatedly +execute the command that number of times. +.Pp +When talking about a character string from a left character to a +right character, the left character is included in the string, while +the right character is not included. +.Pp +If an editor command causes an error, the input character is discarded, +no action occurs, and the terminal bell is rung. +In case of a non-fatal error, the terminal bell is also rung, +but the editor command takes effect anyway. +.Pp +In the following list, the default key bindings are listed after +each editor command. +.Bl -tag -width 4n +.It Ic ed-argument-digit Pq vi command: 1 to 9; emacs: Meta-0 to Meta-9 +If in argument input mode, append the input digit to the argument +being read. +Otherwise, switch to argument input mode and use the input digit +as the most significant digit of the argument. +It is an error if the input character is not a digit or if the +existing argument is already greater than a million. +.It Ic ed-clear-screen Pq vi command: Ctrl-L; emacs: Ctrl-L, Ctrl-Meta-L +Clear the screen and display the edit buffer at the top. +Ignore any argument. +.It Ic ed-command Pq vi command: So \&: Sc ; emacs: Meta-X, Meta-x +Read a line from the terminal bypassing the normal line editing +functionality and execute that line as an +.Xr editrc 5 +builtin command. +If in vi command mode, also switch back to vi insert mode. +Ignore any argument. +.It Ic ed-delete-next-char Pq vi command: x +Delete the character at the cursor position. +With an argument, delete that number of characters. +In emacs mode, it is an error if the cursor is at the end of the +edit buffer. +In vi mode, the last character in the edit buffer is deleted in +that case, and it is an error if the buffer is empty. +.It Ic ed-delete-prev-char Pq vi command: X, Ctrl-H, BS, Ctrl-?, DEL +Delete the character to the left of the cursor position. +With an argument, delete that number of characters. +It is an error if the cursor is at the beginning of the edit buffer. +.It Ic ed-delete-prev-word Pq vi: Ctrl-W; emacs: Ctrl-Meta-H, Ctrl-Meta-? +Move to the left to the closest beginning of a word, delete the +string from that position to the cursor, and save it to the cut +buffer. +With an argument, delete that number of words. +It is an error if the cursor is at the beginning of the edit buffer. +.It Ic ed-digit Pq emacs: 0 to 9 +If in argument input mode, append the input digit to the argument +being read. +Otherwise, call +.Ic ed-insert . +It is an error if the input character is not a digit or if the +existing argument is already greater than a million. +.It Ic ed-end-of-file Pq not bound by default +Discard the edit buffer and indicate end of file to the program. +Ignore any argument. +.It Ic ed-ignore Pq various +Discard the input character and do nothing. +.It Ic ed-insert Pq vi input: almost all; emacs: printable characters +In insert mode, insert the input character left of the cursor +position. +In replace mode, overwrite the character at the cursor and move the +cursor to the right by one character position. +Accept an argument to do this repeatedly. +It is an error if the input character is the NUL character (Ctrl-@). +Failure to enlarge the edit buffer also results in an error. +.It Ic ed-kill-line Pq vi command: D, Ctrl-K; emacs: Ctrl-K, Ctrl-U +Delete the string from the cursor position to the end of the line +and save it to the cut buffer. +Ignore any argument. +.It Ic ed-move-to-beg Pq vi command: ^, Ctrl-A; emacs: Ctrl-A +In vi mode, move the cursor to the first non-space character in the +edit buffer. +In emacs mode, move the cursor to the beginning of the edit buffer. +Ignore any argument. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +.It Ic ed-move-to-end Pq vi command: $, Ctrl-E; emacs: Ctrl-E +Move the cursor to the end of the edit buffer. +Ignore any argument. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +.It Ic ed-newline Pq all modes: Ctrl-J, LF, Ctrl-M, CR +Append a newline character to the edit buffer and return the edit +buffer to the program. +Ignore any argument. +.It Ic ed-next-char Pq vi command: Space, l; emacs: Ctrl-F +Move the cursor one character position to the right. +With an argument, move by that number of characters. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +It is an error if the cursor is already at the end of the edit +buffer. +.It Ic ed-next-history Pq vi command: j, +, Ctrl-N; emacs: Ctrl-N +Replace the edit buffer with the next history line. +That line is older than the current line. +With an argument, go forward by that number of history lines. +It is a non-fatal error to advance by more lines than are available. +.It Ic ed-next-line Pq not bound by default +Move the cursor down one line. +With an argument, move down by that number of lines. +It is an error if the edit buffer does not contain enough newline +characters to the right of the cursor position. +.It Ic ed-prev-char Pq vi command: h; emacs: Ctrl-B +Move the cursor one character position to the left. +With an argument, move by that number of characters. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +It is an error if the cursor is already at the beginning of the +edit buffer. +.It Ic ed-prev-history Pq vi command: k, -, Ctrl-P; emacs: Ctrl-P +Replace the edit buffer with the previous history line. +That line is newer than the current line. +With an argument, go back by that number of lines. +It is a non-fatal error to back up by more lines than are available. +.It Ic ed-prev-line Pq not bound by default +Move the cursor up one line. +With an argument, move up by that number of lines. +It is an error if the edit buffer does not contain enough newline +characters to the left of the cursor position. +.It Ic ed-prev-word Pq emacs: Meta-B, Meta-b +Move the cursor to the left to the closest beginning of a word. +With an argument, repeat that number of times. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +It is an error if the cursor is already at the beginning of the +edit buffer. +.It Ic ed-quoted-insert Pq vi insert, emacs: Ctrl-V +Read one character from the terminal bypassing the normal line +editing functionality and call +.Ic ed-insert +on it. +If trying to read the character returns end of file or an error, +call +.Ic ed-end-of-file +instead. +.It Ic ed-redisplay Pq vi command, emacs: Ctrl-R +Redisplay everything. +Ignore any argument. +.It Ic ed-search-next-history Pq vi command: J; emacs: Meta-N, Meta-n +Replace the edit buffer with the next matching history entry. +.It Ic ed-search-prev-history Pq vi command: K; emacs: Meta-P, Meta-p +Replace the edit buffer with the previous matching history entry. +.It Ic ed-sequence-lead-in Pq vi cmd: O, \&[; emacs: Ctrl-X;\ + both: Meta-O, Meta-[ +Call a macro. +See the section about +.Sx Macros +below for details. +.It Ic ed-start-over Pq not bound by default +Discard the contents of the edit buffer and start from scratch. +Ignore any argument. +.It Ic ed-transpose-chars Pq emacs: Ctrl-T +Exchange the character at the cursor position with the one to the +left of it and move the cursor to the character to the right of the +two exchanged characters. +Ignore any argument. +It is an error if the cursor is at the beginning of the edit buffer +or if the edit buffer contains less than two characters. +.It Ic ed-unassigned Pq all characters not listed +This editor command always results in an error. +.It Ic em-capitol-case Pq emacs: Meta-C, Meta-c +Capitalize the string from the cursor to the end of the current +word. +That is, if it contains at least one alphabetic character, convert +the first alphabetic character to upper case, and convert all +characters to the right of it to lower case. +In any case, move the cursor to the next character after the end +of the current word. +.It Ic em-copy-prev-word Pq emacs: Ctrl-Meta-_ +Copy the string from the beginning of the current word to the cursor +and insert it to the left of the cursor. +Move the cursor to the character after the inserted string. +It is an error if the cursor is at the beginning of the edit buffer. +.It Ic em-copy-region Pq emacs: Meta-W, Meta-w +Copy the string from the cursor to the mark to the cut buffer. +It is an error if the mark is not set. +.It Ic em-delete-next-word Pq emacs: Meta-D, Meta-d +Delete the string from the cursor to the end of the current word +and save it to the cut buffer. +It is an error if the cursor is at the end of the edit buffer. +.It Ic em-delete-or-list Pq emacs: Ctrl-D, EOF +If the cursor is not at the end of the line, delete the character +at the cursor. +If the edit buffer is empty, indicate end of file to the program. +It is an error if the cursor is at the end of the edit buffer and +the edit buffer is not empty. +.It Ic em-delete-prev-char Pq emacs: Ctrl-H, BS, Ctrl-?, DEL +Delete the character to the left of the cursor. +It is an error if the cursor is at the beginning of the edit buffer. +.It Ic em-exchange-mark Pq not bound by default +Exchange the cursor and the mark. +.It Ic em-gosmacs-transpose Pq not bound by default +Exchange the two characters to the left of the cursor. +It is an error if the cursor is on the first or second character +of the edit buffer. +.It Ic em-inc-search-next Pq not bound by default +Emacs incremental next search. +.It Ic em-inc-search-prev Pq not bound by default +Emacs incremental reverse search. +.It Ic em-kill-line Pq not bound by default +Delete the entire contents of the edit buffer and save it to the +cut buffer. +.It Ic em-kill-region Pq emacs: Ctrl-W +Delete the string from the cursor to the mark and save it to the +cut buffer. +It is an error if the mark is not set. +.It Ic em-lower-case Pq emacs: Meta-L, Meta-l +Convert the characters from the cursor to the end of the current +word to lower case. +.It Ic em-meta-next Pq vi command, emacs: Ctrl-[, ESC +Set the bit 0x80 on the next character typed. +Unless the resulting code point is printable, holding down the +.Sq Meta- +key while typing that character is a simpler way to achieve the +same effect. +.It Ic em-next-word Pq Meta-F, Meta-f +Move the cursor to the end of the current word. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +It is an error if the cursor is already at the end of the edit +buffer. +.It Ic em-set-mark Pq emacs: Ctrl-Q, NUL +Set the mark at the current cursor position. +.It Ic em-toggle-overwrite Pq not bound by default +Switch from insert to overwrite mode or vice versa. +.It Ic em-universal-argument Pq not bound by default +If in argument input mode, multiply the argument by 4. +Otherwise, switch to argument input mode and set the argument to 4. +It is an error if the existing argument is already greater than a +million. +.It Ic em-upper-case Pq emacs: Meta-U, Meta-u +Convert the characters from the cursor to the end of the current +word to upper case. +.It Ic em-yank Pq emacs: Ctrl-Y +Paste the cut buffer to the left of the cursor. +.It Ic vi-add Pq vi command: a +Switch to vi insert mode. +Unless the cursor is already at the end of the edit buffer, move +it one character position to the right. +.It Ic vi-add-at-eol Pq vi command: A +Switch to vi insert mode and move the cursor to the end of the edit +buffer. +.It Ic vi-alias Pq vi command: @ +If an alias function was defined by calling the +.Xr el_set 3 +or +.Xr el_wset 3 +function with the argument +.Dv EL_ALIAS_TEXT , +read one character from the terminal bypassing the normal line +editing functionality, call the alias function passing the argument that was specified with +.Dv EL_ALIAS_TEXT +as the first argument and the character read, with an underscore +prepended, as the second argument, and pass the string returned +from the alias function to +.Xr el_wpush 3 . +It is an error if no alias function is defined or if trying to read +the character results in end of file or an error. +.It Ic vi-change-case Pq vi command: ~ +Change the case of the character at the cursor and move the cursor +one character position to the right. +It is an error if the cursor is already at the end of the edit +buffer. +.It Ic vi-change-meta Pq vi command: c +Delete the string from the cursor to the position specified by the +following movement command and save a copy of it to the cut buffer. +When given twice in a row, instead delete the whole contents of the +edit buffer and save a copy of it to the cut buffer. +In either case, switch to vi insert mode after that. +.It Ic vi-change-to-eol Pq vi command: C +Delete the string from the cursor position to the end of the line +and save it to the cut buffer, then switch to vi insert mode. +.It Ic vi-command-mode Pq vi insert: Ctrl-[, ESC +Discard pending actions and arguments and switch to vi command mode. +Unless the cursor is already at the beginning of the edit buffer, +move it to the left by one character position. +.It Ic vi-comment-out Pq vi command: # +Insert a +.Sq # +character at the beginning of the edit buffer and return the edit +buffer to the program. +.It Ic vi-delete-meta Pq vi command: d +Delete the string from the cursor to the position specified by the +following movement command and save a copy of it to the cut buffer. +When given twice in a row, instead delete the whole contents of the +edit buffer and save a copy of it to the cut buffer. +.It Ic vi-delete-prev-char Pq vi insert: Ctrl-H, BS, Ctrl-?, DEL +Delete the character to the left of the cursor. +It is an error if the cursor is already at the beginning of the +edit buffer. +.It Ic vi-end-big-word Pq vi command: E +Move the cursor to the end of the current space delimited word. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +It is an error if the cursor is already at the end of the edit +buffer. +.It Ic vi-end-word Pq vi command: e +Move the cursor to the end of the current word. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +It is an error if the cursor is already at the end of the edit +buffer. +.It Ic vi-history-word Pq vi command: _ +Insert the first word from the most recent history entry after the +cursor, move the cursor after to the character after the inserted +word, and switch to vi insert mode. +It is an error if there is no history entry or the most recent +history entry is empty. +.It Ic vi-insert Pq vi command: i +Enter insert mode. +.It Ic vi-insert-at-bol Pq vi command: I +Move the cursor to the beginning of the edit buffer and switch to +vi insert mode. +.It Ic vi-kill-line-prev Pq vi: Ctrl-U +Delete the string from the beginning of the edit buffer to the +cursor and save it to the cut buffer. +.It Ic vi-list-or-eof Pq vi insert: Ctrl-D, EOF +If the edit buffer is empty, indicate end of file to the program. +It is an error if the edit buffer is not empty. +.It Ic vi-match Pq vi command: % +Consider opening and closing parentheses, braces, and brackets as +delimiters. +If the cursor is not at a delimiter, move it to the right until it +gets to one, then move it to the matching delimiter. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +It is an error if there is no delimiter at the cursor or in the +string to the right of the cursor, or if the first such delimiter +has no matching delimiter. +.It Ic vi-next-big-word Pq vi command: W +Move the cursor to the right to the beginning of the next space +delimited word. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +It is an error if the cursor is already at the end of the edit +buffer or on its last character. +.It Ic vi-next-char Pq vi command: f +Read one character from the terminal bypassing the normal line +editing functionality and move the cursor to the right to the next +instance of that character in the edit buffer. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +If trying to read the character results in end of file or an error, +call +.Ic ed-end-of-file +instead. +It is an error if the character is not found searching to the right +in the edit buffer. +.It Ic vi-next-word Pq vi command: w +Move the cursor to the right to the beginning of the next word. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +It is an error if the cursor is already at the end of the edit +buffer or on its last character. +.It Ic vi-paste-next Pq vi command: p +Insert a copy of the cut buffer to the right of the cursor. +It is an error if the cut buffer is empty. +.It Ic vi-paste-prev Pq vi command: P +Insert a copy of the cut buffer to the left of the cursor. +It is an error if the cut buffer is empty. +.It Ic vi-prev-big-word Pq vi command: B +Move the cursor to the left to the next beginning of a space delimited +word. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +It is an error if the cursor is already at the beginning of the +edit buffer. +.It Ic vi-prev-char Pq vi command: F +Read one character from the terminal bypassing the normal line +editing functionality and move the cursor to the left to the next +instance of that character in the edit buffer. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +If trying to read the character results in end of file or an error, +call +.Ic ed-end-of-file +instead. +It is an error if the character is not found searching to the left +in the edit buffer. +.It Ic vi-prev-word Pq vi command: b +Move the cursor to the left to the next beginning of a word. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +It is an error if the cursor is already at the beginning of the +edit buffer. +.It Ic vi-redo Pq vi command: Sq \&. +Redo the last non-motion command. +.It Ic vi-repeat-next-char Pq vi command: Sq \&; +Repeat the most recent character search in the same search direction. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +.It Ic vi-repeat-prev-char Pq vi command: Sq \&, +Repeat the most recent character search in the opposite search +direction. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +.It Ic vi-repeat-search-next Pq vi command: n +Repeat the most recent history search in the same search direction. +.It Ic vi-repeat-search-prev Pq vi command: N +Repeat the most recent history search in the opposite search +direction. +.It Ic vi-replace-char Pq vi command: r +Switch to vi replace mode, and automatically switch back to vi +command mode after the next character typed. +See +.Ic ed-insert +for a description of replace mode. +It is an error if the cursor is at the end of the edit buffer. +.It Ic vi-replace-mode Pq vi command: R +Switch to vi replace mode. +This is a variant of vi insert mode; see +.Ic ed-insert +for the difference. +.It Ic vi-search-next Pq vi command: \&? +Replace the edit buffer with the next matching history entry. +.It Ic vi-search-prev Pq vi command: / +Replace the edit buffer with the previous matching history entry. +.It Ic vi-substitute-char Pq vi command: s +Delete the character at the cursor and switch to vi insert mode. +.It Ic vi-substitute-line Pq vi command: S +Delete the entire contents of the edit buffer, save a copy of it +in the cut buffer, and enter vi insert mode. +.It Ic vi-to-column Pq vi command: \&| +Move the cursor to the column specified as the argument. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +.It Ic vi-to-history-line Pq vi command: G +Replace the edit buffer with the specified history entry. +.It Ic vi-to-next-char Pq vi command: t +Read one character from the terminal bypassing the normal line +editing functionality and move the cursor to the right to the +character before the next instance of that character in the edit +buffer. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +If trying to read the character results in end of file or an error, +call +.Ic ed-end-of-file +instead. +It is an error if the character is not found searching to the right +in the edit buffer. +.It Ic vi-to-prev-char Pq vi command: T +Read one character from the terminal bypassing the normal line +editing functionality and move the cursor to the left to the character +after the next instance of that character in the edit buffer. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +If trying to read the character results in end of file or an error, +call +.Ic ed-end-of-file +instead. +It is an error if the character is not found searching to the left +in the edit buffer. +.It Ic vi-undo Pq vi command: u +Undo the last change. +.It Ic vi-undo-line Pq vi command: U +Undo all changes to the edit buffer. +.It Ic vi-yank Pq vi command: y +Copy the string from the cursor to the position specified by the +following movement command to the cut buffer. +When given twice in a row, instead copy the whole contents of the +edit buffer to the cut buffer. +.It Ic vi-yank-end Pq vi command: Y +Copy the string from the cursor to the end of the edit buffer to +the cut buffer. +.It Ic vi-zero Pq vi command: 0 +If in argument input mode, multiply the argument by ten. +Otherwise, move the cursor to the beginning of the edit buffer. +Can be used as a movement command after +.Ic vi_change_meta , +.Ic vi_delete_meta , +or +.Ic vi_yank . +.El +.Ss Macros +If an input character is bound to the editor command +.Ic ed-sequence-lead-in , +.Nm +attempts to call a macro. +If the input character by itself forms the name of a macro, that +macro is executed. +Otherwise, additional input characters are read until the string +read forms the name of a macro, in which case that macro is executed, +or until the string read matches the beginning of none of the existing +macro names, in which case the string including the final, mismatching +character is discarded and the terminal bell is rung. +.Pp +There are two kinds of macros. +Command macros execute a single editor command. +Keyboard macros return a string of characters that is appended +as a new line to the +.Sx Input Queue . +.Pp +The following command macros are defined by default in vi command +mode and in emacs mode: +.Bl -column -offset indent "Esc O A, Esc O A" "em-exchange-mark" +.It Esc \&[ A, Esc O A Ta Ic ed-prev-history +.It Esc \&[ B, Esc O B Ta Ic ed-next-history +.It Esc \&[ C, Esc O C Ta Ic ed-next-char +.It Esc \&[ D, Esc O D Ta Ic ed-prev-char +.It Esc \&[ F, Esc O F Ta Ic ed-move-to-end +.It Esc \&[ H, Esc O H Ta Ic ed-move-to-beg +.El +.Pp +In vi command mode, they are also defined by default without the +initial escape character. +.Pp +In addition, the +.Nm +library tries to bind the strings generated by the arrow keys +as reported by the +.Xr terminfo 5 +database to these editor commands, unless that would clobber +user settings. +.Pp +In emacs mode, the two-character string +.Dq Ctrl-X Ctrl-X +is bound to the +.Ic em-exchange-mark +editor command. +.Ss Input Queue +The +.Nm +library maintains an input queue operated in FIFO mode. +Whenever it needs an input character, it takes the first character +from the first line of the input queue. +When the queue is empty, it reads from the terminal. +.Pp +A line can be appended to the end of the input queue in several ways: +.Bl -dash -offset indent +.It +By calling one of the keyboard +.Sx Macros . +.It +By calling the editor command +.Ic vi-redo . +.It +By calling the editor command +.Ic vi-alias . +.It +By pressing a key in emacs incremental search mode that doesn't +have a special meaning in that mode but returns to normal emacs +mode. +.It +If an application program directly calls the functions +.Xr el_push 3 +or +.Xr el_wpush 3 , +it can provide additional, program-specific ways +of appending to the input queue. +.El +.Sh SEE ALSO +.Xr mg 1 , +.Xr vi 1 , +.Xr editline 3 , +.Xr el_wgets 3 , +.Xr el_wpush 3 , +.Xr el_wset 3 , +.Xr editrc 5 +.Sh HISTORY +This manual page first appeared in +.Ox 6.0 +and +.Nx 8 . +.Sh AUTHORS +.An -nosplit +This manual page was written by +.An Ingo Schwarze Aq Mt schwarze@openbsd.org . diff --git a/bin/catsh/libedit/editrc.5 b/bin/catsh/libedit/editrc.5 new file mode 100644 index 00000000..9bc94b8b --- /dev/null +++ b/bin/catsh/libedit/editrc.5 @@ -0,0 +1,325 @@ +.\" $NetBSD: editrc.5,v 1.32.8.1 2017/07/23 14:41:26 snj Exp $ +.\" +.\" Copyright (c) 1997-2000 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This file was contributed to The NetBSD Foundation by Luke Mewburn. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd May 22, 2016 +.Dt EDITRC 5 +.Os +.Sh NAME +.Nm editrc +.Nd configuration file for editline library +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +The +.Nm +file defines various settings to be used by the +.Xr editline 3 +library. +.Pp +The format of each line is: +.Pp +.Dl [prog:]command [arg ...] +.Pp +.Ar command +is one of the +.Xr editline 3 +builtin commands. +Refer to +.Sx BUILTIN COMMANDS +for more information. +.Pp +.Ar prog +is the program name string that a program defines when it calls +.Xr el_init 3 +to set up +.Xr editline 3 , +which is usually +.Va argv[0] . +.Ar command +will be executed for any program which matches +.Ar prog . +.Pp +.Ar prog +may also be a +.Xr regex 3 +style +regular expression, in which case +.Ar command +will be executed for any program that matches the regular expression. +.Pp +If +.Ar prog +is absent, +.Ar command +is executed for all programs. +.Sh BUILTIN COMMANDS +The +.Nm editline +library has some builtin commands, which affect the way +that the line editing and history functions operate. +These are based on similar named builtins present in the +.Xr tcsh 1 +shell. +.Pp +The following builtin commands are available: +.Bl -tag -width 4n +.It Ic bind Oo Fl aeklrsv Oc Op Ar key Op Ar command +Without options and arguments, list all bound keys and macros, and +the editor command or input string to which each one is bound. +If only +.Ar key +is supplied, show the binding for that key or macro. +If +.Ar key command +is supplied, bind the editor +.Ar command +to that key or macro. +.Pp +The options are as follows: +.Bl -tag -width 4n +.It Fl a +List or change key bindings in the +.Xr vi 1 +mode alternate (command mode) key map. +.It Fl e +Bind all keys to the standard GNU Emacs-like bindings. +.It Fl k +.Ar key +is interpreted as a symbolic arrow key name, which may be one of +.Sq up , +.Sq down , +.Sq left +or +.Sq right . +.It Fl l +List all editor commands and a short description of each. +.It Fl r +Remove the binding of the key or macro +.Ar key . +.It Fl s +Define a keyboard macro rather than a key binding or command macro: +.Ar command +is taken as a literal string and appended to the input queue whenever +.Ar key +is typed. +Bound keys and macros in +.Ar command +are themselves reinterpreted, and this continues for ten levels of +interpretation. +.It Fl v +Bind all keys to the standard +.Xr vi 1 Ns -like +bindings. +.El +.Pp +The +.Xr editline 7 +manual documents all editor commands and contains more information +about macros and the input queue. +.Pp +.Ar key +and +.Ar command +can contain control characters of the form +.Sm off +.Sq No ^ Ar character +.Sm on +.Po +e.g.\& +.Sq ^A +.Pc , +and the following backslashed escape sequences: +.Pp +.Bl -tag -compact -offset indent -width 4n +.It Ic \ea +Bell +.It Ic \eb +Backspace +.It Ic \ee +Escape +.It Ic \ef +Formfeed +.It Ic \en +Newline +.It Ic \er +Carriage return +.It Ic \et +Horizontal tab +.It Ic \ev +Vertical tab +.Sm off +.It Sy \e Ar nnn +.Sm on +The ASCII character corresponding to the octal number +.Ar nnn . +.El +.Pp +.Sq \e +nullifies the special meaning of the following character, +if it has any, notably +.Sq \e +and +.Sq ^ . +.It Ic echotc Oo Fl sv Oc Ar arg Ar ... +Exercise terminal capabilities given in +.Ar arg ... . +If +.Ar arg +is +.Sq baud , +.Sq cols , +.Sq lines , +.Sq rows , +.Sq meta , +or +.Sq tabs , +the value of that capability is printed, with +.Dq yes +or +.Dq no +indicating that the terminal does or does not have that capability. +.Pp +.Fl s +returns an empty string for non-existent capabilities, rather than +causing an error. +.Fl v +causes messages to be verbose. +.It Ic edit Op Li on | Li off +Enable or disable the +.Nm editline +functionality in a program. +.It Ic history Ar list | Ar size Dv n | Ar unique Dv n +The +.Ar list +command lists all entries in the history. +The +.Ar size +command sets the history size to +.Dv n +entries. +The +.Ar unique +command controls if history should keep duplicate entries. +If +.Dv n +is non zero, only keep unique history entries. +If +.Dv n +is zero, then keep all entries (the default). +.It Ic settc Ar cap Ar val +Set the terminal capability +.Ar cap +to +.Ar val , +as defined in +.Xr termcap 5 . +No sanity checking is done. +.It Ic setty Oo Fl a Oc Oo Fl d Oc Oo Fl q Oc Oo Fl x Oc Oo Ar +mode Oc \ +Oo Ar -mode Oc Oo Ar mode Oc Oo Ar char=c Oc +Control which tty modes that +.Nm +won't allow the user to change. +.Fl d , +.Fl q +or +.Fl x +tells +.Ic setty +to act on the +.Sq edit , +.Sq quote +or +.Sq execute +set of tty modes respectively; defaulting to +.Fl x . +.Pp +Without other arguments, +.Ic setty +lists the modes in the chosen set which are fixed on +.Po +.Sq +mode +.Pc +or off +.Po +.Sq -mode +.Pc . +.Fl a +lists all tty modes in the chosen set regardless of the setting. +With +.Ar +mode , +.Ar -mode +or +.Ar mode , +fixes +.Ar mode +on or off or removes control of +.Ar mode +in the chosen set. +.Pp +.Ic Setty +can also be used to set tty characters to particular values using +.Ar char=value . +If +.Ar value +is empty +then the character is set to +.Dv _POSIX_VDISABLE . +.It Ic telltc +List the values of all the terminal capabilities (see +.Xr termcap 5 ) . +.El +.Sh ENVIRONMENT +.Bl -tag -width "~/.editrcXXX" +.It Ev EDITRC +Names the default configuration file for the +.Xr editline 3 +library. +.El +.Sh FILES +.Bl -tag -width "~/.editrcXXX" +.It Pa ~/.editrc +Last resort, if no other file is specified, +user configuration file for the +.Xr editline 3 +library. +.El +.Sh SEE ALSO +.Xr editline 3 , +.Xr regex 3 , +.Xr termcap 5 , +.Xr editline 7 +.Sh AUTHORS +.An -nosplit +The +.Nm editline +library was written by +.An Christos Zoulas , +and this manual was written by +.An Luke Mewburn , +with some sections inspired by +.Xr tcsh 1 . diff --git a/bin/catsh/libedit/el.c b/bin/catsh/libedit/el.c new file mode 100644 index 00000000..c30f50ca --- /dev/null +++ b/bin/catsh/libedit/el.c @@ -0,0 +1,649 @@ +/* $NetBSD: el.c,v 1.92.8.1 2017/07/23 14:41:26 snj Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)el.c 8.2 (Berkeley) 1/3/94"; +#else +__RCSID("$NetBSD: el.c,v 1.92.8.1 2017/07/23 14:41:26 snj Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * el.c: EditLine interface functions + */ +#include <sys/types.h> +#include <sys/param.h> +#include <ctype.h> +#include <langinfo.h> +#include <locale.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> + +#include "el.h" +#include "parse.h" +#include "read.h" + +/* el_init(): + * Initialize editline and set default parameters. + */ +EditLine * +el_init(const char *prog, FILE *fin, FILE *fout, FILE *ferr) +{ + return el_init_fd(prog, fin, fout, ferr, fileno(fin), fileno(fout), + fileno(ferr)); +} + +EditLine * +el_init_fd(const char *prog, FILE *fin, FILE *fout, FILE *ferr, + int fdin, int fdout, int fderr) +{ + EditLine *el = el_malloc(sizeof(*el)); + + if (el == NULL) + return NULL; + + memset(el, 0, sizeof(EditLine)); + + el->el_infile = fin; + el->el_outfile = fout; + el->el_errfile = ferr; + + el->el_infd = fdin; + el->el_outfd = fdout; + el->el_errfd = fderr; + + el->el_prog = wcsdup(ct_decode_string(prog, &el->el_scratch)); + if (el->el_prog == NULL) { + el_free(el); + return NULL; + } + + /* + * Initialize all the modules. Order is important!!! + */ + el->el_flags = 0; + if (setlocale(LC_CTYPE, NULL) != NULL){ + if (strcmp(nl_langinfo(CODESET), "UTF-8") == 0) + el->el_flags |= CHARSET_IS_UTF8; + } + + if (terminal_init(el) == -1) { + el_free(el->el_prog); + el_free(el); + return NULL; + } + (void) keymacro_init(el); + (void) map_init(el); + if (tty_init(el) == -1) + el->el_flags |= NO_TTY; + (void) ch_init(el); + (void) search_init(el); + (void) hist_init(el); + (void) prompt_init(el); + (void) sig_init(el); + (void) literal_init(el); + if (read_init(el) == -1) { + el_end(el); + return NULL; + } + return el; +} + + +/* el_end(): + * Clean up. + */ +void +el_end(EditLine *el) +{ + + if (el == NULL) + return; + + el_reset(el); + + terminal_end(el); + keymacro_end(el); + map_end(el); + if (!(el->el_flags & NO_TTY)) + tty_end(el); + ch_end(el); + read_end(el->el_read); + search_end(el); + hist_end(el); + prompt_end(el); + sig_end(el); + literal_end(el); + + el_free(el->el_prog); + el_free(el->el_visual.cbuff); + el_free(el->el_visual.wbuff); + el_free(el->el_scratch.cbuff); + el_free(el->el_scratch.wbuff); + el_free(el->el_lgcyconv.cbuff); + el_free(el->el_lgcyconv.wbuff); + el_free(el); +} + + +/* el_reset(): + * Reset the tty and the parser + */ +void +el_reset(EditLine *el) +{ + + tty_cookedmode(el); + ch_reset(el); /* XXX: Do we want that? */ +} + + +/* el_set(): + * set the editline parameters + */ +int +el_wset(EditLine *el, int op, ...) +{ + va_list ap; + int rv = 0; + + if (el == NULL) + return -1; + va_start(ap, op); + + switch (op) { + case EL_PROMPT: + case EL_RPROMPT: { + el_pfunc_t p = va_arg(ap, el_pfunc_t); + + rv = prompt_set(el, p, 0, op, 1); + break; + } + + case EL_RESIZE: { + el_zfunc_t p = va_arg(ap, el_zfunc_t); + void *arg = va_arg(ap, void *); + rv = ch_resizefun(el, p, arg); + break; + } + + case EL_ALIAS_TEXT: { + el_afunc_t p = va_arg(ap, el_afunc_t); + void *arg = va_arg(ap, void *); + rv = ch_aliasfun(el, p, arg); + break; + } + + case EL_PROMPT_ESC: + case EL_RPROMPT_ESC: { + el_pfunc_t p = va_arg(ap, el_pfunc_t); + int c = va_arg(ap, int); + + rv = prompt_set(el, p, (wchar_t)c, op, 1); + break; + } + + case EL_TERMINAL: + rv = terminal_set(el, va_arg(ap, char *)); + break; + + case EL_EDITOR: + rv = map_set_editor(el, va_arg(ap, wchar_t *)); + break; + + case EL_SIGNAL: + if (va_arg(ap, int)) + el->el_flags |= HANDLE_SIGNALS; + else + el->el_flags &= ~HANDLE_SIGNALS; + break; + + case EL_BIND: + case EL_TELLTC: + case EL_SETTC: + case EL_ECHOTC: + case EL_SETTY: + { + const wchar_t *argv[20]; + int i; + + for (i = 1; i < (int)__arraycount(argv); i++) + if ((argv[i] = va_arg(ap, wchar_t *)) == NULL) + break; + + switch (op) { + case EL_BIND: + argv[0] = L"bind"; + rv = map_bind(el, i, argv); + break; + + case EL_TELLTC: + argv[0] = L"telltc"; + rv = terminal_telltc(el, i, argv); + break; + + case EL_SETTC: + argv[0] = L"settc"; + rv = terminal_settc(el, i, argv); + break; + + case EL_ECHOTC: + argv[0] = L"echotc"; + rv = terminal_echotc(el, i, argv); + break; + + case EL_SETTY: + argv[0] = L"setty"; + rv = tty_stty(el, i, argv); + break; + + default: + rv = -1; + EL_ABORT((el->el_errfile, "Bad op %d\n", op)); + break; + } + break; + } + + case EL_ADDFN: + { + wchar_t *name = va_arg(ap, wchar_t *); + wchar_t *help = va_arg(ap, wchar_t *); + el_func_t func = va_arg(ap, el_func_t); + + rv = map_addfunc(el, name, help, func); + break; + } + + case EL_HIST: + { + hist_fun_t func = va_arg(ap, hist_fun_t); + void *ptr = va_arg(ap, void *); + + rv = hist_set(el, func, ptr); + if (!(el->el_flags & CHARSET_IS_UTF8)) + el->el_flags &= ~NARROW_HISTORY; + break; + } + + case EL_EDITMODE: + if (va_arg(ap, int)) + el->el_flags &= ~EDIT_DISABLED; + else + el->el_flags |= EDIT_DISABLED; + rv = 0; + break; + + case EL_GETCFN: + { + el_rfunc_t rc = va_arg(ap, el_rfunc_t); + rv = el_read_setfn(el->el_read, rc); + break; + } + + case EL_CLIENTDATA: + el->el_data = va_arg(ap, void *); + break; + + case EL_UNBUFFERED: + rv = va_arg(ap, int); + if (rv && !(el->el_flags & UNBUFFERED)) { + el->el_flags |= UNBUFFERED; + read_prepare(el); + } else if (!rv && (el->el_flags & UNBUFFERED)) { + el->el_flags &= ~UNBUFFERED; + read_finish(el); + } + rv = 0; + break; + + case EL_PREP_TERM: + rv = va_arg(ap, int); + if (rv) + (void) tty_rawmode(el); + else + (void) tty_cookedmode(el); + rv = 0; + break; + + case EL_SETFP: + { + FILE *fp; + int what; + + what = va_arg(ap, int); + fp = va_arg(ap, FILE *); + + rv = 0; + switch (what) { + case 0: + el->el_infile = fp; + el->el_infd = fileno(fp); + break; + case 1: + el->el_outfile = fp; + el->el_outfd = fileno(fp); + break; + case 2: + el->el_errfile = fp; + el->el_errfd = fileno(fp); + break; + default: + rv = -1; + break; + } + break; + } + + case EL_REFRESH: + re_clear_display(el); + re_refresh(el); + terminal__flush(el); + break; + + default: + rv = -1; + break; + } + + va_end(ap); + return rv; +} + + +/* el_get(): + * retrieve the editline parameters + */ +int +el_wget(EditLine *el, int op, ...) +{ + va_list ap; + int rv; + + if (el == NULL) + return -1; + + va_start(ap, op); + + switch (op) { + case EL_PROMPT: + case EL_RPROMPT: { + el_pfunc_t *p = va_arg(ap, el_pfunc_t *); + rv = prompt_get(el, p, 0, op); + break; + } + case EL_PROMPT_ESC: + case EL_RPROMPT_ESC: { + el_pfunc_t *p = va_arg(ap, el_pfunc_t *); + wchar_t *c = va_arg(ap, wchar_t *); + + rv = prompt_get(el, p, c, op); + break; + } + + case EL_EDITOR: + rv = map_get_editor(el, va_arg(ap, const wchar_t **)); + break; + + case EL_SIGNAL: + *va_arg(ap, int *) = (el->el_flags & HANDLE_SIGNALS); + rv = 0; + break; + + case EL_EDITMODE: + *va_arg(ap, int *) = !(el->el_flags & EDIT_DISABLED); + rv = 0; + break; + + case EL_TERMINAL: + terminal_get(el, va_arg(ap, const char **)); + rv = 0; + break; + + case EL_GETTC: + { + static char name[] = "gettc"; + char *argv[20]; + int i; + + for (i = 1; i < (int)__arraycount(argv); i++) + if ((argv[i] = va_arg(ap, char *)) == NULL) + break; + + argv[0] = name; + rv = terminal_gettc(el, i, argv); + break; + } + + case EL_GETCFN: + *va_arg(ap, el_rfunc_t *) = el_read_getfn(el->el_read); + rv = 0; + break; + + case EL_CLIENTDATA: + *va_arg(ap, void **) = el->el_data; + rv = 0; + break; + + case EL_UNBUFFERED: + *va_arg(ap, int *) = (el->el_flags & UNBUFFERED) != 0; + rv = 0; + break; + + case EL_GETFP: + { + int what; + FILE **fpp; + + what = va_arg(ap, int); + fpp = va_arg(ap, FILE **); + rv = 0; + switch (what) { + case 0: + *fpp = el->el_infile; + break; + case 1: + *fpp = el->el_outfile; + break; + case 2: + *fpp = el->el_errfile; + break; + default: + rv = -1; + break; + } + break; + } + default: + rv = -1; + break; + } + va_end(ap); + + return rv; +} + + +/* el_line(): + * Return editing info + */ +const LineInfoW * +el_wline(EditLine *el) +{ + + return (const LineInfoW *)(void *)&el->el_line; +} + + +/* el_source(): + * Source a file + */ +int +el_source(EditLine *el, const char *fname) +{ + FILE *fp; + size_t len; + ssize_t slen; + char *ptr; + char *path = NULL; + const wchar_t *dptr; + int error = 0; + + fp = NULL; + if (fname == NULL) { +#ifdef HAVE_ISSETUGID + if (issetugid()) + return -1; + + if ((fname = getenv("EDITRC")) == NULL) { + static const char elpath[] = "/.editrc"; + size_t plen = sizeof(elpath); + + if ((ptr = getenv("HOME")) == NULL) + return -1; + plen += strlen(ptr); + if ((path = el_malloc(plen * sizeof(*path))) == NULL) + return -1; + (void)snprintf(path, plen, "%s%s", ptr, + elpath + (*ptr == '\0')); + fname = path; + } +#else + /* + * If issetugid() is missing, always return an error, in order + * to keep from inadvertently opening up the user to a security + * hole. + */ + return -1; +#endif + } + if (fname[0] == '\0') + return -1; + + if (fp == NULL) + fp = fopen(fname, "r"); + if (fp == NULL) { + el_free(path); + return -1; + } + + ptr = NULL; + len = 0; + while ((slen = getline(&ptr, &len, fp)) != -1) { + if (*ptr == '\n') + continue; /* Empty line. */ + if (slen > 0 && ptr[--slen] == '\n') + ptr[slen] = '\0'; + + dptr = ct_decode_string(ptr, &el->el_scratch); + if (!dptr) + continue; + /* loop until first non-space char or EOL */ + while (*dptr != '\0' && iswspace(*dptr)) + dptr++; + if (*dptr == '#') + continue; /* ignore, this is a comment line */ + if ((error = parse_line(el, dptr)) == -1) + break; + } + free(ptr); + + el_free(path); + (void) fclose(fp); + return error; +} + + +/* el_resize(): + * Called from program when terminal is resized + */ +void +el_resize(EditLine *el) +{ + int lins, cols; + sigset_t oset, nset; + + (void) sigemptyset(&nset); + (void) sigaddset(&nset, SIGWINCH); + (void) sigprocmask(SIG_BLOCK, &nset, &oset); + + /* get the correct window size */ + if (terminal_get_size(el, &lins, &cols)) + terminal_change_size(el, lins, cols); + + (void) sigprocmask(SIG_SETMASK, &oset, NULL); +} + + +/* el_beep(): + * Called from the program to beep + */ +void +el_beep(EditLine *el) +{ + + terminal_beep(el); +} + + +/* el_editmode() + * Set the state of EDIT_DISABLED from the `edit' command. + */ +libedit_private int +/*ARGSUSED*/ +el_editmode(EditLine *el, int argc, const wchar_t **argv) +{ + const wchar_t *how; + + if (argv == NULL || argc != 2 || argv[1] == NULL) + return -1; + + how = argv[1]; + if (wcscmp(how, L"on") == 0) { + el->el_flags &= ~EDIT_DISABLED; + tty_rawmode(el); + } else if (wcscmp(how, L"off") == 0) { + tty_cookedmode(el); + el->el_flags |= EDIT_DISABLED; + } + else { + (void) fprintf(el->el_errfile, "edit: Bad value `%ls'.\n", + how); + return -1; + } + return 0; +} diff --git a/bin/catsh/libedit/el.h b/bin/catsh/libedit/el.h new file mode 100644 index 00000000..862e1976 --- /dev/null +++ b/bin/catsh/libedit/el.h @@ -0,0 +1,155 @@ +/* $NetBSD: el.h,v 1.41.8.1 2017/07/23 14:41:26 snj Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)el.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.h: Internal structures. + */ +#ifndef _h_el +#define _h_el +/* + * Local defaults + */ +#define KSHVI +#define VIDEFAULT +#define ANCHOR + +#include "histedit.h" +#include "chartype.h" + +#define EL_BUFSIZ ((size_t)1024) /* Maximum line size */ + +#define HANDLE_SIGNALS 0x01 +#define NO_TTY 0x02 +#define EDIT_DISABLED 0x04 +#define UNBUFFERED 0x08 +#define CHARSET_IS_UTF8 0x10 +#define NARROW_HISTORY 0x40 + +typedef unsigned char el_action_t; /* Index to command array */ + +typedef struct coord_t { /* Position on the screen */ + int h; + int v; +} coord_t; + +typedef struct el_line_t { + wchar_t *buffer; /* Input line */ + wchar_t *cursor; /* Cursor position */ + wchar_t *lastchar; /* Last character */ + const wchar_t *limit; /* Max position */ +} el_line_t; + +/* + * Editor state + */ +typedef struct el_state_t { + int inputmode; /* What mode are we in? */ + int doingarg; /* Are we getting an argument? */ + int argument; /* Numeric argument */ + int metanext; /* Is the next char a meta char */ + el_action_t lastcmd; /* Previous command */ + el_action_t thiscmd; /* this command */ + wchar_t thisch; /* char that generated it */ +} el_state_t; + +/* + * Until we come up with something better... + */ +#define el_malloc(a) malloc(a) +#define el_realloc(a,b) realloc(a, b) +#define el_free(a) free(a) + +#include "tty.h" +#include "prompt.h" +#include "literal.h" +#include "keymacro.h" +#include "terminal.h" +#include "refresh.h" +#include "chared.h" +#include "search.h" +#include "hist.h" +#include "map.h" +#include "sig.h" + +struct el_read_t; + +struct editline { + wchar_t *el_prog; /* the program name */ + FILE *el_infile; /* Stdio stuff */ + FILE *el_outfile; /* Stdio stuff */ + FILE *el_errfile; /* Stdio stuff */ + int el_infd; /* Input file descriptor */ + int el_outfd; /* Output file descriptor */ + int el_errfd; /* Error file descriptor */ + int el_flags; /* Various flags. */ + coord_t el_cursor; /* Cursor location */ + wint_t **el_display; /* Real screen image = what is there */ + wint_t **el_vdisplay; /* Virtual screen image = what we see */ + void *el_data; /* Client data */ + el_line_t el_line; /* The current line information */ + el_state_t el_state; /* Current editor state */ + el_terminal_t el_terminal; /* Terminal dependent stuff */ + el_tty_t el_tty; /* Tty dependent stuff */ + el_refresh_t el_refresh; /* Refresh stuff */ + el_prompt_t el_prompt; /* Prompt stuff */ + el_prompt_t el_rprompt; /* Prompt stuff */ + el_literal_t el_literal; /* prompt literal bits */ + el_chared_t el_chared; /* Characted editor stuff */ + el_map_t el_map; /* Key mapping stuff */ + el_keymacro_t el_keymacro; /* Key binding stuff */ + el_history_t el_history; /* History stuff */ + el_search_t el_search; /* Search stuff */ + el_signal_t el_signal; /* Signal handling stuff */ + struct el_read_t *el_read; /* Character reading stuff */ + ct_buffer_t el_visual; /* Buffer for displayable str */ + ct_buffer_t el_scratch; /* Scratch conversion buffer */ + ct_buffer_t el_lgcyconv; /* Buffer for legacy wrappers */ + LineInfo el_lgcylinfo; /* Legacy LineInfo buffer */ +}; + +libedit_private int el_editmode(EditLine *, int, const wchar_t **); + +#ifdef DEBUG +#define EL_ABORT(a) do { \ + fprintf(el->el_errfile, "%s, %d: ", \ + __FILE__, __LINE__); \ + fprintf a; \ + abort(); \ + } while( /*CONSTCOND*/0); +#else +#define EL_ABORT(a) abort() +#endif +#endif /* _h_el */ diff --git a/bin/catsh/libedit/eln.c b/bin/catsh/libedit/eln.c new file mode 100644 index 00000000..aa0a5b56 --- /dev/null +++ b/bin/catsh/libedit/eln.c @@ -0,0 +1,388 @@ +/* $NetBSD: eln.c,v 1.34 2016/05/09 21:37:34 christos Exp $ */ + +/*- + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +__RCSID("$NetBSD: eln.c,v 1.34 2016/05/09 21:37:34 christos Exp $"); +#endif /* not lint && not SCCSID */ + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> + +#include "el.h" + +int +el_getc(EditLine *el, char *cp) +{ + int num_read; + wchar_t wc = 0; + + num_read = el_wgetc(el, &wc); + *cp = '\0'; + if (num_read <= 0) + return num_read; + num_read = wctob(wc); + if (num_read == EOF) { + errno = ERANGE; + return -1; + } else { + *cp = (char)num_read; + return 1; + } +} + + +void +el_push(EditLine *el, const char *str) +{ + /* Using multibyte->wide string decoding works fine under single-byte + * character sets too, and Does The Right Thing. */ + el_wpush(el, ct_decode_string(str, &el->el_lgcyconv)); +} + + +const char * +el_gets(EditLine *el, int *nread) +{ + const wchar_t *tmp; + + tmp = el_wgets(el, nread); + if (tmp != NULL) { + int i; + size_t nwread = 0; + + for (i = 0; i < *nread; i++) + nwread += ct_enc_width(tmp[i]); + *nread = (int)nwread; + } + return ct_encode_string(tmp, &el->el_lgcyconv); +} + + +int +el_parse(EditLine *el, int argc, const char *argv[]) +{ + int ret; + const wchar_t **wargv; + + wargv = (void *)ct_decode_argv(argc, argv, &el->el_lgcyconv); + if (!wargv) + return -1; + ret = el_wparse(el, argc, wargv); + el_free(wargv); + + return ret; +} + + +int +el_set(EditLine *el, int op, ...) +{ + va_list ap; + int ret; + + if (!el) + return -1; + va_start(ap, op); + + switch (op) { + case EL_PROMPT: /* el_pfunc_t */ + case EL_RPROMPT: { + el_pfunc_t p = va_arg(ap, el_pfunc_t); + ret = prompt_set(el, p, 0, op, 0); + break; + } + + case EL_RESIZE: { + el_zfunc_t p = va_arg(ap, el_zfunc_t); + void *arg = va_arg(ap, void *); + ret = ch_resizefun(el, p, arg); + break; + } + + case EL_ALIAS_TEXT: { + el_afunc_t p = va_arg(ap, el_afunc_t); + void *arg = va_arg(ap, void *); + ret = ch_aliasfun(el, p, arg); + break; + } + + case EL_PROMPT_ESC: + case EL_RPROMPT_ESC: { + el_pfunc_t p = va_arg(ap, el_pfunc_t); + int c = va_arg(ap, int); + + ret = prompt_set(el, p, c, op, 0); + break; + } + + case EL_TERMINAL: /* const char * */ + ret = el_wset(el, op, va_arg(ap, char *)); + break; + + case EL_EDITOR: /* const wchar_t * */ + ret = el_wset(el, op, ct_decode_string(va_arg(ap, char *), + &el->el_lgcyconv)); + break; + + case EL_SIGNAL: /* int */ + case EL_EDITMODE: + case EL_UNBUFFERED: + case EL_PREP_TERM: + ret = el_wset(el, op, va_arg(ap, int)); + break; + + case EL_BIND: /* const char * list -> const wchar_t * list */ + case EL_TELLTC: + case EL_SETTC: + case EL_ECHOTC: + case EL_SETTY: { + const char *argv[20]; + int i; + const wchar_t **wargv; + for (i = 1; i < (int)__arraycount(argv) - 1; ++i) + if ((argv[i] = va_arg(ap, const char *)) == NULL) + break; + argv[0] = argv[i] = NULL; + wargv = (void *)ct_decode_argv(i + 1, argv, &el->el_lgcyconv); + if (!wargv) { + ret = -1; + goto out; + } + /* + * AFAIK we can't portably pass through our new wargv to + * el_wset(), so we have to reimplement the body of + * el_wset() for these ops. + */ + switch (op) { + case EL_BIND: + wargv[0] = L"bind"; + ret = map_bind(el, i, wargv); + break; + case EL_TELLTC: + wargv[0] = L"telltc"; + ret = terminal_telltc(el, i, wargv); + break; + case EL_SETTC: + wargv[0] = L"settc"; + ret = terminal_settc(el, i, wargv); + break; + case EL_ECHOTC: + wargv[0] = L"echotc"; + ret = terminal_echotc(el, i, wargv); + break; + case EL_SETTY: + wargv[0] = L"setty"; + ret = tty_stty(el, i, wargv); + break; + default: + ret = -1; + } + el_free(wargv); + break; + } + + /* XXX: do we need to change el_func_t too? */ + case EL_ADDFN: { /* const char *, const char *, el_func_t */ + const char *args[2]; + el_func_t func; + wchar_t **wargv; + + args[0] = va_arg(ap, const char *); + args[1] = va_arg(ap, const char *); + func = va_arg(ap, el_func_t); + + wargv = ct_decode_argv(2, args, &el->el_lgcyconv); + if (!wargv) { + ret = -1; + goto out; + } + /* XXX: The two strdup's leak */ + ret = map_addfunc(el, wcsdup(wargv[0]), wcsdup(wargv[1]), + func); + el_free(wargv); + break; + } + case EL_HIST: { /* hist_fun_t, const char * */ + hist_fun_t fun = va_arg(ap, hist_fun_t); + void *ptr = va_arg(ap, void *); + ret = hist_set(el, fun, ptr); + el->el_flags |= NARROW_HISTORY; + break; + } + + case EL_GETCFN: /* el_rfunc_t */ + ret = el_wset(el, op, va_arg(ap, el_rfunc_t)); + break; + + case EL_CLIENTDATA: /* void * */ + ret = el_wset(el, op, va_arg(ap, void *)); + break; + + case EL_SETFP: { /* int, FILE * */ + int what = va_arg(ap, int); + FILE *fp = va_arg(ap, FILE *); + ret = el_wset(el, op, what, fp); + break; + } + + case EL_REFRESH: + re_clear_display(el); + re_refresh(el); + terminal__flush(el); + ret = 0; + break; + + default: + ret = -1; + break; + } + +out: + va_end(ap); + return ret; +} + + +int +el_get(EditLine *el, int op, ...) +{ + va_list ap; + int ret; + + if (!el) + return -1; + + va_start(ap, op); + + switch (op) { + case EL_PROMPT: /* el_pfunc_t * */ + case EL_RPROMPT: { + el_pfunc_t *p = va_arg(ap, el_pfunc_t *); + ret = prompt_get(el, p, 0, op); + break; + } + + case EL_PROMPT_ESC: /* el_pfunc_t *, char **/ + case EL_RPROMPT_ESC: { + el_pfunc_t *p = va_arg(ap, el_pfunc_t *); + char *c = va_arg(ap, char *); + wchar_t wc = 0; + ret = prompt_get(el, p, &wc, op); + *c = (char)wc; + break; + } + + case EL_EDITOR: { + const char **p = va_arg(ap, const char **); + const wchar_t *pw; + ret = el_wget(el, op, &pw); + *p = ct_encode_string(pw, &el->el_lgcyconv); + if (!el->el_lgcyconv.csize) + ret = -1; + break; + } + + case EL_TERMINAL: /* const char ** */ + ret = el_wget(el, op, va_arg(ap, const char **)); + break; + + case EL_SIGNAL: /* int * */ + case EL_EDITMODE: + case EL_UNBUFFERED: + case EL_PREP_TERM: + ret = el_wget(el, op, va_arg(ap, int *)); + break; + + case EL_GETTC: { + char *argv[20]; + static char gettc[] = "gettc"; + int i; + for (i = 1; i < (int)__arraycount(argv); ++i) + if ((argv[i] = va_arg(ap, char *)) == NULL) + break; + argv[0] = gettc; + ret = terminal_gettc(el, i, argv); + break; + } + + case EL_GETCFN: /* el_rfunc_t */ + ret = el_wget(el, op, va_arg(ap, el_rfunc_t *)); + break; + + case EL_CLIENTDATA: /* void ** */ + ret = el_wget(el, op, va_arg(ap, void **)); + break; + + case EL_GETFP: { /* int, FILE ** */ + int what = va_arg(ap, int); + FILE **fpp = va_arg(ap, FILE **); + ret = el_wget(el, op, what, fpp); + break; + } + + default: + ret = -1; + break; + } + + va_end(ap); + return ret; +} + + +const LineInfo * +el_line(EditLine *el) +{ + const LineInfoW *winfo = el_wline(el); + LineInfo *info = &el->el_lgcylinfo; + size_t offset; + const wchar_t *p; + + info->buffer = ct_encode_string(winfo->buffer, &el->el_lgcyconv); + + offset = 0; + for (p = winfo->buffer; p < winfo->cursor; p++) + offset += ct_enc_width(*p); + info->cursor = info->buffer + offset; + + offset = 0; + for (p = winfo->buffer; p < winfo->lastchar; p++) + offset += ct_enc_width(*p); + info->lastchar = info->buffer + offset; + + return info; +} + + +int +el_insertstr(EditLine *el, const char *str) +{ + return el_winsertstr(el, ct_decode_string(str, &el->el_lgcyconv)); +} diff --git a/bin/catsh/libedit/emacs.c b/bin/catsh/libedit/emacs.c new file mode 100644 index 00000000..0636c28b --- /dev/null +++ b/bin/catsh/libedit/emacs.c @@ -0,0 +1,512 @@ +/* $NetBSD: emacs.c,v 1.36 2016/05/09 21:46:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)emacs.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: emacs.c,v 1.36 2016/05/09 21:46:56 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * emacs.c: Emacs functions + */ +#include <ctype.h> + +#include "el.h" +#include "emacs.h" +#include "fcns.h" + +/* em_delete_or_list(): + * Delete character under cursor or list completions if at end of line + * [^D] + */ +libedit_private el_action_t +/*ARGSUSED*/ +em_delete_or_list(EditLine *el, wint_t c) +{ + + if (el->el_line.cursor == el->el_line.lastchar) { + /* if I'm at the end */ + if (el->el_line.cursor == el->el_line.buffer) { + /* and the beginning */ + terminal_writec(el, c); /* then do an EOF */ + return CC_EOF; + } else { + /* + * Here we could list completions, but it is an + * error right now + */ + terminal_beep(el); + return CC_ERROR; + } + } else { + if (el->el_state.doingarg) + c_delafter(el, el->el_state.argument); + else + c_delafter1(el); + if (el->el_line.cursor > el->el_line.lastchar) + el->el_line.cursor = el->el_line.lastchar; + /* bounds check */ + return CC_REFRESH; + } +} + + +/* em_delete_next_word(): + * Cut from cursor to end of current word + * [M-d] + */ +libedit_private el_action_t +/*ARGSUSED*/ +em_delete_next_word(EditLine *el, wint_t c __attribute__((__unused__))) +{ + wchar_t *cp, *p, *kp; + + if (el->el_line.cursor == el->el_line.lastchar) + return CC_ERROR; + + cp = c__next_word(el->el_line.cursor, el->el_line.lastchar, + el->el_state.argument, ce__isword); + + for (p = el->el_line.cursor, kp = el->el_chared.c_kill.buf; p < cp; p++) + /* save the text */ + *kp++ = *p; + el->el_chared.c_kill.last = kp; + + c_delafter(el, (int)(cp - el->el_line.cursor)); /* delete after dot */ + if (el->el_line.cursor > el->el_line.lastchar) + el->el_line.cursor = el->el_line.lastchar; + /* bounds check */ + return CC_REFRESH; +} + + +/* em_yank(): + * Paste cut buffer at cursor position + * [^Y] + */ +libedit_private el_action_t +/*ARGSUSED*/ +em_yank(EditLine *el, wint_t c __attribute__((__unused__))) +{ + wchar_t *kp, *cp; + + if (el->el_chared.c_kill.last == el->el_chared.c_kill.buf) + return CC_NORM; + + if (el->el_line.lastchar + + (el->el_chared.c_kill.last - el->el_chared.c_kill.buf) >= + el->el_line.limit) + return CC_ERROR; + + el->el_chared.c_kill.mark = el->el_line.cursor; + cp = el->el_line.cursor; + + /* open the space, */ + c_insert(el, + (int)(el->el_chared.c_kill.last - el->el_chared.c_kill.buf)); + /* copy the chars */ + for (kp = el->el_chared.c_kill.buf; kp < el->el_chared.c_kill.last; kp++) + *cp++ = *kp; + + /* if an arg, cursor at beginning else cursor at end */ + if (el->el_state.argument == 1) + el->el_line.cursor = cp; + + return CC_REFRESH; +} + + +/* em_kill_line(): + * Cut the entire line and save in cut buffer + * [^U] + */ +libedit_private el_action_t +/*ARGSUSED*/ +em_kill_line(EditLine *el, wint_t c __attribute__((__unused__))) +{ + wchar_t *kp, *cp; + + cp = el->el_line.buffer; + kp = el->el_chared.c_kill.buf; + while (cp < el->el_line.lastchar) + *kp++ = *cp++; /* copy it */ + el->el_chared.c_kill.last = kp; + /* zap! -- delete all of it */ + el->el_line.lastchar = el->el_line.buffer; + el->el_line.cursor = el->el_line.buffer; + return CC_REFRESH; +} + + +/* em_kill_region(): + * Cut area between mark and cursor and save in cut buffer + * [^W] + */ +libedit_private el_action_t +/*ARGSUSED*/ +em_kill_region(EditLine *el, wint_t c __attribute__((__unused__))) +{ + wchar_t *kp, *cp; + + if (!el->el_chared.c_kill.mark) + return CC_ERROR; + + if (el->el_chared.c_kill.mark > el->el_line.cursor) { + cp = el->el_line.cursor; + kp = el->el_chared.c_kill.buf; + while (cp < el->el_chared.c_kill.mark) + *kp++ = *cp++; /* copy it */ + el->el_chared.c_kill.last = kp; + c_delafter(el, (int)(cp - el->el_line.cursor)); + } else { /* mark is before cursor */ + cp = el->el_chared.c_kill.mark; + kp = el->el_chared.c_kill.buf; + while (cp < el->el_line.cursor) + *kp++ = *cp++; /* copy it */ + el->el_chared.c_kill.last = kp; + c_delbefore(el, (int)(cp - el->el_chared.c_kill.mark)); + el->el_line.cursor = el->el_chared.c_kill.mark; + } + return CC_REFRESH; +} + + +/* em_copy_region(): + * Copy area between mark and cursor to cut buffer + * [M-W] + */ +libedit_private el_action_t +/*ARGSUSED*/ +em_copy_region(EditLine *el, wint_t c __attribute__((__unused__))) +{ + wchar_t *kp, *cp; + + if (!el->el_chared.c_kill.mark) + return CC_ERROR; + + if (el->el_chared.c_kill.mark > el->el_line.cursor) { + cp = el->el_line.cursor; + kp = el->el_chared.c_kill.buf; + while (cp < el->el_chared.c_kill.mark) + *kp++ = *cp++; /* copy it */ + el->el_chared.c_kill.last = kp; + } else { + cp = el->el_chared.c_kill.mark; + kp = el->el_chared.c_kill.buf; + while (cp < el->el_line.cursor) + *kp++ = *cp++; /* copy it */ + el->el_chared.c_kill.last = kp; + } + return CC_NORM; +} + + +/* em_gosmacs_transpose(): + * Exchange the two characters before the cursor + * Gosling emacs transpose chars [^T] + */ +libedit_private el_action_t +em_gosmacs_transpose(EditLine *el, wint_t c) +{ + + if (el->el_line.cursor > &el->el_line.buffer[1]) { + /* must have at least two chars entered */ + c = el->el_line.cursor[-2]; + el->el_line.cursor[-2] = el->el_line.cursor[-1]; + el->el_line.cursor[-1] = c; + return CC_REFRESH; + } else + return CC_ERROR; +} + + +/* em_next_word(): + * Move next to end of current word + * [M-f] + */ +libedit_private el_action_t +/*ARGSUSED*/ +em_next_word(EditLine *el, wint_t c __attribute__((__unused__))) +{ + if (el->el_line.cursor == el->el_line.lastchar) + return CC_ERROR; + + el->el_line.cursor = c__next_word(el->el_line.cursor, + el->el_line.lastchar, + el->el_state.argument, + ce__isword); + + if (el->el_map.type == MAP_VI) + if (el->el_chared.c_vcmd.action != NOP) { + cv_delfini(el); + return CC_REFRESH; + } + return CC_CURSOR; +} + + +/* em_upper_case(): + * Uppercase the characters from cursor to end of current word + * [M-u] + */ +libedit_private el_action_t +/*ARGSUSED*/ +em_upper_case(EditLine *el, wint_t c __attribute__((__unused__))) +{ + wchar_t *cp, *ep; + + ep = c__next_word(el->el_line.cursor, el->el_line.lastchar, + el->el_state.argument, ce__isword); + + for (cp = el->el_line.cursor; cp < ep; cp++) + if (iswlower(*cp)) + *cp = towupper(*cp); + + el->el_line.cursor = ep; + if (el->el_line.cursor > el->el_line.lastchar) + el->el_line.cursor = el->el_line.lastchar; + return CC_REFRESH; +} + + +/* em_capitol_case(): + * Capitalize the characters from cursor to end of current word + * [M-c] + */ +libedit_private el_action_t +/*ARGSUSED*/ +em_capitol_case(EditLine *el, wint_t c __attribute__((__unused__))) +{ + wchar_t *cp, *ep; + + ep = c__next_word(el->el_line.cursor, el->el_line.lastchar, + el->el_state.argument, ce__isword); + + for (cp = el->el_line.cursor; cp < ep; cp++) { + if (iswalpha(*cp)) { + if (iswlower(*cp)) + *cp = towupper(*cp); + cp++; + break; + } + } + for (; cp < ep; cp++) + if (iswupper(*cp)) + *cp = towlower(*cp); + + el->el_line.cursor = ep; + if (el->el_line.cursor > el->el_line.lastchar) + el->el_line.cursor = el->el_line.lastchar; + return CC_REFRESH; +} + + +/* em_lower_case(): + * Lowercase the characters from cursor to end of current word + * [M-l] + */ +libedit_private el_action_t +/*ARGSUSED*/ +em_lower_case(EditLine *el, wint_t c __attribute__((__unused__))) +{ + wchar_t *cp, *ep; + + ep = c__next_word(el->el_line.cursor, el->el_line.lastchar, + el->el_state.argument, ce__isword); + + for (cp = el->el_line.cursor; cp < ep; cp++) + if (iswupper(*cp)) + *cp = towlower(*cp); + + el->el_line.cursor = ep; + if (el->el_line.cursor > el->el_line.lastchar) + el->el_line.cursor = el->el_line.lastchar; + return CC_REFRESH; +} + + +/* em_set_mark(): + * Set the mark at cursor + * [^@] + */ +libedit_private el_action_t +/*ARGSUSED*/ +em_set_mark(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + el->el_chared.c_kill.mark = el->el_line.cursor; + return CC_NORM; +} + + +/* em_exchange_mark(): + * Exchange the cursor and mark + * [^X^X] + */ +libedit_private el_action_t +/*ARGSUSED*/ +em_exchange_mark(EditLine *el, wint_t c __attribute__((__unused__))) +{ + wchar_t *cp; + + cp = el->el_line.cursor; + el->el_line.cursor = el->el_chared.c_kill.mark; + el->el_chared.c_kill.mark = cp; + return CC_CURSOR; +} + + +/* em_universal_argument(): + * Universal argument (argument times 4) + * [^U] + */ +libedit_private el_action_t +/*ARGSUSED*/ +em_universal_argument(EditLine *el, wint_t c __attribute__((__unused__))) +{ /* multiply current argument by 4 */ + + if (el->el_state.argument > 1000000) + return CC_ERROR; + el->el_state.doingarg = 1; + el->el_state.argument *= 4; + return CC_ARGHACK; +} + + +/* em_meta_next(): + * Add 8th bit to next character typed + * [<ESC>] + */ +libedit_private el_action_t +/*ARGSUSED*/ +em_meta_next(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + el->el_state.metanext = 1; + return CC_ARGHACK; +} + + +/* em_toggle_overwrite(): + * Switch from insert to overwrite mode or vice versa + */ +libedit_private el_action_t +/*ARGSUSED*/ +em_toggle_overwrite(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + el->el_state.inputmode = (el->el_state.inputmode == MODE_INSERT) ? + MODE_REPLACE : MODE_INSERT; + return CC_NORM; +} + + +/* em_copy_prev_word(): + * Copy current word to cursor + */ +libedit_private el_action_t +/*ARGSUSED*/ +em_copy_prev_word(EditLine *el, wint_t c __attribute__((__unused__))) +{ + wchar_t *cp, *oldc, *dp; + + if (el->el_line.cursor == el->el_line.buffer) + return CC_ERROR; + + oldc = el->el_line.cursor; + /* does a bounds check */ + cp = c__prev_word(el->el_line.cursor, el->el_line.buffer, + el->el_state.argument, ce__isword); + + c_insert(el, (int)(oldc - cp)); + for (dp = oldc; cp < oldc && dp < el->el_line.lastchar; cp++) + *dp++ = *cp; + + el->el_line.cursor = dp;/* put cursor at end */ + + return CC_REFRESH; +} + + +/* em_inc_search_next(): + * Emacs incremental next search + */ +libedit_private el_action_t +/*ARGSUSED*/ +em_inc_search_next(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + el->el_search.patlen = 0; + return ce_inc_search(el, ED_SEARCH_NEXT_HISTORY); +} + + +/* em_inc_search_prev(): + * Emacs incremental reverse search + */ +libedit_private el_action_t +/*ARGSUSED*/ +em_inc_search_prev(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + el->el_search.patlen = 0; + return ce_inc_search(el, ED_SEARCH_PREV_HISTORY); +} + + +/* em_delete_prev_char(): + * Delete the character to the left of the cursor + * [^?] + */ +libedit_private el_action_t +/*ARGSUSED*/ +em_delete_prev_char(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + if (el->el_line.cursor <= el->el_line.buffer) + return CC_ERROR; + + if (el->el_state.doingarg) + c_delbefore(el, el->el_state.argument); + else + c_delbefore1(el); + el->el_line.cursor -= el->el_state.argument; + if (el->el_line.cursor < el->el_line.buffer) + el->el_line.cursor = el->el_line.buffer; + return CC_REFRESH; +} diff --git a/bin/catsh/libedit/filecomplete.c b/bin/catsh/libedit/filecomplete.c new file mode 100644 index 00000000..6ccc2719 --- /dev/null +++ b/bin/catsh/libedit/filecomplete.c @@ -0,0 +1,579 @@ +/* $NetBSD: filecomplete.c,v 1.45 2017/04/21 05:38:03 abhinav Exp $ */ + +/*- + * Copyright (c) 1997 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Jaromir Dolecek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +__RCSID("$NetBSD: filecomplete.c,v 1.45 2017/04/21 05:38:03 abhinav Exp $"); +#endif /* not lint && not SCCSID */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "el.h" +#include "filecomplete.h" + +static const wchar_t break_chars[] = L" \t\n\"\\'`@$><=;|&{("; + +/********************************/ +/* completion functions */ + +/* + * does tilde expansion of strings of type ``~user/foo'' + * if ``user'' isn't valid user name or ``txt'' doesn't start + * w/ '~', returns pointer to strdup()ed copy of ``txt'' + * + * it's the caller's responsibility to free() the returned string + */ +char * +fn_tilde_expand(const char *txt) +{ +#if defined(HAVE_GETPW_R_POSIX) || defined(HAVE_GETPW_R_DRAFT) + struct passwd pwres; + char pwbuf[1024]; +#endif + struct passwd *pass; + char *temp; + size_t len = 0; + + if (txt[0] != '~') + return strdup(txt); + + temp = strchr(txt + 1, '/'); + if (temp == NULL) { + temp = strdup(txt + 1); + if (temp == NULL) + return NULL; + } else { + /* text until string after slash */ + len = (size_t)(temp - txt + 1); + temp = el_malloc(len * sizeof(*temp)); + if (temp == NULL) + return NULL; + (void)strncpy(temp, txt + 1, len - 2); + temp[len - 2] = '\0'; + } + if (temp[0] == 0) { +#ifdef HAVE_GETPW_R_POSIX + if (getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf), + &pass) != 0) + pass = NULL; +#elif HAVE_GETPW_R_DRAFT + pass = getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf)); +#else + pass = getpwuid(getuid()); +#endif + } else { +#ifdef HAVE_GETPW_R_POSIX + if (getpwnam_r(temp, &pwres, pwbuf, sizeof(pwbuf), &pass) != 0) + pass = NULL; +#elif HAVE_GETPW_R_DRAFT + pass = getpwnam_r(temp, &pwres, pwbuf, sizeof(pwbuf)); +#else + pass = getpwnam(temp); +#endif + } + el_free(temp); /* value no more needed */ + if (pass == NULL) + return strdup(txt); + + /* update pointer txt to point at string immedially following */ + /* first slash */ + txt += len; + + len = strlen(pass->pw_dir) + 1 + strlen(txt) + 1; + temp = el_malloc(len * sizeof(*temp)); + if (temp == NULL) + return NULL; + (void)snprintf(temp, len, "%s/%s", pass->pw_dir, txt); + + return temp; +} + + +/* + * return first found file name starting by the ``text'' or NULL if no + * such file can be found + * value of ``state'' is ignored + * + * it's the caller's responsibility to free the returned string + */ +char * +fn_filename_completion_function(const char *text, int state) +{ + static DIR *dir = NULL; + static char *filename = NULL, *dirname = NULL, *dirpath = NULL; + static size_t filename_len = 0; + struct dirent *entry; + char *temp; + size_t len; + + if (state == 0 || dir == NULL) { + temp = strrchr(text, '/'); + if (temp) { + char *nptr; + temp++; + nptr = el_realloc(filename, (strlen(temp) + 1) * + sizeof(*nptr)); + if (nptr == NULL) { + el_free(filename); + filename = NULL; + return NULL; + } + filename = nptr; + (void)strcpy(filename, temp); + len = (size_t)(temp - text); /* including last slash */ + + nptr = el_realloc(dirname, (len + 1) * + sizeof(*nptr)); + if (nptr == NULL) { + el_free(dirname); + dirname = NULL; + return NULL; + } + dirname = nptr; + (void)strncpy(dirname, text, len); + dirname[len] = '\0'; + } else { + el_free(filename); + if (*text == 0) + filename = NULL; + else { + filename = strdup(text); + if (filename == NULL) + return NULL; + } + el_free(dirname); + dirname = NULL; + } + + if (dir != NULL) { + (void)closedir(dir); + dir = NULL; + } + + /* support for ``~user'' syntax */ + + el_free(dirpath); + dirpath = NULL; + if (dirname == NULL) { + if ((dirname = strdup("")) == NULL) + return NULL; + dirpath = strdup("./"); + } else if (*dirname == '~') + dirpath = fn_tilde_expand(dirname); + else + dirpath = strdup(dirname); + + if (dirpath == NULL) + return NULL; + + dir = opendir(dirpath); + if (!dir) + return NULL; /* cannot open the directory */ + + /* will be used in cycle */ + filename_len = filename ? strlen(filename) : 0; + } + + /* find the match */ + while ((entry = readdir(dir)) != NULL) { + /* skip . and .. */ + if (entry->d_name[0] == '.' && (!entry->d_name[1] + || (entry->d_name[1] == '.' && !entry->d_name[2]))) + continue; + if (filename_len == 0) + break; + /* otherwise, get first entry where first */ + /* filename_len characters are equal */ + if (entry->d_name[0] == filename[0] +#if HAVE_STRUCT_DIRENT_D_NAMLEN + && entry->d_namlen >= filename_len +#else + && strlen(entry->d_name) >= filename_len +#endif + && strncmp(entry->d_name, filename, + filename_len) == 0) + break; + } + + if (entry) { /* match found */ + +#if HAVE_STRUCT_DIRENT_D_NAMLEN + len = entry->d_namlen; +#else + len = strlen(entry->d_name); +#endif + + len = strlen(dirname) + len + 1; + temp = el_malloc(len * sizeof(*temp)); + if (temp == NULL) + return NULL; + (void)snprintf(temp, len, "%s%s", dirname, entry->d_name); + } else { + (void)closedir(dir); + dir = NULL; + temp = NULL; + } + + return temp; +} + + +static const char * +append_char_function(const char *name) +{ + struct stat stbuf; + char *expname = *name == '~' ? fn_tilde_expand(name) : NULL; + const char *rs = " "; + + if (stat(expname ? expname : name, &stbuf) == -1) + goto out; + if (S_ISDIR(stbuf.st_mode)) + rs = "/"; +out: + if (expname) + el_free(expname); + return rs; +} +/* + * returns list of completions for text given + * non-static for readline. + */ +char ** completion_matches(const char *, char *(*)(const char *, int)); +char ** +completion_matches(const char *text, char *(*genfunc)(const char *, int)) +{ + char **match_list = NULL, *retstr, *prevstr; + size_t match_list_len, max_equal, which, i; + size_t matches; + + matches = 0; + match_list_len = 1; + while ((retstr = (*genfunc) (text, (int)matches)) != NULL) { + /* allow for list terminator here */ + if (matches + 3 >= match_list_len) { + char **nmatch_list; + while (matches + 3 >= match_list_len) + match_list_len <<= 1; + nmatch_list = el_realloc(match_list, + match_list_len * sizeof(*nmatch_list)); + if (nmatch_list == NULL) { + el_free(match_list); + return NULL; + } + match_list = nmatch_list; + + } + match_list[++matches] = retstr; + } + + if (!match_list) + return NULL; /* nothing found */ + + /* find least denominator and insert it to match_list[0] */ + which = 2; + prevstr = match_list[1]; + max_equal = strlen(prevstr); + for (; which <= matches; which++) { + for (i = 0; i < max_equal && + prevstr[i] == match_list[which][i]; i++) + continue; + max_equal = i; + } + + retstr = el_malloc((max_equal + 1) * sizeof(*retstr)); + if (retstr == NULL) { + el_free(match_list); + return NULL; + } + (void)strncpy(retstr, match_list[1], max_equal); + retstr[max_equal] = '\0'; + match_list[0] = retstr; + + /* add NULL as last pointer to the array */ + match_list[matches + 1] = NULL; + + return match_list; +} + +/* + * Sort function for qsort(). Just wrapper around strcasecmp(). + */ +static int +_fn_qsort_string_compare(const void *i1, const void *i2) +{ + const char *s1 = ((const char * const *)i1)[0]; + const char *s2 = ((const char * const *)i2)[0]; + + return strcasecmp(s1, s2); +} + +/* + * Display list of strings in columnar format on readline's output stream. + * 'matches' is list of strings, 'num' is number of strings in 'matches', + * 'width' is maximum length of string in 'matches'. + * + * matches[0] is not one of the match strings, but it is counted in + * num, so the strings are matches[1] *through* matches[num-1]. + */ +void +fn_display_match_list(EditLine * el, char **matches, size_t num, size_t width, + const char *(*app_func) (const char *)) +{ + size_t line, lines, col, cols, thisguy; + int screenwidth = el->el_terminal.t_size.h; + if (app_func == NULL) + app_func = append_char_function; + + /* Ignore matches[0]. Avoid 1-based array logic below. */ + matches++; + num--; + + /* + * Find out how many entries can be put on one line; count + * with one space between strings the same way it's printed. + */ + cols = (size_t)screenwidth / (width + 1); + if (cols == 0) + cols = 1; + + /* how many lines of output, rounded up */ + lines = (num + cols - 1) / cols; + + /* Sort the items. */ + qsort(matches, num, sizeof(char *), _fn_qsort_string_compare); + + /* + * On the ith line print elements i, i+lines, i+lines*2, etc. + */ + for (line = 0; line < lines; line++) { + for (col = 0; col < cols; col++) { + thisguy = line + col * lines; + if (thisguy >= num) + break; + (void)fprintf(el->el_outfile, "%s%s%s", + col == 0 ? "" : " ", matches[thisguy], + append_char_function(matches[thisguy])); + (void)fprintf(el->el_outfile, "%-*s", + (int) (width - strlen(matches[thisguy])), ""); + } + (void)fprintf(el->el_outfile, "\n"); + } +} + +/* + * Complete the word at or before point, + * 'what_to_do' says what to do with the completion. + * \t means do standard completion. + * `?' means list the possible completions. + * `*' means insert all of the possible completions. + * `!' means to do standard completion, and list all possible completions if + * there is more than one. + * + * Note: '*' support is not implemented + * '!' could never be invoked + */ +int +fn_complete(EditLine *el, + char *(*complet_func)(const char *, int), + char **(*attempted_completion_function)(const char *, int, int), + const wchar_t *word_break, const wchar_t *special_prefixes, + const char *(*app_func)(const char *), size_t query_items, + int *completion_type, int *over, int *point, int *end) +{ + const LineInfoW *li; + wchar_t *temp; + char **matches; + const wchar_t *ctemp; + size_t len; + int what_to_do = '\t'; + int retval = CC_NORM; + + if (el->el_state.lastcmd == el->el_state.thiscmd) + what_to_do = '?'; + + /* readline's rl_complete() has to be told what we did... */ + if (completion_type != NULL) + *completion_type = what_to_do; + + if (!complet_func) + complet_func = fn_filename_completion_function; + if (!app_func) + app_func = append_char_function; + + /* We now look backwards for the start of a filename/variable word */ + li = el_wline(el); + ctemp = li->cursor; + while (ctemp > li->buffer + && !wcschr(word_break, ctemp[-1]) + && (!special_prefixes || !wcschr(special_prefixes, ctemp[-1]) ) ) + ctemp--; + + len = (size_t)(li->cursor - ctemp); + temp = el_malloc((len + 1) * sizeof(*temp)); + (void)wcsncpy(temp, ctemp, len); + temp[len] = '\0'; + + /* these can be used by function called in completion_matches() */ + /* or (*attempted_completion_function)() */ + if (point != NULL) + *point = (int)(li->cursor - li->buffer); + if (end != NULL) + *end = (int)(li->lastchar - li->buffer); + + if (attempted_completion_function) { + int cur_off = (int)(li->cursor - li->buffer); + matches = (*attempted_completion_function)( + ct_encode_string(temp, &el->el_scratch), + cur_off - (int)len, cur_off); + } else + matches = NULL; + if (!attempted_completion_function || + (over != NULL && !*over && !matches)) + matches = completion_matches( + ct_encode_string(temp, &el->el_scratch), complet_func); + + if (over != NULL) + *over = 0; + + if (matches) { + int i; + size_t matches_num, maxlen, match_len, match_display=1; + + retval = CC_REFRESH; + /* + * Only replace the completed string with common part of + * possible matches if there is possible completion. + */ + if (matches[0][0] != '\0') { + el_deletestr(el, (int) len); + el_winsertstr(el, + ct_decode_string(matches[0], &el->el_scratch)); + } + + + if (matches[2] == NULL && + (matches[1] == NULL || strcmp(matches[0], matches[1]) == 0)) { + /* + * We found exact match. Add a space after + * it, unless we do filename completion and the + * object is a directory. + */ + el_winsertstr(el, + ct_decode_string((*app_func)(matches[0]), + &el->el_scratch)); + } else if (what_to_do == '!' || what_to_do == '?') { + /* + * More than one match and requested to list possible + * matches. + */ + + for(i = 1, maxlen = 0; matches[i]; i++) { + match_len = strlen(matches[i]); + if (match_len > maxlen) + maxlen = match_len; + } + /* matches[1] through matches[i-1] are available */ + matches_num = (size_t)(i - 1); + + /* newline to get on next line from command line */ + (void)fprintf(el->el_outfile, "\n"); + + /* + * If there are too many items, ask user for display + * confirmation. + */ + if (matches_num > query_items) { + (void)fprintf(el->el_outfile, + "Display all %zu possibilities? (y or n) ", + matches_num); + (void)fflush(el->el_outfile); + if (getc(stdin) != 'y') + match_display = 0; + (void)fprintf(el->el_outfile, "\n"); + } + + if (match_display) { + /* + * Interface of this function requires the + * strings be matches[1..num-1] for compat. + * We have matches_num strings not counting + * the prefix in matches[0], so we need to + * add 1 to matches_num for the call. + */ + fn_display_match_list(el, matches, + matches_num+1, maxlen, app_func); + } + retval = CC_REDISPLAY; + } else if (matches[0][0]) { + /* + * There was some common match, but the name was + * not complete enough. Next tab will print possible + * completions. + */ + el_beep(el); + } else { + /* lcd is not a valid object - further specification */ + /* is needed */ + el_beep(el); + retval = CC_NORM; + } + + /* free elements of array and the array itself */ + for (i = 0; matches[i]; i++) + el_free(matches[i]); + el_free(matches); + matches = NULL; + } + el_free(temp); + return retval; +} + +/* + * el-compatible wrapper around rl_complete; needed for key binding + */ +/* ARGSUSED */ +unsigned char +_el_fn_complete(EditLine *el, int ch __attribute__((__unused__))) +{ + return (unsigned char)fn_complete(el, NULL, NULL, + break_chars, NULL, NULL, (size_t)100, + NULL, NULL, NULL, NULL); +} diff --git a/bin/catsh/libedit/filecomplete.h b/bin/catsh/libedit/filecomplete.h new file mode 100644 index 00000000..61d81389 --- /dev/null +++ b/bin/catsh/libedit/filecomplete.h @@ -0,0 +1,45 @@ +/* $NetBSD: filecomplete.h,v 1.11 2017/04/21 05:38:03 abhinav Exp $ */ + +/*- + * Copyright (c) 1997 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Jaromir Dolecek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _FILECOMPLETE_H_ +#define _FILECOMPLETE_H_ + +int fn_complete(EditLine *, + char *(*)(const char *, int), + char **(*)(const char *, int, int), + const wchar_t *, const wchar_t *, const char *(*)(const char *), size_t, + int *, int *, int *, int *); + +void fn_display_match_list(EditLine *, char **, size_t, size_t, + const char *(*)(const char *)); +char *fn_tilde_expand(const char *); +char *fn_filename_completion_function(const char *, int); + +#endif diff --git a/bin/catsh/libedit/hist.c b/bin/catsh/libedit/hist.c new file mode 100644 index 00000000..3c9db7dc --- /dev/null +++ b/bin/catsh/libedit/hist.c @@ -0,0 +1,252 @@ +/* $NetBSD: hist.c,v 1.32 2017/03/05 19:23:58 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)hist.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: hist.c,v 1.32 2017/03/05 19:23:58 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * hist.c: History access functions + */ +#include <stdlib.h> +#include <string.h> +#include <vis.h> + +#include "el.h" + +/* hist_init(): + * Initialization function. + */ +libedit_private int +hist_init(EditLine *el) +{ + + el->el_history.fun = NULL; + el->el_history.ref = NULL; + el->el_history.buf = el_malloc(EL_BUFSIZ * sizeof(*el->el_history.buf)); + el->el_history.sz = EL_BUFSIZ; + if (el->el_history.buf == NULL) + return -1; + el->el_history.last = el->el_history.buf; + return 0; +} + + +/* hist_end(): + * clean up history; + */ +libedit_private void +hist_end(EditLine *el) +{ + + el_free(el->el_history.buf); + el->el_history.buf = NULL; +} + + +/* hist_set(): + * Set new history interface + */ +libedit_private int +hist_set(EditLine *el, hist_fun_t fun, void *ptr) +{ + + el->el_history.ref = ptr; + el->el_history.fun = fun; + return 0; +} + + +/* hist_get(): + * Get a history line and update it in the buffer. + * eventno tells us the event to get. + */ +libedit_private el_action_t +hist_get(EditLine *el) +{ + const wchar_t *hp; + int h; + size_t blen, hlen; + + if (el->el_history.eventno == 0) { /* if really the current line */ + (void) wcsncpy(el->el_line.buffer, el->el_history.buf, + el->el_history.sz); + el->el_line.lastchar = el->el_line.buffer + + (el->el_history.last - el->el_history.buf); + +#ifdef KSHVI + if (el->el_map.type == MAP_VI) + el->el_line.cursor = el->el_line.buffer; + else +#endif /* KSHVI */ + el->el_line.cursor = el->el_line.lastchar; + + return CC_REFRESH; + } + if (el->el_history.ref == NULL) + return CC_ERROR; + + hp = HIST_FIRST(el); + + if (hp == NULL) + return CC_ERROR; + + for (h = 1; h < el->el_history.eventno; h++) + if ((hp = HIST_NEXT(el)) == NULL) + goto out; + + hlen = wcslen(hp) + 1; + blen = (size_t)(el->el_line.limit - el->el_line.buffer); + if (hlen > blen && !ch_enlargebufs(el, hlen)) + goto out; + + memcpy(el->el_line.buffer, hp, hlen * sizeof(*hp)); + el->el_line.lastchar = el->el_line.buffer + hlen - 1; + + if (el->el_line.lastchar > el->el_line.buffer + && el->el_line.lastchar[-1] == '\n') + el->el_line.lastchar--; + if (el->el_line.lastchar > el->el_line.buffer + && el->el_line.lastchar[-1] == ' ') + el->el_line.lastchar--; +#ifdef KSHVI + if (el->el_map.type == MAP_VI) + el->el_line.cursor = el->el_line.buffer; + else +#endif /* KSHVI */ + el->el_line.cursor = el->el_line.lastchar; + + return CC_REFRESH; +out: + el->el_history.eventno = h; + return CC_ERROR; + +} + + +/* hist_command() + * process a history command + */ +libedit_private int +hist_command(EditLine *el, int argc, const wchar_t **argv) +{ + const wchar_t *str; + int num; + HistEventW ev; + + if (el->el_history.ref == NULL) + return -1; + + if (argc == 1 || wcscmp(argv[1], L"list") == 0) { + size_t maxlen = 0; + char *buf = NULL; + int hno = 1; + /* List history entries */ + + for (str = HIST_LAST(el); str != NULL; str = HIST_PREV(el)) { + char *ptr = + ct_encode_string(str, &el->el_scratch); + size_t len = strlen(ptr); + if (len > 0 && ptr[len - 1] == '\n') + ptr[--len] = '\0'; + len = len * 4 + 1; + if (len >= maxlen) { + maxlen = len + 1024; + char *nbuf = el_realloc(buf, maxlen); + if (nbuf == NULL) { + el_free(buf); + return -1; + } + buf = nbuf; + } + strvis(buf, ptr, VIS_NL); + (void) fprintf(el->el_outfile, "%d\t%s\n", + hno++, buf); + } + el_free(buf); + return 0; + } + + if (argc != 3) + return -1; + + num = (int)wcstol(argv[2], NULL, 0); + + if (wcscmp(argv[1], L"size") == 0) + return history_w(el->el_history.ref, &ev, H_SETSIZE, num); + + if (wcscmp(argv[1], L"unique") == 0) + return history_w(el->el_history.ref, &ev, H_SETUNIQUE, num); + + return -1; +} + +/* hist_enlargebuf() + * Enlarge history buffer to specified value. Called from el_enlargebufs(). + * Return 0 for failure, 1 for success. + */ +libedit_private int +/*ARGSUSED*/ +hist_enlargebuf(EditLine *el, size_t oldsz, size_t newsz) +{ + wchar_t *newbuf; + + newbuf = el_realloc(el->el_history.buf, newsz * sizeof(*newbuf)); + if (!newbuf) + return 0; + + (void) memset(&newbuf[oldsz], '\0', (newsz - oldsz) * sizeof(*newbuf)); + + el->el_history.last = newbuf + + (el->el_history.last - el->el_history.buf); + el->el_history.buf = newbuf; + el->el_history.sz = newsz; + + return 1; +} + +libedit_private wchar_t * +hist_convert(EditLine *el, int fn, void *arg) +{ + HistEventW ev; + if ((*(el)->el_history.fun)((el)->el_history.ref, &ev, fn, arg) == -1) + return NULL; + return ct_decode_string((const char *)(const void *)ev.str, + &el->el_scratch); +} diff --git a/bin/catsh/libedit/hist.h b/bin/catsh/libedit/hist.h new file mode 100644 index 00000000..c58c5bfe --- /dev/null +++ b/bin/catsh/libedit/hist.h @@ -0,0 +1,79 @@ +/* $NetBSD: hist.h,v 1.22 2016/05/09 21:46:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)hist.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.hist.c: History functions + */ +#ifndef _h_el_hist +#define _h_el_hist + +typedef int (*hist_fun_t)(void *, HistEventW *, int, ...); + +typedef struct el_history_t { + wchar_t *buf; /* The history buffer */ + size_t sz; /* Size of history buffer */ + wchar_t *last; /* The last character */ + int eventno; /* Event we are looking for */ + void *ref; /* Argument for history fcns */ + hist_fun_t fun; /* Event access */ + HistEventW ev; /* Event cookie */ +} el_history_t; + +#define HIST_FUN_INTERNAL(el, fn, arg) \ + ((((*(el)->el_history.fun) ((el)->el_history.ref, &(el)->el_history.ev, \ + fn, arg)) == -1) ? NULL : (el)->el_history.ev.str) +#define HIST_FUN(el, fn, arg) \ + (((el)->el_flags & NARROW_HISTORY) ? hist_convert(el, fn, arg) : \ + HIST_FUN_INTERNAL(el, fn, arg)) + +#define HIST_NEXT(el) HIST_FUN(el, H_NEXT, NULL) +#define HIST_FIRST(el) HIST_FUN(el, H_FIRST, NULL) +#define HIST_LAST(el) HIST_FUN(el, H_LAST, NULL) +#define HIST_PREV(el) HIST_FUN(el, H_PREV, NULL) +#define HIST_SET(el, num) HIST_FUN(el, H_SET, num) +#define HIST_LOAD(el, fname) HIST_FUN(el, H_LOAD fname) +#define HIST_SAVE(el, fname) HIST_FUN(el, H_SAVE fname) +#define HIST_SAVE_FP(el, fp) HIST_FUN(el, H_SAVE_FP fp) + +libedit_private int hist_init(EditLine *); +libedit_private void hist_end(EditLine *); +libedit_private el_action_t hist_get(EditLine *); +libedit_private int hist_set(EditLine *, hist_fun_t, void *); +libedit_private int hist_command(EditLine *, int, const wchar_t **); +libedit_private int hist_enlargebuf(EditLine *, size_t, size_t); +libedit_private wchar_t *hist_convert(EditLine *, int, void *); + +#endif /* _h_el_hist */ diff --git a/bin/catsh/libedit/histedit.h b/bin/catsh/libedit/histedit.h new file mode 100644 index 00000000..ecb42686 --- /dev/null +++ b/bin/catsh/libedit/histedit.h @@ -0,0 +1,315 @@ +/* $NetBSD: histedit.h,v 1.56 2016/04/19 19:50:53 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)histedit.h 8.2 (Berkeley) 1/3/94 + */ + +/* + * histedit.h: Line editor and history interface. + */ +#ifndef _HISTEDIT_H_ +#define _HISTEDIT_H_ + +#define LIBEDIT_MAJOR 2 +#define LIBEDIT_MINOR 11 + +#include <sys/types.h> +#include <stdio.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ==== Editing ==== + */ + +typedef struct editline EditLine; + +/* + * For user-defined function interface + */ +typedef struct lineinfo { + const char *buffer; + const char *cursor; + const char *lastchar; +} LineInfo; + +/* + * EditLine editor function return codes. + * For user-defined function interface + */ +#define CC_NORM 0 +#define CC_NEWLINE 1 +#define CC_EOF 2 +#define CC_ARGHACK 3 +#define CC_REFRESH 4 +#define CC_CURSOR 5 +#define CC_ERROR 6 +#define CC_FATAL 7 +#define CC_REDISPLAY 8 +#define CC_REFRESH_BEEP 9 + +/* + * Initialization, cleanup, and resetting + */ +EditLine *el_init(const char *, FILE *, FILE *, FILE *); +EditLine *el_init_fd(const char *, FILE *, FILE *, FILE *, + int, int, int); +void el_end(EditLine *); +void el_reset(EditLine *); + +/* + * Get a line, a character or push a string back in the input queue + */ +const char *el_gets(EditLine *, int *); +int el_getc(EditLine *, char *); +void el_push(EditLine *, const char *); + +/* + * Beep! + */ +void el_beep(EditLine *); + +/* + * High level function internals control + * Parses argc, argv array and executes builtin editline commands + */ +int el_parse(EditLine *, int, const char **); + +/* + * Low level editline access functions + */ +int el_set(EditLine *, int, ...); +int el_get(EditLine *, int, ...); +unsigned char _el_fn_complete(EditLine *, int); + +/* + * el_set/el_get parameters + * + * When using el_wset/el_wget (as opposed to el_set/el_get): + * Char is wchar_t, otherwise it is char. + * prompt_func is el_wpfunc_t, otherwise it is el_pfunc_t . + + * Prompt function prototypes are: + * typedef char *(*el_pfunct_t) (EditLine *); + * typedef wchar_t *(*el_wpfunct_t) (EditLine *); + * + * For operations that support set or set/get, the argument types listed are for + * the "set" operation. For "get", each listed type must be a pointer. + * E.g. EL_EDITMODE takes an int when set, but an int* when get. + * + * Operations that only support "get" have the correct argument types listed. + */ +#define EL_PROMPT 0 /* , prompt_func); set/get */ +#define EL_TERMINAL 1 /* , const char *); set/get */ +#define EL_EDITOR 2 /* , const Char *); set/get */ +#define EL_SIGNAL 3 /* , int); set/get */ +#define EL_BIND 4 /* , const Char *, ..., NULL); set */ +#define EL_TELLTC 5 /* , const Char *, ..., NULL); set */ +#define EL_SETTC 6 /* , const Char *, ..., NULL); set */ +#define EL_ECHOTC 7 /* , const Char *, ..., NULL); set */ +#define EL_SETTY 8 /* , const Char *, ..., NULL); set */ +#define EL_ADDFN 9 /* , const Char *, const Char, set */ + /* el_func_t); */ +#define EL_HIST 10 /* , hist_fun_t, const void *); set */ +#define EL_EDITMODE 11 /* , int); set/get */ +#define EL_RPROMPT 12 /* , prompt_func); set/get */ +#define EL_GETCFN 13 /* , el_rfunc_t); set/get */ +#define EL_CLIENTDATA 14 /* , void *); set/get */ +#define EL_UNBUFFERED 15 /* , int); set/get */ +#define EL_PREP_TERM 16 /* , int); set */ +#define EL_GETTC 17 /* , const Char *, ..., NULL); get */ +#define EL_GETFP 18 /* , int, FILE **); get */ +#define EL_SETFP 19 /* , int, FILE *); set */ +#define EL_REFRESH 20 /* , void); set */ +#define EL_PROMPT_ESC 21 /* , prompt_func, Char); set/get */ +#define EL_RPROMPT_ESC 22 /* , prompt_func, Char); set/get */ +#define EL_RESIZE 23 /* , el_zfunc_t, void *); set */ +#define EL_ALIAS_TEXT 24 /* , el_afunc_t, void *); set */ + +#define EL_BUILTIN_GETCFN (NULL) + +/* + * Source named file or $PWD/.editrc or $HOME/.editrc + */ +int el_source(EditLine *, const char *); + +/* + * Must be called when the terminal changes size; If EL_SIGNAL + * is set this is done automatically otherwise it is the responsibility + * of the application + */ +void el_resize(EditLine *); + +/* + * User-defined function interface. + */ +const LineInfo *el_line(EditLine *); +int el_insertstr(EditLine *, const char *); +void el_deletestr(EditLine *, int); + + +/* + * ==== History ==== + */ + +typedef struct history History; + +typedef struct HistEvent { + int num; + const char *str; +} HistEvent; + +/* + * History access functions. + */ +History * history_init(void); +void history_end(History *); + +int history(History *, HistEvent *, int, ...); + +#define H_FUNC 0 /* , UTSL */ +#define H_SETSIZE 1 /* , const int); */ +#define H_GETSIZE 2 /* , void); */ +#define H_FIRST 3 /* , void); */ +#define H_LAST 4 /* , void); */ +#define H_PREV 5 /* , void); */ +#define H_NEXT 6 /* , void); */ +#define H_CURR 8 /* , const int); */ +#define H_SET 7 /* , int); */ +#define H_ADD 9 /* , const wchar_t *); */ +#define H_ENTER 10 /* , const wchar_t *); */ +#define H_APPEND 11 /* , const wchar_t *); */ +#define H_END 12 /* , void); */ +#define H_NEXT_STR 13 /* , const wchar_t *); */ +#define H_PREV_STR 14 /* , const wchar_t *); */ +#define H_NEXT_EVENT 15 /* , const int); */ +#define H_PREV_EVENT 16 /* , const int); */ +#define H_LOAD 17 /* , const char *); */ +#define H_SAVE 18 /* , const char *); */ +#define H_CLEAR 19 /* , void); */ +#define H_SETUNIQUE 20 /* , int); */ +#define H_GETUNIQUE 21 /* , void); */ +#define H_DEL 22 /* , int); */ +#define H_NEXT_EVDATA 23 /* , const int, histdata_t *); */ +#define H_DELDATA 24 /* , int, histdata_t *);*/ +#define H_REPLACE 25 /* , const char *, histdata_t); */ +#define H_SAVE_FP 26 /* , FILE *); */ +#define H_SAVE_INCR 27 /* , const char *, int); */ +#define H_SAVE_FP_INCR 28 /* , FILE *, int); */ + + + +/* + * ==== Tokenization ==== + */ + +typedef struct tokenizer Tokenizer; + +/* + * String tokenization functions, using simplified sh(1) quoting rules + */ +Tokenizer *tok_init(const char *); +void tok_end(Tokenizer *); +void tok_reset(Tokenizer *); +int tok_line(Tokenizer *, const LineInfo *, + int *, const char ***, int *, int *); +int tok_str(Tokenizer *, const char *, + int *, const char ***); + +/* + * Begin Wide Character Support + */ +#include <wchar.h> +#include <wctype.h> + +/* + * ==== Editing ==== + */ +typedef struct lineinfow { + const wchar_t *buffer; + const wchar_t *cursor; + const wchar_t *lastchar; +} LineInfoW; + +typedef int (*el_rfunc_t)(EditLine *, wchar_t *); + +const wchar_t *el_wgets(EditLine *, int *); +int el_wgetc(EditLine *, wchar_t *); +void el_wpush(EditLine *, const wchar_t *); + +int el_wparse(EditLine *, int, const wchar_t **); + +int el_wset(EditLine *, int, ...); +int el_wget(EditLine *, int, ...); + +int el_cursor(EditLine *, int); +const LineInfoW *el_wline(EditLine *); +int el_winsertstr(EditLine *, const wchar_t *); +#define el_wdeletestr el_deletestr + +/* + * ==== History ==== + */ +typedef struct histeventW { + int num; + const wchar_t *str; +} HistEventW; + +typedef struct historyW HistoryW; + +HistoryW * history_winit(void); +void history_wend(HistoryW *); + +int history_w(HistoryW *, HistEventW *, int, ...); + +/* + * ==== Tokenization ==== + */ +typedef struct tokenizerW TokenizerW; + +/* Wide character tokenizer support */ +TokenizerW *tok_winit(const wchar_t *); +void tok_wend(TokenizerW *); +void tok_wreset(TokenizerW *); +int tok_wline(TokenizerW *, const LineInfoW *, + int *, const wchar_t ***, int *, int *); +int tok_wstr(TokenizerW *, const wchar_t *, + int *, const wchar_t ***); + +#ifdef __cplusplus +} +#endif + +#endif /* _HISTEDIT_H_ */ diff --git a/bin/catsh/libedit/history.c b/bin/catsh/libedit/history.c new file mode 100644 index 00000000..005fe2a0 --- /dev/null +++ b/bin/catsh/libedit/history.c @@ -0,0 +1,1271 @@ +/* $NetBSD: history.c,v 1.57 2016/04/11 18:56:31 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)history.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: history.c,v 1.57 2016/04/11 18:56:31 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * hist.c: TYPE(History) access functions + */ +#include <sys/file.h> +#include <sys/stat.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <vis.h> + +static const char hist_cookie[] = "_HiStOrY_V2_\n"; + +#include "histedit.h" + + +#ifdef NARROWCHAR + +#define Char char +#define FUN(prefix, rest) prefix ## _ ## rest +#define FUNW(type) type +#define TYPE(type) type +#define STR(x) x + +#define Strlen(s) strlen(s) +#define Strdup(s) strdup(s) +#define Strcmp(d, s) strcmp(d, s) +#define Strncmp(d, s, n) strncmp(d, s, n) +#define Strncpy(d, s, n) strncpy(d, s, n) +#define Strncat(d, s, n) strncat(d, s, n) +#define ct_decode_string(s, b) (s) +#define ct_encode_string(s, b) (s) + +#else +#include "chartype.h" + +#define Char wchar_t +#define FUN(prefix, rest) prefix ## _w ## rest +#define FUNW(type) type ## _w +#define TYPE(type) type ## W +#define STR(x) L ## x + +#define Strlen(s) wcslen(s) +#define Strdup(s) wcsdup(s) +#define Strcmp(d, s) wcscmp(d, s) +#define Strncmp(d, s, n) wcsncmp(d, s, n) +#define Strncpy(d, s, n) wcsncpy(d, s, n) +#define Strncat(d, s, n) wcsncat(d, s, n) + +#endif + + +typedef int (*history_gfun_t)(void *, TYPE(HistEvent) *); +typedef int (*history_efun_t)(void *, TYPE(HistEvent) *, const Char *); +typedef void (*history_vfun_t)(void *, TYPE(HistEvent) *); +typedef int (*history_sfun_t)(void *, TYPE(HistEvent) *, const int); + +struct TYPE(history) { + void *h_ref; /* Argument for history fcns */ + int h_ent; /* Last entry point for history */ + history_gfun_t h_first; /* Get the first element */ + history_gfun_t h_next; /* Get the next element */ + history_gfun_t h_last; /* Get the last element */ + history_gfun_t h_prev; /* Get the previous element */ + history_gfun_t h_curr; /* Get the current element */ + history_sfun_t h_set; /* Set the current element */ + history_sfun_t h_del; /* Set the given element */ + history_vfun_t h_clear; /* Clear the history list */ + history_efun_t h_enter; /* Add an element */ + history_efun_t h_add; /* Append to an element */ +}; + +#define HNEXT(h, ev) (*(h)->h_next)((h)->h_ref, ev) +#define HFIRST(h, ev) (*(h)->h_first)((h)->h_ref, ev) +#define HPREV(h, ev) (*(h)->h_prev)((h)->h_ref, ev) +#define HLAST(h, ev) (*(h)->h_last)((h)->h_ref, ev) +#define HCURR(h, ev) (*(h)->h_curr)((h)->h_ref, ev) +#define HSET(h, ev, n) (*(h)->h_set)((h)->h_ref, ev, n) +#define HCLEAR(h, ev) (*(h)->h_clear)((h)->h_ref, ev) +#define HENTER(h, ev, str) (*(h)->h_enter)((h)->h_ref, ev, str) +#define HADD(h, ev, str) (*(h)->h_add)((h)->h_ref, ev, str) +#define HDEL(h, ev, n) (*(h)->h_del)((h)->h_ref, ev, n) + +#define h_strdup(a) Strdup(a) +#define h_malloc(a) malloc(a) +#define h_realloc(a, b) realloc((a), (b)) +#define h_free(a) free(a) + +typedef struct { + int num; + Char *str; +} HistEventPrivate; + + +static int history_setsize(TYPE(History) *, TYPE(HistEvent) *, int); +static int history_getsize(TYPE(History) *, TYPE(HistEvent) *); +static int history_setunique(TYPE(History) *, TYPE(HistEvent) *, int); +static int history_getunique(TYPE(History) *, TYPE(HistEvent) *); +static int history_set_fun(TYPE(History) *, TYPE(History) *); +static int history_load(TYPE(History) *, const char *); +static int history_save(TYPE(History) *, const char *); +static int history_save_fp(TYPE(History) *, FILE *); +static int history_prev_event(TYPE(History) *, TYPE(HistEvent) *, int); +static int history_next_event(TYPE(History) *, TYPE(HistEvent) *, int); +static int history_next_string(TYPE(History) *, TYPE(HistEvent) *, + const Char *); +static int history_prev_string(TYPE(History) *, TYPE(HistEvent) *, + const Char *); + + +/***********************************************************************/ + +/* + * Builtin- history implementation + */ +typedef struct hentry_t { + TYPE(HistEvent) ev; /* What we return */ + void *data; /* data */ + struct hentry_t *next; /* Next entry */ + struct hentry_t *prev; /* Previous entry */ +} hentry_t; + +typedef struct history_t { + hentry_t list; /* Fake list header element */ + hentry_t *cursor; /* Current element in the list */ + int max; /* Maximum number of events */ + int cur; /* Current number of events */ + int eventid; /* For generation of unique event id */ + int flags; /* TYPE(History) flags */ +#define H_UNIQUE 1 /* Store only unique elements */ +} history_t; + +static int history_def_next(void *, TYPE(HistEvent) *); +static int history_def_first(void *, TYPE(HistEvent) *); +static int history_def_prev(void *, TYPE(HistEvent) *); +static int history_def_last(void *, TYPE(HistEvent) *); +static int history_def_curr(void *, TYPE(HistEvent) *); +static int history_def_set(void *, TYPE(HistEvent) *, const int); +static void history_def_clear(void *, TYPE(HistEvent) *); +static int history_def_enter(void *, TYPE(HistEvent) *, const Char *); +static int history_def_add(void *, TYPE(HistEvent) *, const Char *); +static int history_def_del(void *, TYPE(HistEvent) *, const int); + +static int history_def_init(void **, TYPE(HistEvent) *, int); +static int history_def_insert(history_t *, TYPE(HistEvent) *, const Char *); +static void history_def_delete(history_t *, TYPE(HistEvent) *, hentry_t *); + +static int history_deldata_nth(history_t *, TYPE(HistEvent) *, int, void **); +static int history_set_nth(void *, TYPE(HistEvent) *, int); + +#define history_def_setsize(p, num)(void) (((history_t *)p)->max = (num)) +#define history_def_getsize(p) (((history_t *)p)->cur) +#define history_def_getunique(p) (((((history_t *)p)->flags) & H_UNIQUE) != 0) +#define history_def_setunique(p, uni) \ + if (uni) \ + (((history_t *)p)->flags) |= H_UNIQUE; \ + else \ + (((history_t *)p)->flags) &= ~H_UNIQUE + +#define he_strerror(code) he_errlist[code] +#define he_seterrev(evp, code) {\ + evp->num = code;\ + evp->str = he_strerror(code);\ + } + +/* error messages */ +static const Char *const he_errlist[] = { + STR("OK"), + STR("unknown error"), + STR("malloc() failed"), + STR("first event not found"), + STR("last event not found"), + STR("empty list"), + STR("no next event"), + STR("no previous event"), + STR("current event is invalid"), + STR("event not found"), + STR("can't read history from file"), + STR("can't write history"), + STR("required parameter(s) not supplied"), + STR("history size negative"), + STR("function not allowed with other history-functions-set the default"), + STR("bad parameters") +}; +/* error codes */ +#define _HE_OK 0 +#define _HE_UNKNOWN 1 +#define _HE_MALLOC_FAILED 2 +#define _HE_FIRST_NOTFOUND 3 +#define _HE_LAST_NOTFOUND 4 +#define _HE_EMPTY_LIST 5 +#define _HE_END_REACHED 6 +#define _HE_START_REACHED 7 +#define _HE_CURR_INVALID 8 +#define _HE_NOT_FOUND 9 +#define _HE_HIST_READ 10 +#define _HE_HIST_WRITE 11 +#define _HE_PARAM_MISSING 12 +#define _HE_SIZE_NEGATIVE 13 +#define _HE_NOT_ALLOWED 14 +#define _HE_BAD_PARAM 15 + +/* history_def_first(): + * Default function to return the first event in the history. + */ +static int +history_def_first(void *p, TYPE(HistEvent) *ev) +{ + history_t *h = (history_t *) p; + + h->cursor = h->list.next; + if (h->cursor != &h->list) + *ev = h->cursor->ev; + else { + he_seterrev(ev, _HE_FIRST_NOTFOUND); + return -1; + } + + return 0; +} + + +/* history_def_last(): + * Default function to return the last event in the history. + */ +static int +history_def_last(void *p, TYPE(HistEvent) *ev) +{ + history_t *h = (history_t *) p; + + h->cursor = h->list.prev; + if (h->cursor != &h->list) + *ev = h->cursor->ev; + else { + he_seterrev(ev, _HE_LAST_NOTFOUND); + return -1; + } + + return 0; +} + + +/* history_def_next(): + * Default function to return the next event in the history. + */ +static int +history_def_next(void *p, TYPE(HistEvent) *ev) +{ + history_t *h = (history_t *) p; + + if (h->cursor == &h->list) { + he_seterrev(ev, _HE_EMPTY_LIST); + return -1; + } + + if (h->cursor->next == &h->list) { + he_seterrev(ev, _HE_END_REACHED); + return -1; + } + + h->cursor = h->cursor->next; + *ev = h->cursor->ev; + + return 0; +} + + +/* history_def_prev(): + * Default function to return the previous event in the history. + */ +static int +history_def_prev(void *p, TYPE(HistEvent) *ev) +{ + history_t *h = (history_t *) p; + + if (h->cursor == &h->list) { + he_seterrev(ev, + (h->cur > 0) ? _HE_END_REACHED : _HE_EMPTY_LIST); + return -1; + } + + if (h->cursor->prev == &h->list) { + he_seterrev(ev, _HE_START_REACHED); + return -1; + } + + h->cursor = h->cursor->prev; + *ev = h->cursor->ev; + + return 0; +} + + +/* history_def_curr(): + * Default function to return the current event in the history. + */ +static int +history_def_curr(void *p, TYPE(HistEvent) *ev) +{ + history_t *h = (history_t *) p; + + if (h->cursor != &h->list) + *ev = h->cursor->ev; + else { + he_seterrev(ev, + (h->cur > 0) ? _HE_CURR_INVALID : _HE_EMPTY_LIST); + return -1; + } + + return 0; +} + + +/* history_def_set(): + * Default function to set the current event in the history to the + * given one. + */ +static int +history_def_set(void *p, TYPE(HistEvent) *ev, const int n) +{ + history_t *h = (history_t *) p; + + if (h->cur == 0) { + he_seterrev(ev, _HE_EMPTY_LIST); + return -1; + } + if (h->cursor == &h->list || h->cursor->ev.num != n) { + for (h->cursor = h->list.next; h->cursor != &h->list; + h->cursor = h->cursor->next) + if (h->cursor->ev.num == n) + break; + } + if (h->cursor == &h->list) { + he_seterrev(ev, _HE_NOT_FOUND); + return -1; + } + return 0; +} + + +/* history_set_nth(): + * Default function to set the current event in the history to the + * n-th one. + */ +static int +history_set_nth(void *p, TYPE(HistEvent) *ev, int n) +{ + history_t *h = (history_t *) p; + + if (h->cur == 0) { + he_seterrev(ev, _HE_EMPTY_LIST); + return -1; + } + for (h->cursor = h->list.prev; h->cursor != &h->list; + h->cursor = h->cursor->prev) + if (n-- <= 0) + break; + if (h->cursor == &h->list) { + he_seterrev(ev, _HE_NOT_FOUND); + return -1; + } + return 0; +} + + +/* history_def_add(): + * Append string to element + */ +static int +history_def_add(void *p, TYPE(HistEvent) *ev, const Char *str) +{ + history_t *h = (history_t *) p; + size_t len; + Char *s; + HistEventPrivate *evp = (void *)&h->cursor->ev; + + if (h->cursor == &h->list) + return history_def_enter(p, ev, str); + len = Strlen(evp->str) + Strlen(str) + 1; + s = h_malloc(len * sizeof(*s)); + if (s == NULL) { + he_seterrev(ev, _HE_MALLOC_FAILED); + return -1; + } + (void) Strncpy(s, h->cursor->ev.str, len); + s[len - 1] = '\0'; + (void) Strncat(s, str, len - Strlen(s) - 1); + h_free(evp->str); + evp->str = s; + *ev = h->cursor->ev; + return 0; +} + + +static int +history_deldata_nth(history_t *h, TYPE(HistEvent) *ev, + int num, void **data) +{ + if (history_set_nth(h, ev, num) != 0) + return -1; + /* magic value to skip delete (just set to n-th history) */ + if (data == (void **)-1) + return 0; + ev->str = Strdup(h->cursor->ev.str); + ev->num = h->cursor->ev.num; + if (data) + *data = h->cursor->data; + history_def_delete(h, ev, h->cursor); + return 0; +} + + +/* history_def_del(): + * Delete element hp of the h list + */ +/* ARGSUSED */ +static int +history_def_del(void *p, TYPE(HistEvent) *ev __attribute__((__unused__)), + const int num) +{ + history_t *h = (history_t *) p; + if (history_def_set(h, ev, num) != 0) + return -1; + ev->str = Strdup(h->cursor->ev.str); + ev->num = h->cursor->ev.num; + history_def_delete(h, ev, h->cursor); + return 0; +} + + +/* history_def_delete(): + * Delete element hp of the h list + */ +/* ARGSUSED */ +static void +history_def_delete(history_t *h, + TYPE(HistEvent) *ev __attribute__((__unused__)), hentry_t *hp) +{ + HistEventPrivate *evp = (void *)&hp->ev; + if (hp == &h->list) + abort(); + if (h->cursor == hp) { + h->cursor = hp->prev; + if (h->cursor == &h->list) + h->cursor = hp->next; + } + hp->prev->next = hp->next; + hp->next->prev = hp->prev; + h_free(evp->str); + h_free(hp); + h->cur--; +} + + +/* history_def_insert(): + * Insert element with string str in the h list + */ +static int +history_def_insert(history_t *h, TYPE(HistEvent) *ev, const Char *str) +{ + hentry_t *c; + + c = h_malloc(sizeof(*c)); + if (c == NULL) + goto oomem; + if ((c->ev.str = h_strdup(str)) == NULL) { + h_free(c); + goto oomem; + } + c->data = NULL; + c->ev.num = ++h->eventid; + c->next = h->list.next; + c->prev = &h->list; + h->list.next->prev = c; + h->list.next = c; + h->cur++; + h->cursor = c; + + *ev = c->ev; + return 0; +oomem: + he_seterrev(ev, _HE_MALLOC_FAILED); + return -1; +} + + +/* history_def_enter(): + * Default function to enter an item in the history + */ +static int +history_def_enter(void *p, TYPE(HistEvent) *ev, const Char *str) +{ + history_t *h = (history_t *) p; + + if ((h->flags & H_UNIQUE) != 0 && h->list.next != &h->list && + Strcmp(h->list.next->ev.str, str) == 0) + return 0; + + if (history_def_insert(h, ev, str) == -1) + return -1; /* error, keep error message */ + + /* + * Always keep at least one entry. + * This way we don't have to check for the empty list. + */ + while (h->cur > h->max && h->cur > 0) + history_def_delete(h, ev, h->list.prev); + + return 1; +} + + +/* history_def_init(): + * Default history initialization function + */ +/* ARGSUSED */ +static int +history_def_init(void **p, TYPE(HistEvent) *ev __attribute__((__unused__)), int n) +{ + history_t *h = (history_t *) h_malloc(sizeof(*h)); + if (h == NULL) + return -1; + + if (n <= 0) + n = 0; + h->eventid = 0; + h->cur = 0; + h->max = n; + h->list.next = h->list.prev = &h->list; + h->list.ev.str = NULL; + h->list.ev.num = 0; + h->cursor = &h->list; + h->flags = 0; + *p = h; + return 0; +} + + +/* history_def_clear(): + * Default history cleanup function + */ +static void +history_def_clear(void *p, TYPE(HistEvent) *ev) +{ + history_t *h = (history_t *) p; + + while (h->list.prev != &h->list) + history_def_delete(h, ev, h->list.prev); + h->cursor = &h->list; + h->eventid = 0; + h->cur = 0; +} + + + + +/************************************************************************/ + +/* history_init(): + * Initialization function. + */ +TYPE(History) * +FUN(history,init)(void) +{ + TYPE(HistEvent) ev; + TYPE(History) *h = (TYPE(History) *) h_malloc(sizeof(*h)); + if (h == NULL) + return NULL; + + if (history_def_init(&h->h_ref, &ev, 0) == -1) { + h_free(h); + return NULL; + } + h->h_ent = -1; + h->h_next = history_def_next; + h->h_first = history_def_first; + h->h_last = history_def_last; + h->h_prev = history_def_prev; + h->h_curr = history_def_curr; + h->h_set = history_def_set; + h->h_clear = history_def_clear; + h->h_enter = history_def_enter; + h->h_add = history_def_add; + h->h_del = history_def_del; + + return h; +} + + +/* history_end(): + * clean up history; + */ +void +FUN(history,end)(TYPE(History) *h) +{ + TYPE(HistEvent) ev; + + if (h->h_next == history_def_next) + history_def_clear(h->h_ref, &ev); + h_free(h->h_ref); + h_free(h); +} + + + +/* history_setsize(): + * Set history number of events + */ +static int +history_setsize(TYPE(History) *h, TYPE(HistEvent) *ev, int num) +{ + + if (h->h_next != history_def_next) { + he_seterrev(ev, _HE_NOT_ALLOWED); + return -1; + } + if (num < 0) { + he_seterrev(ev, _HE_BAD_PARAM); + return -1; + } + history_def_setsize(h->h_ref, num); + return 0; +} + + +/* history_getsize(): + * Get number of events currently in history + */ +static int +history_getsize(TYPE(History) *h, TYPE(HistEvent) *ev) +{ + if (h->h_next != history_def_next) { + he_seterrev(ev, _HE_NOT_ALLOWED); + return -1; + } + ev->num = history_def_getsize(h->h_ref); + if (ev->num < -1) { + he_seterrev(ev, _HE_SIZE_NEGATIVE); + return -1; + } + return 0; +} + + +/* history_setunique(): + * Set if adjacent equal events should not be entered in history. + */ +static int +history_setunique(TYPE(History) *h, TYPE(HistEvent) *ev, int uni) +{ + + if (h->h_next != history_def_next) { + he_seterrev(ev, _HE_NOT_ALLOWED); + return -1; + } + history_def_setunique(h->h_ref, uni); + return 0; +} + + +/* history_getunique(): + * Get if adjacent equal events should not be entered in history. + */ +static int +history_getunique(TYPE(History) *h, TYPE(HistEvent) *ev) +{ + if (h->h_next != history_def_next) { + he_seterrev(ev, _HE_NOT_ALLOWED); + return -1; + } + ev->num = history_def_getunique(h->h_ref); + return 0; +} + + +/* history_set_fun(): + * Set history functions + */ +static int +history_set_fun(TYPE(History) *h, TYPE(History) *nh) +{ + TYPE(HistEvent) ev; + + if (nh->h_first == NULL || nh->h_next == NULL || nh->h_last == NULL || + nh->h_prev == NULL || nh->h_curr == NULL || nh->h_set == NULL || + nh->h_enter == NULL || nh->h_add == NULL || nh->h_clear == NULL || + nh->h_del == NULL || nh->h_ref == NULL) { + if (h->h_next != history_def_next) { + if (history_def_init(&h->h_ref, &ev, 0) == -1) + return -1; + h->h_first = history_def_first; + h->h_next = history_def_next; + h->h_last = history_def_last; + h->h_prev = history_def_prev; + h->h_curr = history_def_curr; + h->h_set = history_def_set; + h->h_clear = history_def_clear; + h->h_enter = history_def_enter; + h->h_add = history_def_add; + h->h_del = history_def_del; + } + return -1; + } + if (h->h_next == history_def_next) + history_def_clear(h->h_ref, &ev); + + h->h_ent = -1; + h->h_first = nh->h_first; + h->h_next = nh->h_next; + h->h_last = nh->h_last; + h->h_prev = nh->h_prev; + h->h_curr = nh->h_curr; + h->h_set = nh->h_set; + h->h_clear = nh->h_clear; + h->h_enter = nh->h_enter; + h->h_add = nh->h_add; + h->h_del = nh->h_del; + + return 0; +} + + +/* history_load(): + * TYPE(History) load function + */ +static int +history_load(TYPE(History) *h, const char *fname) +{ + FILE *fp; + char *line = NULL; + size_t llen; + ssize_t sz; + size_t max_size; + char *ptr; + int i = -1; + TYPE(HistEvent) ev; +#ifndef NARROWCHAR + static ct_buffer_t conv; +#endif + + if ((fp = fopen(fname, "r")) == NULL) + return i; + + if (flock(fileno(fp), LOCK_SH) == -1) + goto done; + + llen = 0; + if ((sz = getline(&line, &llen, fp)) == -1) + goto done; + + if (strncmp(line, hist_cookie, (size_t)sz) != 0) + goto done; + + ptr = h_malloc((max_size = 1024) * sizeof(*ptr)); + if (ptr == NULL) + goto done; + for (i = 0; (sz = getline(&line, &llen, fp)) != -1; i++) { + if (sz > 0 && line[sz - 1] == '\n') + line[--sz] = '\0'; + if (max_size < (size_t)sz) { + char *nptr; + max_size = ((size_t)sz + 1024) & (size_t)~1023; + nptr = h_realloc(ptr, max_size * sizeof(*ptr)); + if (nptr == NULL) { + i = -1; + goto oomem; + } + ptr = nptr; + } + (void) strunvis(ptr, line); + if (HENTER(h, &ev, ct_decode_string(ptr, &conv)) == -1) { + i = -1; + goto oomem; + } + } +oomem: + h_free(ptr); +done: + free(line); + (void) fclose(fp); + return i; +} + + +/* history_save_fp(): + * TYPE(History) save function + */ +static int +history_save_fp(TYPE(History) *h, FILE *fp) +{ + TYPE(HistEvent) ev; + int i = -1, retval; + size_t len, max_size; + char *ptr; + const char *str; +#ifndef NARROWCHAR + static ct_buffer_t conv; +#endif + + if (flock(fileno(fp), LOCK_EX) == -1) + goto done; + if (fchmod(fileno(fp), S_IRUSR|S_IWUSR) == -1) + goto done; + if (fputs(hist_cookie, fp) == EOF) + goto done; + ptr = h_malloc((max_size = 1024) * sizeof(*ptr)); + if (ptr == NULL) + goto done; + for (i = 0, retval = HLAST(h, &ev); + retval != -1; + retval = HPREV(h, &ev), i++) { + str = ct_encode_string(ev.str, &conv); + len = strlen(str) * 4 + 1; + if (len > max_size) { + char *nptr; + max_size = (len + 1024) & (size_t)~1023; + nptr = h_realloc(ptr, max_size * sizeof(*ptr)); + if (nptr == NULL) { + i = -1; + goto oomem; + } + ptr = nptr; + } + (void) strvis(ptr, str, VIS_WHITE); + (void) fprintf(fp, "%s\n", ptr); + } +oomem: + h_free(ptr); +done: + (void) flock(fileno(fp), LOCK_UN); + return i; +} + + +/* history_save(): + * History save function + */ +static int +history_save(TYPE(History) *h, const char *fname) +{ + FILE *fp; + int i; + + if ((fp = fopen(fname, "w")) == NULL) + return -1; + + i = history_save_fp(h, fp); + + (void) fclose(fp); + return i; +} + + +static int +history_save_fp_incr(TYPE(History) *h, FILE *fp, int num) +{ + TYPE(HistEvent) ev; + char *line = NULL; + char *ptr; + const char *str; + int i = -1, retval; + size_t len, max_size; + size_t llen = 0; + ssize_t sz; +#ifndef NARROWCHAR + static ct_buffer_t conv; +#endif + + for (retval = HCURR(h, &ev); retval != -1; retval = HNEXT(h, &ev)) + if (ev.num == num) + break; + if (retval == -1) + goto done; + + if (flock(fileno(fp), LOCK_EX) == -1) + goto done; + if (fchmod(fileno(fp), S_IRUSR|S_IWUSR) == -1) + goto done; + + if (fseek(fp, 0, SEEK_SET) == -1) + goto done; + if ((sz = getline(&line, &llen, fp)) == -1) { + if (ferror(fp)) + goto done; + else + sz = 0; + } + if (strncmp(line, hist_cookie, (size_t)sz) != 0) + goto done; + if (fseek(fp, 0, SEEK_END) == -1) + goto done; + if (sz == 0) + if (fputs(hist_cookie, fp) == EOF) + goto done; + + ptr = h_malloc((max_size = 1024) * sizeof(*ptr)); + if (ptr == NULL) + goto done; + for (i = 0, retval = 0; + retval != -1; + retval = HPREV(h, &ev), i++) { + str = ct_encode_string(ev.str, &conv); + len = strlen(str) * 4 + 1; + if (len > max_size) { + char *nptr; + max_size = (len + 1024) & (size_t)~1023; + nptr = h_realloc(ptr, max_size * sizeof(*ptr)); + if (nptr == NULL) { + i = -1; + goto oomem; + } + ptr = nptr; + } + (void) strvis(ptr, str, VIS_WHITE); + (void) fprintf(fp, "%s\n", ptr); + } +oomem: + h_free(ptr); +done: + free(line); + (void) flock(fileno(fp), LOCK_UN); + return i; +} + + +static int +history_save_incr(TYPE(History) *h, const char *fname, int num) +{ + FILE *fp; + int i; + + if ((fp = fopen(fname, "a+")) == NULL) + return -1; + + i = history_save_fp_incr(h, fp, num); + + (void) fclose(fp); + return i; +} + + +/* history_prev_event(): + * Find the previous event, with number given + */ +static int +history_prev_event(TYPE(History) *h, TYPE(HistEvent) *ev, int num) +{ + int retval; + + for (retval = HCURR(h, ev); retval != -1; retval = HPREV(h, ev)) + if (ev->num == num) + return 0; + + he_seterrev(ev, _HE_NOT_FOUND); + return -1; +} + + +static int +history_next_evdata(TYPE(History) *h, TYPE(HistEvent) *ev, int num, void **d) +{ + int retval; + + for (retval = HCURR(h, ev); retval != -1; retval = HPREV(h, ev)) + if (ev->num == num) { + if (d) + *d = ((history_t *)h->h_ref)->cursor->data; + return 0; + } + + he_seterrev(ev, _HE_NOT_FOUND); + return -1; +} + + +/* history_next_event(): + * Find the next event, with number given + */ +static int +history_next_event(TYPE(History) *h, TYPE(HistEvent) *ev, int num) +{ + int retval; + + for (retval = HCURR(h, ev); retval != -1; retval = HNEXT(h, ev)) + if (ev->num == num) + return 0; + + he_seterrev(ev, _HE_NOT_FOUND); + return -1; +} + + +/* history_prev_string(): + * Find the previous event beginning with string + */ +static int +history_prev_string(TYPE(History) *h, TYPE(HistEvent) *ev, const Char *str) +{ + size_t len = Strlen(str); + int retval; + + for (retval = HCURR(h, ev); retval != -1; retval = HNEXT(h, ev)) + if (Strncmp(str, ev->str, len) == 0) + return 0; + + he_seterrev(ev, _HE_NOT_FOUND); + return -1; +} + + +/* history_next_string(): + * Find the next event beginning with string + */ +static int +history_next_string(TYPE(History) *h, TYPE(HistEvent) *ev, const Char *str) +{ + size_t len = Strlen(str); + int retval; + + for (retval = HCURR(h, ev); retval != -1; retval = HPREV(h, ev)) + if (Strncmp(str, ev->str, len) == 0) + return 0; + + he_seterrev(ev, _HE_NOT_FOUND); + return -1; +} + + +/* history(): + * User interface to history functions. + */ +int +FUNW(history)(TYPE(History) *h, TYPE(HistEvent) *ev, int fun, ...) +{ + va_list va; + const Char *str; + int retval; + + va_start(va, fun); + + he_seterrev(ev, _HE_OK); + + switch (fun) { + case H_GETSIZE: + retval = history_getsize(h, ev); + break; + + case H_SETSIZE: + retval = history_setsize(h, ev, va_arg(va, int)); + break; + + case H_GETUNIQUE: + retval = history_getunique(h, ev); + break; + + case H_SETUNIQUE: + retval = history_setunique(h, ev, va_arg(va, int)); + break; + + case H_ADD: + str = va_arg(va, const Char *); + retval = HADD(h, ev, str); + break; + + case H_DEL: + retval = HDEL(h, ev, va_arg(va, const int)); + break; + + case H_ENTER: + str = va_arg(va, const Char *); + if ((retval = HENTER(h, ev, str)) != -1) + h->h_ent = ev->num; + break; + + case H_APPEND: + str = va_arg(va, const Char *); + if ((retval = HSET(h, ev, h->h_ent)) != -1) + retval = HADD(h, ev, str); + break; + + case H_FIRST: + retval = HFIRST(h, ev); + break; + + case H_NEXT: + retval = HNEXT(h, ev); + break; + + case H_LAST: + retval = HLAST(h, ev); + break; + + case H_PREV: + retval = HPREV(h, ev); + break; + + case H_CURR: + retval = HCURR(h, ev); + break; + + case H_SET: + retval = HSET(h, ev, va_arg(va, const int)); + break; + + case H_CLEAR: + HCLEAR(h, ev); + retval = 0; + break; + + case H_LOAD: + retval = history_load(h, va_arg(va, const char *)); + if (retval == -1) + he_seterrev(ev, _HE_HIST_READ); + break; + + case H_SAVE: + retval = history_save(h, va_arg(va, const char *)); + if (retval == -1) + he_seterrev(ev, _HE_HIST_WRITE); + break; + + case H_SAVE_INCR: + { + const char *fname = va_arg(va, const char *); + int num = va_arg(va, int); + retval = history_save_incr(h, fname, num); + if (retval == -1) + he_seterrev(ev, _HE_HIST_WRITE); + break; + } + + case H_SAVE_FP: + retval = history_save_fp(h, va_arg(va, FILE *)); + if (retval == -1) + he_seterrev(ev, _HE_HIST_WRITE); + break; + + case H_SAVE_FP_INCR: + { + FILE *fp = va_arg(va, FILE *); + int num = va_arg(va, int); + retval = history_save_fp_incr(h, fp, num); + if (retval == -1) + he_seterrev(ev, _HE_HIST_WRITE); + break; + } + + case H_PREV_EVENT: + retval = history_prev_event(h, ev, va_arg(va, int)); + break; + + case H_NEXT_EVENT: + retval = history_next_event(h, ev, va_arg(va, int)); + break; + + case H_PREV_STR: + retval = history_prev_string(h, ev, va_arg(va, const Char *)); + break; + + case H_NEXT_STR: + retval = history_next_string(h, ev, va_arg(va, const Char *)); + break; + + case H_FUNC: + { + TYPE(History) hf; + + hf.h_ref = va_arg(va, void *); + h->h_ent = -1; + hf.h_first = va_arg(va, history_gfun_t); + hf.h_next = va_arg(va, history_gfun_t); + hf.h_last = va_arg(va, history_gfun_t); + hf.h_prev = va_arg(va, history_gfun_t); + hf.h_curr = va_arg(va, history_gfun_t); + hf.h_set = va_arg(va, history_sfun_t); + hf.h_clear = va_arg(va, history_vfun_t); + hf.h_enter = va_arg(va, history_efun_t); + hf.h_add = va_arg(va, history_efun_t); + hf.h_del = va_arg(va, history_sfun_t); + + if ((retval = history_set_fun(h, &hf)) == -1) + he_seterrev(ev, _HE_PARAM_MISSING); + break; + } + + case H_END: + FUN(history,end)(h); + retval = 0; + break; + + case H_NEXT_EVDATA: + { + int num = va_arg(va, int); + void **d = va_arg(va, void **); + retval = history_next_evdata(h, ev, num, d); + break; + } + + case H_DELDATA: + { + int num = va_arg(va, int); + void **d = va_arg(va, void **); + retval = history_deldata_nth((history_t *)h->h_ref, ev, num, d); + break; + } + + case H_REPLACE: /* only use after H_NEXT_EVDATA */ + { + const Char *line = va_arg(va, const Char *); + void *d = va_arg(va, void *); + const Char *s; + if(!line || !(s = Strdup(line))) { + retval = -1; + break; + } + ((history_t *)h->h_ref)->cursor->ev.str = s; + ((history_t *)h->h_ref)->cursor->data = d; + retval = 0; + break; + } + + default: + retval = -1; + he_seterrev(ev, _HE_UNKNOWN); + break; + } + va_end(va); + return retval; +} diff --git a/bin/catsh/libedit/historyn.c b/bin/catsh/libedit/historyn.c new file mode 100644 index 00000000..59130dea --- /dev/null +++ b/bin/catsh/libedit/historyn.c @@ -0,0 +1,3 @@ +#include "config.h" +#define NARROWCHAR +#include "history.c" diff --git a/bin/catsh/libedit/keymacro.c b/bin/catsh/libedit/keymacro.c new file mode 100644 index 00000000..13d20893 --- /dev/null +++ b/bin/catsh/libedit/keymacro.c @@ -0,0 +1,669 @@ +/* $NetBSD: keymacro.c,v 1.23 2016/05/24 15:00:45 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)key.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: keymacro.c,v 1.23 2016/05/24 15:00:45 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * keymacro.c: This module contains the procedures for maintaining + * the extended-key map. + * + * An extended-key (key) is a sequence of keystrokes introduced + * with a sequence introducer and consisting of an arbitrary + * number of characters. This module maintains a map (the + * el->el_keymacro.map) + * to convert these extended-key sequences into input strs + * (XK_STR) or editor functions (XK_CMD). + * + * Warning: + * If key is a substr of some other keys, then the longer + * keys are lost!! That is, if the keys "abcd" and "abcef" + * are in el->el_keymacro.map, adding the key "abc" will cause + * the first two definitions to be lost. + * + * Restrictions: + * ------------- + * 1) It is not possible to have one key that is a + * substr of another. + */ +#include <stdlib.h> +#include <string.h> + +#include "el.h" +#include "fcns.h" + +/* + * The Nodes of the el->el_keymacro.map. The el->el_keymacro.map is a + * linked list of these node elements + */ +struct keymacro_node_t { + wchar_t ch; /* single character of key */ + int type; /* node type */ + keymacro_value_t val; /* command code or pointer to str, */ + /* if this is a leaf */ + struct keymacro_node_t *next; /* ptr to next char of this key */ + struct keymacro_node_t *sibling;/* ptr to another key with same prefix*/ +}; + +static int node_trav(EditLine *, keymacro_node_t *, wchar_t *, + keymacro_value_t *); +static int node__try(EditLine *, keymacro_node_t *, + const wchar_t *, keymacro_value_t *, int); +static keymacro_node_t *node__get(wint_t); +static void node__free(keymacro_node_t *); +static void node__put(EditLine *, keymacro_node_t *); +static int node__delete(EditLine *, keymacro_node_t **, + const wchar_t *); +static int node_lookup(EditLine *, const wchar_t *, + keymacro_node_t *, size_t); +static int node_enum(EditLine *, keymacro_node_t *, size_t); + +#define KEY_BUFSIZ EL_BUFSIZ + + +/* keymacro_init(): + * Initialize the key maps + */ +libedit_private int +keymacro_init(EditLine *el) +{ + + el->el_keymacro.buf = el_malloc(KEY_BUFSIZ * + sizeof(*el->el_keymacro.buf)); + if (el->el_keymacro.buf == NULL) + return -1; + el->el_keymacro.map = NULL; + keymacro_reset(el); + return 0; +} + +/* keymacro_end(): + * Free the key maps + */ +libedit_private void +keymacro_end(EditLine *el) +{ + + el_free(el->el_keymacro.buf); + el->el_keymacro.buf = NULL; + node__free(el->el_keymacro.map); +} + + +/* keymacro_map_cmd(): + * Associate cmd with a key value + */ +libedit_private keymacro_value_t * +keymacro_map_cmd(EditLine *el, int cmd) +{ + + el->el_keymacro.val.cmd = (el_action_t) cmd; + return &el->el_keymacro.val; +} + + +/* keymacro_map_str(): + * Associate str with a key value + */ +libedit_private keymacro_value_t * +keymacro_map_str(EditLine *el, wchar_t *str) +{ + + el->el_keymacro.val.str = str; + return &el->el_keymacro.val; +} + + +/* keymacro_reset(): + * Takes all nodes on el->el_keymacro.map and puts them on free list. + * Then initializes el->el_keymacro.map with arrow keys + * [Always bind the ansi arrow keys?] + */ +libedit_private void +keymacro_reset(EditLine *el) +{ + + node__put(el, el->el_keymacro.map); + el->el_keymacro.map = NULL; + return; +} + + +/* keymacro_get(): + * Calls the recursive function with entry point el->el_keymacro.map + * Looks up *ch in map and then reads characters until a + * complete match is found or a mismatch occurs. Returns the + * type of the match found (XK_STR or XK_CMD). + * Returns NULL in val.str and XK_STR for no match. + * Returns XK_NOD for end of file or read error. + * The last character read is returned in *ch. + */ +libedit_private int +keymacro_get(EditLine *el, wchar_t *ch, keymacro_value_t *val) +{ + + return node_trav(el, el->el_keymacro.map, ch, val); +} + + +/* keymacro_add(): + * Adds key to the el->el_keymacro.map and associates the value in + * val with it. If key is already is in el->el_keymacro.map, the new + * code is applied to the existing key. Ntype specifies if code is a + * command, an out str or a unix command. + */ +libedit_private void +keymacro_add(EditLine *el, const wchar_t *key, keymacro_value_t *val, + int ntype) +{ + + if (key[0] == '\0') { + (void) fprintf(el->el_errfile, + "keymacro_add: Null extended-key not allowed.\n"); + return; + } + if (ntype == XK_CMD && val->cmd == ED_SEQUENCE_LEAD_IN) { + (void) fprintf(el->el_errfile, + "keymacro_add: sequence-lead-in command not allowed\n"); + return; + } + if (el->el_keymacro.map == NULL) + /* tree is initially empty. Set up new node to match key[0] */ + el->el_keymacro.map = node__get(key[0]); + /* it is properly initialized */ + + /* Now recurse through el->el_keymacro.map */ + (void) node__try(el, el->el_keymacro.map, key, val, ntype); + return; +} + + +/* keymacro_clear(): + * + */ +libedit_private void +keymacro_clear(EditLine *el, el_action_t *map, const wchar_t *in) +{ + if (*in > N_KEYS) /* can't be in the map */ + return; + if ((map[(unsigned char)*in] == ED_SEQUENCE_LEAD_IN) && + ((map == el->el_map.key && + el->el_map.alt[(unsigned char)*in] != ED_SEQUENCE_LEAD_IN) || + (map == el->el_map.alt && + el->el_map.key[(unsigned char)*in] != ED_SEQUENCE_LEAD_IN))) + (void) keymacro_delete(el, in); +} + + +/* keymacro_delete(): + * Delete the key and all longer keys staring with key, if + * they exists. + */ +libedit_private int +keymacro_delete(EditLine *el, const wchar_t *key) +{ + + if (key[0] == '\0') { + (void) fprintf(el->el_errfile, + "keymacro_delete: Null extended-key not allowed.\n"); + return -1; + } + if (el->el_keymacro.map == NULL) + return 0; + + (void) node__delete(el, &el->el_keymacro.map, key); + return 0; +} + + +/* keymacro_print(): + * Print the binding associated with key key. + * Print entire el->el_keymacro.map if null + */ +libedit_private void +keymacro_print(EditLine *el, const wchar_t *key) +{ + + /* do nothing if el->el_keymacro.map is empty and null key specified */ + if (el->el_keymacro.map == NULL && *key == 0) + return; + + el->el_keymacro.buf[0] = '"'; + if (node_lookup(el, key, el->el_keymacro.map, (size_t)1) <= -1) + /* key is not bound */ + (void) fprintf(el->el_errfile, "Unbound extended key \"%ls" + "\"\n", key); + return; +} + + +/* node_trav(): + * recursively traverses node in tree until match or mismatch is + * found. May read in more characters. + */ +static int +node_trav(EditLine *el, keymacro_node_t *ptr, wchar_t *ch, + keymacro_value_t *val) +{ + + if (ptr->ch == *ch) { + /* match found */ + if (ptr->next) { + /* key not complete so get next char */ + if (el_wgetc(el, ch) != 1) + return XK_NOD; + return node_trav(el, ptr->next, ch, val); + } else { + *val = ptr->val; + if (ptr->type != XK_CMD) + *ch = '\0'; + return ptr->type; + } + } else { + /* no match found here */ + if (ptr->sibling) { + /* try next sibling */ + return node_trav(el, ptr->sibling, ch, val); + } else { + /* no next sibling -- mismatch */ + val->str = NULL; + return XK_STR; + } + } +} + + +/* node__try(): + * Find a node that matches *str or allocate a new one + */ +static int +node__try(EditLine *el, keymacro_node_t *ptr, const wchar_t *str, + keymacro_value_t *val, int ntype) +{ + + if (ptr->ch != *str) { + keymacro_node_t *xm; + + for (xm = ptr; xm->sibling != NULL; xm = xm->sibling) + if (xm->sibling->ch == *str) + break; + if (xm->sibling == NULL) + xm->sibling = node__get(*str); /* setup new node */ + ptr = xm->sibling; + } + if (*++str == '\0') { + /* we're there */ + if (ptr->next != NULL) { + node__put(el, ptr->next); + /* lose longer keys with this prefix */ + ptr->next = NULL; + } + switch (ptr->type) { + case XK_CMD: + case XK_NOD: + break; + case XK_STR: + if (ptr->val.str) + el_free(ptr->val.str); + break; + default: + EL_ABORT((el->el_errfile, "Bad XK_ type %d\n", + ptr->type)); + break; + } + + switch (ptr->type = ntype) { + case XK_CMD: + ptr->val = *val; + break; + case XK_STR: + if ((ptr->val.str = wcsdup(val->str)) == NULL) + return -1; + break; + default: + EL_ABORT((el->el_errfile, "Bad XK_ type %d\n", ntype)); + break; + } + } else { + /* still more chars to go */ + if (ptr->next == NULL) + ptr->next = node__get(*str); /* setup new node */ + (void) node__try(el, ptr->next, str, val, ntype); + } + return 0; +} + + +/* node__delete(): + * Delete node that matches str + */ +static int +node__delete(EditLine *el, keymacro_node_t **inptr, const wchar_t *str) +{ + keymacro_node_t *ptr; + keymacro_node_t *prev_ptr = NULL; + + ptr = *inptr; + + if (ptr->ch != *str) { + keymacro_node_t *xm; + + for (xm = ptr; xm->sibling != NULL; xm = xm->sibling) + if (xm->sibling->ch == *str) + break; + if (xm->sibling == NULL) + return 0; + prev_ptr = xm; + ptr = xm->sibling; + } + if (*++str == '\0') { + /* we're there */ + if (prev_ptr == NULL) + *inptr = ptr->sibling; + else + prev_ptr->sibling = ptr->sibling; + ptr->sibling = NULL; + node__put(el, ptr); + return 1; + } else if (ptr->next != NULL && + node__delete(el, &ptr->next, str) == 1) { + if (ptr->next != NULL) + return 0; + if (prev_ptr == NULL) + *inptr = ptr->sibling; + else + prev_ptr->sibling = ptr->sibling; + ptr->sibling = NULL; + node__put(el, ptr); + return 1; + } else { + return 0; + } +} + + +/* node__put(): + * Puts a tree of nodes onto free list using free(3). + */ +static void +node__put(EditLine *el, keymacro_node_t *ptr) +{ + if (ptr == NULL) + return; + + if (ptr->next != NULL) { + node__put(el, ptr->next); + ptr->next = NULL; + } + node__put(el, ptr->sibling); + + switch (ptr->type) { + case XK_CMD: + case XK_NOD: + break; + case XK_STR: + if (ptr->val.str != NULL) + el_free(ptr->val.str); + break; + default: + EL_ABORT((el->el_errfile, "Bad XK_ type %d\n", ptr->type)); + break; + } + el_free(ptr); +} + + +/* node__get(): + * Returns pointer to a keymacro_node_t for ch. + */ +static keymacro_node_t * +node__get(wint_t ch) +{ + keymacro_node_t *ptr; + + ptr = el_malloc(sizeof(*ptr)); + if (ptr == NULL) + return NULL; + ptr->ch = ch; + ptr->type = XK_NOD; + ptr->val.str = NULL; + ptr->next = NULL; + ptr->sibling = NULL; + return ptr; +} + +static void +node__free(keymacro_node_t *k) +{ + if (k == NULL) + return; + node__free(k->sibling); + node__free(k->next); + el_free(k); +} + +/* node_lookup(): + * look for the str starting at node ptr. + * Print if last node + */ +static int +node_lookup(EditLine *el, const wchar_t *str, keymacro_node_t *ptr, + size_t cnt) +{ + ssize_t used; + + if (ptr == NULL) + return -1; /* cannot have null ptr */ + + if (!str || *str == 0) { + /* no more chars in str. node_enum from here. */ + (void) node_enum(el, ptr, cnt); + return 0; + } else { + /* If match put this char into el->el_keymacro.buf. Recurse */ + if (ptr->ch == *str) { + /* match found */ + used = ct_visual_char(el->el_keymacro.buf + cnt, + KEY_BUFSIZ - cnt, ptr->ch); + if (used == -1) + return -1; /* ran out of buffer space */ + if (ptr->next != NULL) + /* not yet at leaf */ + return (node_lookup(el, str + 1, ptr->next, + (size_t)used + cnt)); + else { + /* next node is null so key should be complete */ + if (str[1] == 0) { + size_t px = cnt + (size_t)used; + el->el_keymacro.buf[px] = '"'; + el->el_keymacro.buf[px + 1] = '\0'; + keymacro_kprint(el, el->el_keymacro.buf, + &ptr->val, ptr->type); + return 0; + } else + return -1; + /* mismatch -- str still has chars */ + } + } else { + /* no match found try sibling */ + if (ptr->sibling) + return (node_lookup(el, str, ptr->sibling, + cnt)); + else + return -1; + } + } +} + + +/* node_enum(): + * Traverse the node printing the characters it is bound in buffer + */ +static int +node_enum(EditLine *el, keymacro_node_t *ptr, size_t cnt) +{ + ssize_t used; + + if (cnt >= KEY_BUFSIZ - 5) { /* buffer too small */ + el->el_keymacro.buf[++cnt] = '"'; + el->el_keymacro.buf[++cnt] = '\0'; + (void) fprintf(el->el_errfile, + "Some extended keys too long for internal print buffer"); + (void) fprintf(el->el_errfile, " \"%ls...\"\n", + el->el_keymacro.buf); + return 0; + } + if (ptr == NULL) { +#ifdef DEBUG_EDIT + (void) fprintf(el->el_errfile, + "node_enum: BUG!! Null ptr passed\n!"); +#endif + return -1; + } + /* put this char at end of str */ + used = ct_visual_char(el->el_keymacro.buf + cnt, KEY_BUFSIZ - cnt, + ptr->ch); + if (ptr->next == NULL) { + /* print this key and function */ + el->el_keymacro.buf[cnt + (size_t)used ] = '"'; + el->el_keymacro.buf[cnt + (size_t)used + 1] = '\0'; + keymacro_kprint(el, el->el_keymacro.buf, &ptr->val, ptr->type); + } else + (void) node_enum(el, ptr->next, cnt + (size_t)used); + + /* go to sibling if there is one */ + if (ptr->sibling) + (void) node_enum(el, ptr->sibling, cnt); + return 0; +} + + +/* keymacro_kprint(): + * Print the specified key and its associated + * function specified by val + */ +libedit_private void +keymacro_kprint(EditLine *el, const wchar_t *key, keymacro_value_t *val, + int ntype) +{ + el_bindings_t *fp; + char unparsbuf[EL_BUFSIZ]; + static const char fmt[] = "%-15s-> %s\n"; + + if (val != NULL) + switch (ntype) { + case XK_STR: + (void) keymacro__decode_str(val->str, unparsbuf, + sizeof(unparsbuf), + ntype == XK_STR ? "\"\"" : "[]"); + (void) fprintf(el->el_outfile, fmt, + ct_encode_string(key, &el->el_scratch), unparsbuf); + break; + case XK_CMD: + for (fp = el->el_map.help; fp->name; fp++) + if (val->cmd == fp->func) { + wcstombs(unparsbuf, fp->name, sizeof(unparsbuf)); + unparsbuf[sizeof(unparsbuf) -1] = '\0'; + (void) fprintf(el->el_outfile, fmt, + ct_encode_string(key, &el->el_scratch), unparsbuf); + break; + } +#ifdef DEBUG_KEY + if (fp->name == NULL) + (void) fprintf(el->el_outfile, + "BUG! Command not found.\n"); +#endif + + break; + default: + EL_ABORT((el->el_errfile, "Bad XK_ type %d\n", ntype)); + break; + } + else + (void) fprintf(el->el_outfile, fmt, ct_encode_string(key, + &el->el_scratch), "no input"); +} + + +#define ADDC(c) \ + if (b < eb) \ + *b++ = c; \ + else \ + b++ +/* keymacro__decode_str(): + * Make a printable version of the ey + */ +libedit_private size_t +keymacro__decode_str(const wchar_t *str, char *buf, size_t len, + const char *sep) +{ + char *b = buf, *eb = b + len; + const wchar_t *p; + + b = buf; + if (sep[0] != '\0') { + ADDC(sep[0]); + } + if (*str == '\0') { + ADDC('^'); + ADDC('@'); + goto add_endsep; + } + for (p = str; *p != 0; p++) { + wchar_t dbuf[VISUAL_WIDTH_MAX]; + wchar_t *p2 = dbuf; + ssize_t l = ct_visual_char(dbuf, VISUAL_WIDTH_MAX, *p); + while (l-- > 0) { + ssize_t n = ct_encode_char(b, (size_t)(eb - b), *p2++); + if (n == -1) /* ran out of space */ + goto add_endsep; + else + b += n; + } + } +add_endsep: + if (sep[0] != '\0' && sep[1] != '\0') { + ADDC(sep[1]); + } + ADDC('\0'); + if ((size_t)(b - buf) >= len) + buf[len - 1] = '\0'; + return (size_t)(b - buf); +} diff --git a/bin/catsh/libedit/keymacro.h b/bin/catsh/libedit/keymacro.h new file mode 100644 index 00000000..0653bbe3 --- /dev/null +++ b/bin/catsh/libedit/keymacro.h @@ -0,0 +1,76 @@ +/* $NetBSD: keymacro.h,v 1.6 2016/05/09 21:46:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)key.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.keymacro.h: Key macro header + */ +#ifndef _h_el_keymacro +#define _h_el_keymacro + +typedef union keymacro_value_t { + el_action_t cmd; /* If it is a command the # */ + wchar_t *str; /* If it is a string... */ +} keymacro_value_t; + +typedef struct keymacro_node_t keymacro_node_t; + +typedef struct el_keymacro_t { + wchar_t *buf; /* Key print buffer */ + keymacro_node_t *map; /* Key map */ + keymacro_value_t val; /* Local conversion buffer */ +} el_keymacro_t; + +#define XK_CMD 0 +#define XK_STR 1 +#define XK_NOD 2 + +libedit_private int keymacro_init(EditLine *); +libedit_private void keymacro_end(EditLine *); +libedit_private keymacro_value_t *keymacro_map_cmd(EditLine *, int); +libedit_private keymacro_value_t *keymacro_map_str(EditLine *, wchar_t *); +libedit_private void keymacro_reset(EditLine *); +libedit_private int keymacro_get(EditLine *, wchar_t *, keymacro_value_t *); +libedit_private void keymacro_add(EditLine *, const wchar_t *, + keymacro_value_t *, int); +libedit_private void keymacro_clear(EditLine *, el_action_t *, const wchar_t *); +libedit_private int keymacro_delete(EditLine *, const wchar_t *); +libedit_private void keymacro_print(EditLine *, const wchar_t *); +libedit_private void keymacro_kprint(EditLine *, const wchar_t *, + keymacro_value_t *, int); +libedit_private size_t keymacro__decode_str(const wchar_t *, char *, size_t, + const char *); + +#endif /* _h_el_keymacro */ diff --git a/bin/catsh/libedit/literal.c b/bin/catsh/libedit/literal.c new file mode 100644 index 00000000..816dc3ce --- /dev/null +++ b/bin/catsh/libedit/literal.c @@ -0,0 +1,136 @@ +/* $NetBSD: literal.c,v 1.3.4.2 2017/07/23 14:41:26 snj Exp $ */ + +/*- + * Copyright (c) 2017 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +__RCSID("$NetBSD: literal.c,v 1.3.4.2 2017/07/23 14:41:26 snj Exp $"); +#endif /* not lint && not SCCSID */ + +/* + * literal.c: Literal sequences handling. + */ +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "el.h" + +libedit_private void +literal_init(EditLine *el) +{ + el_literal_t *l = &el->el_literal; + + memset(l, 0, sizeof(*l)); +} + +libedit_private void +literal_end(EditLine *el) +{ + literal_clear(el); +} + +libedit_private void +literal_clear(EditLine *el) +{ + el_literal_t *l = &el->el_literal; + size_t i; + + if (l->l_len == 0) + return; + + for (i = 0; i < l->l_idx; i++) + el_free(l->l_buf[i]); + el_free(l->l_buf); + l->l_buf = NULL; + l->l_len = 0; + l->l_idx = 0; +} + +libedit_private wint_t +literal_add(EditLine *el, const wchar_t *buf, const wchar_t *end, int *wp) +{ + el_literal_t *l = &el->el_literal; + size_t i, len; + ssize_t w, n; + char *b; + + w = wcwidth(end[1]); /* column width of the visible char */ + *wp = (int)w; + + if (w <= 0) /* we require something to be printed */ + return 0; + + len = (size_t)(end - buf); + for (w = 0, i = 0; i < len; i++) + w += ct_enc_width(buf[i]); + w += ct_enc_width(end[1]); + + b = el_malloc((size_t)(w + 1)); + if (b == NULL) + return 0; + + for (n = 0, i = 0; i < len; i++) + n += ct_encode_char(b + n, w - n, buf[i]); + n += ct_encode_char(b + n, w - n, end[1]); + b[n] = '\0'; + + /* + * Then save this literal string in the list of such strings, + * and return a "magic character" to put into the terminal buffer. + * When that magic char is 'printed' the saved string (which includes + * the char that belongs in that position) gets sent instead. + */ + if (l->l_idx == l->l_len) { + char **bp; + + l->l_len += 4; + bp = el_realloc(l->l_buf, sizeof(*l->l_buf) * l->l_len); + if (bp == NULL) { + free(b); + l->l_len -= 4; + return 0; + } + l->l_buf = bp; + } + l->l_buf[l->l_idx++] = b; + return EL_LITERAL | (wint_t)(l->l_idx - 1); +} + +libedit_private const char * +literal_get(EditLine *el, wint_t idx) +{ + el_literal_t *l = &el->el_literal; + + assert(idx & EL_LITERAL); + idx &= ~EL_LITERAL; + assert(l->l_idx > (size_t)idx); + return l->l_buf[idx]; +} diff --git a/bin/catsh/libedit/literal.h b/bin/catsh/libedit/literal.h new file mode 100644 index 00000000..d1929505 --- /dev/null +++ b/bin/catsh/libedit/literal.h @@ -0,0 +1,53 @@ +/* $NetBSD: literal.h,v 1.2.4.2 2017/07/23 14:41:26 snj Exp $ */ + +/*- + * Copyright (c) 2017 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * el.literal.h: Literal character + */ +#ifndef _h_el_literal +#define _h_el_literal + +#define EL_LITERAL ((wint_t)0x80000000) + +typedef struct el_literal_t { + char **l_buf; /* array of buffers */ + size_t l_idx; /* max in use */ + size_t l_len; /* max allocated */ +} el_literal_t; + +libedit_private void literal_init(EditLine *); +libedit_private void literal_end(EditLine *); +libedit_private void literal_clear(EditLine *); +libedit_private wint_t literal_add(EditLine *, const wchar_t *, + const wchar_t *, int *); +libedit_private const char *literal_get(EditLine *, wint_t); + +#endif /* _h_el_literal */ diff --git a/bin/catsh/libedit/makelist b/bin/catsh/libedit/makelist new file mode 100644 index 00000000..c8f92765 --- /dev/null +++ b/bin/catsh/libedit/makelist @@ -0,0 +1,174 @@ +#!/bin/sh - +# $NetBSD: makelist,v 1.29 2016/05/09 21:46:56 christos Exp $ +# +# Copyright (c) 1992, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Christos Zoulas of Cornell University. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# @(#)makelist 5.3 (Berkeley) 6/4/93 + +# makelist.sh: Automatically generate header files... + +AWK=awk +USAGE="Usage: $0 -h|-fc|-fh|-bh <filenames>" + +if [ "x$1" = "x" ] +then + echo $USAGE 1>&2 + exit 1 +fi + +FLAG="$1" +shift + +FILES="$@" + +case $FLAG in + +-h) + set - `echo $FILES | sed -e 's/\\./_/g'` + hdr="_h_`basename $1`" + cat $FILES | $AWK ' + BEGIN { + printf("/* Automatically generated file, do not edit */\n"); + printf("#ifndef %s\n#define %s\n", "'$hdr'", "'$hdr'"); + } + /\(\):/ { + pr = substr($2, 1, 2); + if (pr == "vi" || pr == "em" || pr == "ed") { + name = substr($2, 1, length($2) - 3); +# +# XXX: need a space between name and prototype so that -fc and -fh +# parsing is much easier +# + printf("libedit_private el_action_t\t%s (EditLine *, wint_t);\n", + name); + } + } + END { + printf("#endif /* %s */\n", "'$hdr'"); + }' + ;; + +# generate help.h from various .c files +# +-bh) + cat $FILES | $AWK ' + BEGIN { + printf("/* Automatically generated file, do not edit */\n"); + printf("static const struct el_bindings_t el_func_help[] = {\n"); + low = "abcdefghijklmnopqrstuvwxyz_"; + high = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_"; + for (i = 1; i <= length(low); i++) + tr[substr(low, i, 1)] = substr(high, i, 1); + } + /\(\):/ { + pr = substr($2, 1, 2); + if (pr == "vi" || pr == "em" || pr == "ed") { + name = substr($2, 1, length($2) - 3); + uname = ""; + fname = ""; + for (i = 1; i <= length(name); i++) { + s = substr(name, i, 1); + uname = uname tr[s]; + if (s == "_") + s = "-"; + fname = fname s; + } + + printf(" { %-30.30s %-30.30s\n","L\"" fname "\",", uname ","); + ok = 1; + } + } + /^ \*/ { + if (ok) { + printf(" L\""); + for (i = 2; i < NF; i++) + printf("%s ", $i); + printf("%s\" },\n", $i); + ok = 0; + } + } + END { + printf("};\n"); + }' + ;; + +# generate fcns.h from various .h files +# +-fh) + cat $FILES | $AWK '/el_action_t/ { print $3 }' | \ + sort | tr '[:lower:]' '[:upper:]' | $AWK ' + BEGIN { + printf("/* Automatically generated file, do not edit */\n"); + count = 0; + } + { + printf("#define\t%-30.30s\t%3d\n", $1, count++); + } + END { + printf("#define\t%-30.30s\t%3d\n", "EL_NUM_FCNS", count); + }' + ;; + +# generate func.h from various .h files +# +-fc) + cat $FILES | $AWK '/el_action_t/ { print $3 }' | sort | $AWK ' + BEGIN { + printf("/* Automatically generated file, do not edit */\n"); + printf("static const el_func_t el_func[] = {"); + maxlen = 80; + needn = 1; + len = 0; + } + { + clen = 25 + 2; + len += clen; + if (len >= maxlen) + needn = 1; + if (needn) { + printf("\n "); + needn = 0; + len = 4 + clen; + } + s = $1 ","; + printf("%-26.26s ", s); + } + END { + printf("\n};\n"); + }' + ;; + +*) + echo $USAGE 1>&2 + exit 1 + ;; + +esac diff --git a/bin/catsh/libedit/map.c b/bin/catsh/libedit/map.c new file mode 100644 index 00000000..f72d2724 --- /dev/null +++ b/bin/catsh/libedit/map.c @@ -0,0 +1,1427 @@ +/* $NetBSD: map.c,v 1.51 2016/05/09 21:46:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)map.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: map.c,v 1.51 2016/05/09 21:46:56 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * map.c: Editor function definitions + */ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include "el.h" +#include "common.h" +#include "emacs.h" +#include "vi.h" +#include "fcns.h" +#include "func.h" +#include "help.h" +#include "parse.h" + +static void map_print_key(EditLine *, el_action_t *, const wchar_t *); +static void map_print_some_keys(EditLine *, el_action_t *, wint_t, wint_t); +static void map_print_all_keys(EditLine *); +static void map_init_nls(EditLine *); +static void map_init_meta(EditLine *); + +/* keymap tables ; should be N_KEYS*sizeof(KEYCMD) bytes long */ + + +static const el_action_t el_map_emacs[] = { + /* 0 */ EM_SET_MARK, /* ^@ */ + /* 1 */ ED_MOVE_TO_BEG, /* ^A */ + /* 2 */ ED_PREV_CHAR, /* ^B */ + /* 3 */ ED_IGNORE, /* ^C */ + /* 4 */ EM_DELETE_OR_LIST, /* ^D */ + /* 5 */ ED_MOVE_TO_END, /* ^E */ + /* 6 */ ED_NEXT_CHAR, /* ^F */ + /* 7 */ ED_UNASSIGNED, /* ^G */ + /* 8 */ EM_DELETE_PREV_CHAR, /* ^H */ + /* 9 */ ED_UNASSIGNED, /* ^I */ + /* 10 */ ED_NEWLINE, /* ^J */ + /* 11 */ ED_KILL_LINE, /* ^K */ + /* 12 */ ED_CLEAR_SCREEN, /* ^L */ + /* 13 */ ED_NEWLINE, /* ^M */ + /* 14 */ ED_NEXT_HISTORY, /* ^N */ + /* 15 */ ED_IGNORE, /* ^O */ + /* 16 */ ED_PREV_HISTORY, /* ^P */ + /* 17 */ ED_IGNORE, /* ^Q */ + /* 18 */ ED_REDISPLAY, /* ^R */ + /* 19 */ ED_IGNORE, /* ^S */ + /* 20 */ ED_TRANSPOSE_CHARS, /* ^T */ + /* 21 */ EM_KILL_LINE, /* ^U */ + /* 22 */ ED_QUOTED_INSERT, /* ^V */ + /* 23 */ EM_KILL_REGION, /* ^W */ + /* 24 */ ED_SEQUENCE_LEAD_IN, /* ^X */ + /* 25 */ EM_YANK, /* ^Y */ + /* 26 */ ED_IGNORE, /* ^Z */ + /* 27 */ EM_META_NEXT, /* ^[ */ + /* 28 */ ED_IGNORE, /* ^\ */ + /* 29 */ ED_IGNORE, /* ^] */ + /* 30 */ ED_UNASSIGNED, /* ^^ */ + /* 31 */ ED_UNASSIGNED, /* ^_ */ + /* 32 */ ED_INSERT, /* SPACE */ + /* 33 */ ED_INSERT, /* ! */ + /* 34 */ ED_INSERT, /* " */ + /* 35 */ ED_INSERT, /* # */ + /* 36 */ ED_INSERT, /* $ */ + /* 37 */ ED_INSERT, /* % */ + /* 38 */ ED_INSERT, /* & */ + /* 39 */ ED_INSERT, /* ' */ + /* 40 */ ED_INSERT, /* ( */ + /* 41 */ ED_INSERT, /* ) */ + /* 42 */ ED_INSERT, /* * */ + /* 43 */ ED_INSERT, /* + */ + /* 44 */ ED_INSERT, /* , */ + /* 45 */ ED_INSERT, /* - */ + /* 46 */ ED_INSERT, /* . */ + /* 47 */ ED_INSERT, /* / */ + /* 48 */ ED_DIGIT, /* 0 */ + /* 49 */ ED_DIGIT, /* 1 */ + /* 50 */ ED_DIGIT, /* 2 */ + /* 51 */ ED_DIGIT, /* 3 */ + /* 52 */ ED_DIGIT, /* 4 */ + /* 53 */ ED_DIGIT, /* 5 */ + /* 54 */ ED_DIGIT, /* 6 */ + /* 55 */ ED_DIGIT, /* 7 */ + /* 56 */ ED_DIGIT, /* 8 */ + /* 57 */ ED_DIGIT, /* 9 */ + /* 58 */ ED_INSERT, /* : */ + /* 59 */ ED_INSERT, /* ; */ + /* 60 */ ED_INSERT, /* < */ + /* 61 */ ED_INSERT, /* = */ + /* 62 */ ED_INSERT, /* > */ + /* 63 */ ED_INSERT, /* ? */ + /* 64 */ ED_INSERT, /* @ */ + /* 65 */ ED_INSERT, /* A */ + /* 66 */ ED_INSERT, /* B */ + /* 67 */ ED_INSERT, /* C */ + /* 68 */ ED_INSERT, /* D */ + /* 69 */ ED_INSERT, /* E */ + /* 70 */ ED_INSERT, /* F */ + /* 71 */ ED_INSERT, /* G */ + /* 72 */ ED_INSERT, /* H */ + /* 73 */ ED_INSERT, /* I */ + /* 74 */ ED_INSERT, /* J */ + /* 75 */ ED_INSERT, /* K */ + /* 76 */ ED_INSERT, /* L */ + /* 77 */ ED_INSERT, /* M */ + /* 78 */ ED_INSERT, /* N */ + /* 79 */ ED_INSERT, /* O */ + /* 80 */ ED_INSERT, /* P */ + /* 81 */ ED_INSERT, /* Q */ + /* 82 */ ED_INSERT, /* R */ + /* 83 */ ED_INSERT, /* S */ + /* 84 */ ED_INSERT, /* T */ + /* 85 */ ED_INSERT, /* U */ + /* 86 */ ED_INSERT, /* V */ + /* 87 */ ED_INSERT, /* W */ + /* 88 */ ED_INSERT, /* X */ + /* 89 */ ED_INSERT, /* Y */ + /* 90 */ ED_INSERT, /* Z */ + /* 91 */ ED_INSERT, /* [ */ + /* 92 */ ED_INSERT, /* \ */ + /* 93 */ ED_INSERT, /* ] */ + /* 94 */ ED_INSERT, /* ^ */ + /* 95 */ ED_INSERT, /* _ */ + /* 96 */ ED_INSERT, /* ` */ + /* 97 */ ED_INSERT, /* a */ + /* 98 */ ED_INSERT, /* b */ + /* 99 */ ED_INSERT, /* c */ + /* 100 */ ED_INSERT, /* d */ + /* 101 */ ED_INSERT, /* e */ + /* 102 */ ED_INSERT, /* f */ + /* 103 */ ED_INSERT, /* g */ + /* 104 */ ED_INSERT, /* h */ + /* 105 */ ED_INSERT, /* i */ + /* 106 */ ED_INSERT, /* j */ + /* 107 */ ED_INSERT, /* k */ + /* 108 */ ED_INSERT, /* l */ + /* 109 */ ED_INSERT, /* m */ + /* 110 */ ED_INSERT, /* n */ + /* 111 */ ED_INSERT, /* o */ + /* 112 */ ED_INSERT, /* p */ + /* 113 */ ED_INSERT, /* q */ + /* 114 */ ED_INSERT, /* r */ + /* 115 */ ED_INSERT, /* s */ + /* 116 */ ED_INSERT, /* t */ + /* 117 */ ED_INSERT, /* u */ + /* 118 */ ED_INSERT, /* v */ + /* 119 */ ED_INSERT, /* w */ + /* 120 */ ED_INSERT, /* x */ + /* 121 */ ED_INSERT, /* y */ + /* 122 */ ED_INSERT, /* z */ + /* 123 */ ED_INSERT, /* { */ + /* 124 */ ED_INSERT, /* | */ + /* 125 */ ED_INSERT, /* } */ + /* 126 */ ED_INSERT, /* ~ */ + /* 127 */ EM_DELETE_PREV_CHAR, /* ^? */ + /* 128 */ ED_UNASSIGNED, /* M-^@ */ + /* 129 */ ED_UNASSIGNED, /* M-^A */ + /* 130 */ ED_UNASSIGNED, /* M-^B */ + /* 131 */ ED_UNASSIGNED, /* M-^C */ + /* 132 */ ED_UNASSIGNED, /* M-^D */ + /* 133 */ ED_UNASSIGNED, /* M-^E */ + /* 134 */ ED_UNASSIGNED, /* M-^F */ + /* 135 */ ED_UNASSIGNED, /* M-^G */ + /* 136 */ ED_DELETE_PREV_WORD, /* M-^H */ + /* 137 */ ED_UNASSIGNED, /* M-^I */ + /* 138 */ ED_UNASSIGNED, /* M-^J */ + /* 139 */ ED_UNASSIGNED, /* M-^K */ + /* 140 */ ED_CLEAR_SCREEN, /* M-^L */ + /* 141 */ ED_UNASSIGNED, /* M-^M */ + /* 142 */ ED_UNASSIGNED, /* M-^N */ + /* 143 */ ED_UNASSIGNED, /* M-^O */ + /* 144 */ ED_UNASSIGNED, /* M-^P */ + /* 145 */ ED_UNASSIGNED, /* M-^Q */ + /* 146 */ ED_UNASSIGNED, /* M-^R */ + /* 147 */ ED_UNASSIGNED, /* M-^S */ + /* 148 */ ED_UNASSIGNED, /* M-^T */ + /* 149 */ ED_UNASSIGNED, /* M-^U */ + /* 150 */ ED_UNASSIGNED, /* M-^V */ + /* 151 */ ED_UNASSIGNED, /* M-^W */ + /* 152 */ ED_UNASSIGNED, /* M-^X */ + /* 153 */ ED_UNASSIGNED, /* M-^Y */ + /* 154 */ ED_UNASSIGNED, /* M-^Z */ + /* 155 */ ED_UNASSIGNED, /* M-^[ */ + /* 156 */ ED_UNASSIGNED, /* M-^\ */ + /* 157 */ ED_UNASSIGNED, /* M-^] */ + /* 158 */ ED_UNASSIGNED, /* M-^^ */ + /* 159 */ EM_COPY_PREV_WORD, /* M-^_ */ + /* 160 */ ED_UNASSIGNED, /* M-SPACE */ + /* 161 */ ED_UNASSIGNED, /* M-! */ + /* 162 */ ED_UNASSIGNED, /* M-" */ + /* 163 */ ED_UNASSIGNED, /* M-# */ + /* 164 */ ED_UNASSIGNED, /* M-$ */ + /* 165 */ ED_UNASSIGNED, /* M-% */ + /* 166 */ ED_UNASSIGNED, /* M-& */ + /* 167 */ ED_UNASSIGNED, /* M-' */ + /* 168 */ ED_UNASSIGNED, /* M-( */ + /* 169 */ ED_UNASSIGNED, /* M-) */ + /* 170 */ ED_UNASSIGNED, /* M-* */ + /* 171 */ ED_UNASSIGNED, /* M-+ */ + /* 172 */ ED_UNASSIGNED, /* M-, */ + /* 173 */ ED_UNASSIGNED, /* M-- */ + /* 174 */ ED_UNASSIGNED, /* M-. */ + /* 175 */ ED_UNASSIGNED, /* M-/ */ + /* 176 */ ED_ARGUMENT_DIGIT, /* M-0 */ + /* 177 */ ED_ARGUMENT_DIGIT, /* M-1 */ + /* 178 */ ED_ARGUMENT_DIGIT, /* M-2 */ + /* 179 */ ED_ARGUMENT_DIGIT, /* M-3 */ + /* 180 */ ED_ARGUMENT_DIGIT, /* M-4 */ + /* 181 */ ED_ARGUMENT_DIGIT, /* M-5 */ + /* 182 */ ED_ARGUMENT_DIGIT, /* M-6 */ + /* 183 */ ED_ARGUMENT_DIGIT, /* M-7 */ + /* 184 */ ED_ARGUMENT_DIGIT, /* M-8 */ + /* 185 */ ED_ARGUMENT_DIGIT, /* M-9 */ + /* 186 */ ED_UNASSIGNED, /* M-: */ + /* 187 */ ED_UNASSIGNED, /* M-; */ + /* 188 */ ED_UNASSIGNED, /* M-< */ + /* 189 */ ED_UNASSIGNED, /* M-= */ + /* 190 */ ED_UNASSIGNED, /* M-> */ + /* 191 */ ED_UNASSIGNED, /* M-? */ + /* 192 */ ED_UNASSIGNED, /* M-@ */ + /* 193 */ ED_UNASSIGNED, /* M-A */ + /* 194 */ ED_PREV_WORD, /* M-B */ + /* 195 */ EM_CAPITOL_CASE, /* M-C */ + /* 196 */ EM_DELETE_NEXT_WORD, /* M-D */ + /* 197 */ ED_UNASSIGNED, /* M-E */ + /* 198 */ EM_NEXT_WORD, /* M-F */ + /* 199 */ ED_UNASSIGNED, /* M-G */ + /* 200 */ ED_UNASSIGNED, /* M-H */ + /* 201 */ ED_UNASSIGNED, /* M-I */ + /* 202 */ ED_UNASSIGNED, /* M-J */ + /* 203 */ ED_UNASSIGNED, /* M-K */ + /* 204 */ EM_LOWER_CASE, /* M-L */ + /* 205 */ ED_UNASSIGNED, /* M-M */ + /* 206 */ ED_SEARCH_NEXT_HISTORY, /* M-N */ + /* 207 */ ED_SEQUENCE_LEAD_IN, /* M-O */ + /* 208 */ ED_SEARCH_PREV_HISTORY, /* M-P */ + /* 209 */ ED_UNASSIGNED, /* M-Q */ + /* 210 */ ED_UNASSIGNED, /* M-R */ + /* 211 */ ED_UNASSIGNED, /* M-S */ + /* 212 */ ED_UNASSIGNED, /* M-T */ + /* 213 */ EM_UPPER_CASE, /* M-U */ + /* 214 */ ED_UNASSIGNED, /* M-V */ + /* 215 */ EM_COPY_REGION, /* M-W */ + /* 216 */ ED_COMMAND, /* M-X */ + /* 217 */ ED_UNASSIGNED, /* M-Y */ + /* 218 */ ED_UNASSIGNED, /* M-Z */ + /* 219 */ ED_SEQUENCE_LEAD_IN, /* M-[ */ + /* 220 */ ED_UNASSIGNED, /* M-\ */ + /* 221 */ ED_UNASSIGNED, /* M-] */ + /* 222 */ ED_UNASSIGNED, /* M-^ */ + /* 223 */ ED_UNASSIGNED, /* M-_ */ + /* 223 */ ED_UNASSIGNED, /* M-` */ + /* 224 */ ED_UNASSIGNED, /* M-a */ + /* 225 */ ED_PREV_WORD, /* M-b */ + /* 226 */ EM_CAPITOL_CASE, /* M-c */ + /* 227 */ EM_DELETE_NEXT_WORD, /* M-d */ + /* 228 */ ED_UNASSIGNED, /* M-e */ + /* 229 */ EM_NEXT_WORD, /* M-f */ + /* 230 */ ED_UNASSIGNED, /* M-g */ + /* 231 */ ED_UNASSIGNED, /* M-h */ + /* 232 */ ED_UNASSIGNED, /* M-i */ + /* 233 */ ED_UNASSIGNED, /* M-j */ + /* 234 */ ED_UNASSIGNED, /* M-k */ + /* 235 */ EM_LOWER_CASE, /* M-l */ + /* 236 */ ED_UNASSIGNED, /* M-m */ + /* 237 */ ED_SEARCH_NEXT_HISTORY, /* M-n */ + /* 238 */ ED_UNASSIGNED, /* M-o */ + /* 239 */ ED_SEARCH_PREV_HISTORY, /* M-p */ + /* 240 */ ED_UNASSIGNED, /* M-q */ + /* 241 */ ED_UNASSIGNED, /* M-r */ + /* 242 */ ED_UNASSIGNED, /* M-s */ + /* 243 */ ED_UNASSIGNED, /* M-t */ + /* 244 */ EM_UPPER_CASE, /* M-u */ + /* 245 */ ED_UNASSIGNED, /* M-v */ + /* 246 */ EM_COPY_REGION, /* M-w */ + /* 247 */ ED_COMMAND, /* M-x */ + /* 248 */ ED_UNASSIGNED, /* M-y */ + /* 249 */ ED_UNASSIGNED, /* M-z */ + /* 250 */ ED_UNASSIGNED, /* M-{ */ + /* 251 */ ED_UNASSIGNED, /* M-| */ + /* 252 */ ED_UNASSIGNED, /* M-} */ + /* 253 */ ED_UNASSIGNED, /* M-~ */ + /* 254 */ ED_DELETE_PREV_WORD /* M-^? */ + /* 255 */ +}; + + +/* + * keymap table for vi. Each index into above tbl; should be + * N_KEYS entries long. Vi mode uses a sticky-extend to do command mode: + * insert mode characters are in the normal keymap, and command mode + * in the extended keymap. + */ +static const el_action_t el_map_vi_insert[] = { +#ifdef KSHVI + /* 0 */ ED_UNASSIGNED, /* ^@ */ + /* 1 */ ED_INSERT, /* ^A */ + /* 2 */ ED_INSERT, /* ^B */ + /* 3 */ ED_INSERT, /* ^C */ + /* 4 */ VI_LIST_OR_EOF, /* ^D */ + /* 5 */ ED_INSERT, /* ^E */ + /* 6 */ ED_INSERT, /* ^F */ + /* 7 */ ED_INSERT, /* ^G */ + /* 8 */ VI_DELETE_PREV_CHAR, /* ^H */ /* BackSpace key */ + /* 9 */ ED_INSERT, /* ^I */ /* Tab Key */ + /* 10 */ ED_NEWLINE, /* ^J */ + /* 11 */ ED_INSERT, /* ^K */ + /* 12 */ ED_INSERT, /* ^L */ + /* 13 */ ED_NEWLINE, /* ^M */ + /* 14 */ ED_INSERT, /* ^N */ + /* 15 */ ED_INSERT, /* ^O */ + /* 16 */ ED_INSERT, /* ^P */ + /* 17 */ ED_IGNORE, /* ^Q */ + /* 18 */ ED_INSERT, /* ^R */ + /* 19 */ ED_IGNORE, /* ^S */ + /* 20 */ ED_INSERT, /* ^T */ + /* 21 */ VI_KILL_LINE_PREV, /* ^U */ + /* 22 */ ED_QUOTED_INSERT, /* ^V */ + /* 23 */ ED_DELETE_PREV_WORD, /* ^W */ + /* ED_DELETE_PREV_WORD: Only until strt edit pos */ + /* 24 */ ED_INSERT, /* ^X */ + /* 25 */ ED_INSERT, /* ^Y */ + /* 26 */ ED_INSERT, /* ^Z */ + /* 27 */ VI_COMMAND_MODE, /* ^[ */ /* [ Esc ] key */ + /* 28 */ ED_IGNORE, /* ^\ */ + /* 29 */ ED_INSERT, /* ^] */ + /* 30 */ ED_INSERT, /* ^^ */ + /* 31 */ ED_INSERT, /* ^_ */ +#else /* !KSHVI */ + /* + * NOTE: These mappings do NOT Correspond well + * to the KSH VI editing assignments. + * On the other and they are convenient and + * many people have have gotten used to them. + */ + /* 0 */ ED_UNASSIGNED, /* ^@ */ + /* 1 */ ED_MOVE_TO_BEG, /* ^A */ + /* 2 */ ED_PREV_CHAR, /* ^B */ + /* 3 */ ED_IGNORE, /* ^C */ + /* 4 */ VI_LIST_OR_EOF, /* ^D */ + /* 5 */ ED_MOVE_TO_END, /* ^E */ + /* 6 */ ED_NEXT_CHAR, /* ^F */ + /* 7 */ ED_UNASSIGNED, /* ^G */ + /* 8 */ VI_DELETE_PREV_CHAR, /* ^H */ /* BackSpace key */ + /* 9 */ ED_UNASSIGNED, /* ^I */ /* Tab Key */ + /* 10 */ ED_NEWLINE, /* ^J */ + /* 11 */ ED_KILL_LINE, /* ^K */ + /* 12 */ ED_CLEAR_SCREEN, /* ^L */ + /* 13 */ ED_NEWLINE, /* ^M */ + /* 14 */ ED_NEXT_HISTORY, /* ^N */ + /* 15 */ ED_IGNORE, /* ^O */ + /* 16 */ ED_PREV_HISTORY, /* ^P */ + /* 17 */ ED_IGNORE, /* ^Q */ + /* 18 */ ED_REDISPLAY, /* ^R */ + /* 19 */ ED_IGNORE, /* ^S */ + /* 20 */ ED_TRANSPOSE_CHARS, /* ^T */ + /* 21 */ VI_KILL_LINE_PREV, /* ^U */ + /* 22 */ ED_QUOTED_INSERT, /* ^V */ + /* 23 */ ED_DELETE_PREV_WORD, /* ^W */ + /* 24 */ ED_UNASSIGNED, /* ^X */ + /* 25 */ ED_IGNORE, /* ^Y */ + /* 26 */ ED_IGNORE, /* ^Z */ + /* 27 */ VI_COMMAND_MODE, /* ^[ */ + /* 28 */ ED_IGNORE, /* ^\ */ + /* 29 */ ED_UNASSIGNED, /* ^] */ + /* 30 */ ED_UNASSIGNED, /* ^^ */ + /* 31 */ ED_UNASSIGNED, /* ^_ */ +#endif /* KSHVI */ + /* 32 */ ED_INSERT, /* SPACE */ + /* 33 */ ED_INSERT, /* ! */ + /* 34 */ ED_INSERT, /* " */ + /* 35 */ ED_INSERT, /* # */ + /* 36 */ ED_INSERT, /* $ */ + /* 37 */ ED_INSERT, /* % */ + /* 38 */ ED_INSERT, /* & */ + /* 39 */ ED_INSERT, /* ' */ + /* 40 */ ED_INSERT, /* ( */ + /* 41 */ ED_INSERT, /* ) */ + /* 42 */ ED_INSERT, /* * */ + /* 43 */ ED_INSERT, /* + */ + /* 44 */ ED_INSERT, /* , */ + /* 45 */ ED_INSERT, /* - */ + /* 46 */ ED_INSERT, /* . */ + /* 47 */ ED_INSERT, /* / */ + /* 48 */ ED_INSERT, /* 0 */ + /* 49 */ ED_INSERT, /* 1 */ + /* 50 */ ED_INSERT, /* 2 */ + /* 51 */ ED_INSERT, /* 3 */ + /* 52 */ ED_INSERT, /* 4 */ + /* 53 */ ED_INSERT, /* 5 */ + /* 54 */ ED_INSERT, /* 6 */ + /* 55 */ ED_INSERT, /* 7 */ + /* 56 */ ED_INSERT, /* 8 */ + /* 57 */ ED_INSERT, /* 9 */ + /* 58 */ ED_INSERT, /* : */ + /* 59 */ ED_INSERT, /* ; */ + /* 60 */ ED_INSERT, /* < */ + /* 61 */ ED_INSERT, /* = */ + /* 62 */ ED_INSERT, /* > */ + /* 63 */ ED_INSERT, /* ? */ + /* 64 */ ED_INSERT, /* @ */ + /* 65 */ ED_INSERT, /* A */ + /* 66 */ ED_INSERT, /* B */ + /* 67 */ ED_INSERT, /* C */ + /* 68 */ ED_INSERT, /* D */ + /* 69 */ ED_INSERT, /* E */ + /* 70 */ ED_INSERT, /* F */ + /* 71 */ ED_INSERT, /* G */ + /* 72 */ ED_INSERT, /* H */ + /* 73 */ ED_INSERT, /* I */ + /* 74 */ ED_INSERT, /* J */ + /* 75 */ ED_INSERT, /* K */ + /* 76 */ ED_INSERT, /* L */ + /* 77 */ ED_INSERT, /* M */ + /* 78 */ ED_INSERT, /* N */ + /* 79 */ ED_INSERT, /* O */ + /* 80 */ ED_INSERT, /* P */ + /* 81 */ ED_INSERT, /* Q */ + /* 82 */ ED_INSERT, /* R */ + /* 83 */ ED_INSERT, /* S */ + /* 84 */ ED_INSERT, /* T */ + /* 85 */ ED_INSERT, /* U */ + /* 86 */ ED_INSERT, /* V */ + /* 87 */ ED_INSERT, /* W */ + /* 88 */ ED_INSERT, /* X */ + /* 89 */ ED_INSERT, /* Y */ + /* 90 */ ED_INSERT, /* Z */ + /* 91 */ ED_INSERT, /* [ */ + /* 92 */ ED_INSERT, /* \ */ + /* 93 */ ED_INSERT, /* ] */ + /* 94 */ ED_INSERT, /* ^ */ + /* 95 */ ED_INSERT, /* _ */ + /* 96 */ ED_INSERT, /* ` */ + /* 97 */ ED_INSERT, /* a */ + /* 98 */ ED_INSERT, /* b */ + /* 99 */ ED_INSERT, /* c */ + /* 100 */ ED_INSERT, /* d */ + /* 101 */ ED_INSERT, /* e */ + /* 102 */ ED_INSERT, /* f */ + /* 103 */ ED_INSERT, /* g */ + /* 104 */ ED_INSERT, /* h */ + /* 105 */ ED_INSERT, /* i */ + /* 106 */ ED_INSERT, /* j */ + /* 107 */ ED_INSERT, /* k */ + /* 108 */ ED_INSERT, /* l */ + /* 109 */ ED_INSERT, /* m */ + /* 110 */ ED_INSERT, /* n */ + /* 111 */ ED_INSERT, /* o */ + /* 112 */ ED_INSERT, /* p */ + /* 113 */ ED_INSERT, /* q */ + /* 114 */ ED_INSERT, /* r */ + /* 115 */ ED_INSERT, /* s */ + /* 116 */ ED_INSERT, /* t */ + /* 117 */ ED_INSERT, /* u */ + /* 118 */ ED_INSERT, /* v */ + /* 119 */ ED_INSERT, /* w */ + /* 120 */ ED_INSERT, /* x */ + /* 121 */ ED_INSERT, /* y */ + /* 122 */ ED_INSERT, /* z */ + /* 123 */ ED_INSERT, /* { */ + /* 124 */ ED_INSERT, /* | */ + /* 125 */ ED_INSERT, /* } */ + /* 126 */ ED_INSERT, /* ~ */ + /* 127 */ VI_DELETE_PREV_CHAR, /* ^? */ + /* 128 */ ED_INSERT, /* M-^@ */ + /* 129 */ ED_INSERT, /* M-^A */ + /* 130 */ ED_INSERT, /* M-^B */ + /* 131 */ ED_INSERT, /* M-^C */ + /* 132 */ ED_INSERT, /* M-^D */ + /* 133 */ ED_INSERT, /* M-^E */ + /* 134 */ ED_INSERT, /* M-^F */ + /* 135 */ ED_INSERT, /* M-^G */ + /* 136 */ ED_INSERT, /* M-^H */ + /* 137 */ ED_INSERT, /* M-^I */ + /* 138 */ ED_INSERT, /* M-^J */ + /* 139 */ ED_INSERT, /* M-^K */ + /* 140 */ ED_INSERT, /* M-^L */ + /* 141 */ ED_INSERT, /* M-^M */ + /* 142 */ ED_INSERT, /* M-^N */ + /* 143 */ ED_INSERT, /* M-^O */ + /* 144 */ ED_INSERT, /* M-^P */ + /* 145 */ ED_INSERT, /* M-^Q */ + /* 146 */ ED_INSERT, /* M-^R */ + /* 147 */ ED_INSERT, /* M-^S */ + /* 148 */ ED_INSERT, /* M-^T */ + /* 149 */ ED_INSERT, /* M-^U */ + /* 150 */ ED_INSERT, /* M-^V */ + /* 151 */ ED_INSERT, /* M-^W */ + /* 152 */ ED_INSERT, /* M-^X */ + /* 153 */ ED_INSERT, /* M-^Y */ + /* 154 */ ED_INSERT, /* M-^Z */ + /* 155 */ ED_INSERT, /* M-^[ */ + /* 156 */ ED_INSERT, /* M-^\ */ + /* 157 */ ED_INSERT, /* M-^] */ + /* 158 */ ED_INSERT, /* M-^^ */ + /* 159 */ ED_INSERT, /* M-^_ */ + /* 160 */ ED_INSERT, /* M-SPACE */ + /* 161 */ ED_INSERT, /* M-! */ + /* 162 */ ED_INSERT, /* M-" */ + /* 163 */ ED_INSERT, /* M-# */ + /* 164 */ ED_INSERT, /* M-$ */ + /* 165 */ ED_INSERT, /* M-% */ + /* 166 */ ED_INSERT, /* M-& */ + /* 167 */ ED_INSERT, /* M-' */ + /* 168 */ ED_INSERT, /* M-( */ + /* 169 */ ED_INSERT, /* M-) */ + /* 170 */ ED_INSERT, /* M-* */ + /* 171 */ ED_INSERT, /* M-+ */ + /* 172 */ ED_INSERT, /* M-, */ + /* 173 */ ED_INSERT, /* M-- */ + /* 174 */ ED_INSERT, /* M-. */ + /* 175 */ ED_INSERT, /* M-/ */ + /* 176 */ ED_INSERT, /* M-0 */ + /* 177 */ ED_INSERT, /* M-1 */ + /* 178 */ ED_INSERT, /* M-2 */ + /* 179 */ ED_INSERT, /* M-3 */ + /* 180 */ ED_INSERT, /* M-4 */ + /* 181 */ ED_INSERT, /* M-5 */ + /* 182 */ ED_INSERT, /* M-6 */ + /* 183 */ ED_INSERT, /* M-7 */ + /* 184 */ ED_INSERT, /* M-8 */ + /* 185 */ ED_INSERT, /* M-9 */ + /* 186 */ ED_INSERT, /* M-: */ + /* 187 */ ED_INSERT, /* M-; */ + /* 188 */ ED_INSERT, /* M-< */ + /* 189 */ ED_INSERT, /* M-= */ + /* 190 */ ED_INSERT, /* M-> */ + /* 191 */ ED_INSERT, /* M-? */ + /* 192 */ ED_INSERT, /* M-@ */ + /* 193 */ ED_INSERT, /* M-A */ + /* 194 */ ED_INSERT, /* M-B */ + /* 195 */ ED_INSERT, /* M-C */ + /* 196 */ ED_INSERT, /* M-D */ + /* 197 */ ED_INSERT, /* M-E */ + /* 198 */ ED_INSERT, /* M-F */ + /* 199 */ ED_INSERT, /* M-G */ + /* 200 */ ED_INSERT, /* M-H */ + /* 201 */ ED_INSERT, /* M-I */ + /* 202 */ ED_INSERT, /* M-J */ + /* 203 */ ED_INSERT, /* M-K */ + /* 204 */ ED_INSERT, /* M-L */ + /* 205 */ ED_INSERT, /* M-M */ + /* 206 */ ED_INSERT, /* M-N */ + /* 207 */ ED_INSERT, /* M-O */ + /* 208 */ ED_INSERT, /* M-P */ + /* 209 */ ED_INSERT, /* M-Q */ + /* 210 */ ED_INSERT, /* M-R */ + /* 211 */ ED_INSERT, /* M-S */ + /* 212 */ ED_INSERT, /* M-T */ + /* 213 */ ED_INSERT, /* M-U */ + /* 214 */ ED_INSERT, /* M-V */ + /* 215 */ ED_INSERT, /* M-W */ + /* 216 */ ED_INSERT, /* M-X */ + /* 217 */ ED_INSERT, /* M-Y */ + /* 218 */ ED_INSERT, /* M-Z */ + /* 219 */ ED_INSERT, /* M-[ */ + /* 220 */ ED_INSERT, /* M-\ */ + /* 221 */ ED_INSERT, /* M-] */ + /* 222 */ ED_INSERT, /* M-^ */ + /* 223 */ ED_INSERT, /* M-_ */ + /* 224 */ ED_INSERT, /* M-` */ + /* 225 */ ED_INSERT, /* M-a */ + /* 226 */ ED_INSERT, /* M-b */ + /* 227 */ ED_INSERT, /* M-c */ + /* 228 */ ED_INSERT, /* M-d */ + /* 229 */ ED_INSERT, /* M-e */ + /* 230 */ ED_INSERT, /* M-f */ + /* 231 */ ED_INSERT, /* M-g */ + /* 232 */ ED_INSERT, /* M-h */ + /* 233 */ ED_INSERT, /* M-i */ + /* 234 */ ED_INSERT, /* M-j */ + /* 235 */ ED_INSERT, /* M-k */ + /* 236 */ ED_INSERT, /* M-l */ + /* 237 */ ED_INSERT, /* M-m */ + /* 238 */ ED_INSERT, /* M-n */ + /* 239 */ ED_INSERT, /* M-o */ + /* 240 */ ED_INSERT, /* M-p */ + /* 241 */ ED_INSERT, /* M-q */ + /* 242 */ ED_INSERT, /* M-r */ + /* 243 */ ED_INSERT, /* M-s */ + /* 244 */ ED_INSERT, /* M-t */ + /* 245 */ ED_INSERT, /* M-u */ + /* 246 */ ED_INSERT, /* M-v */ + /* 247 */ ED_INSERT, /* M-w */ + /* 248 */ ED_INSERT, /* M-x */ + /* 249 */ ED_INSERT, /* M-y */ + /* 250 */ ED_INSERT, /* M-z */ + /* 251 */ ED_INSERT, /* M-{ */ + /* 252 */ ED_INSERT, /* M-| */ + /* 253 */ ED_INSERT, /* M-} */ + /* 254 */ ED_INSERT, /* M-~ */ + /* 255 */ ED_INSERT /* M-^? */ +}; + +static const el_action_t el_map_vi_command[] = { + /* 0 */ ED_UNASSIGNED, /* ^@ */ + /* 1 */ ED_MOVE_TO_BEG, /* ^A */ + /* 2 */ ED_UNASSIGNED, /* ^B */ + /* 3 */ ED_IGNORE, /* ^C */ + /* 4 */ ED_UNASSIGNED, /* ^D */ + /* 5 */ ED_MOVE_TO_END, /* ^E */ + /* 6 */ ED_UNASSIGNED, /* ^F */ + /* 7 */ ED_UNASSIGNED, /* ^G */ + /* 8 */ ED_DELETE_PREV_CHAR, /* ^H */ + /* 9 */ ED_UNASSIGNED, /* ^I */ + /* 10 */ ED_NEWLINE, /* ^J */ + /* 11 */ ED_KILL_LINE, /* ^K */ + /* 12 */ ED_CLEAR_SCREEN, /* ^L */ + /* 13 */ ED_NEWLINE, /* ^M */ + /* 14 */ ED_NEXT_HISTORY, /* ^N */ + /* 15 */ ED_IGNORE, /* ^O */ + /* 16 */ ED_PREV_HISTORY, /* ^P */ + /* 17 */ ED_IGNORE, /* ^Q */ + /* 18 */ ED_REDISPLAY, /* ^R */ + /* 19 */ ED_IGNORE, /* ^S */ + /* 20 */ ED_UNASSIGNED, /* ^T */ + /* 21 */ VI_KILL_LINE_PREV, /* ^U */ + /* 22 */ ED_UNASSIGNED, /* ^V */ + /* 23 */ ED_DELETE_PREV_WORD, /* ^W */ + /* 24 */ ED_UNASSIGNED, /* ^X */ + /* 25 */ ED_UNASSIGNED, /* ^Y */ + /* 26 */ ED_UNASSIGNED, /* ^Z */ + /* 27 */ EM_META_NEXT, /* ^[ */ + /* 28 */ ED_IGNORE, /* ^\ */ + /* 29 */ ED_UNASSIGNED, /* ^] */ + /* 30 */ ED_UNASSIGNED, /* ^^ */ + /* 31 */ ED_UNASSIGNED, /* ^_ */ + /* 32 */ ED_NEXT_CHAR, /* SPACE */ + /* 33 */ ED_UNASSIGNED, /* ! */ + /* 34 */ ED_UNASSIGNED, /* " */ + /* 35 */ VI_COMMENT_OUT, /* # */ + /* 36 */ ED_MOVE_TO_END, /* $ */ + /* 37 */ VI_MATCH, /* % */ + /* 38 */ ED_UNASSIGNED, /* & */ + /* 39 */ ED_UNASSIGNED, /* ' */ + /* 40 */ ED_UNASSIGNED, /* ( */ + /* 41 */ ED_UNASSIGNED, /* ) */ + /* 42 */ ED_UNASSIGNED, /* * */ + /* 43 */ ED_NEXT_HISTORY, /* + */ + /* 44 */ VI_REPEAT_PREV_CHAR, /* , */ + /* 45 */ ED_PREV_HISTORY, /* - */ + /* 46 */ VI_REDO, /* . */ + /* 47 */ VI_SEARCH_PREV, /* / */ + /* 48 */ VI_ZERO, /* 0 */ + /* 49 */ ED_ARGUMENT_DIGIT, /* 1 */ + /* 50 */ ED_ARGUMENT_DIGIT, /* 2 */ + /* 51 */ ED_ARGUMENT_DIGIT, /* 3 */ + /* 52 */ ED_ARGUMENT_DIGIT, /* 4 */ + /* 53 */ ED_ARGUMENT_DIGIT, /* 5 */ + /* 54 */ ED_ARGUMENT_DIGIT, /* 6 */ + /* 55 */ ED_ARGUMENT_DIGIT, /* 7 */ + /* 56 */ ED_ARGUMENT_DIGIT, /* 8 */ + /* 57 */ ED_ARGUMENT_DIGIT, /* 9 */ + /* 58 */ ED_COMMAND, /* : */ + /* 59 */ VI_REPEAT_NEXT_CHAR, /* ; */ + /* 60 */ ED_UNASSIGNED, /* < */ + /* 61 */ ED_UNASSIGNED, /* = */ + /* 62 */ ED_UNASSIGNED, /* > */ + /* 63 */ VI_SEARCH_NEXT, /* ? */ + /* 64 */ VI_ALIAS, /* @ */ + /* 65 */ VI_ADD_AT_EOL, /* A */ + /* 66 */ VI_PREV_BIG_WORD, /* B */ + /* 67 */ VI_CHANGE_TO_EOL, /* C */ + /* 68 */ ED_KILL_LINE, /* D */ + /* 69 */ VI_END_BIG_WORD, /* E */ + /* 70 */ VI_PREV_CHAR, /* F */ + /* 71 */ VI_TO_HISTORY_LINE, /* G */ + /* 72 */ ED_UNASSIGNED, /* H */ + /* 73 */ VI_INSERT_AT_BOL, /* I */ + /* 74 */ ED_SEARCH_NEXT_HISTORY, /* J */ + /* 75 */ ED_SEARCH_PREV_HISTORY, /* K */ + /* 76 */ ED_UNASSIGNED, /* L */ + /* 77 */ ED_UNASSIGNED, /* M */ + /* 78 */ VI_REPEAT_SEARCH_PREV, /* N */ + /* 79 */ ED_SEQUENCE_LEAD_IN, /* O */ + /* 80 */ VI_PASTE_PREV, /* P */ + /* 81 */ ED_UNASSIGNED, /* Q */ + /* 82 */ VI_REPLACE_MODE, /* R */ + /* 83 */ VI_SUBSTITUTE_LINE, /* S */ + /* 84 */ VI_TO_PREV_CHAR, /* T */ + /* 85 */ VI_UNDO_LINE, /* U */ + /* 86 */ ED_UNASSIGNED, /* V */ + /* 87 */ VI_NEXT_BIG_WORD, /* W */ + /* 88 */ ED_DELETE_PREV_CHAR, /* X */ + /* 89 */ VI_YANK_END, /* Y */ + /* 90 */ ED_UNASSIGNED, /* Z */ + /* 91 */ ED_SEQUENCE_LEAD_IN, /* [ */ + /* 92 */ ED_UNASSIGNED, /* \ */ + /* 93 */ ED_UNASSIGNED, /* ] */ + /* 94 */ ED_MOVE_TO_BEG, /* ^ */ + /* 95 */ VI_HISTORY_WORD, /* _ */ + /* 96 */ ED_UNASSIGNED, /* ` */ + /* 97 */ VI_ADD, /* a */ + /* 98 */ VI_PREV_WORD, /* b */ + /* 99 */ VI_CHANGE_META, /* c */ + /* 100 */ VI_DELETE_META, /* d */ + /* 101 */ VI_END_WORD, /* e */ + /* 102 */ VI_NEXT_CHAR, /* f */ + /* 103 */ ED_UNASSIGNED, /* g */ + /* 104 */ ED_PREV_CHAR, /* h */ + /* 105 */ VI_INSERT, /* i */ + /* 106 */ ED_NEXT_HISTORY, /* j */ + /* 107 */ ED_PREV_HISTORY, /* k */ + /* 108 */ ED_NEXT_CHAR, /* l */ + /* 109 */ ED_UNASSIGNED, /* m */ + /* 110 */ VI_REPEAT_SEARCH_NEXT, /* n */ + /* 111 */ ED_UNASSIGNED, /* o */ + /* 112 */ VI_PASTE_NEXT, /* p */ + /* 113 */ ED_UNASSIGNED, /* q */ + /* 114 */ VI_REPLACE_CHAR, /* r */ + /* 115 */ VI_SUBSTITUTE_CHAR, /* s */ + /* 116 */ VI_TO_NEXT_CHAR, /* t */ + /* 117 */ VI_UNDO, /* u */ + /* 118 */ VI_HISTEDIT, /* v */ + /* 119 */ VI_NEXT_WORD, /* w */ + /* 120 */ ED_DELETE_NEXT_CHAR, /* x */ + /* 121 */ VI_YANK, /* y */ + /* 122 */ ED_UNASSIGNED, /* z */ + /* 123 */ ED_UNASSIGNED, /* { */ + /* 124 */ VI_TO_COLUMN, /* | */ + /* 125 */ ED_UNASSIGNED, /* } */ + /* 126 */ VI_CHANGE_CASE, /* ~ */ + /* 127 */ ED_DELETE_PREV_CHAR, /* ^? */ + /* 128 */ ED_UNASSIGNED, /* M-^@ */ + /* 129 */ ED_UNASSIGNED, /* M-^A */ + /* 130 */ ED_UNASSIGNED, /* M-^B */ + /* 131 */ ED_UNASSIGNED, /* M-^C */ + /* 132 */ ED_UNASSIGNED, /* M-^D */ + /* 133 */ ED_UNASSIGNED, /* M-^E */ + /* 134 */ ED_UNASSIGNED, /* M-^F */ + /* 135 */ ED_UNASSIGNED, /* M-^G */ + /* 136 */ ED_UNASSIGNED, /* M-^H */ + /* 137 */ ED_UNASSIGNED, /* M-^I */ + /* 138 */ ED_UNASSIGNED, /* M-^J */ + /* 139 */ ED_UNASSIGNED, /* M-^K */ + /* 140 */ ED_UNASSIGNED, /* M-^L */ + /* 141 */ ED_UNASSIGNED, /* M-^M */ + /* 142 */ ED_UNASSIGNED, /* M-^N */ + /* 143 */ ED_UNASSIGNED, /* M-^O */ + /* 144 */ ED_UNASSIGNED, /* M-^P */ + /* 145 */ ED_UNASSIGNED, /* M-^Q */ + /* 146 */ ED_UNASSIGNED, /* M-^R */ + /* 147 */ ED_UNASSIGNED, /* M-^S */ + /* 148 */ ED_UNASSIGNED, /* M-^T */ + /* 149 */ ED_UNASSIGNED, /* M-^U */ + /* 150 */ ED_UNASSIGNED, /* M-^V */ + /* 151 */ ED_UNASSIGNED, /* M-^W */ + /* 152 */ ED_UNASSIGNED, /* M-^X */ + /* 153 */ ED_UNASSIGNED, /* M-^Y */ + /* 154 */ ED_UNASSIGNED, /* M-^Z */ + /* 155 */ ED_UNASSIGNED, /* M-^[ */ + /* 156 */ ED_UNASSIGNED, /* M-^\ */ + /* 157 */ ED_UNASSIGNED, /* M-^] */ + /* 158 */ ED_UNASSIGNED, /* M-^^ */ + /* 159 */ ED_UNASSIGNED, /* M-^_ */ + /* 160 */ ED_UNASSIGNED, /* M-SPACE */ + /* 161 */ ED_UNASSIGNED, /* M-! */ + /* 162 */ ED_UNASSIGNED, /* M-" */ + /* 163 */ ED_UNASSIGNED, /* M-# */ + /* 164 */ ED_UNASSIGNED, /* M-$ */ + /* 165 */ ED_UNASSIGNED, /* M-% */ + /* 166 */ ED_UNASSIGNED, /* M-& */ + /* 167 */ ED_UNASSIGNED, /* M-' */ + /* 168 */ ED_UNASSIGNED, /* M-( */ + /* 169 */ ED_UNASSIGNED, /* M-) */ + /* 170 */ ED_UNASSIGNED, /* M-* */ + /* 171 */ ED_UNASSIGNED, /* M-+ */ + /* 172 */ ED_UNASSIGNED, /* M-, */ + /* 173 */ ED_UNASSIGNED, /* M-- */ + /* 174 */ ED_UNASSIGNED, /* M-. */ + /* 175 */ ED_UNASSIGNED, /* M-/ */ + /* 176 */ ED_UNASSIGNED, /* M-0 */ + /* 177 */ ED_UNASSIGNED, /* M-1 */ + /* 178 */ ED_UNASSIGNED, /* M-2 */ + /* 179 */ ED_UNASSIGNED, /* M-3 */ + /* 180 */ ED_UNASSIGNED, /* M-4 */ + /* 181 */ ED_UNASSIGNED, /* M-5 */ + /* 182 */ ED_UNASSIGNED, /* M-6 */ + /* 183 */ ED_UNASSIGNED, /* M-7 */ + /* 184 */ ED_UNASSIGNED, /* M-8 */ + /* 185 */ ED_UNASSIGNED, /* M-9 */ + /* 186 */ ED_UNASSIGNED, /* M-: */ + /* 187 */ ED_UNASSIGNED, /* M-; */ + /* 188 */ ED_UNASSIGNED, /* M-< */ + /* 189 */ ED_UNASSIGNED, /* M-= */ + /* 190 */ ED_UNASSIGNED, /* M-> */ + /* 191 */ ED_UNASSIGNED, /* M-? */ + /* 192 */ ED_UNASSIGNED, /* M-@ */ + /* 193 */ ED_UNASSIGNED, /* M-A */ + /* 194 */ ED_UNASSIGNED, /* M-B */ + /* 195 */ ED_UNASSIGNED, /* M-C */ + /* 196 */ ED_UNASSIGNED, /* M-D */ + /* 197 */ ED_UNASSIGNED, /* M-E */ + /* 198 */ ED_UNASSIGNED, /* M-F */ + /* 199 */ ED_UNASSIGNED, /* M-G */ + /* 200 */ ED_UNASSIGNED, /* M-H */ + /* 201 */ ED_UNASSIGNED, /* M-I */ + /* 202 */ ED_UNASSIGNED, /* M-J */ + /* 203 */ ED_UNASSIGNED, /* M-K */ + /* 204 */ ED_UNASSIGNED, /* M-L */ + /* 205 */ ED_UNASSIGNED, /* M-M */ + /* 206 */ ED_UNASSIGNED, /* M-N */ + /* 207 */ ED_SEQUENCE_LEAD_IN, /* M-O */ + /* 208 */ ED_UNASSIGNED, /* M-P */ + /* 209 */ ED_UNASSIGNED, /* M-Q */ + /* 210 */ ED_UNASSIGNED, /* M-R */ + /* 211 */ ED_UNASSIGNED, /* M-S */ + /* 212 */ ED_UNASSIGNED, /* M-T */ + /* 213 */ ED_UNASSIGNED, /* M-U */ + /* 214 */ ED_UNASSIGNED, /* M-V */ + /* 215 */ ED_UNASSIGNED, /* M-W */ + /* 216 */ ED_UNASSIGNED, /* M-X */ + /* 217 */ ED_UNASSIGNED, /* M-Y */ + /* 218 */ ED_UNASSIGNED, /* M-Z */ + /* 219 */ ED_SEQUENCE_LEAD_IN, /* M-[ */ + /* 220 */ ED_UNASSIGNED, /* M-\ */ + /* 221 */ ED_UNASSIGNED, /* M-] */ + /* 222 */ ED_UNASSIGNED, /* M-^ */ + /* 223 */ ED_UNASSIGNED, /* M-_ */ + /* 224 */ ED_UNASSIGNED, /* M-` */ + /* 225 */ ED_UNASSIGNED, /* M-a */ + /* 226 */ ED_UNASSIGNED, /* M-b */ + /* 227 */ ED_UNASSIGNED, /* M-c */ + /* 228 */ ED_UNASSIGNED, /* M-d */ + /* 229 */ ED_UNASSIGNED, /* M-e */ + /* 230 */ ED_UNASSIGNED, /* M-f */ + /* 231 */ ED_UNASSIGNED, /* M-g */ + /* 232 */ ED_UNASSIGNED, /* M-h */ + /* 233 */ ED_UNASSIGNED, /* M-i */ + /* 234 */ ED_UNASSIGNED, /* M-j */ + /* 235 */ ED_UNASSIGNED, /* M-k */ + /* 236 */ ED_UNASSIGNED, /* M-l */ + /* 237 */ ED_UNASSIGNED, /* M-m */ + /* 238 */ ED_UNASSIGNED, /* M-n */ + /* 239 */ ED_UNASSIGNED, /* M-o */ + /* 240 */ ED_UNASSIGNED, /* M-p */ + /* 241 */ ED_UNASSIGNED, /* M-q */ + /* 242 */ ED_UNASSIGNED, /* M-r */ + /* 243 */ ED_UNASSIGNED, /* M-s */ + /* 244 */ ED_UNASSIGNED, /* M-t */ + /* 245 */ ED_UNASSIGNED, /* M-u */ + /* 246 */ ED_UNASSIGNED, /* M-v */ + /* 247 */ ED_UNASSIGNED, /* M-w */ + /* 248 */ ED_UNASSIGNED, /* M-x */ + /* 249 */ ED_UNASSIGNED, /* M-y */ + /* 250 */ ED_UNASSIGNED, /* M-z */ + /* 251 */ ED_UNASSIGNED, /* M-{ */ + /* 252 */ ED_UNASSIGNED, /* M-| */ + /* 253 */ ED_UNASSIGNED, /* M-} */ + /* 254 */ ED_UNASSIGNED, /* M-~ */ + /* 255 */ ED_UNASSIGNED /* M-^? */ +}; + + +/* map_init(): + * Initialize and allocate the maps + */ +libedit_private int +map_init(EditLine *el) +{ + + /* + * Make sure those are correct before starting. + */ +#ifdef MAP_DEBUG + if (sizeof(el_map_emacs) != N_KEYS * sizeof(el_action_t)) + EL_ABORT((el->errfile, "Emacs map incorrect\n")); + if (sizeof(el_map_vi_command) != N_KEYS * sizeof(el_action_t)) + EL_ABORT((el->errfile, "Vi command map incorrect\n")); + if (sizeof(el_map_vi_insert) != N_KEYS * sizeof(el_action_t)) + EL_ABORT((el->errfile, "Vi insert map incorrect\n")); +#endif + + el->el_map.alt = el_malloc(sizeof(*el->el_map.alt) * N_KEYS); + if (el->el_map.alt == NULL) + return -1; + el->el_map.key = el_malloc(sizeof(*el->el_map.key) * N_KEYS); + if (el->el_map.key == NULL) + return -1; + el->el_map.emacs = el_map_emacs; + el->el_map.vic = el_map_vi_command; + el->el_map.vii = el_map_vi_insert; + el->el_map.help = el_malloc(sizeof(*el->el_map.help) * EL_NUM_FCNS); + if (el->el_map.help == NULL) + return -1; + (void) memcpy(el->el_map.help, el_func_help, + sizeof(*el->el_map.help) * EL_NUM_FCNS); + el->el_map.func = el_malloc(sizeof(*el->el_map.func) * EL_NUM_FCNS); + if (el->el_map.func == NULL) + return -1; + memcpy(el->el_map.func, el_func, sizeof(*el->el_map.func) + * EL_NUM_FCNS); + el->el_map.nfunc = EL_NUM_FCNS; + +#ifdef VIDEFAULT + map_init_vi(el); +#else + map_init_emacs(el); +#endif /* VIDEFAULT */ + return 0; +} + + +/* map_end(): + * Free the space taken by the editor maps + */ +libedit_private void +map_end(EditLine *el) +{ + + el_free(el->el_map.alt); + el->el_map.alt = NULL; + el_free(el->el_map.key); + el->el_map.key = NULL; + el->el_map.emacs = NULL; + el->el_map.vic = NULL; + el->el_map.vii = NULL; + el_free(el->el_map.help); + el->el_map.help = NULL; + el_free(el->el_map.func); + el->el_map.func = NULL; +} + + +/* map_init_nls(): + * Find all the printable keys and bind them to self insert + */ +static void +map_init_nls(EditLine *el) +{ + int i; + + el_action_t *map = el->el_map.key; + + for (i = 0200; i <= 0377; i++) + if (iswprint(i)) + map[i] = ED_INSERT; +} + + +/* map_init_meta(): + * Bind all the meta keys to the appropriate ESC-<key> sequence + */ +static void +map_init_meta(EditLine *el) +{ + wchar_t buf[3]; + int i; + el_action_t *map = el->el_map.key; + el_action_t *alt = el->el_map.alt; + + for (i = 0; i <= 0377 && map[i] != EM_META_NEXT; i++) + continue; + + if (i > 0377) { + for (i = 0; i <= 0377 && alt[i] != EM_META_NEXT; i++) + continue; + if (i > 0377) { + i = 033; + if (el->el_map.type == MAP_VI) + map = alt; + } else + map = alt; + } + buf[0] = (wchar_t)i; + buf[2] = 0; + for (i = 0200; i <= 0377; i++) + switch (map[i]) { + case ED_INSERT: + case ED_UNASSIGNED: + case ED_SEQUENCE_LEAD_IN: + break; + default: + buf[1] = i & 0177; + keymacro_add(el, buf, keymacro_map_cmd(el, (int) map[i]), XK_CMD); + break; + } + map[(int) buf[0]] = ED_SEQUENCE_LEAD_IN; +} + + +/* map_init_vi(): + * Initialize the vi bindings + */ +libedit_private void +map_init_vi(EditLine *el) +{ + int i; + el_action_t *key = el->el_map.key; + el_action_t *alt = el->el_map.alt; + const el_action_t *vii = el->el_map.vii; + const el_action_t *vic = el->el_map.vic; + + el->el_map.type = MAP_VI; + el->el_map.current = el->el_map.key; + + keymacro_reset(el); + + for (i = 0; i < N_KEYS; i++) { + key[i] = vii[i]; + alt[i] = vic[i]; + } + + map_init_meta(el); + map_init_nls(el); + + tty_bind_char(el, 1); + terminal_bind_arrow(el); +} + + +/* map_init_emacs(): + * Initialize the emacs bindings + */ +libedit_private void +map_init_emacs(EditLine *el) +{ + int i; + wchar_t buf[3]; + el_action_t *key = el->el_map.key; + el_action_t *alt = el->el_map.alt; + const el_action_t *emacs = el->el_map.emacs; + + el->el_map.type = MAP_EMACS; + el->el_map.current = el->el_map.key; + keymacro_reset(el); + + for (i = 0; i < N_KEYS; i++) { + key[i] = emacs[i]; + alt[i] = ED_UNASSIGNED; + } + + map_init_meta(el); + map_init_nls(el); + + buf[0] = CONTROL('X'); + buf[1] = CONTROL('X'); + buf[2] = 0; + keymacro_add(el, buf, keymacro_map_cmd(el, EM_EXCHANGE_MARK), XK_CMD); + + tty_bind_char(el, 1); + terminal_bind_arrow(el); +} + + +/* map_set_editor(): + * Set the editor + */ +libedit_private int +map_set_editor(EditLine *el, wchar_t *editor) +{ + + if (wcscmp(editor, L"emacs") == 0) { + map_init_emacs(el); + return 0; + } + if (wcscmp(editor, L"vi") == 0) { + map_init_vi(el); + return 0; + } + return -1; +} + + +/* map_get_editor(): + * Retrieve the editor + */ +libedit_private int +map_get_editor(EditLine *el, const wchar_t **editor) +{ + + if (editor == NULL) + return -1; + switch (el->el_map.type) { + case MAP_EMACS: + *editor = L"emacs"; + return 0; + case MAP_VI: + *editor = L"vi"; + return 0; + } + return -1; +} + + +/* map_print_key(): + * Print the function description for 1 key + */ +static void +map_print_key(EditLine *el, el_action_t *map, const wchar_t *in) +{ + char outbuf[EL_BUFSIZ]; + el_bindings_t *bp, *ep; + + if (in[0] == '\0' || in[1] == '\0') { + (void) keymacro__decode_str(in, outbuf, sizeof(outbuf), ""); + ep = &el->el_map.help[el->el_map.nfunc]; + for (bp = el->el_map.help; bp < ep; bp++) + if (bp->func == map[(unsigned char) *in]) { + (void) fprintf(el->el_outfile, + "%s\t->\t%ls\n", outbuf, bp->name); + return; + } + } else + keymacro_print(el, in); +} + + +/* map_print_some_keys(): + * Print keys from first to last + */ +static void +map_print_some_keys(EditLine *el, el_action_t *map, wint_t first, wint_t last) +{ + el_bindings_t *bp, *ep; + wchar_t firstbuf[2], lastbuf[2]; + char unparsbuf[EL_BUFSIZ], extrabuf[EL_BUFSIZ]; + + firstbuf[0] = first; + firstbuf[1] = 0; + lastbuf[0] = last; + lastbuf[1] = 0; + if (map[first] == ED_UNASSIGNED) { + if (first == last) { + (void) keymacro__decode_str(firstbuf, unparsbuf, + sizeof(unparsbuf), STRQQ); + (void) fprintf(el->el_outfile, + "%-15s-> is undefined\n", unparsbuf); + } + return; + } + ep = &el->el_map.help[el->el_map.nfunc]; + for (bp = el->el_map.help; bp < ep; bp++) { + if (bp->func == map[first]) { + if (first == last) { + (void) keymacro__decode_str(firstbuf, unparsbuf, + sizeof(unparsbuf), STRQQ); + (void) fprintf(el->el_outfile, "%-15s-> %ls\n", + unparsbuf, bp->name); + } else { + (void) keymacro__decode_str(firstbuf, unparsbuf, + sizeof(unparsbuf), STRQQ); + (void) keymacro__decode_str(lastbuf, extrabuf, + sizeof(extrabuf), STRQQ); + (void) fprintf(el->el_outfile, + "%-4s to %-7s-> %ls\n", + unparsbuf, extrabuf, bp->name); + } + return; + } + } +#ifdef MAP_DEBUG + if (map == el->el_map.key) { + (void) keymacro__decode_str(firstbuf, unparsbuf, + sizeof(unparsbuf), STRQQ); + (void) fprintf(el->el_outfile, + "BUG!!! %s isn't bound to anything.\n", unparsbuf); + (void) fprintf(el->el_outfile, "el->el_map.key[%d] == %d\n", + first, el->el_map.key[first]); + } else { + (void) keymacro__decode_str(firstbuf, unparsbuf, + sizeof(unparsbuf), STRQQ); + (void) fprintf(el->el_outfile, + "BUG!!! %s isn't bound to anything.\n", unparsbuf); + (void) fprintf(el->el_outfile, "el->el_map.alt[%d] == %d\n", + first, el->el_map.alt[first]); + } +#endif + EL_ABORT((el->el_errfile, "Error printing keys\n")); +} + + +/* map_print_all_keys(): + * Print the function description for all keys. + */ +static void +map_print_all_keys(EditLine *el) +{ + int prev, i; + + (void) fprintf(el->el_outfile, "Standard key bindings\n"); + prev = 0; + for (i = 0; i < N_KEYS; i++) { + if (el->el_map.key[prev] == el->el_map.key[i]) + continue; + map_print_some_keys(el, el->el_map.key, prev, i - 1); + prev = i; + } + map_print_some_keys(el, el->el_map.key, prev, i - 1); + + (void) fprintf(el->el_outfile, "Alternative key bindings\n"); + prev = 0; + for (i = 0; i < N_KEYS; i++) { + if (el->el_map.alt[prev] == el->el_map.alt[i]) + continue; + map_print_some_keys(el, el->el_map.alt, prev, i - 1); + prev = i; + } + map_print_some_keys(el, el->el_map.alt, prev, i - 1); + + (void) fprintf(el->el_outfile, "Multi-character bindings\n"); + keymacro_print(el, L""); + (void) fprintf(el->el_outfile, "Arrow key bindings\n"); + terminal_print_arrow(el, L""); +} + + +/* map_bind(): + * Add/remove/change bindings + */ +libedit_private int +map_bind(EditLine *el, int argc, const wchar_t **argv) +{ + el_action_t *map; + int ntype, rem; + const wchar_t *p; + wchar_t inbuf[EL_BUFSIZ]; + wchar_t outbuf[EL_BUFSIZ]; + const wchar_t *in = NULL; + wchar_t *out; + el_bindings_t *bp, *ep; + int cmd; + int key; + + if (argv == NULL) + return -1; + + map = el->el_map.key; + ntype = XK_CMD; + key = rem = 0; + for (argc = 1; (p = argv[argc]) != NULL; argc++) + if (p[0] == '-') + switch (p[1]) { + case 'a': + map = el->el_map.alt; + break; + + case 's': + ntype = XK_STR; + break; + case 'k': + key = 1; + break; + + case 'r': + rem = 1; + break; + + case 'v': + map_init_vi(el); + return 0; + + case 'e': + map_init_emacs(el); + return 0; + + case 'l': + ep = &el->el_map.help[el->el_map.nfunc]; + for (bp = el->el_map.help; bp < ep; bp++) + (void) fprintf(el->el_outfile, + "%ls\n\t%ls\n", + bp->name, bp->description); + return 0; + default: + (void) fprintf(el->el_errfile, + "%ls: Invalid switch `%lc'.\n", + argv[0], (wint_t)p[1]); + } + else + break; + + if (argv[argc] == NULL) { + map_print_all_keys(el); + return 0; + } + if (key) + in = argv[argc++]; + else if ((in = parse__string(inbuf, argv[argc++])) == NULL) { + (void) fprintf(el->el_errfile, + "%ls: Invalid \\ or ^ in instring.\n", + argv[0]); + return -1; + } + if (rem) { + if (key) { + (void) terminal_clear_arrow(el, in); + return -1; + } + if (in[1]) + (void) keymacro_delete(el, in); + else if (map[(unsigned char) *in] == ED_SEQUENCE_LEAD_IN) + (void) keymacro_delete(el, in); + else + map[(unsigned char) *in] = ED_UNASSIGNED; + return 0; + } + if (argv[argc] == NULL) { + if (key) + terminal_print_arrow(el, in); + else + map_print_key(el, map, in); + return 0; + } +#ifdef notyet + if (argv[argc + 1] != NULL) { + bindkeymacro_usage(); + return -1; + } +#endif + + switch (ntype) { + case XK_STR: + if ((out = parse__string(outbuf, argv[argc])) == NULL) { + (void) fprintf(el->el_errfile, + "%ls: Invalid \\ or ^ in outstring.\n", argv[0]); + return -1; + } + if (key) + terminal_set_arrow(el, in, keymacro_map_str(el, out), ntype); + else + keymacro_add(el, in, keymacro_map_str(el, out), ntype); + map[(unsigned char) *in] = ED_SEQUENCE_LEAD_IN; + break; + + case XK_CMD: + if ((cmd = parse_cmd(el, argv[argc])) == -1) { + (void) fprintf(el->el_errfile, + "%ls: Invalid command `%ls'.\n", + argv[0], argv[argc]); + return -1; + } + if (key) + terminal_set_arrow(el, in, keymacro_map_cmd(el, cmd), ntype); + else { + if (in[1]) { + keymacro_add(el, in, keymacro_map_cmd(el, cmd), ntype); + map[(unsigned char) *in] = ED_SEQUENCE_LEAD_IN; + } else { + keymacro_clear(el, map, in); + map[(unsigned char) *in] = (el_action_t)cmd; + } + } + break; + + /* coverity[dead_error_begin] */ + default: + EL_ABORT((el->el_errfile, "Bad XK_ type %d\n", ntype)); + break; + } + return 0; +} + + +/* map_addfunc(): + * add a user defined function + */ +libedit_private int +map_addfunc(EditLine *el, const wchar_t *name, const wchar_t *help, + el_func_t func) +{ + void *p; + size_t nf = el->el_map.nfunc + 1; + + if (name == NULL || help == NULL || func == NULL) + return -1; + + if ((p = el_realloc(el->el_map.func, nf * + sizeof(*el->el_map.func))) == NULL) + return -1; + el->el_map.func = p; + if ((p = el_realloc(el->el_map.help, nf * sizeof(*el->el_map.help))) + == NULL) + return -1; + el->el_map.help = p; + + nf = (size_t)el->el_map.nfunc; + el->el_map.func[nf] = func; + + el->el_map.help[nf].name = name; + el->el_map.help[nf].func = (int)nf; + el->el_map.help[nf].description = help; + el->el_map.nfunc++; + + return 0; +} diff --git a/bin/catsh/libedit/map.h b/bin/catsh/libedit/map.h new file mode 100644 index 00000000..b4e4e289 --- /dev/null +++ b/bin/catsh/libedit/map.h @@ -0,0 +1,79 @@ +/* $NetBSD: map.h,v 1.13 2016/05/09 21:46:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)map.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.map.h: Editor maps + */ +#ifndef _h_el_map +#define _h_el_map + +typedef el_action_t (*el_func_t)(EditLine *, wint_t); + +typedef struct el_bindings_t { /* for the "bind" shell command */ + const wchar_t *name; /* function name for bind command */ + int func; /* function numeric value */ + const wchar_t *description; /* description of function */ +} el_bindings_t; + +typedef struct el_map_t { + el_action_t *alt; /* The current alternate key map */ + el_action_t *key; /* The current normal key map */ + el_action_t *current; /* The keymap we are using */ + const el_action_t *emacs; /* The default emacs key map */ + const el_action_t *vic; /* The vi command mode key map */ + const el_action_t *vii; /* The vi insert mode key map */ + int type; /* Emacs or vi */ + el_bindings_t *help; /* The help for the editor functions */ + el_func_t *func; /* List of available functions */ + size_t nfunc; /* The number of functions/help items */ +} el_map_t; + +#define MAP_EMACS 0 +#define MAP_VI 1 + +#define N_KEYS 256 + +libedit_private int map_bind(EditLine *, int, const wchar_t **); +libedit_private int map_init(EditLine *); +libedit_private void map_end(EditLine *); +libedit_private void map_init_vi(EditLine *); +libedit_private void map_init_emacs(EditLine *); +libedit_private int map_set_editor(EditLine *, wchar_t *); +libedit_private int map_get_editor(EditLine *, const wchar_t **); +libedit_private int map_addfunc(EditLine *, const wchar_t *, const wchar_t *, + el_func_t); + +#endif /* _h_el_map */ diff --git a/bin/catsh/libedit/parse.c b/bin/catsh/libedit/parse.c new file mode 100644 index 00000000..fdd0d847 --- /dev/null +++ b/bin/catsh/libedit/parse.c @@ -0,0 +1,289 @@ +/* $NetBSD: parse.c,v 1.40 2016/05/09 21:46:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)parse.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: parse.c,v 1.40 2016/05/09 21:46:56 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * parse.c: parse an editline extended command + * + * commands are: + * + * bind + * echotc + * edit + * gettc + * history + * settc + * setty + */ +#include <stdlib.h> +#include <string.h> + +#include "el.h" +#include "parse.h" + +static const struct { + const wchar_t *name; + int (*func)(EditLine *, int, const wchar_t **); +} cmds[] = { + { L"bind", map_bind }, + { L"echotc", terminal_echotc }, + { L"edit", el_editmode }, + { L"history", hist_command }, + { L"telltc", terminal_telltc }, + { L"settc", terminal_settc }, + { L"setty", tty_stty }, + { NULL, NULL } +}; + + +/* parse_line(): + * Parse a line and dispatch it + */ +libedit_private int +parse_line(EditLine *el, const wchar_t *line) +{ + const wchar_t **argv; + int argc; + TokenizerW *tok; + + tok = tok_winit(NULL); + tok_wstr(tok, line, &argc, &argv); + argc = el_wparse(el, argc, argv); + tok_wend(tok); + return argc; +} + + +/* el_parse(): + * Command dispatcher + */ +int +el_wparse(EditLine *el, int argc, const wchar_t *argv[]) +{ + const wchar_t *ptr; + int i; + + if (argc < 1) + return -1; + ptr = wcschr(argv[0], L':'); + if (ptr != NULL) { + wchar_t *tprog; + size_t l; + + if (ptr == argv[0]) + return 0; + l = (size_t)(ptr - argv[0] - 1); + tprog = el_malloc((l + 1) * sizeof(*tprog)); + if (tprog == NULL) + return 0; + (void) wcsncpy(tprog, argv[0], l); + tprog[l] = '\0'; + ptr++; + l = (size_t)el_match(el->el_prog, tprog); + el_free(tprog); + if (!l) + return 0; + } else + ptr = argv[0]; + + for (i = 0; cmds[i].name != NULL; i++) + if (wcscmp(cmds[i].name, ptr) == 0) { + i = (*cmds[i].func) (el, argc, argv); + return -i; + } + return -1; +} + + +/* parse__escape(): + * Parse a string of the form ^<char> \<odigit> \<char> \U+xxxx and return + * the appropriate character or -1 if the escape is not valid + */ +libedit_private int +parse__escape(const wchar_t **ptr) +{ + const wchar_t *p; + wint_t c; + + p = *ptr; + + if (p[1] == 0) + return -1; + + if (*p == '\\') { + p++; + switch (*p) { + case 'a': + c = '\007'; /* Bell */ + break; + case 'b': + c = '\010'; /* Backspace */ + break; + case 't': + c = '\011'; /* Horizontal Tab */ + break; + case 'n': + c = '\012'; /* New Line */ + break; + case 'v': + c = '\013'; /* Vertical Tab */ + break; + case 'f': + c = '\014'; /* Form Feed */ + break; + case 'r': + c = '\015'; /* Carriage Return */ + break; + case 'e': + c = '\033'; /* Escape */ + break; + case 'U': /* Unicode \U+xxxx or \U+xxxxx format */ + { + int i; + const wchar_t hex[] = L"0123456789ABCDEF"; + const wchar_t *h; + ++p; + if (*p++ != '+') + return -1; + c = 0; + for (i = 0; i < 5; ++i) { + h = wcschr(hex, *p++); + if (!h && i < 4) + return -1; + else if (h) + c = (c << 4) | ((int)(h - hex)); + else + --p; + } + if (c > 0x10FFFF) /* outside valid character range */ + return -1; + break; + } + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + { + int cnt, ch; + + for (cnt = 0, c = 0; cnt < 3; cnt++) { + ch = *p++; + if (ch < '0' || ch > '7') { + p--; + break; + } + c = (c << 3) | (ch - '0'); + } + if ((c & (wint_t)0xffffff00) != (wint_t)0) + return -1; + --p; + break; + } + default: + c = *p; + break; + } + } else if (*p == '^') { + p++; + c = (*p == '?') ? '\177' : (*p & 0237); + } else + c = *p; + *ptr = ++p; + return c; +} + +/* parse__string(): + * Parse the escapes from in and put the raw string out + */ +libedit_private wchar_t * +parse__string(wchar_t *out, const wchar_t *in) +{ + wchar_t *rv = out; + int n; + + for (;;) + switch (*in) { + case '\0': + *out = '\0'; + return rv; + + case '\\': + case '^': + if ((n = parse__escape(&in)) == -1) + return NULL; + *out++ = (wchar_t)n; + break; + + case 'M': + if (in[1] == '-' && in[2] != '\0') { + *out++ = '\033'; + in += 2; + break; + } + /*FALLTHROUGH*/ + + default: + *out++ = *in++; + break; + } +} + + +/* parse_cmd(): + * Return the command number for the command string given + * or -1 if one is not found + */ +libedit_private int +parse_cmd(EditLine *el, const wchar_t *cmd) +{ + el_bindings_t *b = el->el_map.help; + size_t i; + + for (i = 0; i < el->el_map.nfunc; i++) + if (wcscmp(b[i].name, cmd) == 0) + return b[i].func; + return -1; +} diff --git a/bin/catsh/libedit/parse.h b/bin/catsh/libedit/parse.h new file mode 100644 index 00000000..fe8eb473 --- /dev/null +++ b/bin/catsh/libedit/parse.h @@ -0,0 +1,48 @@ +/* $NetBSD: parse.h,v 1.9 2016/05/09 21:46:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)parse.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.parse.h: Parser functions + */ +#ifndef _h_el_parse +#define _h_el_parse + +libedit_private int parse_line(EditLine *, const wchar_t *); +libedit_private int parse__escape(const wchar_t **); +libedit_private wchar_t *parse__string(wchar_t *, const wchar_t *); +libedit_private int parse_cmd(EditLine *, const wchar_t *); + +#endif /* _h_el_parse */ diff --git a/bin/catsh/libedit/prompt.c b/bin/catsh/libedit/prompt.c new file mode 100644 index 00000000..6a63f612 --- /dev/null +++ b/bin/catsh/libedit/prompt.c @@ -0,0 +1,202 @@ +/* $NetBSD: prompt.c,v 1.26.8.1 2017/07/23 14:41:26 snj Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)prompt.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: prompt.c,v 1.26.8.1 2017/07/23 14:41:26 snj Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * prompt.c: Prompt printing functions + */ +#include <stdio.h> +#include "el.h" + +static wchar_t *prompt_default(EditLine *); +static wchar_t *prompt_default_r(EditLine *); + +/* prompt_default(): + * Just a default prompt, in case the user did not provide one + */ +static wchar_t * +/*ARGSUSED*/ +prompt_default(EditLine *el __attribute__((__unused__))) +{ + static wchar_t a[3] = L"? "; + + return a; +} + + +/* prompt_default_r(): + * Just a default rprompt, in case the user did not provide one + */ +static wchar_t * +/*ARGSUSED*/ +prompt_default_r(EditLine *el __attribute__((__unused__))) +{ + static wchar_t a[1] = L""; + + return a; +} + + +/* prompt_print(): + * Print the prompt and update the prompt position. + */ +libedit_private void +prompt_print(EditLine *el, int op) +{ + el_prompt_t *elp; + wchar_t *p; + + if (op == EL_PROMPT) + elp = &el->el_prompt; + else + elp = &el->el_rprompt; + + if (elp->p_wide) + p = (*elp->p_func)(el); + else + p = ct_decode_string((char *)(void *)(*elp->p_func)(el), + &el->el_scratch); + + for (; *p; p++) { + if (elp->p_ignore == *p) { + wchar_t *litstart = ++p; + while (*p && *p != elp->p_ignore) + p++; + if (!*p || !p[1]) { + // XXX: We lose the last literal + break; + } + re_putliteral(el, litstart, p++); + continue; + } + re_putc(el, *p, 1); + } + + elp->p_pos.v = el->el_refresh.r_cursor.v; + elp->p_pos.h = el->el_refresh.r_cursor.h; +} + + +/* prompt_init(): + * Initialize the prompt stuff + */ +libedit_private int +prompt_init(EditLine *el) +{ + + el->el_prompt.p_func = prompt_default; + el->el_prompt.p_pos.v = 0; + el->el_prompt.p_pos.h = 0; + el->el_prompt.p_ignore = '\0'; + el->el_rprompt.p_func = prompt_default_r; + el->el_rprompt.p_pos.v = 0; + el->el_rprompt.p_pos.h = 0; + el->el_rprompt.p_ignore = '\0'; + return 0; +} + + +/* prompt_end(): + * Clean up the prompt stuff + */ +libedit_private void +/*ARGSUSED*/ +prompt_end(EditLine *el __attribute__((__unused__))) +{ +} + + +/* prompt_set(): + * Install a prompt printing function + */ +libedit_private int +prompt_set(EditLine *el, el_pfunc_t prf, wchar_t c, int op, int wide) +{ + el_prompt_t *p; + + if (op == EL_PROMPT || op == EL_PROMPT_ESC) + p = &el->el_prompt; + else + p = &el->el_rprompt; + + if (prf == NULL) { + if (op == EL_PROMPT || op == EL_PROMPT_ESC) + p->p_func = prompt_default; + else + p->p_func = prompt_default_r; + } else { + p->p_func = prf; + } + + p->p_ignore = c; + + p->p_pos.v = 0; + p->p_pos.h = 0; + p->p_wide = wide; + + return 0; +} + + +/* prompt_get(): + * Retrieve the prompt printing function + */ +libedit_private int +prompt_get(EditLine *el, el_pfunc_t *prf, wchar_t *c, int op) +{ + el_prompt_t *p; + + if (prf == NULL) + return -1; + + if (op == EL_PROMPT) + p = &el->el_prompt; + else + p = &el->el_rprompt; + + if (prf) + *prf = p->p_func; + if (c) + *c = p->p_ignore; + + return 0; +} diff --git a/bin/catsh/libedit/prompt.h b/bin/catsh/libedit/prompt.h new file mode 100644 index 00000000..2931428d --- /dev/null +++ b/bin/catsh/libedit/prompt.h @@ -0,0 +1,58 @@ +/* $NetBSD: prompt.h,v 1.15 2016/05/09 21:46:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)prompt.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.prompt.h: Prompt printing stuff + */ +#ifndef _h_el_prompt +#define _h_el_prompt + +typedef wchar_t *(*el_pfunc_t)(EditLine *); + +typedef struct el_prompt_t { + el_pfunc_t p_func; /* Function to return the prompt */ + coord_t p_pos; /* position in the line after prompt */ + wchar_t p_ignore; /* character to start/end literal */ + int p_wide; +} el_prompt_t; + +libedit_private void prompt_print(EditLine *, int); +libedit_private int prompt_set(EditLine *, el_pfunc_t, wchar_t, int, int); +libedit_private int prompt_get(EditLine *, el_pfunc_t *, wchar_t *, int); +libedit_private int prompt_init(EditLine *); +libedit_private void prompt_end(EditLine *); + +#endif /* _h_el_prompt */ diff --git a/bin/catsh/libedit/read.c b/bin/catsh/libedit/read.c new file mode 100644 index 00000000..f9b6684d --- /dev/null +++ b/bin/catsh/libedit/read.c @@ -0,0 +1,628 @@ +/* $NetBSD: read.c,v 1.102.6.1 2017/07/23 14:41:26 snj Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)read.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: read.c,v 1.102.6.1 2017/07/23 14:41:26 snj Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * read.c: Terminal read functions + */ +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "el.h" +#include "fcns.h" +#include "read.h" + +#define EL_MAXMACRO 10 + +struct macros { + wchar_t **macro; + int level; + int offset; +}; + +struct el_read_t { + struct macros macros; + el_rfunc_t read_char; /* Function to read a character. */ + int read_errno; +}; + +static int read__fixio(int, int); +static int read_char(EditLine *, wchar_t *); +static int read_getcmd(EditLine *, el_action_t *, wchar_t *); +static void read_clearmacros(struct macros *); +static void read_pop(struct macros *); +static const wchar_t *noedit_wgets(EditLine *, int *); + +/* read_init(): + * Initialize the read stuff + */ +libedit_private int +read_init(EditLine *el) +{ + struct macros *ma; + + if ((el->el_read = el_malloc(sizeof(*el->el_read))) == NULL) + return -1; + + ma = &el->el_read->macros; + if ((ma->macro = el_malloc(EL_MAXMACRO * + sizeof(*ma->macro))) == NULL) { + free(el->el_read); + return -1; + } + ma->level = -1; + ma->offset = 0; + + /* builtin read_char */ + el->el_read->read_char = read_char; + return 0; +} + +/* el_read_end(): + * Free the data structures used by the read stuff. + */ +libedit_private void +read_end(struct el_read_t *el_read) +{ + read_clearmacros(&el_read->macros); + el_free(el_read->macros.macro); + el_read->macros.macro = NULL; + el_free(el_read); +} + +/* el_read_setfn(): + * Set the read char function to the one provided. + * If it is set to EL_BUILTIN_GETCFN, then reset to the builtin one. + */ +libedit_private int +el_read_setfn(struct el_read_t *el_read, el_rfunc_t rc) +{ + el_read->read_char = (rc == EL_BUILTIN_GETCFN) ? read_char : rc; + return 0; +} + + +/* el_read_getfn(): + * return the current read char function, or EL_BUILTIN_GETCFN + * if it is the default one + */ +libedit_private el_rfunc_t +el_read_getfn(struct el_read_t *el_read) +{ + return el_read->read_char == read_char ? + EL_BUILTIN_GETCFN : el_read->read_char; +} + + +/* read__fixio(): + * Try to recover from a read error + */ +/* ARGSUSED */ +static int +read__fixio(int fd __attribute__((__unused__)), int e) +{ + + switch (e) { + case -1: /* Make sure that the code is reachable */ + +#ifdef EWOULDBLOCK + case EWOULDBLOCK: +#ifndef TRY_AGAIN +#define TRY_AGAIN +#endif +#endif /* EWOULDBLOCK */ + +#if defined(POSIX) && defined(EAGAIN) +#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN + case EAGAIN: +#ifndef TRY_AGAIN +#define TRY_AGAIN +#endif +#endif /* EWOULDBLOCK && EWOULDBLOCK != EAGAIN */ +#endif /* POSIX && EAGAIN */ + + e = 0; +#ifdef TRY_AGAIN +#if defined(F_SETFL) && defined(O_NDELAY) + if ((e = fcntl(fd, F_GETFL, 0)) == -1) + return -1; + + if (fcntl(fd, F_SETFL, e & ~O_NDELAY) == -1) + return -1; + else + e = 1; +#endif /* F_SETFL && O_NDELAY */ + +#ifdef FIONBIO + { + int zero = 0; + + if (ioctl(fd, FIONBIO, &zero) == -1) + return -1; + else + e = 1; + } +#endif /* FIONBIO */ + +#endif /* TRY_AGAIN */ + return e ? 0 : -1; + + case EINTR: + return 0; + + default: + return -1; + } +} + + +/* el_push(): + * Push a macro + */ +void +el_wpush(EditLine *el, const wchar_t *str) +{ + struct macros *ma = &el->el_read->macros; + + if (str != NULL && ma->level + 1 < EL_MAXMACRO) { + ma->level++; + if ((ma->macro[ma->level] = wcsdup(str)) != NULL) + return; + ma->level--; + } + terminal_beep(el); + terminal__flush(el); +} + + +/* read_getcmd(): + * Get next command from the input stream, + * return 0 on success or -1 on EOF or error. + * Character values > 255 are not looked up in the map, but inserted. + */ +static int +read_getcmd(EditLine *el, el_action_t *cmdnum, wchar_t *ch) +{ + static const wchar_t meta = (wchar_t)0x80; + el_action_t cmd; + + do { + if (el_wgetc(el, ch) != 1) + return -1; + +#ifdef KANJI + if ((*ch & meta)) { + el->el_state.metanext = 0; + cmd = CcViMap[' ']; + break; + } else +#endif /* KANJI */ + + if (el->el_state.metanext) { + el->el_state.metanext = 0; + *ch |= meta; + } + if (*ch >= N_KEYS) + cmd = ED_INSERT; + else + cmd = el->el_map.current[(unsigned char) *ch]; + if (cmd == ED_SEQUENCE_LEAD_IN) { + keymacro_value_t val; + switch (keymacro_get(el, ch, &val)) { + case XK_CMD: + cmd = val.cmd; + break; + case XK_STR: + el_wpush(el, val.str); + break; + case XK_NOD: + return -1; + default: + EL_ABORT((el->el_errfile, "Bad XK_ type \n")); + break; + } + } + } while (cmd == ED_SEQUENCE_LEAD_IN); + *cmdnum = cmd; + return 0; +} + +/* read_char(): + * Read a character from the tty. + */ +static int +read_char(EditLine *el, wchar_t *cp) +{ + ssize_t num_read; + int tried = 0; + char cbuf[MB_LEN_MAX]; + size_t cbp = 0; + int save_errno = errno; + + again: + el->el_signal->sig_no = 0; + while ((num_read = read(el->el_infd, cbuf + cbp, (size_t)1)) == -1) { + int e = errno; + switch (el->el_signal->sig_no) { + case SIGCONT: + el_wset(el, EL_REFRESH); + /*FALLTHROUGH*/ + case SIGWINCH: + sig_set(el); + goto again; + default: + break; + } + if (!tried && read__fixio(el->el_infd, e) == 0) { + errno = save_errno; + tried = 1; + } else { + errno = e; + *cp = L'\0'; + return -1; + } + } + + /* Test for EOF */ + if (num_read == 0) { + *cp = L'\0'; + return 0; + } + + for (;;) { + mbstate_t mbs; + + ++cbp; + /* This only works because UTF8 is stateless. */ + memset(&mbs, 0, sizeof(mbs)); + switch (mbrtowc(cp, cbuf, cbp, &mbs)) { + case (size_t)-1: + if (cbp > 1) { + /* + * Invalid sequence, discard all bytes + * except the last one. + */ + cbuf[0] = cbuf[cbp - 1]; + cbp = 0; + break; + } else { + /* Invalid byte, discard it. */ + cbp = 0; + goto again; + } + case (size_t)-2: + /* + * We don't support other multibyte charsets. + * The second condition shouldn't happen + * and is here merely for additional safety. + */ + if ((el->el_flags & CHARSET_IS_UTF8) == 0 || + cbp >= MB_LEN_MAX) { + errno = EILSEQ; + *cp = L'\0'; + return -1; + } + /* Incomplete sequence, read another byte. */ + goto again; + default: + /* Valid character, process it. */ + return 1; + } + } +} + +/* read_pop(): + * Pop a macro from the stack + */ +static void +read_pop(struct macros *ma) +{ + int i; + + el_free(ma->macro[0]); + for (i = 0; i < ma->level; i++) + ma->macro[i] = ma->macro[i + 1]; + ma->level--; + ma->offset = 0; +} + +static void +read_clearmacros(struct macros *ma) +{ + while (ma->level >= 0) + el_free(ma->macro[ma->level--]); + ma->offset = 0; +} + +/* el_wgetc(): + * Read a wide character + */ +int +el_wgetc(EditLine *el, wchar_t *cp) +{ + struct macros *ma = &el->el_read->macros; + int num_read; + + terminal__flush(el); + for (;;) { + if (ma->level < 0) + break; + + if (ma->macro[0][ma->offset] == '\0') { + read_pop(ma); + continue; + } + + *cp = ma->macro[0][ma->offset++]; + + if (ma->macro[0][ma->offset] == '\0') { + /* Needed for QuoteMode On */ + read_pop(ma); + } + + return 1; + } + + if (tty_rawmode(el) < 0)/* make sure the tty is set up correctly */ + return 0; + + num_read = (*el->el_read->read_char)(el, cp); + + /* + * Remember the original reason of a read failure + * such that el_wgets() can restore it after doing + * various cleanup operation that might change errno. + */ + if (num_read < 0) + el->el_read->read_errno = errno; + + return num_read; +} + +libedit_private void +read_prepare(EditLine *el) +{ + if (el->el_flags & HANDLE_SIGNALS) + sig_set(el); + if (el->el_flags & NO_TTY) + return; + if ((el->el_flags & (UNBUFFERED|EDIT_DISABLED)) == UNBUFFERED) + tty_rawmode(el); + + /* This is relatively cheap, and things go terribly wrong if + we have the wrong size. */ + el_resize(el); + re_clear_display(el); /* reset the display stuff */ + ch_reset(el); + re_refresh(el); /* print the prompt */ + + if (el->el_flags & UNBUFFERED) + terminal__flush(el); +} + +libedit_private void +read_finish(EditLine *el) +{ + if ((el->el_flags & UNBUFFERED) == 0) + (void) tty_cookedmode(el); + if (el->el_flags & HANDLE_SIGNALS) + sig_clr(el); +} + +static const wchar_t * +noedit_wgets(EditLine *el, int *nread) +{ + el_line_t *lp = &el->el_line; + int num; + + while ((num = (*el->el_read->read_char)(el, lp->lastchar)) == 1) { + if (lp->lastchar + 1 >= lp->limit && + !ch_enlargebufs(el, (size_t)2)) + break; + lp->lastchar++; + if (el->el_flags & UNBUFFERED || + lp->lastchar[-1] == '\r' || + lp->lastchar[-1] == '\n') + break; + } + if (num == -1 && errno == EINTR) + lp->lastchar = lp->buffer; + lp->cursor = lp->lastchar; + *lp->lastchar = '\0'; + *nread = (int)(lp->lastchar - lp->buffer); + return *nread ? lp->buffer : NULL; +} + +const wchar_t * +el_wgets(EditLine *el, int *nread) +{ + int retval; + el_action_t cmdnum = 0; + int num; /* how many chars we have read at NL */ + wchar_t ch; + int nrb; + + if (nread == NULL) + nread = &nrb; + *nread = 0; + el->el_read->read_errno = 0; + + if (el->el_flags & NO_TTY) { + el->el_line.lastchar = el->el_line.buffer; + return noedit_wgets(el, nread); + } + +#ifdef FIONREAD + if (el->el_tty.t_mode == EX_IO && el->el_read->macros.level < 0) { + int chrs = 0; + + (void) ioctl(el->el_infd, FIONREAD, &chrs); + if (chrs == 0) { + if (tty_rawmode(el) < 0) { + errno = 0; + *nread = 0; + return NULL; + } + } + } +#endif /* FIONREAD */ + + if ((el->el_flags & UNBUFFERED) == 0) + read_prepare(el); + + if (el->el_flags & EDIT_DISABLED) { + if ((el->el_flags & UNBUFFERED) == 0) + el->el_line.lastchar = el->el_line.buffer; + terminal__flush(el); + return noedit_wgets(el, nread); + } + + for (num = -1; num == -1;) { /* while still editing this line */ + /* if EOF or error */ + if (read_getcmd(el, &cmdnum, &ch) == -1) + break; + if ((size_t)cmdnum >= el->el_map.nfunc) /* BUG CHECK command */ + continue; /* try again */ + /* now do the real command */ + /* vi redo needs these way down the levels... */ + el->el_state.thiscmd = cmdnum; + el->el_state.thisch = ch; + if (el->el_map.type == MAP_VI && + el->el_map.current == el->el_map.key && + el->el_chared.c_redo.pos < el->el_chared.c_redo.lim) { + if (cmdnum == VI_DELETE_PREV_CHAR && + el->el_chared.c_redo.pos != el->el_chared.c_redo.buf + && iswprint(el->el_chared.c_redo.pos[-1])) + el->el_chared.c_redo.pos--; + else + *el->el_chared.c_redo.pos++ = ch; + } + retval = (*el->el_map.func[cmdnum]) (el, ch); + + /* save the last command here */ + el->el_state.lastcmd = cmdnum; + + /* use any return value */ + switch (retval) { + case CC_CURSOR: + re_refresh_cursor(el); + break; + + case CC_REDISPLAY: + re_clear_lines(el); + re_clear_display(el); + /* FALLTHROUGH */ + + case CC_REFRESH: + re_refresh(el); + break; + + case CC_REFRESH_BEEP: + re_refresh(el); + terminal_beep(el); + break; + + case CC_NORM: /* normal char */ + break; + + case CC_ARGHACK: /* Suggested by Rich Salz */ + /* <rsalz@pineapple.bbn.com> */ + continue; /* keep going... */ + + case CC_EOF: /* end of file typed */ + if ((el->el_flags & UNBUFFERED) == 0) + num = 0; + else if (num == -1) { + *el->el_line.lastchar++ = CONTROL('d'); + el->el_line.cursor = el->el_line.lastchar; + num = 1; + } + break; + + case CC_NEWLINE: /* normal end of line */ + num = (int)(el->el_line.lastchar - el->el_line.buffer); + break; + + case CC_FATAL: /* fatal error, reset to known state */ + /* put (real) cursor in a known place */ + re_clear_display(el); /* reset the display stuff */ + ch_reset(el); /* reset the input pointers */ + read_clearmacros(&el->el_read->macros); + re_refresh(el); /* print the prompt again */ + break; + + case CC_ERROR: + default: /* functions we don't know about */ + terminal_beep(el); + terminal__flush(el); + break; + } + el->el_state.argument = 1; + el->el_state.doingarg = 0; + el->el_chared.c_vcmd.action = NOP; + if (el->el_flags & UNBUFFERED) + break; + } + + terminal__flush(el); /* flush any buffered output */ + /* make sure the tty is set up correctly */ + if ((el->el_flags & UNBUFFERED) == 0) { + read_finish(el); + *nread = num != -1 ? num : 0; + } else + *nread = (int)(el->el_line.lastchar - el->el_line.buffer); + + if (*nread == 0) { + if (num == -1) { + *nread = -1; + if (el->el_read->read_errno) + errno = el->el_read->read_errno; + } + return NULL; + } else + return el->el_line.buffer; +} diff --git a/bin/catsh/libedit/read.h b/bin/catsh/libedit/read.h new file mode 100644 index 00000000..1acf5d67 --- /dev/null +++ b/bin/catsh/libedit/read.h @@ -0,0 +1,45 @@ +/* $NetBSD: read.h,v 1.12 2016/05/22 19:44:26 christos Exp $ */ + +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Anthony Mallet. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * el.read.h: Character reading functions + */ +#ifndef _h_el_read +#define _h_el_read + +libedit_private int read_init(EditLine *); +libedit_private void read_end(struct el_read_t *); +libedit_private void read_prepare(EditLine *); +libedit_private void read_finish(EditLine *); +libedit_private int el_read_setfn(struct el_read_t *, el_rfunc_t); +libedit_private el_rfunc_t el_read_getfn(struct el_read_t *); + +#endif /* _h_el_read */ diff --git a/bin/catsh/libedit/readline.c b/bin/catsh/libedit/readline.c new file mode 100644 index 00000000..bd5602de --- /dev/null +++ b/bin/catsh/libedit/readline.c @@ -0,0 +1,2371 @@ +/* $NetBSD: readline.c,v 1.141 2017/04/21 05:38:03 abhinav Exp $ */ + +/*- + * Copyright (c) 1997 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Jaromir Dolecek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +__RCSID("$NetBSD: readline.c,v 1.141 2017/04/21 05:38:03 abhinav Exp $"); +#endif /* not lint && not SCCSID */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <pwd.h> +#include <setjmp.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <vis.h> + +#include "readline/readline.h" +#include "el.h" +#include "fcns.h" +#include "filecomplete.h" + +void rl_prep_terminal(int); +void rl_deprep_terminal(void); + +/* for rl_complete() */ +#define TAB '\r' + +/* see comment at the #ifdef for sense of this */ +/* #define GDB_411_HACK */ + +/* readline compatibility stuff - look at readline sources/documentation */ +/* to see what these variables mean */ +const char *rl_library_version = "EditLine wrapper"; +int rl_readline_version = RL_READLINE_VERSION; +static char empty[] = { '\0' }; +static char expand_chars[] = { ' ', '\t', '\n', '=', '(', '\0' }; +static char break_chars[] = { ' ', '\t', '\n', '"', '\\', '\'', '`', '@', '$', + '>', '<', '=', ';', '|', '&', '{', '(', '\0' }; +char *rl_readline_name = empty; +FILE *rl_instream = NULL; +FILE *rl_outstream = NULL; +int rl_point = 0; +int rl_end = 0; +char *rl_line_buffer = NULL; +rl_vcpfunc_t *rl_linefunc = NULL; +int rl_done = 0; +VFunction *rl_event_hook = NULL; +KEYMAP_ENTRY_ARRAY emacs_standard_keymap, + emacs_meta_keymap, + emacs_ctlx_keymap; +/* + * The following is not implemented; we always catch signals in the + * libedit fashion: set handlers on entry to el_gets() and clear them + * on the way out. This simplistic approach works for most cases; if + * it does not work for your application, please let us know. + */ +int rl_catch_signals = 1; +int rl_catch_sigwinch = 1; + +int history_base = 1; /* probably never subject to change */ +int history_length = 0; +int history_offset = 0; +int max_input_history = 0; +char history_expansion_char = '!'; +char history_subst_char = '^'; +char *history_no_expand_chars = expand_chars; +Function *history_inhibit_expansion_function = NULL; +char *history_arg_extract(int start, int end, const char *str); + +int rl_inhibit_completion = 0; +int rl_attempted_completion_over = 0; +char *rl_basic_word_break_characters = break_chars; +char *rl_completer_word_break_characters = NULL; +char *rl_completer_quote_characters = NULL; +rl_compentry_func_t *rl_completion_entry_function = NULL; +char *(*rl_completion_word_break_hook)(void) = NULL; +rl_completion_func_t *rl_attempted_completion_function = NULL; +Function *rl_pre_input_hook = NULL; +Function *rl_startup1_hook = NULL; +int (*rl_getc_function)(FILE *) = NULL; +char *rl_terminal_name = NULL; +int rl_already_prompted = 0; +int rl_filename_completion_desired = 0; +int rl_ignore_completion_duplicates = 0; +int readline_echoing_p = 1; +int _rl_print_completions_horizontally = 0; +VFunction *rl_redisplay_function = NULL; +Function *rl_startup_hook = NULL; +VFunction *rl_completion_display_matches_hook = NULL; +VFunction *rl_prep_term_function = (VFunction *)rl_prep_terminal; +VFunction *rl_deprep_term_function = (VFunction *)rl_deprep_terminal; +KEYMAP_ENTRY_ARRAY emacs_meta_keymap; + +/* + * The current prompt string. + */ +char *rl_prompt = NULL; +/* + * This is set to character indicating type of completion being done by + * rl_complete_internal(); this is available for application completion + * functions. + */ +int rl_completion_type = 0; + +/* + * If more than this number of items results from query for possible + * completions, we ask user if they are sure to really display the list. + */ +int rl_completion_query_items = 100; + +/* + * List of characters which are word break characters, but should be left + * in the parsed text when it is passed to the completion function. + * Shell uses this to help determine what kind of completing to do. + */ +char *rl_special_prefixes = NULL; + +/* + * This is the character appended to the completed words if at the end of + * the line. Default is ' ' (a space). + */ +int rl_completion_append_character = ' '; + +/* stuff below is used internally by libedit for readline emulation */ + +static History *h = NULL; +static EditLine *e = NULL; +static rl_command_func_t *map[256]; +static jmp_buf topbuf; + +/* internal functions */ +static unsigned char _el_rl_complete(EditLine *, int); +static unsigned char _el_rl_tstp(EditLine *, int); +static char *_get_prompt(EditLine *); +static int _getc_function(EditLine *, wchar_t *); +static int _history_expand_command(const char *, size_t, size_t, + char **); +static char *_rl_compat_sub(const char *, const char *, + const char *, int); +static int _rl_event_read_char(EditLine *, wchar_t *); +static void _rl_update_pos(void); + +static HIST_ENTRY rl_he; + +/* ARGSUSED */ +static char * +_get_prompt(EditLine *el __attribute__((__unused__))) +{ + rl_already_prompted = 1; + return rl_prompt; +} + + +/* + * read one key from user defined input function + */ +static int +/*ARGSUSED*/ +_getc_function(EditLine *el __attribute__((__unused__)), wchar_t *c) +{ + int i; + + i = (*rl_getc_function)(rl_instream); + if (i == -1) + return 0; + *c = (wchar_t)i; + return 1; +} + +static void +_resize_fun(EditLine *el, void *a) +{ + const LineInfo *li; + char **ap = a; + + li = el_line(el); + /* a cheesy way to get rid of const cast. */ + *ap = memchr(li->buffer, *li->buffer, (size_t)1); +} + +static const char * +_default_history_file(void) +{ + struct passwd *p; + static char *path; + size_t len; + + if (path) + return path; + + if ((p = getpwuid(getuid())) == NULL) + return NULL; + + len = strlen(p->pw_dir) + sizeof("/.history"); + if ((path = malloc(len)) == NULL) + return NULL; + + (void)snprintf(path, len, "%s/.history", p->pw_dir); + return path; +} + +/* + * READLINE compatibility stuff + */ + +/* + * Set the prompt + */ +int +rl_set_prompt(const char *prompt) +{ + char *p; + + if (!prompt) + prompt = ""; + if (rl_prompt != NULL && strcmp(rl_prompt, prompt) == 0) + return 0; + if (rl_prompt) + el_free(rl_prompt); + rl_prompt = strdup(prompt); + if (rl_prompt == NULL) + return -1; + + while ((p = strchr(rl_prompt, RL_PROMPT_END_IGNORE)) != NULL) + *p = RL_PROMPT_START_IGNORE; + + return 0; +} + +/* + * initialize rl compat stuff + */ +int +rl_initialize(void) +{ + HistEvent ev; + int editmode = 1; + struct termios t; + + if (e != NULL) + el_end(e); + if (h != NULL) + history_end(h); + + if (!rl_instream) + rl_instream = stdin; + if (!rl_outstream) + rl_outstream = stdout; + + /* + * See if we don't really want to run the editor + */ + if (tcgetattr(fileno(rl_instream), &t) != -1 && (t.c_lflag & ECHO) == 0) + editmode = 0; + + e = el_init(rl_readline_name, rl_instream, rl_outstream, stderr); + + if (!editmode) + el_set(e, EL_EDITMODE, 0); + + h = history_init(); + if (!e || !h) + return -1; + + history(h, &ev, H_SETSIZE, INT_MAX); /* unlimited */ + history_length = 0; + max_input_history = INT_MAX; + el_set(e, EL_HIST, history, h); + + /* Setup resize function */ + el_set(e, EL_RESIZE, _resize_fun, &rl_line_buffer); + + /* setup getc function if valid */ + if (rl_getc_function) + el_set(e, EL_GETCFN, _getc_function); + + /* for proper prompt printing in readline() */ + if (rl_set_prompt("") == -1) { + history_end(h); + el_end(e); + return -1; + } + el_set(e, EL_PROMPT, _get_prompt, RL_PROMPT_START_IGNORE); + el_set(e, EL_SIGNAL, rl_catch_signals); + + /* set default mode to "emacs"-style and read setting afterwards */ + /* so this can be overridden */ + el_set(e, EL_EDITOR, "emacs"); + if (rl_terminal_name != NULL) + el_set(e, EL_TERMINAL, rl_terminal_name); + else + el_get(e, EL_TERMINAL, &rl_terminal_name); + + /* + * Word completion - this has to go AFTER rebinding keys + * to emacs-style. + */ + el_set(e, EL_ADDFN, "rl_complete", + "ReadLine compatible completion function", + _el_rl_complete); + el_set(e, EL_BIND, "^I", "rl_complete", NULL); + + /* + * Send TSTP when ^Z is pressed. + */ + el_set(e, EL_ADDFN, "rl_tstp", + "ReadLine compatible suspend function", + _el_rl_tstp); + el_set(e, EL_BIND, "^Z", "rl_tstp", NULL); + + /* + * Set some readline compatible key-bindings. + */ + el_set(e, EL_BIND, "^R", "em-inc-search-prev", NULL); + + /* + * Allow the use of Home/End keys. + */ + el_set(e, EL_BIND, "\\e[1~", "ed-move-to-beg", NULL); + el_set(e, EL_BIND, "\\e[4~", "ed-move-to-end", NULL); + el_set(e, EL_BIND, "\\e[7~", "ed-move-to-beg", NULL); + el_set(e, EL_BIND, "\\e[8~", "ed-move-to-end", NULL); + el_set(e, EL_BIND, "\\e[H", "ed-move-to-beg", NULL); + el_set(e, EL_BIND, "\\e[F", "ed-move-to-end", NULL); + + /* + * Allow the use of the Delete/Insert keys. + */ + el_set(e, EL_BIND, "\\e[3~", "ed-delete-next-char", NULL); + el_set(e, EL_BIND, "\\e[2~", "ed-quoted-insert", NULL); + + /* + * Ctrl-left-arrow and Ctrl-right-arrow for word moving. + */ + el_set(e, EL_BIND, "\\e[1;5C", "em-next-word", NULL); + el_set(e, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL); + el_set(e, EL_BIND, "\\e[5C", "em-next-word", NULL); + el_set(e, EL_BIND, "\\e[5D", "ed-prev-word", NULL); + el_set(e, EL_BIND, "\\e\\e[C", "em-next-word", NULL); + el_set(e, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL); + + /* read settings from configuration file */ + el_source(e, NULL); + + /* + * Unfortunately, some applications really do use rl_point + * and rl_line_buffer directly. + */ + _resize_fun(e, &rl_line_buffer); + _rl_update_pos(); + + if (rl_startup_hook) + (*rl_startup_hook)(NULL, 0); + + return 0; +} + + +/* + * read one line from input stream and return it, chomping + * trailing newline (if there is any) + */ +char * +readline(const char *p) +{ + HistEvent ev; + const char * volatile prompt = p; + int count; + const char *ret; + char *buf; + static int used_event_hook; + + if (e == NULL || h == NULL) + rl_initialize(); + + rl_done = 0; + + (void)setjmp(topbuf); + + /* update prompt accordingly to what has been passed */ + if (rl_set_prompt(prompt) == -1) + return NULL; + + if (rl_pre_input_hook) + (*rl_pre_input_hook)(NULL, 0); + + if (rl_event_hook && !(e->el_flags&NO_TTY)) { + el_set(e, EL_GETCFN, _rl_event_read_char); + used_event_hook = 1; + } + + if (!rl_event_hook && used_event_hook) { + el_set(e, EL_GETCFN, EL_BUILTIN_GETCFN); + used_event_hook = 0; + } + + rl_already_prompted = 0; + + /* get one line from input stream */ + ret = el_gets(e, &count); + + if (ret && count > 0) { + int lastidx; + + buf = strdup(ret); + if (buf == NULL) + return NULL; + lastidx = count - 1; + if (buf[lastidx] == '\n') + buf[lastidx] = '\0'; + } else + buf = NULL; + + history(h, &ev, H_GETSIZE); + history_length = ev.num; + + return buf; +} + +/* + * history functions + */ + +/* + * is normally called before application starts to use + * history expansion functions + */ +void +using_history(void) +{ + if (h == NULL || e == NULL) + rl_initialize(); + history_offset = history_length; +} + + +/* + * substitute ``what'' with ``with'', returning resulting string; if + * globally == 1, substitutes all occurrences of what, otherwise only the + * first one + */ +static char * +_rl_compat_sub(const char *str, const char *what, const char *with, + int globally) +{ + const char *s; + char *r, *result; + size_t len, with_len, what_len; + + len = strlen(str); + with_len = strlen(with); + what_len = strlen(what); + + /* calculate length we need for result */ + s = str; + while (*s) { + if (*s == *what && !strncmp(s, what, what_len)) { + len += with_len - what_len; + if (!globally) + break; + s += what_len; + } else + s++; + } + r = result = el_malloc((len + 1) * sizeof(*r)); + if (result == NULL) + return NULL; + s = str; + while (*s) { + if (*s == *what && !strncmp(s, what, what_len)) { + (void)strncpy(r, with, with_len); + r += with_len; + s += what_len; + if (!globally) { + (void)strcpy(r, s); + return result; + } + } else + *r++ = *s++; + } + *r = '\0'; + return result; +} + +static char *last_search_pat; /* last !?pat[?] search pattern */ +static char *last_search_match; /* last !?pat[?] that matched */ + +const char * +get_history_event(const char *cmd, int *cindex, int qchar) +{ + int idx, sign, sub, num, begin, ret; + size_t len; + char *pat; + const char *rptr; + HistEvent ev; + + idx = *cindex; + if (cmd[idx++] != history_expansion_char) + return NULL; + + /* find out which event to take */ + if (cmd[idx] == history_expansion_char || cmd[idx] == '\0') { + if (history(h, &ev, H_FIRST) != 0) + return NULL; + *cindex = cmd[idx]? (idx + 1):idx; + return ev.str; + } + sign = 0; + if (cmd[idx] == '-') { + sign = 1; + idx++; + } + + if ('0' <= cmd[idx] && cmd[idx] <= '9') { + HIST_ENTRY *he; + + num = 0; + while (cmd[idx] && '0' <= cmd[idx] && cmd[idx] <= '9') { + num = num * 10 + cmd[idx] - '0'; + idx++; + } + if (sign) + num = history_length - num + history_base; + + if (!(he = history_get(num))) + return NULL; + + *cindex = idx; + return he->line; + } + sub = 0; + if (cmd[idx] == '?') { + sub = 1; + idx++; + } + begin = idx; + while (cmd[idx]) { + if (cmd[idx] == '\n') + break; + if (sub && cmd[idx] == '?') + break; + if (!sub && (cmd[idx] == ':' || cmd[idx] == ' ' + || cmd[idx] == '\t' || cmd[idx] == qchar)) + break; + idx++; + } + len = (size_t)idx - (size_t)begin; + if (sub && cmd[idx] == '?') + idx++; + if (sub && len == 0 && last_search_pat && *last_search_pat) + pat = last_search_pat; + else if (len == 0) + return NULL; + else { + if ((pat = el_malloc((len + 1) * sizeof(*pat))) == NULL) + return NULL; + (void)strncpy(pat, cmd + begin, len); + pat[len] = '\0'; + } + + if (history(h, &ev, H_CURR) != 0) { + if (pat != last_search_pat) + el_free(pat); + return NULL; + } + num = ev.num; + + if (sub) { + if (pat != last_search_pat) { + if (last_search_pat) + el_free(last_search_pat); + last_search_pat = pat; + } + ret = history_search(pat, -1); + } else + ret = history_search_prefix(pat, -1); + + if (ret == -1) { + /* restore to end of list on failed search */ + history(h, &ev, H_FIRST); + (void)fprintf(rl_outstream, "%s: Event not found\n", pat); + if (pat != last_search_pat) + el_free(pat); + return NULL; + } + + if (sub && len) { + if (last_search_match && last_search_match != pat) + el_free(last_search_match); + last_search_match = pat; + } + + if (pat != last_search_pat) + el_free(pat); + + if (history(h, &ev, H_CURR) != 0) + return NULL; + *cindex = idx; + rptr = ev.str; + + /* roll back to original position */ + (void)history(h, &ev, H_SET, num); + + return rptr; +} + +/* + * the real function doing history expansion - takes as argument command + * to do and data upon which the command should be executed + * does expansion the way I've understood readline documentation + * + * returns 0 if data was not modified, 1 if it was and 2 if the string + * should be only printed and not executed; in case of error, + * returns -1 and *result points to NULL + * it's the caller's responsibility to free() the string returned in *result + */ +static int +_history_expand_command(const char *command, size_t offs, size_t cmdlen, + char **result) +{ + char *tmp, *search = NULL, *aptr; + const char *ptr, *cmd; + static char *from = NULL, *to = NULL; + int start, end, idx, has_mods = 0; + int p_on = 0, g_on = 0; + + *result = NULL; + aptr = NULL; + ptr = NULL; + + /* First get event specifier */ + idx = 0; + + if (strchr(":^*$", command[offs + 1])) { + char str[4]; + /* + * "!:" is shorthand for "!!:". + * "!^", "!*" and "!$" are shorthand for + * "!!:^", "!!:*" and "!!:$" respectively. + */ + str[0] = str[1] = '!'; + str[2] = '0'; + ptr = get_history_event(str, &idx, 0); + idx = (command[offs + 1] == ':')? 1:0; + has_mods = 1; + } else { + if (command[offs + 1] == '#') { + /* use command so far */ + if ((aptr = el_malloc((offs + 1) * sizeof(*aptr))) + == NULL) + return -1; + (void)strncpy(aptr, command, offs); + aptr[offs] = '\0'; + idx = 1; + } else { + int qchar; + + qchar = (offs > 0 && command[offs - 1] == '"')? '"':0; + ptr = get_history_event(command + offs, &idx, qchar); + } + has_mods = command[offs + (size_t)idx] == ':'; + } + + if (ptr == NULL && aptr == NULL) + return -1; + + if (!has_mods) { + *result = strdup(aptr ? aptr : ptr); + if (aptr) + el_free(aptr); + if (*result == NULL) + return -1; + return 1; + } + + cmd = command + offs + idx + 1; + + /* Now parse any word designators */ + + if (*cmd == '%') /* last word matched by ?pat? */ + tmp = strdup(last_search_match? last_search_match:""); + else if (strchr("^*$-0123456789", *cmd)) { + start = end = -1; + if (*cmd == '^') + start = end = 1, cmd++; + else if (*cmd == '$') + start = -1, cmd++; + else if (*cmd == '*') + start = 1, cmd++; + else if (*cmd == '-' || isdigit((unsigned char) *cmd)) { + start = 0; + while (*cmd && '0' <= *cmd && *cmd <= '9') + start = start * 10 + *cmd++ - '0'; + + if (*cmd == '-') { + if (isdigit((unsigned char) cmd[1])) { + cmd++; + end = 0; + while (*cmd && '0' <= *cmd && *cmd <= '9') + end = end * 10 + *cmd++ - '0'; + } else if (cmd[1] == '$') { + cmd += 2; + end = -1; + } else { + cmd++; + end = -2; + } + } else if (*cmd == '*') + end = -1, cmd++; + else + end = start; + } + tmp = history_arg_extract(start, end, aptr? aptr:ptr); + if (tmp == NULL) { + (void)fprintf(rl_outstream, "%s: Bad word specifier", + command + offs + idx); + if (aptr) + el_free(aptr); + return -1; + } + } else + tmp = strdup(aptr? aptr:ptr); + + if (aptr) + el_free(aptr); + + if (*cmd == '\0' || ((size_t)(cmd - (command + offs)) >= cmdlen)) { + *result = tmp; + return 1; + } + + for (; *cmd; cmd++) { + if (*cmd == ':') + continue; + else if (*cmd == 'h') { /* remove trailing path */ + if ((aptr = strrchr(tmp, '/')) != NULL) + *aptr = '\0'; + } else if (*cmd == 't') { /* remove leading path */ + if ((aptr = strrchr(tmp, '/')) != NULL) { + aptr = strdup(aptr + 1); + el_free(tmp); + tmp = aptr; + } + } else if (*cmd == 'r') { /* remove trailing suffix */ + if ((aptr = strrchr(tmp, '.')) != NULL) + *aptr = '\0'; + } else if (*cmd == 'e') { /* remove all but suffix */ + if ((aptr = strrchr(tmp, '.')) != NULL) { + aptr = strdup(aptr); + el_free(tmp); + tmp = aptr; + } + } else if (*cmd == 'p') /* print only */ + p_on = 1; + else if (*cmd == 'g') + g_on = 2; + else if (*cmd == 's' || *cmd == '&') { + char *what, *with, delim; + size_t len, from_len; + size_t size; + + if (*cmd == '&' && (from == NULL || to == NULL)) + continue; + else if (*cmd == 's') { + delim = *(++cmd), cmd++; + size = 16; + what = el_realloc(from, size * sizeof(*what)); + if (what == NULL) { + el_free(from); + el_free(tmp); + return 0; + } + len = 0; + for (; *cmd && *cmd != delim; cmd++) { + if (*cmd == '\\' && cmd[1] == delim) + cmd++; + if (len >= size) { + char *nwhat; + nwhat = el_realloc(what, + (size <<= 1) * + sizeof(*nwhat)); + if (nwhat == NULL) { + el_free(what); + el_free(tmp); + return 0; + } + what = nwhat; + } + what[len++] = *cmd; + } + what[len] = '\0'; + from = what; + if (*what == '\0') { + el_free(what); + if (search) { + from = strdup(search); + if (from == NULL) { + el_free(tmp); + return 0; + } + } else { + from = NULL; + el_free(tmp); + return -1; + } + } + cmd++; /* shift after delim */ + if (!*cmd) + continue; + + size = 16; + with = el_realloc(to, size * sizeof(*with)); + if (with == NULL) { + el_free(to); + el_free(tmp); + return -1; + } + len = 0; + from_len = strlen(from); + for (; *cmd && *cmd != delim; cmd++) { + if (len + from_len + 1 >= size) { + char *nwith; + size += from_len + 1; + nwith = el_realloc(with, + size * sizeof(*nwith)); + if (nwith == NULL) { + el_free(with); + el_free(tmp); + return -1; + } + with = nwith; + } + if (*cmd == '&') { + /* safe */ + (void)strcpy(&with[len], from); + len += from_len; + continue; + } + if (*cmd == '\\' + && (*(cmd + 1) == delim + || *(cmd + 1) == '&')) + cmd++; + with[len++] = *cmd; + } + with[len] = '\0'; + to = with; + } + + aptr = _rl_compat_sub(tmp, from, to, g_on); + if (aptr) { + el_free(tmp); + tmp = aptr; + } + g_on = 0; + } + } + *result = tmp; + return p_on? 2:1; +} + + +/* + * csh-style history expansion + */ +int +history_expand(char *str, char **output) +{ + int ret = 0; + size_t idx, i, size; + char *tmp, *result; + + if (h == NULL || e == NULL) + rl_initialize(); + + if (history_expansion_char == 0) { + *output = strdup(str); + return 0; + } + + *output = NULL; + if (str[0] == history_subst_char) { + /* ^foo^foo2^ is equivalent to !!:s^foo^foo2^ */ + *output = el_malloc((strlen(str) + 4 + 1) * sizeof(**output)); + if (*output == NULL) + return 0; + (*output)[0] = (*output)[1] = history_expansion_char; + (*output)[2] = ':'; + (*output)[3] = 's'; + (void)strcpy((*output) + 4, str); + str = *output; + } else { + *output = strdup(str); + if (*output == NULL) + return 0; + } + +#define ADD_STRING(what, len, fr) \ + { \ + if (idx + len + 1 > size) { \ + char *nresult = el_realloc(result, \ + (size += len + 1) * sizeof(*nresult)); \ + if (nresult == NULL) { \ + el_free(*output); \ + if (/*CONSTCOND*/fr) \ + el_free(tmp); \ + return 0; \ + } \ + result = nresult; \ + } \ + (void)strncpy(&result[idx], what, len); \ + idx += len; \ + result[idx] = '\0'; \ + } + + result = NULL; + size = idx = 0; + tmp = NULL; + for (i = 0; str[i];) { + int qchar, loop_again; + size_t len, start, j; + + qchar = 0; + loop_again = 1; + start = j = i; +loop: + for (; str[j]; j++) { + if (str[j] == '\\' && + str[j + 1] == history_expansion_char) { + len = strlen(&str[j + 1]) + 1; + memmove(&str[j], &str[j + 1], len); + continue; + } + if (!loop_again) { + if (isspace((unsigned char) str[j]) + || str[j] == qchar) + break; + } + if (str[j] == history_expansion_char + && !strchr(history_no_expand_chars, str[j + 1]) + && (!history_inhibit_expansion_function || + (*history_inhibit_expansion_function)(str, + (int)j) == 0)) + break; + } + + if (str[j] && loop_again) { + i = j; + qchar = (j > 0 && str[j - 1] == '"' )? '"':0; + j++; + if (str[j] == history_expansion_char) + j++; + loop_again = 0; + goto loop; + } + len = i - start; + ADD_STRING(&str[start], len, 0); + + if (str[i] == '\0' || str[i] != history_expansion_char) { + len = j - i; + ADD_STRING(&str[i], len, 0); + if (start == 0) + ret = 0; + else + ret = 1; + break; + } + ret = _history_expand_command (str, i, (j - i), &tmp); + if (ret > 0 && tmp) { + len = strlen(tmp); + ADD_STRING(tmp, len, 1); + } + if (tmp) { + el_free(tmp); + tmp = NULL; + } + i = j; + } + + /* ret is 2 for "print only" option */ + if (ret == 2) { + add_history(result); +#ifdef GDB_411_HACK + /* gdb 4.11 has been shipped with readline, where */ + /* history_expand() returned -1 when the line */ + /* should not be executed; in readline 2.1+ */ + /* it should return 2 in such a case */ + ret = -1; +#endif + } + el_free(*output); + *output = result; + + return ret; +} + +/* +* Return a string consisting of arguments of "str" from "start" to "end". +*/ +char * +history_arg_extract(int start, int end, const char *str) +{ + size_t i, len, max; + char **arr, *result = NULL; + + arr = history_tokenize(str); + if (!arr) + return NULL; + if (arr && *arr == NULL) + goto out; + + for (max = 0; arr[max]; max++) + continue; + max--; + + if (start == '$') + start = (int)max; + if (end == '$') + end = (int)max; + if (end < 0) + end = (int)max + end + 1; + if (start < 0) + start = end; + + if (start < 0 || end < 0 || (size_t)start > max || + (size_t)end > max || start > end) + goto out; + + for (i = (size_t)start, len = 0; i <= (size_t)end; i++) + len += strlen(arr[i]) + 1; + len++; + result = el_malloc(len * sizeof(*result)); + if (result == NULL) + goto out; + + for (i = (size_t)start, len = 0; i <= (size_t)end; i++) { + (void)strcpy(result + len, arr[i]); + len += strlen(arr[i]); + if (i < (size_t)end) + result[len++] = ' '; + } + result[len] = '\0'; + +out: + for (i = 0; arr[i]; i++) + el_free(arr[i]); + el_free(arr); + + return result; +} + +/* + * Parse the string into individual tokens, + * similar to how shell would do it. + */ +char ** +history_tokenize(const char *str) +{ + int size = 1, idx = 0, i, start; + size_t len; + char **result = NULL, *temp, delim = '\0'; + + for (i = 0; str[i];) { + while (isspace((unsigned char) str[i])) + i++; + start = i; + for (; str[i];) { + if (str[i] == '\\') { + if (str[i+1] != '\0') + i++; + } else if (str[i] == delim) + delim = '\0'; + else if (!delim && + (isspace((unsigned char) str[i]) || + strchr("()<>;&|$", str[i]))) + break; + else if (!delim && strchr("'`\"", str[i])) + delim = str[i]; + if (str[i]) + i++; + } + + if (idx + 2 >= size) { + char **nresult; + size <<= 1; + nresult = el_realloc(result, (size_t)size * sizeof(*nresult)); + if (nresult == NULL) { + el_free(result); + return NULL; + } + result = nresult; + } + len = (size_t)i - (size_t)start; + temp = el_malloc((size_t)(len + 1) * sizeof(*temp)); + if (temp == NULL) { + for (i = 0; i < idx; i++) + el_free(result[i]); + el_free(result); + return NULL; + } + (void)strncpy(temp, &str[start], len); + temp[len] = '\0'; + result[idx++] = temp; + result[idx] = NULL; + if (str[i]) + i++; + } + return result; +} + + +/* + * limit size of history record to ``max'' events + */ +void +stifle_history(int max) +{ + HistEvent ev; + HIST_ENTRY *he; + + if (h == NULL || e == NULL) + rl_initialize(); + + if (history(h, &ev, H_SETSIZE, max) == 0) { + max_input_history = max; + if (history_length > max) + history_base = history_length - max; + while (history_length > max) { + he = remove_history(0); + el_free(he->data); + el_free((void *)(unsigned long)he->line); + el_free(he); + } + } +} + + +/* + * "unlimit" size of history - set the limit to maximum allowed int value + */ +int +unstifle_history(void) +{ + HistEvent ev; + int omax; + + history(h, &ev, H_SETSIZE, INT_MAX); + omax = max_input_history; + max_input_history = INT_MAX; + return omax; /* some value _must_ be returned */ +} + + +int +history_is_stifled(void) +{ + + /* cannot return true answer */ + return max_input_history != INT_MAX; +} + +static const char _history_tmp_template[] = "/tmp/.historyXXXXXX"; + +int +history_truncate_file (const char *filename, int nlines) +{ + int ret = 0; + FILE *fp, *tp; + char template[sizeof(_history_tmp_template)]; + char buf[4096]; + int fd; + char *cp; + off_t off; + int count = 0; + ssize_t left = 0; + + if (filename == NULL && (filename = _default_history_file()) == NULL) + return errno; + if ((fp = fopen(filename, "r+")) == NULL) + return errno; + strcpy(template, _history_tmp_template); + if ((fd = mkstemp(template)) == -1) { + ret = errno; + goto out1; + } + + if ((tp = fdopen(fd, "r+")) == NULL) { + close(fd); + ret = errno; + goto out2; + } + + for(;;) { + if (fread(buf, sizeof(buf), (size_t)1, fp) != 1) { + if (ferror(fp)) { + ret = errno; + break; + } + if (fseeko(fp, (off_t)sizeof(buf) * count, SEEK_SET) == + (off_t)-1) { + ret = errno; + break; + } + left = (ssize_t)fread(buf, (size_t)1, sizeof(buf), fp); + if (ferror(fp)) { + ret = errno; + break; + } + if (left == 0) { + count--; + left = sizeof(buf); + } else if (fwrite(buf, (size_t)left, (size_t)1, tp) + != 1) { + ret = errno; + break; + } + fflush(tp); + break; + } + if (fwrite(buf, sizeof(buf), (size_t)1, tp) != 1) { + ret = errno; + break; + } + count++; + } + if (ret) + goto out3; + cp = buf + left - 1; + if(*cp != '\n') + cp++; + for(;;) { + while (--cp >= buf) { + if (*cp == '\n') { + if (--nlines == 0) { + if (++cp >= buf + sizeof(buf)) { + count++; + cp = buf; + } + break; + } + } + } + if (nlines <= 0 || count == 0) + break; + count--; + if (fseeko(tp, (off_t)sizeof(buf) * count, SEEK_SET) < 0) { + ret = errno; + break; + } + if (fread(buf, sizeof(buf), (size_t)1, tp) != 1) { + if (ferror(tp)) { + ret = errno; + break; + } + ret = EAGAIN; + break; + } + cp = buf + sizeof(buf); + } + + if (ret || nlines > 0) + goto out3; + + if (fseeko(fp, (off_t)0, SEEK_SET) == (off_t)-1) { + ret = errno; + goto out3; + } + + if (fseeko(tp, (off_t)sizeof(buf) * count + (cp - buf), SEEK_SET) == + (off_t)-1) { + ret = errno; + goto out3; + } + + for(;;) { + if ((left = (ssize_t)fread(buf, (size_t)1, sizeof(buf), tp)) == 0) { + if (ferror(fp)) + ret = errno; + break; + } + if (fwrite(buf, (size_t)left, (size_t)1, fp) != 1) { + ret = errno; + break; + } + } + fflush(fp); + if((off = ftello(fp)) > 0) + (void)ftruncate(fileno(fp), off); +out3: + fclose(tp); +out2: + unlink(template); +out1: + fclose(fp); + + return ret; +} + + +/* + * read history from a file given + */ +int +read_history(const char *filename) +{ + HistEvent ev; + + if (h == NULL || e == NULL) + rl_initialize(); + if (filename == NULL && (filename = _default_history_file()) == NULL) + return errno; + return history(h, &ev, H_LOAD, filename) == -1 ? + (errno ? errno : EINVAL) : 0; +} + + +/* + * write history to a file given + */ +int +write_history(const char *filename) +{ + HistEvent ev; + + if (h == NULL || e == NULL) + rl_initialize(); + if (filename == NULL && (filename = _default_history_file()) == NULL) + return errno; + return history(h, &ev, H_SAVE, filename) == -1 ? + (errno ? errno : EINVAL) : 0; +} + + +/* + * returns history ``num''th event + * + * returned pointer points to static variable + */ +HIST_ENTRY * +history_get(int num) +{ + static HIST_ENTRY she; + HistEvent ev; + int curr_num; + + if (h == NULL || e == NULL) + rl_initialize(); + + if (num < history_base) + return NULL; + + /* save current position */ + if (history(h, &ev, H_CURR) != 0) + return NULL; + curr_num = ev.num; + + /* + * use H_DELDATA to set to nth history (without delete) by passing + * (void **)-1 -- as in history_set_pos + */ + if (history(h, &ev, H_DELDATA, num - history_base, (void **)-1) != 0) + goto out; + + /* get current entry */ + if (history(h, &ev, H_CURR) != 0) + goto out; + if (history(h, &ev, H_NEXT_EVDATA, ev.num, &she.data) != 0) + goto out; + she.line = ev.str; + + /* restore pointer to where it was */ + (void)history(h, &ev, H_SET, curr_num); + + return &she; + +out: + /* restore pointer to where it was */ + (void)history(h, &ev, H_SET, curr_num); + return NULL; +} + + +/* + * add the line to history table + */ +int +add_history(const char *line) +{ + HistEvent ev; + + if (h == NULL || e == NULL) + rl_initialize(); + + if (history(h, &ev, H_ENTER, line) == -1) + return 0; + + (void)history(h, &ev, H_GETSIZE); + if (ev.num == history_length) + history_base++; + else + history_length = ev.num; + return 0; +} + + +/* + * remove the specified entry from the history list and return it. + */ +HIST_ENTRY * +remove_history(int num) +{ + HIST_ENTRY *he; + HistEvent ev; + + if (h == NULL || e == NULL) + rl_initialize(); + + if ((he = el_malloc(sizeof(*he))) == NULL) + return NULL; + + if (history(h, &ev, H_DELDATA, num, &he->data) != 0) { + el_free(he); + return NULL; + } + + he->line = ev.str; + if (history(h, &ev, H_GETSIZE) == 0) + history_length = ev.num; + + return he; +} + + +/* + * replace the line and data of the num-th entry + */ +HIST_ENTRY * +replace_history_entry(int num, const char *line, histdata_t data) +{ + HIST_ENTRY *he; + HistEvent ev; + int curr_num; + + if (h == NULL || e == NULL) + rl_initialize(); + + /* save current position */ + if (history(h, &ev, H_CURR) != 0) + return NULL; + curr_num = ev.num; + + /* start from the oldest */ + if (history(h, &ev, H_LAST) != 0) + return NULL; /* error */ + + if ((he = el_malloc(sizeof(*he))) == NULL) + return NULL; + + /* look forwards for event matching specified offset */ + if (history(h, &ev, H_NEXT_EVDATA, num, &he->data)) + goto out; + + he->line = strdup(ev.str); + if (he->line == NULL) + goto out; + + if (history(h, &ev, H_REPLACE, line, data)) + goto out; + + /* restore pointer to where it was */ + if (history(h, &ev, H_SET, curr_num)) + goto out; + + return he; +out: + el_free(he); + return NULL; +} + +/* + * clear the history list - delete all entries + */ +void +clear_history(void) +{ + HistEvent ev; + + if (h == NULL || e == NULL) + rl_initialize(); + + (void)history(h, &ev, H_CLEAR); + history_offset = history_length = 0; +} + + +/* + * returns offset of the current history event + */ +int +where_history(void) +{ + return history_offset; +} + +static HIST_ENTRY **_history_listp; +static HIST_ENTRY *_history_list; + +HIST_ENTRY ** +history_list(void) +{ + HistEvent ev; + HIST_ENTRY **nlp, *nl; + int i; + + if (history(h, &ev, H_LAST) != 0) + return NULL; + + if ((nlp = el_realloc(_history_listp, + (size_t)history_length * sizeof(*nlp))) == NULL) + return NULL; + _history_listp = nlp; + + if ((nl = el_realloc(_history_list, + (size_t)history_length * sizeof(*nl))) == NULL) + return NULL; + _history_list = nl; + + i = 0; + do { + _history_listp[i] = &_history_list[i]; + _history_list[i].line = ev.str; + _history_list[i].data = NULL; + if (i++ == history_length) + abort(); + } while (history(h, &ev, H_PREV) == 0); + return _history_listp; +} + +/* + * returns current history event or NULL if there is no such event + */ +HIST_ENTRY * +current_history(void) +{ + HistEvent ev; + + if (history(h, &ev, H_PREV_EVENT, history_offset + 1) != 0) + return NULL; + + rl_he.line = ev.str; + rl_he.data = NULL; + return &rl_he; +} + + +/* + * returns total number of bytes history events' data are using + */ +int +history_total_bytes(void) +{ + HistEvent ev; + int curr_num; + size_t size; + + if (history(h, &ev, H_CURR) != 0) + return -1; + curr_num = ev.num; + + (void)history(h, &ev, H_FIRST); + size = 0; + do + size += strlen(ev.str) * sizeof(*ev.str); + while (history(h, &ev, H_NEXT) == 0); + + /* get to the same position as before */ + history(h, &ev, H_PREV_EVENT, curr_num); + + return (int)size; +} + + +/* + * sets the position in the history list to ``pos'' + */ +int +history_set_pos(int pos) +{ + if (pos >= history_length || pos < 0) + return 0; + + history_offset = pos; + return 1; +} + + +/* + * returns previous event in history and shifts pointer accordingly + * Note that readline and editline define directions in opposite ways. + */ +HIST_ENTRY * +previous_history(void) +{ + HistEvent ev; + + if (history_offset == 0) + return NULL; + + if (history(h, &ev, H_LAST) != 0) + return NULL; + + history_offset--; + return current_history(); +} + + +/* + * returns next event in history and shifts pointer accordingly + */ +HIST_ENTRY * +next_history(void) +{ + HistEvent ev; + + if (history_offset >= history_length) + return NULL; + + if (history(h, &ev, H_LAST) != 0) + return NULL; + + history_offset++; + return current_history(); +} + + +/* + * searches for first history event containing the str + */ +int +history_search(const char *str, int direction) +{ + HistEvent ev; + const char *strp; + int curr_num; + + if (history(h, &ev, H_CURR) != 0) + return -1; + curr_num = ev.num; + + for (;;) { + if ((strp = strstr(ev.str, str)) != NULL) + return (int)(strp - ev.str); + if (history(h, &ev, direction < 0 ? H_NEXT:H_PREV) != 0) + break; + } + (void)history(h, &ev, H_SET, curr_num); + return -1; +} + + +/* + * searches for first history event beginning with str + */ +int +history_search_prefix(const char *str, int direction) +{ + HistEvent ev; + + return (history(h, &ev, direction < 0 ? + H_PREV_STR : H_NEXT_STR, str)); +} + + +/* + * search for event in history containing str, starting at offset + * abs(pos); continue backward, if pos<0, forward otherwise + */ +/* ARGSUSED */ +int +history_search_pos(const char *str, + int direction __attribute__((__unused__)), int pos) +{ + HistEvent ev; + int curr_num, off; + + off = (pos > 0) ? pos : -pos; + pos = (pos > 0) ? 1 : -1; + + if (history(h, &ev, H_CURR) != 0) + return -1; + curr_num = ev.num; + + if (!history_set_pos(off) || history(h, &ev, H_CURR) != 0) + return -1; + + for (;;) { + if (strstr(ev.str, str)) + return off; + if (history(h, &ev, (pos < 0) ? H_PREV : H_NEXT) != 0) + break; + } + + /* set "current" pointer back to previous state */ + (void)history(h, &ev, + pos < 0 ? H_NEXT_EVENT : H_PREV_EVENT, curr_num); + + return -1; +} + + +/********************************/ +/* completion functions */ + +char * +tilde_expand(char *name) +{ + return fn_tilde_expand(name); +} + +char * +filename_completion_function(const char *name, int state) +{ + return fn_filename_completion_function(name, state); +} + +/* + * a completion generator for usernames; returns _first_ username + * which starts with supplied text + * text contains a partial username preceded by random character + * (usually '~'); state resets search from start (??? should we do that anyway) + * it's the caller's responsibility to free the returned value + */ +char * +username_completion_function(const char *text, int state) +{ +#if defined(HAVE_GETPW_R_POSIX) || defined(HAVE_GETPW_R_DRAFT) + struct passwd pwres; + char pwbuf[1024]; +#endif + struct passwd *pass = NULL; + + if (text[0] == '\0') + return NULL; + + if (*text == '~') + text++; + + if (state == 0) + setpwent(); + + while ( +#if defined(HAVE_GETPW_R_POSIX) || defined(HAVE_GETPW_R_DRAFT) + getpwent_r(&pwres, pwbuf, sizeof(pwbuf), &pass) == 0 && pass != NULL +#else + (pass = getpwent()) != NULL +#endif + && text[0] == pass->pw_name[0] + && strcmp(text, pass->pw_name) == 0) + continue; + + if (pass == NULL) { + endpwent(); + return NULL; + } + return strdup(pass->pw_name); +} + + +/* + * el-compatible wrapper to send TSTP on ^Z + */ +/* ARGSUSED */ +static unsigned char +_el_rl_tstp(EditLine *el __attribute__((__unused__)), int ch __attribute__((__unused__))) +{ + (void)kill(0, SIGTSTP); + return CC_NORM; +} + +static const char * +/*ARGSUSED*/ +_rl_completion_append_character_function(const char *dummy + __attribute__((__unused__))) +{ + static char buf[2]; + buf[0] = (char)rl_completion_append_character; + buf[1] = '\0'; + return buf; +} + + +/* + * Display list of strings in columnar format on readline's output stream. + * 'matches' is list of strings, 'len' is number of strings in 'matches', + * 'max' is maximum length of string in 'matches'. + */ +void +rl_display_match_list(char **matches, int len, int max) +{ + + fn_display_match_list(e, matches, (size_t)len, (size_t)max, + _rl_completion_append_character_function); +} + +/* + * complete word at current point + */ +/* ARGSUSED */ +int +rl_complete(int ignore __attribute__((__unused__)), int invoking_key) +{ + static ct_buffer_t wbreak_conv, sprefix_conv; + char *breakchars; + + if (h == NULL || e == NULL) + rl_initialize(); + + if (rl_inhibit_completion) { + char arr[2]; + arr[0] = (char)invoking_key; + arr[1] = '\0'; + el_insertstr(e, arr); + return CC_REFRESH; + } + + if (rl_completion_word_break_hook != NULL) + breakchars = (*rl_completion_word_break_hook)(); + else + breakchars = rl_basic_word_break_characters; + + _rl_update_pos(); + + /* Just look at how many global variables modify this operation! */ + return fn_complete(e, + (rl_compentry_func_t *)rl_completion_entry_function, + rl_attempted_completion_function, + ct_decode_string(rl_basic_word_break_characters, &wbreak_conv), + ct_decode_string(breakchars, &sprefix_conv), + _rl_completion_append_character_function, + (size_t)rl_completion_query_items, + &rl_completion_type, &rl_attempted_completion_over, + &rl_point, &rl_end); + + +} + + +/* ARGSUSED */ +static unsigned char +_el_rl_complete(EditLine *el __attribute__((__unused__)), int ch) +{ + return (unsigned char)rl_complete(0, ch); +} + +/* + * misc other functions + */ + +/* + * bind key c to readline-type function func + */ +int +rl_bind_key(int c, rl_command_func_t *func) +{ + int retval = -1; + + if (h == NULL || e == NULL) + rl_initialize(); + + if (func == rl_insert) { + /* XXX notice there is no range checking of ``c'' */ + e->el_map.key[c] = ED_INSERT; + retval = 0; + } + return retval; +} + + +/* + * read one key from input - handles chars pushed back + * to input stream also + */ +int +rl_read_key(void) +{ + char fooarr[2 * sizeof(int)]; + + if (e == NULL || h == NULL) + rl_initialize(); + + return el_getc(e, fooarr); +} + + +/* + * reset the terminal + */ +/* ARGSUSED */ +void +rl_reset_terminal(const char *p __attribute__((__unused__))) +{ + + if (h == NULL || e == NULL) + rl_initialize(); + el_reset(e); +} + + +/* + * insert character ``c'' back into input stream, ``count'' times + */ +int +rl_insert(int count, int c) +{ + char arr[2]; + + if (h == NULL || e == NULL) + rl_initialize(); + + /* XXX - int -> char conversion can lose on multichars */ + arr[0] = (char)c; + arr[1] = '\0'; + + for (; count > 0; count--) + el_push(e, arr); + + return 0; +} + +int +rl_insert_text(const char *text) +{ + if (!text || *text == 0) + return 0; + + if (h == NULL || e == NULL) + rl_initialize(); + + if (el_insertstr(e, text) < 0) + return 0; + return (int)strlen(text); +} + +/*ARGSUSED*/ +int +rl_newline(int count __attribute__((__unused__)), + int c __attribute__((__unused__))) +{ + /* + * Readline-4.0 appears to ignore the args. + */ + return rl_insert(1, '\n'); +} + +/*ARGSUSED*/ +static unsigned char +rl_bind_wrapper(EditLine *el __attribute__((__unused__)), unsigned char c) +{ + if (map[c] == NULL) + return CC_ERROR; + + _rl_update_pos(); + + (*map[c])(1, c); + + /* If rl_done was set by the above call, deal with it here */ + if (rl_done) + return CC_EOF; + + return CC_NORM; +} + +int +rl_add_defun(const char *name, rl_command_func_t *fun, int c) +{ + char dest[8]; + if ((size_t)c >= sizeof(map) / sizeof(map[0]) || c < 0) + return -1; + map[(unsigned char)c] = fun; + el_set(e, EL_ADDFN, name, name, rl_bind_wrapper); + vis(dest, c, VIS_WHITE|VIS_NOSLASH, 0); + el_set(e, EL_BIND, dest, name, NULL); + return 0; +} + +void +rl_callback_read_char(void) +{ + int count = 0, done = 0; + const char *buf = el_gets(e, &count); + char *wbuf; + + if (buf == NULL || count-- <= 0) + return; + if (count == 0 && buf[0] == e->el_tty.t_c[TS_IO][C_EOF]) + done = 1; + if (buf[count] == '\n' || buf[count] == '\r') + done = 2; + + if (done && rl_linefunc != NULL) { + el_set(e, EL_UNBUFFERED, 0); + if (done == 2) { + if ((wbuf = strdup(buf)) != NULL) + wbuf[count] = '\0'; + } else + wbuf = NULL; + (*(void (*)(const char *))rl_linefunc)(wbuf); + el_set(e, EL_UNBUFFERED, 1); + } +} + +void +rl_callback_handler_install(const char *prompt, rl_vcpfunc_t *linefunc) +{ + if (e == NULL) { + rl_initialize(); + } + (void)rl_set_prompt(prompt); + rl_linefunc = linefunc; + el_set(e, EL_UNBUFFERED, 1); +} + +void +rl_callback_handler_remove(void) +{ + el_set(e, EL_UNBUFFERED, 0); + rl_linefunc = NULL; +} + +void +rl_redisplay(void) +{ + char a[2]; + a[0] = (char)e->el_tty.t_c[TS_IO][C_REPRINT]; + a[1] = '\0'; + el_push(e, a); +} + +int +rl_get_previous_history(int count, int key) +{ + char a[2]; + a[0] = (char)key; + a[1] = '\0'; + while (count--) + el_push(e, a); + return 0; +} + +void +/*ARGSUSED*/ +rl_prep_terminal(int meta_flag __attribute__((__unused__))) +{ + el_set(e, EL_PREP_TERM, 1); +} + +void +rl_deprep_terminal(void) +{ + el_set(e, EL_PREP_TERM, 0); +} + +int +rl_read_init_file(const char *s) +{ + return el_source(e, s); +} + +int +rl_parse_and_bind(const char *line) +{ + const char **argv; + int argc; + Tokenizer *tok; + + tok = tok_init(NULL); + tok_str(tok, line, &argc, &argv); + argc = el_parse(e, argc, argv); + tok_end(tok); + return argc ? 1 : 0; +} + +int +rl_variable_bind(const char *var, const char *value) +{ + /* + * The proper return value is undocument, but this is what the + * readline source seems to do. + */ + return el_set(e, EL_BIND, "", var, value, NULL) == -1 ? 1 : 0; +} + +void +rl_stuff_char(int c) +{ + char buf[2]; + + buf[0] = (char)c; + buf[1] = '\0'; + el_insertstr(e, buf); +} + +static int +_rl_event_read_char(EditLine *el, wchar_t *wc) +{ + char ch; + int n; + ssize_t num_read = 0; + + ch = '\0'; + *wc = L'\0'; + while (rl_event_hook) { + + (*rl_event_hook)(); + +#if defined(FIONREAD) + if (ioctl(el->el_infd, FIONREAD, &n) < 0) + return -1; + if (n) + num_read = read(el->el_infd, &ch, (size_t)1); + else + num_read = 0; +#elif defined(F_SETFL) && defined(O_NDELAY) + if ((n = fcntl(el->el_infd, F_GETFL, 0)) < 0) + return -1; + if (fcntl(el->el_infd, F_SETFL, n|O_NDELAY) < 0) + return -1; + num_read = read(el->el_infd, &ch, 1); + if (fcntl(el->el_infd, F_SETFL, n)) + return -1; +#else + /* not non-blocking, but what you gonna do? */ + num_read = read(el->el_infd, &ch, 1); + return -1; +#endif + + if (num_read < 0 && errno == EAGAIN) + continue; + if (num_read == 0) + continue; + break; + } + if (!rl_event_hook) + el_set(el, EL_GETCFN, EL_BUILTIN_GETCFN); + *wc = (wchar_t)ch; + return (int)num_read; +} + +static void +_rl_update_pos(void) +{ + const LineInfo *li = el_line(e); + + rl_point = (int)(li->cursor - li->buffer); + rl_end = (int)(li->lastchar - li->buffer); +} + +void +rl_get_screen_size(int *rows, int *cols) +{ + if (rows) + el_get(e, EL_GETTC, "li", rows, (void *)0); + if (cols) + el_get(e, EL_GETTC, "co", cols, (void *)0); +} + +void +rl_set_screen_size(int rows, int cols) +{ + char buf[64]; + (void)snprintf(buf, sizeof(buf), "%d", rows); + el_set(e, EL_SETTC, "li", buf, NULL); + (void)snprintf(buf, sizeof(buf), "%d", cols); + el_set(e, EL_SETTC, "co", buf, NULL); +} + +char ** +rl_completion_matches(const char *str, rl_compentry_func_t *fun) +{ + size_t len, max, i, j, min; + char **list, *match, *a, *b; + + len = 1; + max = 10; + if ((list = el_malloc(max * sizeof(*list))) == NULL) + return NULL; + + while ((match = (*fun)(str, (int)(len - 1))) != NULL) { + list[len++] = match; + if (len == max) { + char **nl; + max += 10; + if ((nl = el_realloc(list, max * sizeof(*nl))) == NULL) + goto out; + list = nl; + } + } + if (len == 1) + goto out; + list[len] = NULL; + if (len == 2) { + if ((list[0] = strdup(list[1])) == NULL) + goto out; + return list; + } + qsort(&list[1], len - 1, sizeof(*list), + (int (*)(const void *, const void *)) strcmp); + min = SIZE_MAX; + for (i = 1, a = list[i]; i < len - 1; i++, a = b) { + b = list[i + 1]; + for (j = 0; a[j] && a[j] == b[j]; j++) + continue; + if (min > j) + min = j; + } + if (min == 0 && *str) { + if ((list[0] = strdup(str)) == NULL) + goto out; + } else { + if ((list[0] = el_malloc((min + 1) * sizeof(*list[0]))) == NULL) + goto out; + (void)memcpy(list[0], list[1], min); + list[0][min] = '\0'; + } + return list; + +out: + el_free(list); + return NULL; +} + +char * +rl_filename_completion_function (const char *text, int state) +{ + return fn_filename_completion_function(text, state); +} + +void +rl_forced_update_display(void) +{ + el_set(e, EL_REFRESH); +} + +int +_rl_abort_internal(void) +{ + el_beep(e); + longjmp(topbuf, 1); + /*NOTREACHED*/ +} + +int +_rl_qsort_string_compare(char **s1, char **s2) +{ + return strcoll(*s1, *s2); +} + +HISTORY_STATE * +history_get_history_state(void) +{ + HISTORY_STATE *hs; + + if ((hs = el_malloc(sizeof(*hs))) == NULL) + return NULL; + hs->length = history_length; + return hs; +} + +int +/*ARGSUSED*/ +rl_kill_text(int from __attribute__((__unused__)), + int to __attribute__((__unused__))) +{ + return 0; +} + +Keymap +rl_make_bare_keymap(void) +{ + return NULL; +} + +Keymap +rl_get_keymap(void) +{ + return NULL; +} + +void +/*ARGSUSED*/ +rl_set_keymap(Keymap k __attribute__((__unused__))) +{ +} + +int +/*ARGSUSED*/ +rl_generic_bind(int type __attribute__((__unused__)), + const char * keyseq __attribute__((__unused__)), + const char * data __attribute__((__unused__)), + Keymap k __attribute__((__unused__))) +{ + return 0; +} + +int +/*ARGSUSED*/ +rl_bind_key_in_map(int key __attribute__((__unused__)), + rl_command_func_t *fun __attribute__((__unused__)), + Keymap k __attribute__((__unused__))) +{ + return 0; +} + +/* unsupported, but needed by python */ +void +rl_cleanup_after_signal(void) +{ +} + +int +rl_on_new_line(void) +{ + return 0; +} + +void +rl_free_line_state(void) +{ +} + +int +/*ARGSUSED*/ +rl_set_keyboard_input_timeout(int u __attribute__((__unused__))) +{ + return 0; +} diff --git a/bin/catsh/libedit/readline/Makefile b/bin/catsh/libedit/readline/Makefile new file mode 100644 index 00000000..265540b7 --- /dev/null +++ b/bin/catsh/libedit/readline/Makefile @@ -0,0 +1,13 @@ +# $NetBSD: Makefile,v 1.8 2016/02/17 19:47:49 christos Exp $ + +NOOBJ= # defined + +.include <bsd.own.mk> + +.PATH: ${NETBSDSRCDIR}/lib/libedit + +INCS= readline.h +INCSDIR= /usr/include/readline +INCSYMLINKS= readline.h ${INCSDIR}/history.h + +.include <bsd.prog.mk> diff --git a/bin/catsh/libedit/readline/readline.h b/bin/catsh/libedit/readline/readline.h new file mode 100644 index 00000000..777a4c6f --- /dev/null +++ b/bin/catsh/libedit/readline/readline.h @@ -0,0 +1,227 @@ +/* $NetBSD: readline.h,v 1.41 2016/10/28 18:32:35 christos Exp $ */ + +/*- + * Copyright (c) 1997 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Jaromir Dolecek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _READLINE_H_ +#define _READLINE_H_ + +#include <sys/types.h> +#include <stdio.h> + +/* list of readline stuff supported by editline library's readline wrapper */ + +/* typedefs */ +typedef int Function(const char *, int); +typedef void VFunction(void); +typedef void rl_vcpfunc_t(char *); +typedef char **rl_completion_func_t(const char *, int, int); +typedef char *rl_compentry_func_t(const char *, int); +typedef int rl_command_func_t(int, int); + +/* only supports length */ +typedef struct { + int length; +} HISTORY_STATE; + +typedef void *histdata_t; + +typedef struct _hist_entry { + const char *line; + histdata_t data; +} HIST_ENTRY; + +typedef struct _keymap_entry { + char type; +#define ISFUNC 0 +#define ISKMAP 1 +#define ISMACR 2 + Function *function; +} KEYMAP_ENTRY; + +#define KEYMAP_SIZE 256 + +typedef KEYMAP_ENTRY KEYMAP_ENTRY_ARRAY[KEYMAP_SIZE]; +typedef KEYMAP_ENTRY *Keymap; + +#define control_character_threshold 0x20 +#define control_character_bit 0x40 + +#ifndef CTRL +#include <sys/ioctl.h> +#if !defined(__sun) && !defined(__hpux) && !defined(_AIX) +#include <sys/ttydefaults.h> +#endif +#ifndef CTRL +#define CTRL(c) ((c) & 037) +#endif +#endif +#ifndef UNCTRL +#define UNCTRL(c) (((c) - 'a' + 'A')|control_character_bit) +#endif + +#define RUBOUT 0x7f +#define ABORT_CHAR CTRL('G') +#define RL_READLINE_VERSION 0x0402 +#define RL_PROMPT_START_IGNORE '\1' +#define RL_PROMPT_END_IGNORE '\2' + +/* global variables used by readline enabled applications */ +#ifdef __cplusplus +extern "C" { +#endif +extern const char *rl_library_version; +extern int rl_readline_version; +extern char *rl_readline_name; +extern FILE *rl_instream; +extern FILE *rl_outstream; +extern char *rl_line_buffer; +extern int rl_point, rl_end; +extern int history_base, history_length; +extern int max_input_history; +extern char *rl_basic_word_break_characters; +extern char *rl_completer_word_break_characters; +extern char *rl_completer_quote_characters; +extern rl_compentry_func_t *rl_completion_entry_function; +extern char *(*rl_completion_word_break_hook)(void); +extern rl_completion_func_t *rl_attempted_completion_function; +extern int rl_attempted_completion_over; +extern int rl_completion_type; +extern int rl_completion_query_items; +extern char *rl_special_prefixes; +extern int rl_completion_append_character; +extern int rl_inhibit_completion; +extern Function *rl_pre_input_hook; +extern Function *rl_startup_hook; +extern char *rl_terminal_name; +extern int rl_already_prompted; +extern char *rl_prompt; +extern int rl_done; +/* + * The following is not implemented + */ +extern int rl_catch_signals; +extern int rl_catch_sigwinch; +extern KEYMAP_ENTRY_ARRAY emacs_standard_keymap, + emacs_meta_keymap, + emacs_ctlx_keymap; +extern int rl_filename_completion_desired; +extern int rl_ignore_completion_duplicates; +extern int (*rl_getc_function)(FILE *); +extern VFunction *rl_redisplay_function; +extern VFunction *rl_completion_display_matches_hook; +extern VFunction *rl_prep_term_function; +extern VFunction *rl_deprep_term_function; +extern int readline_echoing_p; +extern int _rl_print_completions_horizontally; + +/* supported functions */ +char *readline(const char *); +int rl_initialize(void); + +void using_history(void); +int add_history(const char *); +void clear_history(void); +void stifle_history(int); +int unstifle_history(void); +int history_is_stifled(void); +int where_history(void); +HIST_ENTRY *current_history(void); +HIST_ENTRY *history_get(int); +HIST_ENTRY *remove_history(int); +HIST_ENTRY *replace_history_entry(int, const char *, histdata_t); +int history_total_bytes(void); +int history_set_pos(int); +HIST_ENTRY *previous_history(void); +HIST_ENTRY *next_history(void); +HIST_ENTRY **history_list(void); +int history_search(const char *, int); +int history_search_prefix(const char *, int); +int history_search_pos(const char *, int, int); +int read_history(const char *); +int write_history(const char *); +int history_truncate_file (const char *, int); +int history_expand(char *, char **); +char **history_tokenize(const char *); +const char *get_history_event(const char *, int *, int); +char *history_arg_extract(int, int, const char *); + +char *tilde_expand(char *); +char *filename_completion_function(const char *, int); +char *username_completion_function(const char *, int); +int rl_complete(int, int); +int rl_read_key(void); +char **completion_matches(const char *, rl_compentry_func_t *); +void rl_display_match_list(char **, int, int); + +int rl_insert(int, int); +int rl_insert_text(const char *); +void rl_reset_terminal(const char *); +int rl_bind_key(int, rl_command_func_t *); +int rl_newline(int, int); +void rl_callback_read_char(void); +void rl_callback_handler_install(const char *, rl_vcpfunc_t *); +void rl_callback_handler_remove(void); +void rl_redisplay(void); +int rl_get_previous_history(int, int); +void rl_prep_terminal(int); +void rl_deprep_terminal(void); +int rl_read_init_file(const char *); +int rl_parse_and_bind(const char *); +int rl_variable_bind(const char *, const char *); +void rl_stuff_char(int); +int rl_add_defun(const char *, rl_command_func_t *, int); +HISTORY_STATE *history_get_history_state(void); +void rl_get_screen_size(int *, int *); +void rl_set_screen_size(int, int); +char *rl_filename_completion_function (const char *, int); +int _rl_abort_internal(void); +int _rl_qsort_string_compare(char **, char **); +char **rl_completion_matches(const char *, rl_compentry_func_t *); +void rl_forced_update_display(void); +int rl_set_prompt(const char *); +int rl_on_new_line(void); + +/* + * The following are not implemented + */ +int rl_kill_text(int, int); +Keymap rl_get_keymap(void); +void rl_set_keymap(Keymap); +Keymap rl_make_bare_keymap(void); +int rl_generic_bind(int, const char *, const char *, Keymap); +int rl_bind_key_in_map(int, rl_command_func_t *, Keymap); +void rl_cleanup_after_signal(void); +void rl_free_line_state(void); +int rl_set_keyboard_input_timeout(int); + +#ifdef __cplusplus +} +#endif + +#endif /* _READLINE_H_ */ diff --git a/bin/catsh/libedit/refresh.c b/bin/catsh/libedit/refresh.c new file mode 100644 index 00000000..37c6041d --- /dev/null +++ b/bin/catsh/libedit/refresh.c @@ -0,0 +1,1219 @@ +/* $NetBSD: refresh.c,v 1.51.8.1 2017/07/23 14:41:26 snj Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)refresh.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: refresh.c,v 1.51.8.1 2017/07/23 14:41:26 snj Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * refresh.c: Lower level screen refreshing functions + */ +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "el.h" + +static void re_nextline(EditLine *); +static void re_addc(EditLine *, wint_t); +static void re_update_line(EditLine *, wchar_t *, wchar_t *, int); +static void re_insert (EditLine *, wchar_t *, int, int, wchar_t *, int); +static void re_delete(EditLine *, wchar_t *, int, int, int); +static void re_fastputc(EditLine *, wint_t); +static void re_clear_eol(EditLine *, int, int, int); +static void re__strncopy(wchar_t *, wchar_t *, size_t); +static void re__copy_and_pad(wchar_t *, const wchar_t *, size_t); + +#ifdef DEBUG_REFRESH +static void re_printstr(EditLine *, const char *, wchar_t *, wchar_t *); +#define __F el->el_errfile +#define ELRE_ASSERT(a, b, c) do \ + if (/*CONSTCOND*/ a) { \ + (void) fprintf b; \ + c; \ + } \ + while (/*CONSTCOND*/0) +#define ELRE_DEBUG(a, b) ELRE_ASSERT(a,b,;) + +/* re_printstr(): + * Print a string on the debugging pty + */ +static void +re_printstr(EditLine *el, const char *str, wchar_t *f, wchar_t *t) +{ + + ELRE_DEBUG(1, (__F, "%s:\"", str)); + while (f < t) + ELRE_DEBUG(1, (__F, "%c", *f++ & 0177)); + ELRE_DEBUG(1, (__F, "\"\r\n")); +} +#else +#define ELRE_ASSERT(a, b, c) +#define ELRE_DEBUG(a, b) +#endif + +/* re_nextline(): + * Move to the next line or scroll + */ +static void +re_nextline(EditLine *el) +{ + el->el_refresh.r_cursor.h = 0; /* reset it. */ + + /* + * If we would overflow (input is longer than terminal size), + * emulate scroll by dropping first line and shuffling the rest. + * We do this via pointer shuffling - it's safe in this case + * and we avoid memcpy(). + */ + if (el->el_refresh.r_cursor.v + 1 >= el->el_terminal.t_size.v) { + int i, lins = el->el_terminal.t_size.v; + wchar_t *firstline = el->el_vdisplay[0]; + + for(i = 1; i < lins; i++) + el->el_vdisplay[i - 1] = el->el_vdisplay[i]; + + firstline[0] = '\0'; /* empty the string */ + el->el_vdisplay[i - 1] = firstline; + } else + el->el_refresh.r_cursor.v++; + + ELRE_ASSERT(el->el_refresh.r_cursor.v >= el->el_terminal.t_size.v, + (__F, "\r\nre_putc: overflow! r_cursor.v == %d > %d\r\n", + el->el_refresh.r_cursor.v, el->el_terminal.t_size.v), + abort()); +} + +/* re_addc(): + * Draw c, expanding tabs, control chars etc. + */ +static void +re_addc(EditLine *el, wint_t c) +{ + switch (ct_chr_class(c)) { + case CHTYPE_TAB: /* expand the tab */ + for (;;) { + re_putc(el, ' ', 1); + if ((el->el_refresh.r_cursor.h & 07) == 0) + break; /* go until tab stop */ + } + break; + case CHTYPE_NL: { + int oldv = el->el_refresh.r_cursor.v; + re_putc(el, '\0', 0); /* assure end of line */ + if (oldv == el->el_refresh.r_cursor.v) /* XXX */ + re_nextline(el); + break; + } + case CHTYPE_PRINT: + re_putc(el, c, 1); + break; + default: { + wchar_t visbuf[VISUAL_WIDTH_MAX]; + ssize_t i, n = + ct_visual_char(visbuf, VISUAL_WIDTH_MAX, c); + for (i = 0; n-- > 0; ++i) + re_putc(el, visbuf[i], 1); + break; + } + } +} + +/* re_putliteral(): + * Place the literal string given + */ +libedit_private void +re_putliteral(EditLine *el, const wchar_t *begin, const wchar_t *end) +{ + coord_t *cur = &el->el_refresh.r_cursor; + wint_t c; + int sizeh = el->el_terminal.t_size.h; + int i, w; + + c = literal_add(el, begin, end, &w); + if (c == 0 || w <= 0) + return; + el->el_vdisplay[cur->v][cur->h] = c; + + i = w; + if (i > sizeh - cur->h) /* avoid overflow */ + i = sizeh - cur->h; + while (--i > 0) + el->el_vdisplay[cur->v][cur->h + i] = MB_FILL_CHAR; + + cur->h += w; + if (cur->h >= sizeh) { + /* assure end of line */ + el->el_vdisplay[cur->v][sizeh] = '\0'; + re_nextline(el); + } +} + +/* re_putc(): + * Draw the character given + */ +libedit_private void +re_putc(EditLine *el, wint_t c, int shift) +{ + coord_t *cur = &el->el_refresh.r_cursor; + int i, w = wcwidth(c); + int sizeh = el->el_terminal.t_size.h; + + ELRE_DEBUG(1, (__F, "printing %5x '%lc'\r\n", c, c)); + if (w == -1) + w = 0; + + while (shift && (cur->h + w > sizeh)) + re_putc(el, ' ', 1); + + el->el_vdisplay[cur->v][cur->h] = c; + /* assumes !shift is only used for single-column chars */ + i = w; + while (--i > 0) + el->el_vdisplay[cur->v][cur->h + i] = MB_FILL_CHAR; + + if (!shift) + return; + + cur->h += w; /* advance to next place */ + if (cur->h >= sizeh) { + /* assure end of line */ + el->el_vdisplay[cur->v][sizeh] = '\0'; + re_nextline(el); + } +} + + +/* re_refresh(): + * draws the new virtual screen image from the current input + * line, then goes line-by-line changing the real image to the new + * virtual image. The routine to re-draw a line can be replaced + * easily in hopes of a smarter one being placed there. + */ +libedit_private void +re_refresh(EditLine *el) +{ + int i, rhdiff; + wchar_t *cp, *st; + coord_t cur; +#ifdef notyet + size_t termsz; +#endif + + ELRE_DEBUG(1, (__F, "el->el_line.buffer = :%ls:\r\n", + el->el_line.buffer)); + + literal_clear(el); + /* reset the Drawing cursor */ + el->el_refresh.r_cursor.h = 0; + el->el_refresh.r_cursor.v = 0; + + terminal_move_to_char(el, 0); + + /* temporarily draw rprompt to calculate its size */ + prompt_print(el, EL_RPROMPT); + + /* reset the Drawing cursor */ + el->el_refresh.r_cursor.h = 0; + el->el_refresh.r_cursor.v = 0; + + if (el->el_line.cursor >= el->el_line.lastchar) { + if (el->el_map.current == el->el_map.alt + && el->el_line.lastchar != el->el_line.buffer) + el->el_line.cursor = el->el_line.lastchar - 1; + else + el->el_line.cursor = el->el_line.lastchar; + } + + cur.h = -1; /* set flag in case I'm not set */ + cur.v = 0; + + prompt_print(el, EL_PROMPT); + + /* draw the current input buffer */ +#if notyet + termsz = el->el_terminal.t_size.h * el->el_terminal.t_size.v; + if (el->el_line.lastchar - el->el_line.buffer > termsz) { + /* + * If line is longer than terminal, process only part + * of line which would influence display. + */ + size_t rem = (el->el_line.lastchar-el->el_line.buffer)%termsz; + + st = el->el_line.lastchar - rem + - (termsz - (((rem / el->el_terminal.t_size.v) - 1) + * el->el_terminal.t_size.v)); + } else +#endif + st = el->el_line.buffer; + + for (cp = st; cp < el->el_line.lastchar; cp++) { + if (cp == el->el_line.cursor) { + int w = wcwidth(*cp); + /* save for later */ + cur.h = el->el_refresh.r_cursor.h; + cur.v = el->el_refresh.r_cursor.v; + /* handle being at a linebroken doublewidth char */ + if (w > 1 && el->el_refresh.r_cursor.h + w > + el->el_terminal.t_size.h) { + cur.h = 0; + cur.v++; + } + } + re_addc(el, *cp); + } + + if (cur.h == -1) { /* if I haven't been set yet, I'm at the end */ + cur.h = el->el_refresh.r_cursor.h; + cur.v = el->el_refresh.r_cursor.v; + } + rhdiff = el->el_terminal.t_size.h - el->el_refresh.r_cursor.h - + el->el_rprompt.p_pos.h; + if (el->el_rprompt.p_pos.h && !el->el_rprompt.p_pos.v && + !el->el_refresh.r_cursor.v && rhdiff > 1) { + /* + * have a right-hand side prompt that will fit + * on the end of the first line with at least + * one character gap to the input buffer. + */ + while (--rhdiff > 0) /* pad out with spaces */ + re_putc(el, ' ', 1); + prompt_print(el, EL_RPROMPT); + } else { + el->el_rprompt.p_pos.h = 0; /* flag "not using rprompt" */ + el->el_rprompt.p_pos.v = 0; + } + + re_putc(el, '\0', 0); /* make line ended with NUL, no cursor shift */ + + el->el_refresh.r_newcv = el->el_refresh.r_cursor.v; + + ELRE_DEBUG(1, (__F, + "term.h=%d vcur.h=%d vcur.v=%d vdisplay[0]=\r\n:%80.80s:\r\n", + el->el_terminal.t_size.h, el->el_refresh.r_cursor.h, + el->el_refresh.r_cursor.v, ct_encode_string(el->el_vdisplay[0], + &el->el_scratch))); + + ELRE_DEBUG(1, (__F, "updating %d lines.\r\n", el->el_refresh.r_newcv)); + for (i = 0; i <= el->el_refresh.r_newcv; i++) { + /* NOTE THAT re_update_line MAY CHANGE el_display[i] */ + re_update_line(el, el->el_display[i], el->el_vdisplay[i], i); + + /* + * Copy the new line to be the current one, and pad out with + * spaces to the full width of the terminal so that if we try + * moving the cursor by writing the character that is at the + * end of the screen line, it won't be a NUL or some old + * leftover stuff. + */ + re__copy_and_pad(el->el_display[i], el->el_vdisplay[i], + (size_t) el->el_terminal.t_size.h); + } + ELRE_DEBUG(1, (__F, + "\r\nel->el_refresh.r_cursor.v=%d,el->el_refresh.r_oldcv=%d i=%d\r\n", + el->el_refresh.r_cursor.v, el->el_refresh.r_oldcv, i)); + + if (el->el_refresh.r_oldcv > el->el_refresh.r_newcv) + for (; i <= el->el_refresh.r_oldcv; i++) { + terminal_move_to_line(el, i); + terminal_move_to_char(el, 0); + /* This wcslen should be safe even with MB_FILL_CHARs */ + terminal_clear_EOL(el, (int) wcslen(el->el_display[i])); +#ifdef DEBUG_REFRESH + terminal_overwrite(el, L"C\b", 2); +#endif /* DEBUG_REFRESH */ + el->el_display[i][0] = '\0'; + } + + el->el_refresh.r_oldcv = el->el_refresh.r_newcv; /* set for next time */ + ELRE_DEBUG(1, (__F, + "\r\ncursor.h = %d, cursor.v = %d, cur.h = %d, cur.v = %d\r\n", + el->el_refresh.r_cursor.h, el->el_refresh.r_cursor.v, + cur.h, cur.v)); + terminal_move_to_line(el, cur.v); /* go to where the cursor is */ + terminal_move_to_char(el, cur.h); +} + + +/* re_goto_bottom(): + * used to go to last used screen line + */ +libedit_private void +re_goto_bottom(EditLine *el) +{ + + terminal_move_to_line(el, el->el_refresh.r_oldcv); + terminal__putc(el, '\n'); + re_clear_display(el); + terminal__flush(el); +} + + +/* re_insert(): + * insert num characters of s into d (in front of the character) + * at dat, maximum length of d is dlen + */ +static void +/*ARGSUSED*/ +re_insert(EditLine *el __attribute__((__unused__)), + wchar_t *d, int dat, int dlen, wchar_t *s, int num) +{ + wchar_t *a, *b; + + if (num <= 0) + return; + if (num > dlen - dat) + num = dlen - dat; + + ELRE_DEBUG(1, + (__F, "re_insert() starting: %d at %d max %d, d == \"%s\"\n", + num, dat, dlen, ct_encode_string(d, &el->el_scratch))); + ELRE_DEBUG(1, (__F, "s == \"%s\"\n", ct_encode_string(s, + &el->el_scratch))); + + /* open up the space for num chars */ + if (num > 0) { + b = d + dlen - 1; + a = b - num; + while (a >= &d[dat]) + *b-- = *a--; + d[dlen] = '\0'; /* just in case */ + } + + ELRE_DEBUG(1, (__F, + "re_insert() after insert: %d at %d max %d, d == \"%s\"\n", + num, dat, dlen, ct_encode_string(d, &el->el_scratch))); + ELRE_DEBUG(1, (__F, "s == \"%s\"\n", ct_encode_string(s, + &el->el_scratch))); + + /* copy the characters */ + for (a = d + dat; (a < d + dlen) && (num > 0); num--) + *a++ = *s++; + +#ifdef notyet + /* ct_encode_string() uses a static buffer, so we can't conveniently + * encode both d & s here */ + ELRE_DEBUG(1, + (__F, "re_insert() after copy: %d at %d max %d, %s == \"%s\"\n", + num, dat, dlen, d, s)); + ELRE_DEBUG(1, (__F, "s == \"%s\"\n", s)); +#endif +} + + +/* re_delete(): + * delete num characters d at dat, maximum length of d is dlen + */ +static void +/*ARGSUSED*/ +re_delete(EditLine *el __attribute__((__unused__)), + wchar_t *d, int dat, int dlen, int num) +{ + wchar_t *a, *b; + + if (num <= 0) + return; + if (dat + num >= dlen) { + d[dat] = '\0'; + return; + } + ELRE_DEBUG(1, + (__F, "re_delete() starting: %d at %d max %d, d == \"%s\"\n", + num, dat, dlen, ct_encode_string(d, &el->el_scratch))); + + /* open up the space for num chars */ + if (num > 0) { + b = d + dat; + a = b + num; + while (a < &d[dlen]) + *b++ = *a++; + d[dlen] = '\0'; /* just in case */ + } + ELRE_DEBUG(1, + (__F, "re_delete() after delete: %d at %d max %d, d == \"%s\"\n", + num, dat, dlen, ct_encode_string(d, &el->el_scratch))); +} + + +/* re__strncopy(): + * Like strncpy without padding. + */ +static void +re__strncopy(wchar_t *a, wchar_t *b, size_t n) +{ + + while (n-- && *b) + *a++ = *b++; +} + +/* re_clear_eol(): + * Find the number of characters we need to clear till the end of line + * in order to make sure that we have cleared the previous contents of + * the line. fx and sx is the number of characters inserted or deleted + * in the first or second diff, diff is the difference between the + * number of characters between the new and old line. + */ +static void +re_clear_eol(EditLine *el, int fx, int sx, int diff) +{ + + ELRE_DEBUG(1, (__F, "re_clear_eol sx %d, fx %d, diff %d\n", + sx, fx, diff)); + + if (fx < 0) + fx = -fx; + if (sx < 0) + sx = -sx; + if (fx > diff) + diff = fx; + if (sx > diff) + diff = sx; + + ELRE_DEBUG(1, (__F, "re_clear_eol %d\n", diff)); + terminal_clear_EOL(el, diff); +} + +/***************************************************************** + re_update_line() is based on finding the middle difference of each line + on the screen; vis: + + /old first difference + /beginning of line | /old last same /old EOL + v v v v +old: eddie> Oh, my little gruntle-buggy is to me, as lurgid as +new: eddie> Oh, my little buggy says to me, as lurgid as + ^ ^ ^ ^ + \beginning of line | \new last same \new end of line + \new first difference + + all are character pointers for the sake of speed. Special cases for + no differences, as well as for end of line additions must be handled. +**************************************************************** */ + +/* Minimum at which doing an insert it "worth it". This should be about + * half the "cost" of going into insert mode, inserting a character, and + * going back out. This should really be calculated from the termcap + * data... For the moment, a good number for ANSI terminals. + */ +#define MIN_END_KEEP 4 + +static void +re_update_line(EditLine *el, wchar_t *old, wchar_t *new, int i) +{ + wchar_t *o, *n, *p, c; + wchar_t *ofd, *ols, *oe, *nfd, *nls, *ne; + wchar_t *osb, *ose, *nsb, *nse; + int fx, sx; + size_t len; + + /* + * find first diff + */ + for (o = old, n = new; *o && (*o == *n); o++, n++) + continue; + ofd = o; + nfd = n; + + /* + * Find the end of both old and new + */ + while (*o) + o++; + /* + * Remove any trailing blanks off of the end, being careful not to + * back up past the beginning. + */ + while (ofd < o) { + if (o[-1] != ' ') + break; + o--; + } + oe = o; + *oe = '\0'; + + while (*n) + n++; + + /* remove blanks from end of new */ + while (nfd < n) { + if (n[-1] != ' ') + break; + n--; + } + ne = n; + *ne = '\0'; + + /* + * if no diff, continue to next line of redraw + */ + if (*ofd == '\0' && *nfd == '\0') { + ELRE_DEBUG(1, (__F, "no difference.\r\n")); + return; + } + /* + * find last same pointer + */ + while ((o > ofd) && (n > nfd) && (*--o == *--n)) + continue; + ols = ++o; + nls = ++n; + + /* + * find same beginning and same end + */ + osb = ols; + nsb = nls; + ose = ols; + nse = nls; + + /* + * case 1: insert: scan from nfd to nls looking for *ofd + */ + if (*ofd) { + for (c = *ofd, n = nfd; n < nls; n++) { + if (c == *n) { + for (o = ofd, p = n; + p < nls && o < ols && *o == *p; + o++, p++) + continue; + /* + * if the new match is longer and it's worth + * keeping, then we take it + */ + if (((nse - nsb) < (p - n)) && + (2 * (p - n) > n - nfd)) { + nsb = n; + nse = p; + osb = ofd; + ose = o; + } + } + } + } + /* + * case 2: delete: scan from ofd to ols looking for *nfd + */ + if (*nfd) { + for (c = *nfd, o = ofd; o < ols; o++) { + if (c == *o) { + for (n = nfd, p = o; + p < ols && n < nls && *p == *n; + p++, n++) + continue; + /* + * if the new match is longer and it's worth + * keeping, then we take it + */ + if (((ose - osb) < (p - o)) && + (2 * (p - o) > o - ofd)) { + nsb = nfd; + nse = n; + osb = o; + ose = p; + } + } + } + } + /* + * Pragmatics I: If old trailing whitespace or not enough characters to + * save to be worth it, then don't save the last same info. + */ + if ((oe - ols) < MIN_END_KEEP) { + ols = oe; + nls = ne; + } + /* + * Pragmatics II: if the terminal isn't smart enough, make the data + * dumber so the smart update doesn't try anything fancy + */ + + /* + * fx is the number of characters we need to insert/delete: in the + * beginning to bring the two same begins together + */ + fx = (int)((nsb - nfd) - (osb - ofd)); + /* + * sx is the number of characters we need to insert/delete: in the + * end to bring the two same last parts together + */ + sx = (int)((nls - nse) - (ols - ose)); + + if (!EL_CAN_INSERT) { + if (fx > 0) { + osb = ols; + ose = ols; + nsb = nls; + nse = nls; + } + if (sx > 0) { + ols = oe; + nls = ne; + } + if ((ols - ofd) < (nls - nfd)) { + ols = oe; + nls = ne; + } + } + if (!EL_CAN_DELETE) { + if (fx < 0) { + osb = ols; + ose = ols; + nsb = nls; + nse = nls; + } + if (sx < 0) { + ols = oe; + nls = ne; + } + if ((ols - ofd) > (nls - nfd)) { + ols = oe; + nls = ne; + } + } + /* + * Pragmatics III: make sure the middle shifted pointers are correct if + * they don't point to anything (we may have moved ols or nls). + */ + /* if the change isn't worth it, don't bother */ + /* was: if (osb == ose) */ + if ((ose - osb) < MIN_END_KEEP) { + osb = ols; + ose = ols; + nsb = nls; + nse = nls; + } + /* + * Now that we are done with pragmatics we recompute fx, sx + */ + fx = (int)((nsb - nfd) - (osb - ofd)); + sx = (int)((nls - nse) - (ols - ose)); + + ELRE_DEBUG(1, (__F, "fx %d, sx %d\n", fx, sx)); + ELRE_DEBUG(1, (__F, "ofd %td, osb %td, ose %td, ols %td, oe %td\n", + ofd - old, osb - old, ose - old, ols - old, oe - old)); + ELRE_DEBUG(1, (__F, "nfd %td, nsb %td, nse %td, nls %td, ne %td\n", + nfd - new, nsb - new, nse - new, nls - new, ne - new)); + ELRE_DEBUG(1, (__F, + "xxx-xxx:\"00000000001111111111222222222233333333334\"\r\n")); + ELRE_DEBUG(1, (__F, + "xxx-xxx:\"01234567890123456789012345678901234567890\"\r\n")); +#ifdef DEBUG_REFRESH + re_printstr(el, "old- oe", old, oe); + re_printstr(el, "new- ne", new, ne); + re_printstr(el, "old-ofd", old, ofd); + re_printstr(el, "new-nfd", new, nfd); + re_printstr(el, "ofd-osb", ofd, osb); + re_printstr(el, "nfd-nsb", nfd, nsb); + re_printstr(el, "osb-ose", osb, ose); + re_printstr(el, "nsb-nse", nsb, nse); + re_printstr(el, "ose-ols", ose, ols); + re_printstr(el, "nse-nls", nse, nls); + re_printstr(el, "ols- oe", ols, oe); + re_printstr(el, "nls- ne", nls, ne); +#endif /* DEBUG_REFRESH */ + + /* + * el_cursor.v to this line i MUST be in this routine so that if we + * don't have to change the line, we don't move to it. el_cursor.h to + * first diff char + */ + terminal_move_to_line(el, i); + + /* + * at this point we have something like this: + * + * /old /ofd /osb /ose /ols /oe + * v.....................v v..................v v........v + * eddie> Oh, my fredded gruntle-buggy is to me, as foo var lurgid as + * eddie> Oh, my fredded quiux buggy is to me, as gruntle-lurgid as + * ^.....................^ ^..................^ ^........^ + * \new \nfd \nsb \nse \nls \ne + * + * fx is the difference in length between the chars between nfd and + * nsb, and the chars between ofd and osb, and is thus the number of + * characters to delete if < 0 (new is shorter than old, as above), + * or insert (new is longer than short). + * + * sx is the same for the second differences. + */ + + /* + * if we have a net insert on the first difference, AND inserting the + * net amount ((nsb-nfd) - (osb-ofd)) won't push the last useful + * character (which is ne if nls != ne, otherwise is nse) off the edge + * of the screen (el->el_terminal.t_size.h) else we do the deletes first + * so that we keep everything we need to. + */ + + /* + * if the last same is the same like the end, there is no last same + * part, otherwise we want to keep the last same part set p to the + * last useful old character + */ + p = (ols != oe) ? oe : ose; + + /* + * if (There is a diffence in the beginning) && (we need to insert + * characters) && (the number of characters to insert is less than + * the term width) + * We need to do an insert! + * else if (we need to delete characters) + * We need to delete characters! + * else + * No insert or delete + */ + if ((nsb != nfd) && fx > 0 && + ((p - old) + fx <= el->el_terminal.t_size.h)) { + ELRE_DEBUG(1, + (__F, "first diff insert at %td...\r\n", nfd - new)); + /* + * Move to the first char to insert, where the first diff is. + */ + terminal_move_to_char(el, (int)(nfd - new)); + /* + * Check if we have stuff to keep at end + */ + if (nsb != ne) { + ELRE_DEBUG(1, (__F, "with stuff to keep at end\r\n")); + /* + * insert fx chars of new starting at nfd + */ + if (fx > 0) { + ELRE_DEBUG(!EL_CAN_INSERT, (__F, + "ERROR: cannot insert in early first diff\n")); + terminal_insertwrite(el, nfd, fx); + re_insert(el, old, (int)(ofd - old), + el->el_terminal.t_size.h, nfd, fx); + } + /* + * write (nsb-nfd) - fx chars of new starting at + * (nfd + fx) + */ + len = (size_t) ((nsb - nfd) - fx); + terminal_overwrite(el, (nfd + fx), len); + re__strncopy(ofd + fx, nfd + fx, len); + } else { + ELRE_DEBUG(1, (__F, "without anything to save\r\n")); + len = (size_t)(nsb - nfd); + terminal_overwrite(el, nfd, len); + re__strncopy(ofd, nfd, len); + /* + * Done + */ + return; + } + } else if (fx < 0) { + ELRE_DEBUG(1, + (__F, "first diff delete at %td...\r\n", ofd - old)); + /* + * move to the first char to delete where the first diff is + */ + terminal_move_to_char(el, (int)(ofd - old)); + /* + * Check if we have stuff to save + */ + if (osb != oe) { + ELRE_DEBUG(1, (__F, "with stuff to save at end\r\n")); + /* + * fx is less than zero *always* here but we check + * for code symmetry + */ + if (fx < 0) { + ELRE_DEBUG(!EL_CAN_DELETE, (__F, + "ERROR: cannot delete in first diff\n")); + terminal_deletechars(el, -fx); + re_delete(el, old, (int)(ofd - old), + el->el_terminal.t_size.h, -fx); + } + /* + * write (nsb-nfd) chars of new starting at nfd + */ + len = (size_t) (nsb - nfd); + terminal_overwrite(el, nfd, len); + re__strncopy(ofd, nfd, len); + + } else { + ELRE_DEBUG(1, (__F, + "but with nothing left to save\r\n")); + /* + * write (nsb-nfd) chars of new starting at nfd + */ + terminal_overwrite(el, nfd, (size_t)(nsb - nfd)); + re_clear_eol(el, fx, sx, + (int)((oe - old) - (ne - new))); + /* + * Done + */ + return; + } + } else + fx = 0; + + if (sx < 0 && (ose - old) + fx < el->el_terminal.t_size.h) { + ELRE_DEBUG(1, (__F, + "second diff delete at %td...\r\n", (ose - old) + fx)); + /* + * Check if we have stuff to delete + */ + /* + * fx is the number of characters inserted (+) or deleted (-) + */ + + terminal_move_to_char(el, (int)((ose - old) + fx)); + /* + * Check if we have stuff to save + */ + if (ols != oe) { + ELRE_DEBUG(1, (__F, "with stuff to save at end\r\n")); + /* + * Again a duplicate test. + */ + if (sx < 0) { + ELRE_DEBUG(!EL_CAN_DELETE, (__F, + "ERROR: cannot delete in second diff\n")); + terminal_deletechars(el, -sx); + } + /* + * write (nls-nse) chars of new starting at nse + */ + terminal_overwrite(el, nse, (size_t)(nls - nse)); + } else { + ELRE_DEBUG(1, (__F, + "but with nothing left to save\r\n")); + terminal_overwrite(el, nse, (size_t)(nls - nse)); + re_clear_eol(el, fx, sx, + (int)((oe - old) - (ne - new))); + } + } + /* + * if we have a first insert AND WE HAVEN'T ALREADY DONE IT... + */ + if ((nsb != nfd) && (osb - ofd) <= (nsb - nfd) && (fx == 0)) { + ELRE_DEBUG(1, (__F, "late first diff insert at %td...\r\n", + nfd - new)); + + terminal_move_to_char(el, (int)(nfd - new)); + /* + * Check if we have stuff to keep at the end + */ + if (nsb != ne) { + ELRE_DEBUG(1, (__F, "with stuff to keep at end\r\n")); + /* + * We have to recalculate fx here because we set it + * to zero above as a flag saying that we hadn't done + * an early first insert. + */ + fx = (int)((nsb - nfd) - (osb - ofd)); + if (fx > 0) { + /* + * insert fx chars of new starting at nfd + */ + ELRE_DEBUG(!EL_CAN_INSERT, (__F, + "ERROR: cannot insert in late first diff\n")); + terminal_insertwrite(el, nfd, fx); + re_insert(el, old, (int)(ofd - old), + el->el_terminal.t_size.h, nfd, fx); + } + /* + * write (nsb-nfd) - fx chars of new starting at + * (nfd + fx) + */ + len = (size_t) ((nsb - nfd) - fx); + terminal_overwrite(el, (nfd + fx), len); + re__strncopy(ofd + fx, nfd + fx, len); + } else { + ELRE_DEBUG(1, (__F, "without anything to save\r\n")); + len = (size_t) (nsb - nfd); + terminal_overwrite(el, nfd, len); + re__strncopy(ofd, nfd, len); + } + } + /* + * line is now NEW up to nse + */ + if (sx >= 0) { + ELRE_DEBUG(1, (__F, + "second diff insert at %d...\r\n", (int)(nse - new))); + terminal_move_to_char(el, (int)(nse - new)); + if (ols != oe) { + ELRE_DEBUG(1, (__F, "with stuff to keep at end\r\n")); + if (sx > 0) { + /* insert sx chars of new starting at nse */ + ELRE_DEBUG(!EL_CAN_INSERT, (__F, + "ERROR: cannot insert in second diff\n")); + terminal_insertwrite(el, nse, sx); + } + /* + * write (nls-nse) - sx chars of new starting at + * (nse + sx) + */ + terminal_overwrite(el, (nse + sx), + (size_t)((nls - nse) - sx)); + } else { + ELRE_DEBUG(1, (__F, "without anything to save\r\n")); + terminal_overwrite(el, nse, (size_t)(nls - nse)); + + /* + * No need to do a clear-to-end here because we were + * doing a second insert, so we will have over + * written all of the old string. + */ + } + } + ELRE_DEBUG(1, (__F, "done.\r\n")); +} + + +/* re__copy_and_pad(): + * Copy string and pad with spaces + */ +static void +re__copy_and_pad(wchar_t *dst, const wchar_t *src, size_t width) +{ + size_t i; + + for (i = 0; i < width; i++) { + if (*src == '\0') + break; + *dst++ = *src++; + } + + for (; i < width; i++) + *dst++ = ' '; + + *dst = '\0'; +} + + +/* re_refresh_cursor(): + * Move to the new cursor position + */ +libedit_private void +re_refresh_cursor(EditLine *el) +{ + wchar_t *cp; + int h, v, th, w; + + if (el->el_line.cursor >= el->el_line.lastchar) { + if (el->el_map.current == el->el_map.alt + && el->el_line.lastchar != el->el_line.buffer) + el->el_line.cursor = el->el_line.lastchar - 1; + else + el->el_line.cursor = el->el_line.lastchar; + } + + /* first we must find where the cursor is... */ + h = el->el_prompt.p_pos.h; + v = el->el_prompt.p_pos.v; + th = el->el_terminal.t_size.h; /* optimize for speed */ + + /* do input buffer to el->el_line.cursor */ + for (cp = el->el_line.buffer; cp < el->el_line.cursor; cp++) { + switch (ct_chr_class(*cp)) { + case CHTYPE_NL: /* handle newline in data part too */ + h = 0; + v++; + break; + case CHTYPE_TAB: /* if a tab, to next tab stop */ + while (++h & 07) + continue; + break; + default: + w = wcwidth(*cp); + if (w > 1 && h + w > th) { /* won't fit on line */ + h = 0; + v++; + } + h += ct_visual_width(*cp); + break; + } + + if (h >= th) { /* check, extra long tabs picked up here also */ + h -= th; + v++; + } + } + /* if we have a next character, and it's a doublewidth one, we need to + * check whether we need to linebreak for it to fit */ + if (cp < el->el_line.lastchar && (w = wcwidth(*cp)) > 1) + if (h + w > th) { + h = 0; + v++; + } + + /* now go there */ + terminal_move_to_line(el, v); + terminal_move_to_char(el, h); + terminal__flush(el); +} + + +/* re_fastputc(): + * Add a character fast. + */ +static void +re_fastputc(EditLine *el, wint_t c) +{ + int w = wcwidth(c); + while (w > 1 && el->el_cursor.h + w > el->el_terminal.t_size.h) + re_fastputc(el, ' '); + + terminal__putc(el, c); + el->el_display[el->el_cursor.v][el->el_cursor.h++] = c; + while (--w > 0) + el->el_display[el->el_cursor.v][el->el_cursor.h++] + = MB_FILL_CHAR; + + if (el->el_cursor.h >= el->el_terminal.t_size.h) { + /* if we must overflow */ + el->el_cursor.h = 0; + + /* + * If we would overflow (input is longer than terminal size), + * emulate scroll by dropping first line and shuffling the rest. + * We do this via pointer shuffling - it's safe in this case + * and we avoid memcpy(). + */ + if (el->el_cursor.v + 1 >= el->el_terminal.t_size.v) { + int i, lins = el->el_terminal.t_size.v; + wchar_t *firstline = el->el_display[0]; + + for(i = 1; i < lins; i++) + el->el_display[i - 1] = el->el_display[i]; + + re__copy_and_pad(firstline, L"", (size_t)0); + el->el_display[i - 1] = firstline; + } else { + el->el_cursor.v++; + el->el_refresh.r_oldcv++; + } + if (EL_HAS_AUTO_MARGINS) { + if (EL_HAS_MAGIC_MARGINS) { + terminal__putc(el, ' '); + terminal__putc(el, '\b'); + } + } else { + terminal__putc(el, '\r'); + terminal__putc(el, '\n'); + } + } +} + + +/* re_fastaddc(): + * we added just one char, handle it fast. + * Assumes that screen cursor == real cursor + */ +libedit_private void +re_fastaddc(EditLine *el) +{ + wchar_t c; + int rhdiff; + + c = el->el_line.cursor[-1]; + + if (c == '\t' || el->el_line.cursor != el->el_line.lastchar) { + re_refresh(el); /* too hard to handle */ + return; + } + rhdiff = el->el_terminal.t_size.h - el->el_cursor.h - + el->el_rprompt.p_pos.h; + if (el->el_rprompt.p_pos.h && rhdiff < 3) { + re_refresh(el); /* clear out rprompt if less than 1 char gap */ + return; + } /* else (only do at end of line, no TAB) */ + switch (ct_chr_class(c)) { + case CHTYPE_TAB: /* already handled, should never happen here */ + break; + case CHTYPE_NL: + case CHTYPE_PRINT: + re_fastputc(el, c); + break; + case CHTYPE_ASCIICTL: + case CHTYPE_NONPRINT: { + wchar_t visbuf[VISUAL_WIDTH_MAX]; + ssize_t i, n = + ct_visual_char(visbuf, VISUAL_WIDTH_MAX, c); + for (i = 0; n-- > 0; ++i) + re_fastputc(el, visbuf[i]); + break; + } + } + terminal__flush(el); +} + + +/* re_clear_display(): + * clear the screen buffers so that new new prompt starts fresh. + */ +libedit_private void +re_clear_display(EditLine *el) +{ + int i; + + el->el_cursor.v = 0; + el->el_cursor.h = 0; + for (i = 0; i < el->el_terminal.t_size.v; i++) + el->el_display[i][0] = '\0'; + el->el_refresh.r_oldcv = 0; +} + + +/* re_clear_lines(): + * Make sure all lines are *really* blank + */ +libedit_private void +re_clear_lines(EditLine *el) +{ + + if (EL_CAN_CEOL) { + int i; + for (i = el->el_refresh.r_oldcv; i >= 0; i--) { + /* for each line on the screen */ + terminal_move_to_line(el, i); + terminal_move_to_char(el, 0); + terminal_clear_EOL(el, el->el_terminal.t_size.h); + } + } else { + terminal_move_to_line(el, el->el_refresh.r_oldcv); + /* go to last line */ + terminal__putc(el, '\r'); /* go to BOL */ + terminal__putc(el, '\n'); /* go to new line */ + } +} diff --git a/bin/catsh/libedit/refresh.h b/bin/catsh/libedit/refresh.h new file mode 100644 index 00000000..09b22ef6 --- /dev/null +++ b/bin/catsh/libedit/refresh.h @@ -0,0 +1,59 @@ +/* $NetBSD: refresh.h,v 1.10.8.1 2017/07/23 14:41:26 snj Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)refresh.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.refresh.h: Screen refresh functions + */ +#ifndef _h_el_refresh +#define _h_el_refresh + +typedef struct { + coord_t r_cursor; /* Refresh cursor position */ + int r_oldcv; /* Vertical locations */ + int r_newcv; +} el_refresh_t; + +libedit_private void re_putc(EditLine *, wint_t, int); +libedit_private void re_putliteral(EditLine *, const wchar_t *, + const wchar_t *); +libedit_private void re_clear_lines(EditLine *); +libedit_private void re_clear_display(EditLine *); +libedit_private void re_refresh(EditLine *); +libedit_private void re_refresh_cursor(EditLine *); +libedit_private void re_fastaddc(EditLine *); +libedit_private void re_goto_bottom(EditLine *); + +#endif /* _h_el_refresh */ diff --git a/bin/catsh/libedit/search.c b/bin/catsh/libedit/search.c new file mode 100644 index 00000000..5226cf5f --- /dev/null +++ b/bin/catsh/libedit/search.c @@ -0,0 +1,641 @@ +/* $NetBSD: search.c,v 1.47 2016/05/09 21:46:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)search.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: search.c,v 1.47 2016/05/09 21:46:56 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * search.c: History and character search functions + */ +#include <stdlib.h> +#include <string.h> +#if defined(REGEX) +#include <regex.h> +#elif defined(REGEXP) +#include <regexp.h> +#endif + +#include "el.h" +#include "common.h" +#include "fcns.h" + +/* + * Adjust cursor in vi mode to include the character under it + */ +#define EL_CURSOR(el) \ + ((el)->el_line.cursor + (((el)->el_map.type == MAP_VI) && \ + ((el)->el_map.current == (el)->el_map.alt))) + +/* search_init(): + * Initialize the search stuff + */ +libedit_private int +search_init(EditLine *el) +{ + + el->el_search.patbuf = el_malloc(EL_BUFSIZ * + sizeof(*el->el_search.patbuf)); + if (el->el_search.patbuf == NULL) + return -1; + el->el_search.patbuf[0] = L'\0'; + el->el_search.patlen = 0; + el->el_search.patdir = -1; + el->el_search.chacha = L'\0'; + el->el_search.chadir = CHAR_FWD; + el->el_search.chatflg = 0; + return 0; +} + + +/* search_end(): + * Initialize the search stuff + */ +libedit_private void +search_end(EditLine *el) +{ + + el_free(el->el_search.patbuf); + el->el_search.patbuf = NULL; +} + + +#ifdef REGEXP +/* regerror(): + * Handle regular expression errors + */ +void +/*ARGSUSED*/ +regerror(const char *msg) +{ +} +#endif + + +/* el_match(): + * Return if string matches pattern + */ +libedit_private int +el_match(const wchar_t *str, const wchar_t *pat) +{ + static ct_buffer_t conv; +#if defined (REGEX) + regex_t re; + int rv; +#elif defined (REGEXP) + regexp *rp; + int rv; +#else + extern char *re_comp(const char *); + extern int re_exec(const char *); +#endif + + if (wcsstr(str, pat) != 0) + return 1; + +#if defined(REGEX) + if (regcomp(&re, ct_encode_string(pat, &conv), 0) == 0) { + rv = regexec(&re, ct_encode_string(str, &conv), (size_t)0, NULL, + 0) == 0; + regfree(&re); + } else { + rv = 0; + } + return rv; +#elif defined(REGEXP) + if ((re = regcomp(ct_encode_string(pat, &conv))) != NULL) { + rv = regexec(re, ct_encode_string(str, &conv)); + el_free(re); + } else { + rv = 0; + } + return rv; +#else + if (re_comp(ct_encode_string(pat, &conv)) != NULL) + return 0; + else + return re_exec(ct_encode_string(str, &conv)) == 1; +#endif +} + + +/* c_hmatch(): + * return True if the pattern matches the prefix + */ +libedit_private int +c_hmatch(EditLine *el, const wchar_t *str) +{ +#ifdef SDEBUG + (void) fprintf(el->el_errfile, "match `%s' with `%s'\n", + el->el_search.patbuf, str); +#endif /* SDEBUG */ + + return el_match(str, el->el_search.patbuf); +} + + +/* c_setpat(): + * Set the history seatch pattern + */ +libedit_private void +c_setpat(EditLine *el) +{ + if (el->el_state.lastcmd != ED_SEARCH_PREV_HISTORY && + el->el_state.lastcmd != ED_SEARCH_NEXT_HISTORY) { + el->el_search.patlen = + (size_t)(EL_CURSOR(el) - el->el_line.buffer); + if (el->el_search.patlen >= EL_BUFSIZ) + el->el_search.patlen = EL_BUFSIZ - 1; + if (el->el_search.patlen != 0) { + (void) wcsncpy(el->el_search.patbuf, el->el_line.buffer, + el->el_search.patlen); + el->el_search.patbuf[el->el_search.patlen] = '\0'; + } else + el->el_search.patlen = wcslen(el->el_search.patbuf); + } +#ifdef SDEBUG + (void) fprintf(el->el_errfile, "\neventno = %d\n", + el->el_history.eventno); + (void) fprintf(el->el_errfile, "patlen = %d\n", el->el_search.patlen); + (void) fprintf(el->el_errfile, "patbuf = \"%s\"\n", + el->el_search.patbuf); + (void) fprintf(el->el_errfile, "cursor %d lastchar %d\n", + EL_CURSOR(el) - el->el_line.buffer, + el->el_line.lastchar - el->el_line.buffer); +#endif +} + + +/* ce_inc_search(): + * Emacs incremental search + */ +libedit_private el_action_t +ce_inc_search(EditLine *el, int dir) +{ + static const wchar_t STRfwd[] = L"fwd", STRbck[] = L"bck"; + static wchar_t pchar = L':'; /* ':' = normal, '?' = failed */ + static wchar_t endcmd[2] = {'\0', '\0'}; + wchar_t *ocursor = el->el_line.cursor, oldpchar = pchar, ch; + const wchar_t *cp; + + el_action_t ret = CC_NORM; + + int ohisteventno = el->el_history.eventno; + size_t oldpatlen = el->el_search.patlen; + int newdir = dir; + int done, redo; + + if (el->el_line.lastchar + sizeof(STRfwd) / + sizeof(*el->el_line.lastchar) + 2 + + el->el_search.patlen >= el->el_line.limit) + return CC_ERROR; + + for (;;) { + + if (el->el_search.patlen == 0) { /* first round */ + pchar = ':'; +#ifdef ANCHOR +#define LEN 2 + el->el_search.patbuf[el->el_search.patlen++] = '.'; + el->el_search.patbuf[el->el_search.patlen++] = '*'; +#else +#define LEN 0 +#endif + } + done = redo = 0; + *el->el_line.lastchar++ = '\n'; + for (cp = (newdir == ED_SEARCH_PREV_HISTORY) ? STRbck : STRfwd; + *cp; *el->el_line.lastchar++ = *cp++) + continue; + *el->el_line.lastchar++ = pchar; + for (cp = &el->el_search.patbuf[LEN]; + cp < &el->el_search.patbuf[el->el_search.patlen]; + *el->el_line.lastchar++ = *cp++) + continue; + *el->el_line.lastchar = '\0'; + re_refresh(el); + + if (el_wgetc(el, &ch) != 1) + return ed_end_of_file(el, 0); + + switch (el->el_map.current[(unsigned char) ch]) { + case ED_INSERT: + case ED_DIGIT: + if (el->el_search.patlen >= EL_BUFSIZ - LEN) + terminal_beep(el); + else { + el->el_search.patbuf[el->el_search.patlen++] = + ch; + *el->el_line.lastchar++ = ch; + *el->el_line.lastchar = '\0'; + re_refresh(el); + } + break; + + case EM_INC_SEARCH_NEXT: + newdir = ED_SEARCH_NEXT_HISTORY; + redo++; + break; + + case EM_INC_SEARCH_PREV: + newdir = ED_SEARCH_PREV_HISTORY; + redo++; + break; + + case EM_DELETE_PREV_CHAR: + case ED_DELETE_PREV_CHAR: + if (el->el_search.patlen > LEN) + done++; + else + terminal_beep(el); + break; + + default: + switch (ch) { + case 0007: /* ^G: Abort */ + ret = CC_ERROR; + done++; + break; + + case 0027: /* ^W: Append word */ + /* No can do if globbing characters in pattern */ + for (cp = &el->el_search.patbuf[LEN];; cp++) + if (cp >= &el->el_search.patbuf[ + el->el_search.patlen]) { + el->el_line.cursor += + el->el_search.patlen - LEN - 1; + cp = c__next_word(el->el_line.cursor, + el->el_line.lastchar, 1, + ce__isword); + while (el->el_line.cursor < cp && + *el->el_line.cursor != '\n') { + if (el->el_search.patlen >= + EL_BUFSIZ - LEN) { + terminal_beep(el); + break; + } + el->el_search.patbuf[el->el_search.patlen++] = + *el->el_line.cursor; + *el->el_line.lastchar++ = + *el->el_line.cursor++; + } + el->el_line.cursor = ocursor; + *el->el_line.lastchar = '\0'; + re_refresh(el); + break; + } else if (isglob(*cp)) { + terminal_beep(el); + break; + } + break; + + default: /* Terminate and execute cmd */ + endcmd[0] = ch; + el_wpush(el, endcmd); + /* FALLTHROUGH */ + + case 0033: /* ESC: Terminate */ + ret = CC_REFRESH; + done++; + break; + } + break; + } + + while (el->el_line.lastchar > el->el_line.buffer && + *el->el_line.lastchar != '\n') + *el->el_line.lastchar-- = '\0'; + *el->el_line.lastchar = '\0'; + + if (!done) { + + /* Can't search if unmatched '[' */ + for (cp = &el->el_search.patbuf[el->el_search.patlen-1], + ch = L']'; + cp >= &el->el_search.patbuf[LEN]; + cp--) + if (*cp == '[' || *cp == ']') { + ch = *cp; + break; + } + if (el->el_search.patlen > LEN && ch != L'[') { + if (redo && newdir == dir) { + if (pchar == '?') { /* wrap around */ + el->el_history.eventno = + newdir == ED_SEARCH_PREV_HISTORY ? 0 : 0x7fffffff; + if (hist_get(el) == CC_ERROR) + /* el->el_history.event + * no was fixed by + * first call */ + (void) hist_get(el); + el->el_line.cursor = newdir == + ED_SEARCH_PREV_HISTORY ? + el->el_line.lastchar : + el->el_line.buffer; + } else + el->el_line.cursor += + newdir == + ED_SEARCH_PREV_HISTORY ? + -1 : 1; + } +#ifdef ANCHOR + el->el_search.patbuf[el->el_search.patlen++] = + '.'; + el->el_search.patbuf[el->el_search.patlen++] = + '*'; +#endif + el->el_search.patbuf[el->el_search.patlen] = + '\0'; + if (el->el_line.cursor < el->el_line.buffer || + el->el_line.cursor > el->el_line.lastchar || + (ret = ce_search_line(el, newdir)) + == CC_ERROR) { + /* avoid c_setpat */ + el->el_state.lastcmd = + (el_action_t) newdir; + ret = (el_action_t) + (newdir == ED_SEARCH_PREV_HISTORY ? + ed_search_prev_history(el, 0) : + ed_search_next_history(el, 0)); + if (ret != CC_ERROR) { + el->el_line.cursor = newdir == + ED_SEARCH_PREV_HISTORY ? + el->el_line.lastchar : + el->el_line.buffer; + (void) ce_search_line(el, + newdir); + } + } + el->el_search.patlen -= LEN; + el->el_search.patbuf[el->el_search.patlen] = + '\0'; + if (ret == CC_ERROR) { + terminal_beep(el); + if (el->el_history.eventno != + ohisteventno) { + el->el_history.eventno = + ohisteventno; + if (hist_get(el) == CC_ERROR) + return CC_ERROR; + } + el->el_line.cursor = ocursor; + pchar = '?'; + } else { + pchar = ':'; + } + } + ret = ce_inc_search(el, newdir); + + if (ret == CC_ERROR && pchar == '?' && oldpchar == ':') + /* + * break abort of failed search at last + * non-failed + */ + ret = CC_NORM; + + } + if (ret == CC_NORM || (ret == CC_ERROR && oldpatlen == 0)) { + /* restore on normal return or error exit */ + pchar = oldpchar; + el->el_search.patlen = oldpatlen; + if (el->el_history.eventno != ohisteventno) { + el->el_history.eventno = ohisteventno; + if (hist_get(el) == CC_ERROR) + return CC_ERROR; + } + el->el_line.cursor = ocursor; + if (ret == CC_ERROR) + re_refresh(el); + } + if (done || ret != CC_NORM) + return ret; + } +} + + +/* cv_search(): + * Vi search. + */ +libedit_private el_action_t +cv_search(EditLine *el, int dir) +{ + wchar_t ch; + wchar_t tmpbuf[EL_BUFSIZ]; + ssize_t tmplen; + +#ifdef ANCHOR + tmpbuf[0] = '.'; + tmpbuf[1] = '*'; +#endif + tmplen = LEN; + + el->el_search.patdir = dir; + + tmplen = c_gets(el, &tmpbuf[LEN], + dir == ED_SEARCH_PREV_HISTORY ? L"\n/" : L"\n?" ); + if (tmplen == -1) + return CC_REFRESH; + + tmplen += LEN; + ch = tmpbuf[tmplen]; + tmpbuf[tmplen] = '\0'; + + if (tmplen == LEN) { + /* + * Use the old pattern, but wild-card it. + */ + if (el->el_search.patlen == 0) { + re_refresh(el); + return CC_ERROR; + } +#ifdef ANCHOR + if (el->el_search.patbuf[0] != '.' && + el->el_search.patbuf[0] != '*') { + (void) wcsncpy(tmpbuf, el->el_search.patbuf, + sizeof(tmpbuf) / sizeof(*tmpbuf) - 1); + el->el_search.patbuf[0] = '.'; + el->el_search.patbuf[1] = '*'; + (void) wcsncpy(&el->el_search.patbuf[2], tmpbuf, + EL_BUFSIZ - 3); + el->el_search.patlen++; + el->el_search.patbuf[el->el_search.patlen++] = '.'; + el->el_search.patbuf[el->el_search.patlen++] = '*'; + el->el_search.patbuf[el->el_search.patlen] = '\0'; + } +#endif + } else { +#ifdef ANCHOR + tmpbuf[tmplen++] = '.'; + tmpbuf[tmplen++] = '*'; +#endif + tmpbuf[tmplen] = '\0'; + (void) wcsncpy(el->el_search.patbuf, tmpbuf, EL_BUFSIZ - 1); + el->el_search.patlen = (size_t)tmplen; + } + el->el_state.lastcmd = (el_action_t) dir; /* avoid c_setpat */ + el->el_line.cursor = el->el_line.lastchar = el->el_line.buffer; + if ((dir == ED_SEARCH_PREV_HISTORY ? ed_search_prev_history(el, 0) : + ed_search_next_history(el, 0)) == CC_ERROR) { + re_refresh(el); + return CC_ERROR; + } + if (ch == 0033) { + re_refresh(el); + return ed_newline(el, 0); + } + return CC_REFRESH; +} + + +/* ce_search_line(): + * Look for a pattern inside a line + */ +libedit_private el_action_t +ce_search_line(EditLine *el, int dir) +{ + wchar_t *cp = el->el_line.cursor; + wchar_t *pattern = el->el_search.patbuf; + wchar_t oc, *ocp; +#ifdef ANCHOR + ocp = &pattern[1]; + oc = *ocp; + *ocp = '^'; +#else + ocp = pattern; + oc = *ocp; +#endif + + if (dir == ED_SEARCH_PREV_HISTORY) { + for (; cp >= el->el_line.buffer; cp--) { + if (el_match(cp, ocp)) { + *ocp = oc; + el->el_line.cursor = cp; + return CC_NORM; + } + } + *ocp = oc; + return CC_ERROR; + } else { + for (; *cp != '\0' && cp < el->el_line.limit; cp++) { + if (el_match(cp, ocp)) { + *ocp = oc; + el->el_line.cursor = cp; + return CC_NORM; + } + } + *ocp = oc; + return CC_ERROR; + } +} + + +/* cv_repeat_srch(): + * Vi repeat search + */ +libedit_private el_action_t +cv_repeat_srch(EditLine *el, wint_t c) +{ + +#ifdef SDEBUG + (void) fprintf(el->el_errfile, "dir %d patlen %d patbuf %s\n", + c, el->el_search.patlen, ct_encode_string(el->el_search.patbuf)); +#endif + + el->el_state.lastcmd = (el_action_t) c; /* Hack to stop c_setpat */ + el->el_line.lastchar = el->el_line.buffer; + + switch (c) { + case ED_SEARCH_NEXT_HISTORY: + return ed_search_next_history(el, 0); + case ED_SEARCH_PREV_HISTORY: + return ed_search_prev_history(el, 0); + default: + return CC_ERROR; + } +} + + +/* cv_csearch(): + * Vi character search + */ +libedit_private el_action_t +cv_csearch(EditLine *el, int direction, wint_t ch, int count, int tflag) +{ + wchar_t *cp; + + if (ch == 0) + return CC_ERROR; + + if (ch == (wint_t)-1) { + if (el_wgetc(el, &ch) != 1) + return ed_end_of_file(el, 0); + } + + /* Save for ';' and ',' commands */ + el->el_search.chacha = ch; + el->el_search.chadir = direction; + el->el_search.chatflg = (char)tflag; + + cp = el->el_line.cursor; + while (count--) { + if ((wint_t)*cp == ch) + cp += direction; + for (;;cp += direction) { + if (cp >= el->el_line.lastchar) + return CC_ERROR; + if (cp < el->el_line.buffer) + return CC_ERROR; + if ((wint_t)*cp == ch) + break; + } + } + + if (tflag) + cp -= direction; + + el->el_line.cursor = cp; + + if (el->el_chared.c_vcmd.action != NOP) { + if (direction > 0) + el->el_line.cursor++; + cv_delfini(el); + return CC_REFRESH; + } + return CC_CURSOR; +} diff --git a/bin/catsh/libedit/search.h b/bin/catsh/libedit/search.h new file mode 100644 index 00000000..4ca39c4c --- /dev/null +++ b/bin/catsh/libedit/search.h @@ -0,0 +1,64 @@ +/* $NetBSD: search.h,v 1.14 2016/05/09 21:46:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)search.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.search.h: Line and history searching utilities + */ +#ifndef _h_el_search +#define _h_el_search + +typedef struct el_search_t { + wchar_t *patbuf; /* The pattern buffer */ + size_t patlen; /* Length of the pattern buffer */ + int patdir; /* Direction of the last search */ + int chadir; /* Character search direction */ + wchar_t chacha; /* Character we are looking for */ + char chatflg; /* 0 if f, 1 if t */ +} el_search_t; + + +libedit_private int el_match(const wchar_t *, const wchar_t *); +libedit_private int search_init(EditLine *); +libedit_private void search_end(EditLine *); +libedit_private int c_hmatch(EditLine *, const wchar_t *); +libedit_private void c_setpat(EditLine *); +libedit_private el_action_t ce_inc_search(EditLine *, int); +libedit_private el_action_t cv_search(EditLine *, int); +libedit_private el_action_t ce_search_line(EditLine *, int); +libedit_private el_action_t cv_repeat_srch(EditLine *, wint_t); +libedit_private el_action_t cv_csearch(EditLine *, int, wint_t, int, int); + +#endif /* _h_el_search */ diff --git a/bin/catsh/libedit/shlib_version b/bin/catsh/libedit/shlib_version new file mode 100644 index 00000000..303609d2 --- /dev/null +++ b/bin/catsh/libedit/shlib_version @@ -0,0 +1,5 @@ +# $NetBSD: shlib_version,v 1.19 2013/01/22 20:23:21 christos Exp $ +# Remember to update distrib/sets/lists/base/shl.* when changing +# +major=3 +minor=1 diff --git a/bin/catsh/libedit/sig.c b/bin/catsh/libedit/sig.c new file mode 100644 index 00000000..83742a3d --- /dev/null +++ b/bin/catsh/libedit/sig.c @@ -0,0 +1,205 @@ +/* $NetBSD: sig.c,v 1.26 2016/05/09 21:46:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)sig.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: sig.c,v 1.26 2016/05/09 21:46:56 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * sig.c: Signal handling stuff. + * our policy is to trap all signals, set a good state + * and pass the ball to our caller. + */ +#include <errno.h> +#include <stdlib.h> + +#include "el.h" +#include "common.h" + +static EditLine *sel = NULL; + +static const int sighdl[] = { +#define _DO(a) (a), + ALLSIGS +#undef _DO + - 1 +}; + +static void sig_handler(int); + +/* sig_handler(): + * This is the handler called for all signals + * XXX: we cannot pass any data so we just store the old editline + * state in a private variable + */ +static void +sig_handler(int signo) +{ + int i, save_errno; + sigset_t nset, oset; + + save_errno = errno; + (void) sigemptyset(&nset); + (void) sigaddset(&nset, signo); + (void) sigprocmask(SIG_BLOCK, &nset, &oset); + + sel->el_signal->sig_no = signo; + + switch (signo) { + case SIGCONT: + tty_rawmode(sel); + if (ed_redisplay(sel, 0) == CC_REFRESH) + re_refresh(sel); + terminal__flush(sel); + break; + + case SIGWINCH: + el_resize(sel); + break; + + default: + tty_cookedmode(sel); + break; + } + + for (i = 0; sighdl[i] != -1; i++) + if (signo == sighdl[i]) + break; + + (void) sigaction(signo, &sel->el_signal->sig_action[i], NULL); + sel->el_signal->sig_action[i].sa_handler = SIG_ERR; + sel->el_signal->sig_action[i].sa_flags = 0; + sigemptyset(&sel->el_signal->sig_action[i].sa_mask); + (void) sigprocmask(SIG_SETMASK, &oset, NULL); + (void) kill(0, signo); + errno = save_errno; +} + + +/* sig_init(): + * Initialize all signal stuff + */ +libedit_private int +sig_init(EditLine *el) +{ + size_t i; + sigset_t *nset, oset; + + el->el_signal = el_malloc(sizeof(*el->el_signal)); + if (el->el_signal == NULL) + return -1; + + nset = &el->el_signal->sig_set; + (void) sigemptyset(nset); +#define _DO(a) (void) sigaddset(nset, a); + ALLSIGS +#undef _DO + (void) sigprocmask(SIG_BLOCK, nset, &oset); + + for (i = 0; sighdl[i] != -1; i++) { + el->el_signal->sig_action[i].sa_handler = SIG_ERR; + el->el_signal->sig_action[i].sa_flags = 0; + sigemptyset(&el->el_signal->sig_action[i].sa_mask); + } + + (void) sigprocmask(SIG_SETMASK, &oset, NULL); + + return 0; +} + + +/* sig_end(): + * Clear all signal stuff + */ +libedit_private void +sig_end(EditLine *el) +{ + + el_free(el->el_signal); + el->el_signal = NULL; +} + + +/* sig_set(): + * set all the signal handlers + */ +libedit_private void +sig_set(EditLine *el) +{ + size_t i; + sigset_t oset; + struct sigaction osa, nsa; + + nsa.sa_handler = sig_handler; + nsa.sa_flags = 0; + sigemptyset(&nsa.sa_mask); + + (void) sigprocmask(SIG_BLOCK, &el->el_signal->sig_set, &oset); + + for (i = 0; sighdl[i] != -1; i++) { + /* This could happen if we get interrupted */ + if (sigaction(sighdl[i], &nsa, &osa) != -1 && + osa.sa_handler != sig_handler) + el->el_signal->sig_action[i] = osa; + } + sel = el; + (void) sigprocmask(SIG_SETMASK, &oset, NULL); +} + + +/* sig_clr(): + * clear all the signal handlers + */ +libedit_private void +sig_clr(EditLine *el) +{ + size_t i; + sigset_t oset; + + (void) sigprocmask(SIG_BLOCK, &el->el_signal->sig_set, &oset); + + for (i = 0; sighdl[i] != -1; i++) + if (el->el_signal->sig_action[i].sa_handler != SIG_ERR) + (void)sigaction(sighdl[i], + &el->el_signal->sig_action[i], NULL); + + sel = NULL; /* we are going to die if the handler is + * called */ + (void)sigprocmask(SIG_SETMASK, &oset, NULL); +} diff --git a/bin/catsh/libedit/sig.h b/bin/catsh/libedit/sig.h new file mode 100644 index 00000000..5ee453fb --- /dev/null +++ b/bin/catsh/libedit/sig.h @@ -0,0 +1,70 @@ +/* $NetBSD: sig.h,v 1.11 2016/05/09 21:46:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)sig.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.sig.h: Signal handling functions + */ +#ifndef _h_el_sig +#define _h_el_sig + +#include <signal.h> + +/* + * Define here all the signals we are going to handle + * The _DO macro is used to iterate in the source code + */ +#define ALLSIGS \ + _DO(SIGINT) \ + _DO(SIGTSTP) \ + _DO(SIGQUIT) \ + _DO(SIGHUP) \ + _DO(SIGTERM) \ + _DO(SIGCONT) \ + _DO(SIGWINCH) +#define ALLSIGSNO 7 + +typedef struct { + struct sigaction sig_action[ALLSIGSNO]; + sigset_t sig_set; + volatile sig_atomic_t sig_no; +} *el_signal_t; + +libedit_private void sig_end(EditLine*); +libedit_private int sig_init(EditLine*); +libedit_private void sig_set(EditLine*); +libedit_private void sig_clr(EditLine*); + +#endif /* _h_el_sig */ diff --git a/bin/catsh/libedit/sys.h b/bin/catsh/libedit/sys.h new file mode 100644 index 00000000..dc0a8cb9 --- /dev/null +++ b/bin/catsh/libedit/sys.h @@ -0,0 +1,113 @@ +/* $NetBSD: sys.h,v 1.27 2016/05/09 21:46:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)sys.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * sys.h: Put all the stupid compiler and system dependencies here... + */ +#ifndef _h_sys +#define _h_sys + +#ifdef HAVE_SYS_CDEFS_H +#include <sys/cdefs.h> +#endif + +#if !defined(__attribute__) && (defined(__cplusplus) || !defined(__GNUC__) || __GNUC__ == 2 && __GNUC_MINOR__ < 8) +# define __attribute__(A) +#endif + +#ifndef __BEGIN_DECLS +# ifdef __cplusplus +# define __BEGIN_DECLS extern "C" { +# define __END_DECLS } +# else +# define __BEGIN_DECLS +# define __END_DECLS +# endif +#endif + +/* If your compiler does not support this, define it to be empty. */ +#define libedit_private __attribute__((__visibility__("hidden"))) + +#ifndef __arraycount +# define __arraycount(a) (sizeof(a) / sizeof(*(a))) +#endif + +#include <stdio.h> + +#ifndef HAVE_STRLCAT +#define strlcat libedit_strlcat +size_t strlcat(char *dst, const char *src, size_t size); +#endif + +#ifndef HAVE_STRLCPY +#define strlcpy libedit_strlcpy +size_t strlcpy(char *dst, const char *src, size_t size); +#endif + +#ifndef HAVE_GETLINE +#define getline libedit_getline +ssize_t getline(char **line, size_t *len, FILE *fp); +#endif + +#ifndef _DIAGASSERT +#define _DIAGASSERT(x) +#endif + +#ifndef __RCSID +#define __RCSID(x) +#endif + +#ifndef HAVE_U_INT32_T +typedef unsigned int u_int32_t; +#endif + +#ifndef HAVE_SIZE_MAX +#define SIZE_MAX ((size_t)-1) +#endif + +#define REGEX /* Use POSIX.2 regular expression functions */ +#undef REGEXP /* Use UNIX V8 regular expression functions */ + +#if defined(__sun) +extern int tgetent(char *, const char *); +extern int tgetflag(char *); +extern int tgetnum(char *); +extern int tputs(const char *, int, int (*)(int)); +extern char* tgoto(const char*, int, int); +extern char* tgetstr(char*, char**); +#endif + +#endif /* _h_sys */ diff --git a/bin/catsh/libedit/terminal.c b/bin/catsh/libedit/terminal.c new file mode 100644 index 00000000..edaa5b2b --- /dev/null +++ b/bin/catsh/libedit/terminal.c @@ -0,0 +1,1681 @@ +/* $NetBSD: terminal.c,v 1.32.8.1 2017/07/23 14:41:26 snj Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)term.c 8.2 (Berkeley) 4/30/95"; +#else +__RCSID("$NetBSD: terminal.c,v 1.32.8.1 2017/07/23 14:41:26 snj Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * terminal.c: Editor/termcap-curses interface + * We have to declare a static variable here, since the + * termcap putchar routine does not take an argument! + */ +#include <sys/types.h> +#include <sys/ioctl.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#ifdef HAVE_TERMCAP_H +#include <termcap.h> +#endif +#ifdef HAVE_CURSES_H +#include <curses.h> +#elif HAVE_NCURSES_H +#include <ncurses.h> +#endif + +/* Solaris's term.h does horrid things. */ +#if defined(HAVE_TERM_H) && !defined(__sun) && !defined(HAVE_TERMCAP_H) +#include <term.h> +#endif + +#ifdef _REENTRANT +#include <pthread.h> +#endif + +#include "el.h" +#include "fcns.h" + +/* + * IMPORTANT NOTE: these routines are allowed to look at the current screen + * and the current position assuming that it is correct. If this is not + * true, then the update will be WRONG! This is (should be) a valid + * assumption... + */ + +#define TC_BUFSIZE ((size_t)2048) + +#define GoodStr(a) (el->el_terminal.t_str[a] != NULL && \ + el->el_terminal.t_str[a][0] != '\0') +#define Str(a) el->el_terminal.t_str[a] +#define Val(a) el->el_terminal.t_val[a] + +static const struct termcapstr { + const char *name; + const char *long_name; +} tstr[] = { +#define T_al 0 + { "al", "add new blank line" }, +#define T_bl 1 + { "bl", "audible bell" }, +#define T_cd 2 + { "cd", "clear to bottom" }, +#define T_ce 3 + { "ce", "clear to end of line" }, +#define T_ch 4 + { "ch", "cursor to horiz pos" }, +#define T_cl 5 + { "cl", "clear screen" }, +#define T_dc 6 + { "dc", "delete a character" }, +#define T_dl 7 + { "dl", "delete a line" }, +#define T_dm 8 + { "dm", "start delete mode" }, +#define T_ed 9 + { "ed", "end delete mode" }, +#define T_ei 10 + { "ei", "end insert mode" }, +#define T_fs 11 + { "fs", "cursor from status line" }, +#define T_ho 12 + { "ho", "home cursor" }, +#define T_ic 13 + { "ic", "insert character" }, +#define T_im 14 + { "im", "start insert mode" }, +#define T_ip 15 + { "ip", "insert padding" }, +#define T_kd 16 + { "kd", "sends cursor down" }, +#define T_kl 17 + { "kl", "sends cursor left" }, +#define T_kr 18 + { "kr", "sends cursor right" }, +#define T_ku 19 + { "ku", "sends cursor up" }, +#define T_md 20 + { "md", "begin bold" }, +#define T_me 21 + { "me", "end attributes" }, +#define T_nd 22 + { "nd", "non destructive space" }, +#define T_se 23 + { "se", "end standout" }, +#define T_so 24 + { "so", "begin standout" }, +#define T_ts 25 + { "ts", "cursor to status line" }, +#define T_up 26 + { "up", "cursor up one" }, +#define T_us 27 + { "us", "begin underline" }, +#define T_ue 28 + { "ue", "end underline" }, +#define T_vb 29 + { "vb", "visible bell" }, +#define T_DC 30 + { "DC", "delete multiple chars" }, +#define T_DO 31 + { "DO", "cursor down multiple" }, +#define T_IC 32 + { "IC", "insert multiple chars" }, +#define T_LE 33 + { "LE", "cursor left multiple" }, +#define T_RI 34 + { "RI", "cursor right multiple" }, +#define T_UP 35 + { "UP", "cursor up multiple" }, +#define T_kh 36 + { "kh", "send cursor home" }, +#define T_at7 37 + { "@7", "send cursor end" }, +#define T_kD 38 + { "kD", "send cursor delete" }, +#define T_str 39 + { NULL, NULL } +}; + +static const struct termcapval { + const char *name; + const char *long_name; +} tval[] = { +#define T_am 0 + { "am", "has automatic margins" }, +#define T_pt 1 + { "pt", "has physical tabs" }, +#define T_li 2 + { "li", "Number of lines" }, +#define T_co 3 + { "co", "Number of columns" }, +#define T_km 4 + { "km", "Has meta key" }, +#define T_xt 5 + { "xt", "Tab chars destructive" }, +#define T_xn 6 + { "xn", "newline ignored at right margin" }, +#define T_MT 7 + { "MT", "Has meta key" }, /* XXX? */ +#define T_val 8 + { NULL, NULL, } +}; +/* do two or more of the attributes use me */ + +static void terminal_setflags(EditLine *); +static int terminal_rebuffer_display(EditLine *); +static void terminal_free_display(EditLine *); +static int terminal_alloc_display(EditLine *); +static void terminal_alloc(EditLine *, const struct termcapstr *, + const char *); +static void terminal_init_arrow(EditLine *); +static void terminal_reset_arrow(EditLine *); +static int terminal_putc(int); +static void terminal_tputs(EditLine *, const char *, int); + +#ifdef _REENTRANT +static pthread_mutex_t terminal_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif +static FILE *terminal_outfile = NULL; + + +/* terminal_setflags(): + * Set the terminal capability flags + */ +static void +terminal_setflags(EditLine *el) +{ + EL_FLAGS = 0; + if (el->el_tty.t_tabs) + EL_FLAGS |= (Val(T_pt) && !Val(T_xt)) ? TERM_CAN_TAB : 0; + + EL_FLAGS |= (Val(T_km) || Val(T_MT)) ? TERM_HAS_META : 0; + EL_FLAGS |= GoodStr(T_ce) ? TERM_CAN_CEOL : 0; + EL_FLAGS |= (GoodStr(T_dc) || GoodStr(T_DC)) ? TERM_CAN_DELETE : 0; + EL_FLAGS |= (GoodStr(T_im) || GoodStr(T_ic) || GoodStr(T_IC)) ? + TERM_CAN_INSERT : 0; + EL_FLAGS |= (GoodStr(T_up) || GoodStr(T_UP)) ? TERM_CAN_UP : 0; + EL_FLAGS |= Val(T_am) ? TERM_HAS_AUTO_MARGINS : 0; + EL_FLAGS |= Val(T_xn) ? TERM_HAS_MAGIC_MARGINS : 0; + + if (GoodStr(T_me) && GoodStr(T_ue)) + EL_FLAGS |= (strcmp(Str(T_me), Str(T_ue)) == 0) ? + TERM_CAN_ME : 0; + else + EL_FLAGS &= ~TERM_CAN_ME; + if (GoodStr(T_me) && GoodStr(T_se)) + EL_FLAGS |= (strcmp(Str(T_me), Str(T_se)) == 0) ? + TERM_CAN_ME : 0; + + +#ifdef DEBUG_SCREEN + if (!EL_CAN_UP) { + (void) fprintf(el->el_errfile, + "WARNING: Your terminal cannot move up.\n"); + (void) fprintf(el->el_errfile, + "Editing may be odd for long lines.\n"); + } + if (!EL_CAN_CEOL) + (void) fprintf(el->el_errfile, "no clear EOL capability.\n"); + if (!EL_CAN_DELETE) + (void) fprintf(el->el_errfile, "no delete char capability.\n"); + if (!EL_CAN_INSERT) + (void) fprintf(el->el_errfile, "no insert char capability.\n"); +#endif /* DEBUG_SCREEN */ +} + +/* terminal_init(): + * Initialize the terminal stuff + */ +libedit_private int +terminal_init(EditLine *el) +{ + + el->el_terminal.t_buf = el_malloc(TC_BUFSIZE * + sizeof(*el->el_terminal.t_buf)); + if (el->el_terminal.t_buf == NULL) + goto fail1; + el->el_terminal.t_cap = el_malloc(TC_BUFSIZE * + sizeof(*el->el_terminal.t_cap)); + if (el->el_terminal.t_cap == NULL) + goto fail2; + el->el_terminal.t_fkey = el_malloc(A_K_NKEYS * + sizeof(*el->el_terminal.t_fkey)); + if (el->el_terminal.t_fkey == NULL) + goto fail3; + el->el_terminal.t_loc = 0; + el->el_terminal.t_str = el_malloc(T_str * + sizeof(*el->el_terminal.t_str)); + if (el->el_terminal.t_str == NULL) + goto fail4; + (void) memset(el->el_terminal.t_str, 0, T_str * + sizeof(*el->el_terminal.t_str)); + el->el_terminal.t_val = el_malloc(T_val * + sizeof(*el->el_terminal.t_val)); + if (el->el_terminal.t_val == NULL) + goto fail5; + (void) memset(el->el_terminal.t_val, 0, T_val * + sizeof(*el->el_terminal.t_val)); + (void) terminal_set(el, NULL); + terminal_init_arrow(el); + return 0; +fail5: + free(el->el_terminal.t_str); + el->el_terminal.t_str = NULL; +fail4: + free(el->el_terminal.t_fkey); + el->el_terminal.t_fkey = NULL; +fail3: + free(el->el_terminal.t_cap); + el->el_terminal.t_cap = NULL; +fail2: + free(el->el_terminal.t_buf); + el->el_terminal.t_buf = NULL; +fail1: + return -1; +} + +/* terminal_end(): + * Clean up the terminal stuff + */ +libedit_private void +terminal_end(EditLine *el) +{ + + el_free(el->el_terminal.t_buf); + el->el_terminal.t_buf = NULL; + el_free(el->el_terminal.t_cap); + el->el_terminal.t_cap = NULL; + el->el_terminal.t_loc = 0; + el_free(el->el_terminal.t_str); + el->el_terminal.t_str = NULL; + el_free(el->el_terminal.t_val); + el->el_terminal.t_val = NULL; + el_free(el->el_terminal.t_fkey); + el->el_terminal.t_fkey = NULL; + terminal_free_display(el); +} + + +/* terminal_alloc(): + * Maintain a string pool for termcap strings + */ +static void +terminal_alloc(EditLine *el, const struct termcapstr *t, const char *cap) +{ + char termbuf[TC_BUFSIZE]; + size_t tlen, clen; + char **tlist = el->el_terminal.t_str; + char **tmp, **str = &tlist[t - tstr]; + + (void) memset(termbuf, 0, sizeof(termbuf)); + if (cap == NULL || *cap == '\0') { + *str = NULL; + return; + } else + clen = strlen(cap); + + tlen = *str == NULL ? 0 : strlen(*str); + + /* + * New string is shorter; no need to allocate space + */ + if (clen <= tlen) { + if (*str) + (void) strcpy(*str, cap); /* XXX strcpy is safe */ + return; + } + /* + * New string is longer; see if we have enough space to append + */ + if (el->el_terminal.t_loc + 3 < TC_BUFSIZE) { + /* XXX strcpy is safe */ + (void) strcpy(*str = &el->el_terminal.t_buf[ + el->el_terminal.t_loc], cap); + el->el_terminal.t_loc += clen + 1; /* one for \0 */ + return; + } + /* + * Compact our buffer; no need to check compaction, cause we know it + * fits... + */ + tlen = 0; + for (tmp = tlist; tmp < &tlist[T_str]; tmp++) + if (*tmp != NULL && **tmp != '\0' && *tmp != *str) { + char *ptr; + + for (ptr = *tmp; *ptr != '\0'; termbuf[tlen++] = *ptr++) + continue; + termbuf[tlen++] = '\0'; + } + memcpy(el->el_terminal.t_buf, termbuf, TC_BUFSIZE); + el->el_terminal.t_loc = tlen; + if (el->el_terminal.t_loc + 3 >= TC_BUFSIZE) { + (void) fprintf(el->el_errfile, + "Out of termcap string space.\n"); + return; + } + /* XXX strcpy is safe */ + (void) strcpy(*str = &el->el_terminal.t_buf[el->el_terminal.t_loc], + cap); + el->el_terminal.t_loc += (size_t)clen + 1; /* one for \0 */ + return; +} + + +/* terminal_rebuffer_display(): + * Rebuffer the display after the screen changed size + */ +static int +terminal_rebuffer_display(EditLine *el) +{ + coord_t *c = &el->el_terminal.t_size; + + terminal_free_display(el); + + c->h = Val(T_co); + c->v = Val(T_li); + + if (terminal_alloc_display(el) == -1) + return -1; + return 0; +} + +static wchar_t ** +terminal_alloc_buffer(EditLine *el) +{ + wint_t **b; + coord_t *c = &el->el_terminal.t_size; + int i; + + b = el_malloc(sizeof(*b) * (size_t)(c->v + 1)); + if (b == NULL) + return NULL; + for (i = 0; i < c->v; i++) { + b[i] = el_malloc(sizeof(**b) * (size_t)(c->h + 1)); + if (b[i] == NULL) { + while (--i >= 0) + el_free(b[i]); + el_free(b); + return NULL; + } + } + b[c->v] = NULL; + return b; +} + +static void +terminal_free_buffer(wint_t ***bp) +{ + wint_t **b; + wint_t **bufp; + + if (*bp == NULL) + return; + + b = *bp; + *bp = NULL; + + for (bufp = b; *bufp != NULL; bufp++) + el_free(*bufp); + el_free(b); +} + +/* terminal_alloc_display(): + * Allocate a new display. + */ +static int +terminal_alloc_display(EditLine *el) +{ + el->el_display = terminal_alloc_buffer(el); + if (el->el_display == NULL) + goto done; + el->el_vdisplay = terminal_alloc_buffer(el); + if (el->el_vdisplay == NULL) + goto done; + return 0; +done: + terminal_free_display(el); + return -1; +} + + +/* terminal_free_display(): + * Free the display buffers + */ +static void +terminal_free_display(EditLine *el) +{ + terminal_free_buffer(&el->el_display); + terminal_free_buffer(&el->el_vdisplay); +} + + +/* terminal_move_to_line(): + * move to line <where> (first line == 0) + * as efficiently as possible + */ +libedit_private void +terminal_move_to_line(EditLine *el, int where) +{ + int del; + + if (where == el->el_cursor.v) + return; + + if (where > el->el_terminal.t_size.v) { +#ifdef DEBUG_SCREEN + (void) fprintf(el->el_errfile, + "%s: where is ridiculous: %d\r\n", __func__, where); +#endif /* DEBUG_SCREEN */ + return; + } + if ((del = where - el->el_cursor.v) > 0) { + while (del > 0) { + if (EL_HAS_AUTO_MARGINS && + el->el_display[el->el_cursor.v][0] != '\0') { + size_t h = (size_t) + (el->el_terminal.t_size.h - 1); + for (; h > 0 && + el->el_display[el->el_cursor.v][h] == + MB_FILL_CHAR; + h--) + continue; + /* move without newline */ + terminal_move_to_char(el, (int)h); + terminal_overwrite(el, &el->el_display + [el->el_cursor.v][el->el_cursor.h], + (size_t)(el->el_terminal.t_size.h - + el->el_cursor.h)); + /* updates Cursor */ + del--; + } else { + if ((del > 1) && GoodStr(T_DO)) { + terminal_tputs(el, tgoto(Str(T_DO), del, + del), del); + del = 0; + } else { + for (; del > 0; del--) + terminal__putc(el, '\n'); + /* because the \n will become \r\n */ + el->el_cursor.h = 0; + } + } + } + } else { /* del < 0 */ + if (GoodStr(T_UP) && (-del > 1 || !GoodStr(T_up))) + terminal_tputs(el, tgoto(Str(T_UP), -del, -del), -del); + else { + if (GoodStr(T_up)) + for (; del < 0; del++) + terminal_tputs(el, Str(T_up), 1); + } + } + el->el_cursor.v = where;/* now where is here */ +} + + +/* terminal_move_to_char(): + * Move to the character position specified + */ +libedit_private void +terminal_move_to_char(EditLine *el, int where) +{ + int del, i; + +mc_again: + if (where == el->el_cursor.h) + return; + + if (where > el->el_terminal.t_size.h) { +#ifdef DEBUG_SCREEN + (void) fprintf(el->el_errfile, + "%s: where is ridiculous: %d\r\n", __func__, where); +#endif /* DEBUG_SCREEN */ + return; + } + if (!where) { /* if where is first column */ + terminal__putc(el, '\r'); /* do a CR */ + el->el_cursor.h = 0; + return; + } + del = where - el->el_cursor.h; + + if ((del < -4 || del > 4) && GoodStr(T_ch)) + /* go there directly */ + terminal_tputs(el, tgoto(Str(T_ch), where, where), where); + else { + if (del > 0) { /* moving forward */ + if ((del > 4) && GoodStr(T_RI)) + terminal_tputs(el, tgoto(Str(T_RI), del, del), + del); + else { + /* if I can do tabs, use them */ + if (EL_CAN_TAB) { + if ((el->el_cursor.h & 0370) != + (where & ~0x7) + && (el->el_display[ + el->el_cursor.v][where & 0370] != + MB_FILL_CHAR) + ) { + /* if not within tab stop */ + for (i = + (el->el_cursor.h & 0370); + i < (where & ~0x7); + i += 8) + terminal__putc(el, + '\t'); + /* then tab over */ + el->el_cursor.h = where & ~0x7; + } + } + /* + * it's usually cheaper to just write the + * chars, so we do. + */ + /* + * NOTE THAT terminal_overwrite() WILL CHANGE + * el->el_cursor.h!!! + */ + terminal_overwrite(el, &el->el_display[ + el->el_cursor.v][el->el_cursor.h], + (size_t)(where - el->el_cursor.h)); + + } + } else { /* del < 0 := moving backward */ + if ((-del > 4) && GoodStr(T_LE)) + terminal_tputs(el, tgoto(Str(T_LE), -del, -del), + -del); + else { /* can't go directly there */ + /* + * if the "cost" is greater than the "cost" + * from col 0 + */ + if (EL_CAN_TAB ? + ((unsigned int)-del > + (((unsigned int) where >> 3) + + (where & 07))) + : (-del > where)) { + terminal__putc(el, '\r');/* do a CR */ + el->el_cursor.h = 0; + goto mc_again; /* and try again */ + } + for (i = 0; i < -del; i++) + terminal__putc(el, '\b'); + } + } + } + el->el_cursor.h = where; /* now where is here */ +} + + +/* terminal_overwrite(): + * Overstrike num characters + * Assumes MB_FILL_CHARs are present to keep the column count correct + */ +libedit_private void +terminal_overwrite(EditLine *el, const wchar_t *cp, size_t n) +{ + if (n == 0) + return; + + if (n > (size_t)el->el_terminal.t_size.h) { +#ifdef DEBUG_SCREEN + (void) fprintf(el->el_errfile, + "%s: n is ridiculous: %zu\r\n", __func__, n); +#endif /* DEBUG_SCREEN */ + return; + } + + do { + /* terminal__putc() ignores any MB_FILL_CHARs */ + terminal__putc(el, *cp++); + el->el_cursor.h++; + } while (--n); + + if (el->el_cursor.h >= el->el_terminal.t_size.h) { /* wrap? */ + if (EL_HAS_AUTO_MARGINS) { /* yes */ + el->el_cursor.h = 0; + el->el_cursor.v++; + if (EL_HAS_MAGIC_MARGINS) { + /* force the wrap to avoid the "magic" + * situation */ + wchar_t c; + if ((c = el->el_display[el->el_cursor.v] + [el->el_cursor.h]) != '\0') { + terminal_overwrite(el, &c, (size_t)1); + while (el->el_display[el->el_cursor.v] + [el->el_cursor.h] == MB_FILL_CHAR) + el->el_cursor.h++; + } else { + terminal__putc(el, ' '); + el->el_cursor.h = 1; + } + } + } else /* no wrap, but cursor stays on screen */ + el->el_cursor.h = el->el_terminal.t_size.h - 1; + } +} + + +/* terminal_deletechars(): + * Delete num characters + */ +libedit_private void +terminal_deletechars(EditLine *el, int num) +{ + if (num <= 0) + return; + + if (!EL_CAN_DELETE) { +#ifdef DEBUG_EDIT + (void) fprintf(el->el_errfile, " ERROR: cannot delete \n"); +#endif /* DEBUG_EDIT */ + return; + } + if (num > el->el_terminal.t_size.h) { +#ifdef DEBUG_SCREEN + (void) fprintf(el->el_errfile, + "%s: num is ridiculous: %d\r\n", __func__, num); +#endif /* DEBUG_SCREEN */ + return; + } + if (GoodStr(T_DC)) /* if I have multiple delete */ + if ((num > 1) || !GoodStr(T_dc)) { /* if dc would be more + * expen. */ + terminal_tputs(el, tgoto(Str(T_DC), num, num), num); + return; + } + if (GoodStr(T_dm)) /* if I have delete mode */ + terminal_tputs(el, Str(T_dm), 1); + + if (GoodStr(T_dc)) /* else do one at a time */ + while (num--) + terminal_tputs(el, Str(T_dc), 1); + + if (GoodStr(T_ed)) /* if I have delete mode */ + terminal_tputs(el, Str(T_ed), 1); +} + + +/* terminal_insertwrite(): + * Puts terminal in insert character mode or inserts num + * characters in the line + * Assumes MB_FILL_CHARs are present to keep column count correct + */ +libedit_private void +terminal_insertwrite(EditLine *el, wchar_t *cp, int num) +{ + if (num <= 0) + return; + if (!EL_CAN_INSERT) { +#ifdef DEBUG_EDIT + (void) fprintf(el->el_errfile, " ERROR: cannot insert \n"); +#endif /* DEBUG_EDIT */ + return; + } + if (num > el->el_terminal.t_size.h) { +#ifdef DEBUG_SCREEN + (void) fprintf(el->el_errfile, + "%s: num is ridiculous: %d\r\n", __func__, num); +#endif /* DEBUG_SCREEN */ + return; + } + if (GoodStr(T_IC)) /* if I have multiple insert */ + if ((num > 1) || !GoodStr(T_ic)) { + /* if ic would be more expensive */ + terminal_tputs(el, tgoto(Str(T_IC), num, num), num); + terminal_overwrite(el, cp, (size_t)num); + /* this updates el_cursor.h */ + return; + } + if (GoodStr(T_im) && GoodStr(T_ei)) { /* if I have insert mode */ + terminal_tputs(el, Str(T_im), 1); + + el->el_cursor.h += num; + do + terminal__putc(el, *cp++); + while (--num); + + if (GoodStr(T_ip)) /* have to make num chars insert */ + terminal_tputs(el, Str(T_ip), 1); + + terminal_tputs(el, Str(T_ei), 1); + return; + } + do { + if (GoodStr(T_ic)) /* have to make num chars insert */ + terminal_tputs(el, Str(T_ic), 1); + + terminal__putc(el, *cp++); + + el->el_cursor.h++; + + if (GoodStr(T_ip)) /* have to make num chars insert */ + terminal_tputs(el, Str(T_ip), 1); + /* pad the inserted char */ + + } while (--num); +} + + +/* terminal_clear_EOL(): + * clear to end of line. There are num characters to clear + */ +libedit_private void +terminal_clear_EOL(EditLine *el, int num) +{ + int i; + + if (EL_CAN_CEOL && GoodStr(T_ce)) + terminal_tputs(el, Str(T_ce), 1); + else { + for (i = 0; i < num; i++) + terminal__putc(el, ' '); + el->el_cursor.h += num; /* have written num spaces */ + } +} + + +/* terminal_clear_screen(): + * Clear the screen + */ +libedit_private void +terminal_clear_screen(EditLine *el) +{ /* clear the whole screen and home */ + + if (GoodStr(T_cl)) + /* send the clear screen code */ + terminal_tputs(el, Str(T_cl), Val(T_li)); + else if (GoodStr(T_ho) && GoodStr(T_cd)) { + terminal_tputs(el, Str(T_ho), Val(T_li)); /* home */ + /* clear to bottom of screen */ + terminal_tputs(el, Str(T_cd), Val(T_li)); + } else { + terminal__putc(el, '\r'); + terminal__putc(el, '\n'); + } +} + + +/* terminal_beep(): + * Beep the way the terminal wants us + */ +libedit_private void +terminal_beep(EditLine *el) +{ + if (GoodStr(T_bl)) + /* what termcap says we should use */ + terminal_tputs(el, Str(T_bl), 1); + else + terminal__putc(el, '\007'); /* an ASCII bell; ^G */ +} + + +libedit_private void +terminal_get(EditLine *el, const char **term) +{ + *term = el->el_terminal.t_name; +} + + +/* terminal_set(): + * Read in the terminal capabilities from the requested terminal + */ +libedit_private int +terminal_set(EditLine *el, const char *term) +{ + int i; + char buf[TC_BUFSIZE]; + char *area; + const struct termcapstr *t; + sigset_t oset, nset; + int lins, cols; + + (void) sigemptyset(&nset); + (void) sigaddset(&nset, SIGWINCH); + (void) sigprocmask(SIG_BLOCK, &nset, &oset); + + area = buf; + + + if (term == NULL) + term = getenv("TERM"); + + if (!term || !term[0]) + term = "dumb"; + + if (strcmp(term, "emacs") == 0) + el->el_flags |= EDIT_DISABLED; + + (void) memset(el->el_terminal.t_cap, 0, TC_BUFSIZE); + + i = tgetent(el->el_terminal.t_cap, term); + + if (i <= 0) { + if (i == -1) + (void) fprintf(el->el_errfile, + "Cannot read termcap database;\n"); + else if (i == 0) + (void) fprintf(el->el_errfile, + "No entry for terminal type \"%s\";\n", term); + (void) fprintf(el->el_errfile, + "using dumb terminal settings.\n"); + Val(T_co) = 80; /* do a dumb terminal */ + Val(T_pt) = Val(T_km) = Val(T_li) = 0; + Val(T_xt) = Val(T_MT); + for (t = tstr; t->name != NULL; t++) + terminal_alloc(el, t, NULL); + } else { + /* auto/magic margins */ + Val(T_am) = tgetflag("am"); + Val(T_xn) = tgetflag("xn"); + /* Can we tab */ + Val(T_pt) = tgetflag("pt"); + Val(T_xt) = tgetflag("xt"); + /* do we have a meta? */ + Val(T_km) = tgetflag("km"); + Val(T_MT) = tgetflag("MT"); + /* Get the size */ + Val(T_co) = tgetnum("co"); + Val(T_li) = tgetnum("li"); + for (t = tstr; t->name != NULL; t++) { + /* XXX: some systems' tgetstr needs non const */ + terminal_alloc(el, t, tgetstr(strchr(t->name, *t->name), + &area)); + } + } + + if (Val(T_co) < 2) + Val(T_co) = 80; /* just in case */ + if (Val(T_li) < 1) + Val(T_li) = 24; + + el->el_terminal.t_size.v = Val(T_co); + el->el_terminal.t_size.h = Val(T_li); + + terminal_setflags(el); + + /* get the correct window size */ + (void) terminal_get_size(el, &lins, &cols); + if (terminal_change_size(el, lins, cols) == -1) + return -1; + (void) sigprocmask(SIG_SETMASK, &oset, NULL); + terminal_bind_arrow(el); + el->el_terminal.t_name = term; + return i <= 0 ? -1 : 0; +} + + +/* terminal_get_size(): + * Return the new window size in lines and cols, and + * true if the size was changed. + */ +libedit_private int +terminal_get_size(EditLine *el, int *lins, int *cols) +{ + + *cols = Val(T_co); + *lins = Val(T_li); + +#ifdef TIOCGWINSZ + { + struct winsize ws; + if (ioctl(el->el_infd, TIOCGWINSZ, &ws) != -1) { + if (ws.ws_col) + *cols = ws.ws_col; + if (ws.ws_row) + *lins = ws.ws_row; + } + } +#endif +#ifdef TIOCGSIZE + { + struct ttysize ts; + if (ioctl(el->el_infd, TIOCGSIZE, &ts) != -1) { + if (ts.ts_cols) + *cols = ts.ts_cols; + if (ts.ts_lines) + *lins = ts.ts_lines; + } + } +#endif + return Val(T_co) != *cols || Val(T_li) != *lins; +} + + +/* terminal_change_size(): + * Change the size of the terminal + */ +libedit_private int +terminal_change_size(EditLine *el, int lins, int cols) +{ + /* + * Just in case + */ + Val(T_co) = (cols < 2) ? 80 : cols; + Val(T_li) = (lins < 1) ? 24 : lins; + + /* re-make display buffers */ + if (terminal_rebuffer_display(el) == -1) + return -1; + re_clear_display(el); + return 0; +} + + +/* terminal_init_arrow(): + * Initialize the arrow key bindings from termcap + */ +static void +terminal_init_arrow(EditLine *el) +{ + funckey_t *arrow = el->el_terminal.t_fkey; + + arrow[A_K_DN].name = L"down"; + arrow[A_K_DN].key = T_kd; + arrow[A_K_DN].fun.cmd = ED_NEXT_HISTORY; + arrow[A_K_DN].type = XK_CMD; + + arrow[A_K_UP].name = L"up"; + arrow[A_K_UP].key = T_ku; + arrow[A_K_UP].fun.cmd = ED_PREV_HISTORY; + arrow[A_K_UP].type = XK_CMD; + + arrow[A_K_LT].name = L"left"; + arrow[A_K_LT].key = T_kl; + arrow[A_K_LT].fun.cmd = ED_PREV_CHAR; + arrow[A_K_LT].type = XK_CMD; + + arrow[A_K_RT].name = L"right"; + arrow[A_K_RT].key = T_kr; + arrow[A_K_RT].fun.cmd = ED_NEXT_CHAR; + arrow[A_K_RT].type = XK_CMD; + + arrow[A_K_HO].name = L"home"; + arrow[A_K_HO].key = T_kh; + arrow[A_K_HO].fun.cmd = ED_MOVE_TO_BEG; + arrow[A_K_HO].type = XK_CMD; + + arrow[A_K_EN].name = L"end"; + arrow[A_K_EN].key = T_at7; + arrow[A_K_EN].fun.cmd = ED_MOVE_TO_END; + arrow[A_K_EN].type = XK_CMD; + + arrow[A_K_DE].name = L"delete"; + arrow[A_K_DE].key = T_kD; + arrow[A_K_DE].fun.cmd = ED_DELETE_NEXT_CHAR; + arrow[A_K_DE].type = XK_CMD; +} + + +/* terminal_reset_arrow(): + * Reset arrow key bindings + */ +static void +terminal_reset_arrow(EditLine *el) +{ + funckey_t *arrow = el->el_terminal.t_fkey; + static const wchar_t strA[] = L"\033[A"; + static const wchar_t strB[] = L"\033[B"; + static const wchar_t strC[] = L"\033[C"; + static const wchar_t strD[] = L"\033[D"; + static const wchar_t strH[] = L"\033[H"; + static const wchar_t strF[] = L"\033[F"; + static const wchar_t stOA[] = L"\033OA"; + static const wchar_t stOB[] = L"\033OB"; + static const wchar_t stOC[] = L"\033OC"; + static const wchar_t stOD[] = L"\033OD"; + static const wchar_t stOH[] = L"\033OH"; + static const wchar_t stOF[] = L"\033OF"; + + keymacro_add(el, strA, &arrow[A_K_UP].fun, arrow[A_K_UP].type); + keymacro_add(el, strB, &arrow[A_K_DN].fun, arrow[A_K_DN].type); + keymacro_add(el, strC, &arrow[A_K_RT].fun, arrow[A_K_RT].type); + keymacro_add(el, strD, &arrow[A_K_LT].fun, arrow[A_K_LT].type); + keymacro_add(el, strH, &arrow[A_K_HO].fun, arrow[A_K_HO].type); + keymacro_add(el, strF, &arrow[A_K_EN].fun, arrow[A_K_EN].type); + keymacro_add(el, stOA, &arrow[A_K_UP].fun, arrow[A_K_UP].type); + keymacro_add(el, stOB, &arrow[A_K_DN].fun, arrow[A_K_DN].type); + keymacro_add(el, stOC, &arrow[A_K_RT].fun, arrow[A_K_RT].type); + keymacro_add(el, stOD, &arrow[A_K_LT].fun, arrow[A_K_LT].type); + keymacro_add(el, stOH, &arrow[A_K_HO].fun, arrow[A_K_HO].type); + keymacro_add(el, stOF, &arrow[A_K_EN].fun, arrow[A_K_EN].type); + + if (el->el_map.type != MAP_VI) + return; + keymacro_add(el, &strA[1], &arrow[A_K_UP].fun, arrow[A_K_UP].type); + keymacro_add(el, &strB[1], &arrow[A_K_DN].fun, arrow[A_K_DN].type); + keymacro_add(el, &strC[1], &arrow[A_K_RT].fun, arrow[A_K_RT].type); + keymacro_add(el, &strD[1], &arrow[A_K_LT].fun, arrow[A_K_LT].type); + keymacro_add(el, &strH[1], &arrow[A_K_HO].fun, arrow[A_K_HO].type); + keymacro_add(el, &strF[1], &arrow[A_K_EN].fun, arrow[A_K_EN].type); + keymacro_add(el, &stOA[1], &arrow[A_K_UP].fun, arrow[A_K_UP].type); + keymacro_add(el, &stOB[1], &arrow[A_K_DN].fun, arrow[A_K_DN].type); + keymacro_add(el, &stOC[1], &arrow[A_K_RT].fun, arrow[A_K_RT].type); + keymacro_add(el, &stOD[1], &arrow[A_K_LT].fun, arrow[A_K_LT].type); + keymacro_add(el, &stOH[1], &arrow[A_K_HO].fun, arrow[A_K_HO].type); + keymacro_add(el, &stOF[1], &arrow[A_K_EN].fun, arrow[A_K_EN].type); +} + + +/* terminal_set_arrow(): + * Set an arrow key binding + */ +libedit_private int +terminal_set_arrow(EditLine *el, const wchar_t *name, keymacro_value_t *fun, + int type) +{ + funckey_t *arrow = el->el_terminal.t_fkey; + int i; + + for (i = 0; i < A_K_NKEYS; i++) + if (wcscmp(name, arrow[i].name) == 0) { + arrow[i].fun = *fun; + arrow[i].type = type; + return 0; + } + return -1; +} + + +/* terminal_clear_arrow(): + * Clear an arrow key binding + */ +libedit_private int +terminal_clear_arrow(EditLine *el, const wchar_t *name) +{ + funckey_t *arrow = el->el_terminal.t_fkey; + int i; + + for (i = 0; i < A_K_NKEYS; i++) + if (wcscmp(name, arrow[i].name) == 0) { + arrow[i].type = XK_NOD; + return 0; + } + return -1; +} + + +/* terminal_print_arrow(): + * Print the arrow key bindings + */ +libedit_private void +terminal_print_arrow(EditLine *el, const wchar_t *name) +{ + int i; + funckey_t *arrow = el->el_terminal.t_fkey; + + for (i = 0; i < A_K_NKEYS; i++) + if (*name == '\0' || wcscmp(name, arrow[i].name) == 0) + if (arrow[i].type != XK_NOD) + keymacro_kprint(el, arrow[i].name, + &arrow[i].fun, arrow[i].type); +} + + +/* terminal_bind_arrow(): + * Bind the arrow keys + */ +libedit_private void +terminal_bind_arrow(EditLine *el) +{ + el_action_t *map; + const el_action_t *dmap; + int i, j; + char *p; + funckey_t *arrow = el->el_terminal.t_fkey; + + /* Check if the components needed are initialized */ + if (el->el_terminal.t_buf == NULL || el->el_map.key == NULL) + return; + + map = el->el_map.type == MAP_VI ? el->el_map.alt : el->el_map.key; + dmap = el->el_map.type == MAP_VI ? el->el_map.vic : el->el_map.emacs; + + terminal_reset_arrow(el); + + for (i = 0; i < A_K_NKEYS; i++) { + wchar_t wt_str[VISUAL_WIDTH_MAX]; + wchar_t *px; + size_t n; + + p = el->el_terminal.t_str[arrow[i].key]; + if (!p || !*p) + continue; + for (n = 0; n < VISUAL_WIDTH_MAX && p[n]; ++n) + wt_str[n] = p[n]; + while (n < VISUAL_WIDTH_MAX) + wt_str[n++] = '\0'; + px = wt_str; + j = (unsigned char) *p; + /* + * Assign the arrow keys only if: + * + * 1. They are multi-character arrow keys and the user + * has not re-assigned the leading character, or + * has re-assigned the leading character to be + * ED_SEQUENCE_LEAD_IN + * 2. They are single arrow keys pointing to an + * unassigned key. + */ + if (arrow[i].type == XK_NOD) + keymacro_clear(el, map, px); + else { + if (p[1] && (dmap[j] == map[j] || + map[j] == ED_SEQUENCE_LEAD_IN)) { + keymacro_add(el, px, &arrow[i].fun, + arrow[i].type); + map[j] = ED_SEQUENCE_LEAD_IN; + } else if (map[j] == ED_UNASSIGNED) { + keymacro_clear(el, map, px); + if (arrow[i].type == XK_CMD) + map[j] = arrow[i].fun.cmd; + else + keymacro_add(el, px, &arrow[i].fun, + arrow[i].type); + } + } + } +} + +/* terminal_putc(): + * Add a character + */ +static int +terminal_putc(int c) +{ + if (terminal_outfile == NULL) + return -1; + return fputc(c, terminal_outfile); +} + +static void +terminal_tputs(EditLine *el, const char *cap, int affcnt) +{ +#ifdef _REENTRANT + pthread_mutex_lock(&terminal_mutex); +#endif + terminal_outfile = el->el_outfile; + (void)tputs(cap, affcnt, terminal_putc); +#ifdef _REENTRANT + pthread_mutex_unlock(&terminal_mutex); +#endif +} + +/* terminal__putc(): + * Add a character + */ +libedit_private int +terminal__putc(EditLine *el, wint_t c) +{ + char buf[MB_LEN_MAX +1]; + ssize_t i; + if (c == (wint_t)MB_FILL_CHAR) + return 0; + if (c & EL_LITERAL) + return fputs(literal_get(el, c), el->el_outfile); + i = ct_encode_char(buf, (size_t)MB_LEN_MAX, c); + if (i <= 0) + return (int)i; + buf[i] = '\0'; + return fputs(buf, el->el_outfile); +} + +/* terminal__flush(): + * Flush output + */ +libedit_private void +terminal__flush(EditLine *el) +{ + + (void) fflush(el->el_outfile); +} + +/* terminal_writec(): + * Write the given character out, in a human readable form + */ +libedit_private void +terminal_writec(EditLine *el, wint_t c) +{ + wchar_t visbuf[VISUAL_WIDTH_MAX +1]; + ssize_t vcnt = ct_visual_char(visbuf, VISUAL_WIDTH_MAX, c); + if (vcnt < 0) + vcnt = 0; + visbuf[vcnt] = '\0'; + terminal_overwrite(el, visbuf, (size_t)vcnt); + terminal__flush(el); +} + + +/* terminal_telltc(): + * Print the current termcap characteristics + */ +libedit_private int +/*ARGSUSED*/ +terminal_telltc(EditLine *el, int argc __attribute__((__unused__)), + const wchar_t **argv __attribute__((__unused__))) +{ + const struct termcapstr *t; + char **ts; + + (void) fprintf(el->el_outfile, "\n\tYour terminal has the\n"); + (void) fprintf(el->el_outfile, "\tfollowing characteristics:\n\n"); + (void) fprintf(el->el_outfile, "\tIt has %d columns and %d lines\n", + Val(T_co), Val(T_li)); + (void) fprintf(el->el_outfile, + "\tIt has %s meta key\n", EL_HAS_META ? "a" : "no"); + (void) fprintf(el->el_outfile, + "\tIt can%suse tabs\n", EL_CAN_TAB ? " " : "not "); + (void) fprintf(el->el_outfile, "\tIt %s automatic margins\n", + EL_HAS_AUTO_MARGINS ? "has" : "does not have"); + if (EL_HAS_AUTO_MARGINS) + (void) fprintf(el->el_outfile, "\tIt %s magic margins\n", + EL_HAS_MAGIC_MARGINS ? "has" : "does not have"); + + for (t = tstr, ts = el->el_terminal.t_str; t->name != NULL; t++, ts++) { + const char *ub; + if (*ts && **ts) { + ub = ct_encode_string(ct_visual_string( + ct_decode_string(*ts, &el->el_scratch), + &el->el_visual), &el->el_scratch); + } else { + ub = "(empty)"; + } + (void) fprintf(el->el_outfile, "\t%25s (%s) == %s\n", + t->long_name, t->name, ub); + } + (void) fputc('\n', el->el_outfile); + return 0; +} + + +/* terminal_settc(): + * Change the current terminal characteristics + */ +libedit_private int +/*ARGSUSED*/ +terminal_settc(EditLine *el, int argc __attribute__((__unused__)), + const wchar_t **argv) +{ + const struct termcapstr *ts; + const struct termcapval *tv; + char what[8], how[8]; + + if (argv == NULL || argv[1] == NULL || argv[2] == NULL) + return -1; + + strncpy(what, ct_encode_string(argv[1], &el->el_scratch), sizeof(what)); + what[sizeof(what) - 1] = '\0'; + strncpy(how, ct_encode_string(argv[2], &el->el_scratch), sizeof(how)); + how[sizeof(how) - 1] = '\0'; + + /* + * Do the strings first + */ + for (ts = tstr; ts->name != NULL; ts++) + if (strcmp(ts->name, what) == 0) + break; + + if (ts->name != NULL) { + terminal_alloc(el, ts, how); + terminal_setflags(el); + return 0; + } + /* + * Do the numeric ones second + */ + for (tv = tval; tv->name != NULL; tv++) + if (strcmp(tv->name, what) == 0) + break; + + if (tv->name != NULL) + return -1; + + if (tv == &tval[T_pt] || tv == &tval[T_km] || + tv == &tval[T_am] || tv == &tval[T_xn]) { + if (strcmp(how, "yes") == 0) + el->el_terminal.t_val[tv - tval] = 1; + else if (strcmp(how, "no") == 0) + el->el_terminal.t_val[tv - tval] = 0; + else { + (void) fprintf(el->el_errfile, + "%ls: Bad value `%s'.\n", argv[0], how); + return -1; + } + terminal_setflags(el); + if (terminal_change_size(el, Val(T_li), Val(T_co)) == -1) + return -1; + return 0; + } else { + long i; + char *ep; + + i = strtol(how, &ep, 10); + if (*ep != '\0') { + (void) fprintf(el->el_errfile, + "%ls: Bad value `%s'.\n", argv[0], how); + return -1; + } + el->el_terminal.t_val[tv - tval] = (int) i; + el->el_terminal.t_size.v = Val(T_co); + el->el_terminal.t_size.h = Val(T_li); + if (tv == &tval[T_co] || tv == &tval[T_li]) + if (terminal_change_size(el, Val(T_li), Val(T_co)) + == -1) + return -1; + return 0; + } +} + + +/* terminal_gettc(): + * Get the current terminal characteristics + */ +libedit_private int +/*ARGSUSED*/ +terminal_gettc(EditLine *el, int argc __attribute__((__unused__)), char **argv) +{ + const struct termcapstr *ts; + const struct termcapval *tv; + char *what; + void *how; + + if (argv == NULL || argv[1] == NULL || argv[2] == NULL) + return -1; + + what = argv[1]; + how = argv[2]; + + /* + * Do the strings first + */ + for (ts = tstr; ts->name != NULL; ts++) + if (strcmp(ts->name, what) == 0) + break; + + if (ts->name != NULL) { + *(char **)how = el->el_terminal.t_str[ts - tstr]; + return 0; + } + /* + * Do the numeric ones second + */ + for (tv = tval; tv->name != NULL; tv++) + if (strcmp(tv->name, what) == 0) + break; + + if (tv->name == NULL) + return -1; + + if (tv == &tval[T_pt] || tv == &tval[T_km] || + tv == &tval[T_am] || tv == &tval[T_xn]) { + static char yes[] = "yes"; + static char no[] = "no"; + if (el->el_terminal.t_val[tv - tval]) + *(char **)how = yes; + else + *(char **)how = no; + return 0; + } else { + *(int *)how = el->el_terminal.t_val[tv - tval]; + return 0; + } +} + +/* terminal_echotc(): + * Print the termcap string out with variable substitution + */ +libedit_private int +/*ARGSUSED*/ +terminal_echotc(EditLine *el, int argc __attribute__((__unused__)), + const wchar_t **argv) +{ + char *cap, *scap; + wchar_t *ep; + int arg_need, arg_cols, arg_rows; + int verbose = 0, silent = 0; + char *area; + static const char fmts[] = "%s\n", fmtd[] = "%d\n"; + const struct termcapstr *t; + char buf[TC_BUFSIZE]; + long i; + + area = buf; + + if (argv == NULL || argv[1] == NULL) + return -1; + argv++; + + if (argv[0][0] == '-') { + switch (argv[0][1]) { + case 'v': + verbose = 1; + break; + case 's': + silent = 1; + break; + default: + /* stderror(ERR_NAME | ERR_TCUSAGE); */ + break; + } + argv++; + } + if (!*argv || *argv[0] == '\0') + return 0; + if (wcscmp(*argv, L"tabs") == 0) { + (void) fprintf(el->el_outfile, fmts, EL_CAN_TAB ? "yes" : "no"); + return 0; + } else if (wcscmp(*argv, L"meta") == 0) { + (void) fprintf(el->el_outfile, fmts, Val(T_km) ? "yes" : "no"); + return 0; + } else if (wcscmp(*argv, L"xn") == 0) { + (void) fprintf(el->el_outfile, fmts, EL_HAS_MAGIC_MARGINS ? + "yes" : "no"); + return 0; + } else if (wcscmp(*argv, L"am") == 0) { + (void) fprintf(el->el_outfile, fmts, EL_HAS_AUTO_MARGINS ? + "yes" : "no"); + return 0; + } else if (wcscmp(*argv, L"baud") == 0) { + (void) fprintf(el->el_outfile, fmtd, (int)el->el_tty.t_speed); + return 0; + } else if (wcscmp(*argv, L"rows") == 0 || + wcscmp(*argv, L"lines") == 0) { + (void) fprintf(el->el_outfile, fmtd, Val(T_li)); + return 0; + } else if (wcscmp(*argv, L"cols") == 0) { + (void) fprintf(el->el_outfile, fmtd, Val(T_co)); + return 0; + } + /* + * Try to use our local definition first + */ + scap = NULL; + for (t = tstr; t->name != NULL; t++) + if (strcmp(t->name, + ct_encode_string(*argv, &el->el_scratch)) == 0) { + scap = el->el_terminal.t_str[t - tstr]; + break; + } + if (t->name == NULL) { + /* XXX: some systems' tgetstr needs non const */ + scap = tgetstr(ct_encode_string(*argv, &el->el_scratch), &area); + } + if (!scap || scap[0] == '\0') { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Termcap parameter `%ls' not found.\n", + *argv); + return -1; + } + /* + * Count home many values we need for this capability. + */ + for (cap = scap, arg_need = 0; *cap; cap++) + if (*cap == '%') + switch (*++cap) { + case 'd': + case '2': + case '3': + case '.': + case '+': + arg_need++; + break; + case '%': + case '>': + case 'i': + case 'r': + case 'n': + case 'B': + case 'D': + break; + default: + /* + * hpux has lot's of them... + */ + if (verbose) + (void) fprintf(el->el_errfile, + "echotc: Warning: unknown termcap %% `%c'.\n", + *cap); + /* This is bad, but I won't complain */ + break; + } + + switch (arg_need) { + case 0: + argv++; + if (*argv && *argv[0]) { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Warning: Extra argument `%ls'.\n", + *argv); + return -1; + } + terminal_tputs(el, scap, 1); + break; + case 1: + argv++; + if (!*argv || *argv[0] == '\0') { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Warning: Missing argument.\n"); + return -1; + } + arg_cols = 0; + i = wcstol(*argv, &ep, 10); + if (*ep != '\0' || i < 0) { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Bad value `%ls' for rows.\n", + *argv); + return -1; + } + arg_rows = (int) i; + argv++; + if (*argv && *argv[0]) { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Warning: Extra argument `%ls" + "'.\n", *argv); + return -1; + } + terminal_tputs(el, tgoto(scap, arg_cols, arg_rows), 1); + break; + default: + /* This is wrong, but I will ignore it... */ + if (verbose) + (void) fprintf(el->el_errfile, + "echotc: Warning: Too many required arguments (%d).\n", + arg_need); + /* FALLTHROUGH */ + case 2: + argv++; + if (!*argv || *argv[0] == '\0') { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Warning: Missing argument.\n"); + return -1; + } + i = wcstol(*argv, &ep, 10); + if (*ep != '\0' || i < 0) { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Bad value `%ls' for cols.\n", + *argv); + return -1; + } + arg_cols = (int) i; + argv++; + if (!*argv || *argv[0] == '\0') { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Warning: Missing argument.\n"); + return -1; + } + i = wcstol(*argv, &ep, 10); + if (*ep != '\0' || i < 0) { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Bad value `%ls' for rows.\n", + *argv); + return -1; + } + arg_rows = (int) i; + if (*ep != '\0') { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Bad value `%ls'.\n", *argv); + return -1; + } + argv++; + if (*argv && *argv[0]) { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Warning: Extra argument `%ls" + "'.\n", *argv); + return -1; + } + terminal_tputs(el, tgoto(scap, arg_cols, arg_rows), arg_rows); + break; + } + return 0; +} diff --git a/bin/catsh/libedit/terminal.h b/bin/catsh/libedit/terminal.h new file mode 100644 index 00000000..ae61beb1 --- /dev/null +++ b/bin/catsh/libedit/terminal.h @@ -0,0 +1,125 @@ +/* $NetBSD: terminal.h,v 1.9 2016/05/09 21:46:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)term.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.term.h: Termcap header + */ +#ifndef _h_el_terminal +#define _h_el_terminal + +typedef struct { /* Symbolic function key bindings */ + const wchar_t *name; /* name of the key */ + int key; /* Index in termcap table */ + keymacro_value_t fun; /* Function bound to it */ + int type; /* Type of function */ +} funckey_t; + +typedef struct { + const char *t_name; /* the terminal name */ + coord_t t_size; /* # lines and cols */ + int t_flags; +#define TERM_CAN_INSERT 0x001 /* Has insert cap */ +#define TERM_CAN_DELETE 0x002 /* Has delete cap */ +#define TERM_CAN_CEOL 0x004 /* Has CEOL cap */ +#define TERM_CAN_TAB 0x008 /* Can use tabs */ +#define TERM_CAN_ME 0x010 /* Can turn all attrs. */ +#define TERM_CAN_UP 0x020 /* Can move up */ +#define TERM_HAS_META 0x040 /* Has a meta key */ +#define TERM_HAS_AUTO_MARGINS 0x080 /* Has auto margins */ +#define TERM_HAS_MAGIC_MARGINS 0x100 /* Has magic margins */ + char *t_buf; /* Termcap buffer */ + size_t t_loc; /* location used */ + char **t_str; /* termcap strings */ + int *t_val; /* termcap values */ + char *t_cap; /* Termcap buffer */ + funckey_t *t_fkey; /* Array of keys */ +} el_terminal_t; + +/* + * fKey indexes + */ +#define A_K_DN 0 +#define A_K_UP 1 +#define A_K_LT 2 +#define A_K_RT 3 +#define A_K_HO 4 +#define A_K_EN 5 +#define A_K_DE 6 +#define A_K_NKEYS 7 + +libedit_private void terminal_move_to_line(EditLine *, int); +libedit_private void terminal_move_to_char(EditLine *, int); +libedit_private void terminal_clear_EOL(EditLine *, int); +libedit_private void terminal_overwrite(EditLine *, const wchar_t *, size_t); +libedit_private void terminal_insertwrite(EditLine *, wchar_t *, int); +libedit_private void terminal_deletechars(EditLine *, int); +libedit_private void terminal_clear_screen(EditLine *); +libedit_private void terminal_beep(EditLine *); +libedit_private int terminal_change_size(EditLine *, int, int); +libedit_private int terminal_get_size(EditLine *, int *, int *); +libedit_private int terminal_init(EditLine *); +libedit_private void terminal_bind_arrow(EditLine *); +libedit_private void terminal_print_arrow(EditLine *, const wchar_t *); +libedit_private int terminal_clear_arrow(EditLine *, const wchar_t *); +libedit_private int terminal_set_arrow(EditLine *, const wchar_t *, + keymacro_value_t *, int); +libedit_private void terminal_end(EditLine *); +libedit_private void terminal_get(EditLine *, const char **); +libedit_private int terminal_set(EditLine *, const char *); +libedit_private int terminal_settc(EditLine *, int, const wchar_t **); +libedit_private int terminal_gettc(EditLine *, int, char **); +libedit_private int terminal_telltc(EditLine *, int, const wchar_t **); +libedit_private int terminal_echotc(EditLine *, int, const wchar_t **); +libedit_private void terminal_writec(EditLine *, wint_t); +libedit_private int terminal__putc(EditLine *, wint_t); +libedit_private void terminal__flush(EditLine *); + +/* + * Easy access macros + */ +#define EL_FLAGS (el)->el_terminal.t_flags + +#define EL_CAN_INSERT (EL_FLAGS & TERM_CAN_INSERT) +#define EL_CAN_DELETE (EL_FLAGS & TERM_CAN_DELETE) +#define EL_CAN_CEOL (EL_FLAGS & TERM_CAN_CEOL) +#define EL_CAN_TAB (EL_FLAGS & TERM_CAN_TAB) +#define EL_CAN_ME (EL_FLAGS & TERM_CAN_ME) +#define EL_CAN_UP (EL_FLAGS & TERM_CAN_UP) +#define EL_HAS_META (EL_FLAGS & TERM_HAS_META) +#define EL_HAS_AUTO_MARGINS (EL_FLAGS & TERM_HAS_AUTO_MARGINS) +#define EL_HAS_MAGIC_MARGINS (EL_FLAGS & TERM_HAS_MAGIC_MARGINS) + +#endif /* _h_el_terminal */ diff --git a/bin/catsh/libedit/tokenizer.c b/bin/catsh/libedit/tokenizer.c new file mode 100644 index 00000000..18532240 --- /dev/null +++ b/bin/catsh/libedit/tokenizer.c @@ -0,0 +1,466 @@ +/* $NetBSD: tokenizer.c,v 1.28 2016/04/11 18:56:31 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)tokenizer.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: tokenizer.c,v 1.28 2016/04/11 18:56:31 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* We build this file twice, once as NARROW, once as WIDE. */ +/* + * tokenize.c: Bourne shell like tokenizer + */ +#include <stdlib.h> +#include <string.h> + +#include "histedit.h" + +typedef enum { + Q_none, Q_single, Q_double, Q_one, Q_doubleone +} quote_t; + +#define TOK_KEEP 1 +#define TOK_EAT 2 + +#define WINCR 20 +#define AINCR 10 + +#define IFS STR("\t \n") + +#define tok_malloc(a) malloc(a) +#define tok_free(a) free(a) +#define tok_realloc(a, b) realloc(a, b) + +#ifdef NARROWCHAR +#define Char char +#define FUN(prefix, rest) prefix ## _ ## rest +#define TYPE(type) type +#define STR(x) x +#define Strchr(s, c) strchr(s, c) +#define tok_strdup(s) strdup(s) +#else +#define Char wchar_t +#define FUN(prefix, rest) prefix ## _w ## rest +#define TYPE(type) type ## W +#define STR(x) L ## x +#define Strchr(s, c) wcschr(s, c) +#define tok_strdup(s) wcsdup(s) +#endif + +struct TYPE(tokenizer) { + Char *ifs; /* In field separator */ + size_t argc, amax; /* Current and maximum number of args */ + Char **argv; /* Argument list */ + Char *wptr, *wmax; /* Space and limit on the word buffer */ + Char *wstart; /* Beginning of next word */ + Char *wspace; /* Space of word buffer */ + quote_t quote; /* Quoting state */ + int flags; /* flags; */ +}; + + +static void FUN(tok,finish)(TYPE(Tokenizer) *); + + +/* FUN(tok,finish)(): + * Finish a word in the tokenizer. + */ +static void +FUN(tok,finish)(TYPE(Tokenizer) *tok) +{ + + *tok->wptr = '\0'; + if ((tok->flags & TOK_KEEP) || tok->wptr != tok->wstart) { + tok->argv[tok->argc++] = tok->wstart; + tok->argv[tok->argc] = NULL; + tok->wstart = ++tok->wptr; + } + tok->flags &= ~TOK_KEEP; +} + + +/* FUN(tok,init)(): + * Initialize the tokenizer + */ +TYPE(Tokenizer) * +FUN(tok,init)(const Char *ifs) +{ + TYPE(Tokenizer) *tok = tok_malloc(sizeof(*tok)); + + if (tok == NULL) + return NULL; + tok->ifs = tok_strdup(ifs ? ifs : IFS); + if (tok->ifs == NULL) { + tok_free(tok); + return NULL; + } + tok->argc = 0; + tok->amax = AINCR; + tok->argv = tok_malloc(sizeof(*tok->argv) * tok->amax); + if (tok->argv == NULL) { + tok_free(tok->ifs); + tok_free(tok); + return NULL; + } + tok->argv[0] = NULL; + tok->wspace = tok_malloc(WINCR * sizeof(*tok->wspace)); + if (tok->wspace == NULL) { + tok_free(tok->argv); + tok_free(tok->ifs); + tok_free(tok); + return NULL; + } + tok->wmax = tok->wspace + WINCR; + tok->wstart = tok->wspace; + tok->wptr = tok->wspace; + tok->flags = 0; + tok->quote = Q_none; + + return tok; +} + + +/* FUN(tok,reset)(): + * Reset the tokenizer + */ +void +FUN(tok,reset)(TYPE(Tokenizer) *tok) +{ + + tok->argc = 0; + tok->wstart = tok->wspace; + tok->wptr = tok->wspace; + tok->flags = 0; + tok->quote = Q_none; +} + + +/* FUN(tok,end)(): + * Clean up + */ +void +FUN(tok,end)(TYPE(Tokenizer) *tok) +{ + + tok_free(tok->ifs); + tok_free(tok->wspace); + tok_free(tok->argv); + tok_free(tok); +} + + + +/* FUN(tok,line)(): + * Bourne shell (sh(1)) like tokenizing + * Arguments: + * tok current tokenizer state (setup with FUN(tok,init)()) + * line line to parse + * Returns: + * -1 Internal error + * 3 Quoted return + * 2 Unmatched double quote + * 1 Unmatched single quote + * 0 Ok + * Modifies (if return value is 0): + * argc number of arguments + * argv argument array + * cursorc if !NULL, argv element containing cursor + * cursorv if !NULL, offset in argv[cursorc] of cursor + */ +int +FUN(tok,line)(TYPE(Tokenizer) *tok, const TYPE(LineInfo) *line, + int *argc, const Char ***argv, int *cursorc, int *cursoro) +{ + const Char *ptr; + int cc, co; + + cc = co = -1; + ptr = line->buffer; + for (ptr = line->buffer; ;ptr++) { + if (ptr >= line->lastchar) + ptr = STR(""); + if (ptr == line->cursor) { + cc = (int)tok->argc; + co = (int)(tok->wptr - tok->wstart); + } + switch (*ptr) { + case '\'': + tok->flags |= TOK_KEEP; + tok->flags &= ~TOK_EAT; + switch (tok->quote) { + case Q_none: + tok->quote = Q_single; /* Enter single quote + * mode */ + break; + + case Q_single: /* Exit single quote mode */ + tok->quote = Q_none; + break; + + case Q_one: /* Quote this ' */ + tok->quote = Q_none; + *tok->wptr++ = *ptr; + break; + + case Q_double: /* Stay in double quote mode */ + *tok->wptr++ = *ptr; + break; + + case Q_doubleone: /* Quote this ' */ + tok->quote = Q_double; + *tok->wptr++ = *ptr; + break; + + default: + return -1; + } + break; + + case '"': + tok->flags &= ~TOK_EAT; + tok->flags |= TOK_KEEP; + switch (tok->quote) { + case Q_none: /* Enter double quote mode */ + tok->quote = Q_double; + break; + + case Q_double: /* Exit double quote mode */ + tok->quote = Q_none; + break; + + case Q_one: /* Quote this " */ + tok->quote = Q_none; + *tok->wptr++ = *ptr; + break; + + case Q_single: /* Stay in single quote mode */ + *tok->wptr++ = *ptr; + break; + + case Q_doubleone: /* Quote this " */ + tok->quote = Q_double; + *tok->wptr++ = *ptr; + break; + + default: + return -1; + } + break; + + case '\\': + tok->flags |= TOK_KEEP; + tok->flags &= ~TOK_EAT; + switch (tok->quote) { + case Q_none: /* Quote next character */ + tok->quote = Q_one; + break; + + case Q_double: /* Quote next character */ + tok->quote = Q_doubleone; + break; + + case Q_one: /* Quote this, restore state */ + *tok->wptr++ = *ptr; + tok->quote = Q_none; + break; + + case Q_single: /* Stay in single quote mode */ + *tok->wptr++ = *ptr; + break; + + case Q_doubleone: /* Quote this \ */ + tok->quote = Q_double; + *tok->wptr++ = *ptr; + break; + + default: + return -1; + } + break; + + case '\n': + tok->flags &= ~TOK_EAT; + switch (tok->quote) { + case Q_none: + goto tok_line_outok; + + case Q_single: + case Q_double: + *tok->wptr++ = *ptr; /* Add the return */ + break; + + case Q_doubleone: /* Back to double, eat the '\n' */ + tok->flags |= TOK_EAT; + tok->quote = Q_double; + break; + + case Q_one: /* No quote, more eat the '\n' */ + tok->flags |= TOK_EAT; + tok->quote = Q_none; + break; + + default: + return 0; + } + break; + + case '\0': + switch (tok->quote) { + case Q_none: + /* Finish word and return */ + if (tok->flags & TOK_EAT) { + tok->flags &= ~TOK_EAT; + return 3; + } + goto tok_line_outok; + + case Q_single: + return 1; + + case Q_double: + return 2; + + case Q_doubleone: + tok->quote = Q_double; + *tok->wptr++ = *ptr; + break; + + case Q_one: + tok->quote = Q_none; + *tok->wptr++ = *ptr; + break; + + default: + return -1; + } + break; + + default: + tok->flags &= ~TOK_EAT; + switch (tok->quote) { + case Q_none: + if (Strchr(tok->ifs, *ptr) != NULL) + FUN(tok,finish)(tok); + else + *tok->wptr++ = *ptr; + break; + + case Q_single: + case Q_double: + *tok->wptr++ = *ptr; + break; + + + case Q_doubleone: + *tok->wptr++ = '\\'; + tok->quote = Q_double; + *tok->wptr++ = *ptr; + break; + + case Q_one: + tok->quote = Q_none; + *tok->wptr++ = *ptr; + break; + + default: + return -1; + + } + break; + } + + if (tok->wptr >= tok->wmax - 4) { + size_t size = (size_t)(tok->wmax - tok->wspace + WINCR); + Char *s = tok_realloc(tok->wspace, + size * sizeof(*s)); + if (s == NULL) + return -1; + + if (s != tok->wspace) { + size_t i; + for (i = 0; i < tok->argc; i++) { + tok->argv[i] = + (tok->argv[i] - tok->wspace) + s; + } + tok->wptr = (tok->wptr - tok->wspace) + s; + tok->wstart = (tok->wstart - tok->wspace) + s; + tok->wspace = s; + } + tok->wmax = s + size; + } + if (tok->argc >= tok->amax - 4) { + Char **p; + tok->amax += AINCR; + p = tok_realloc(tok->argv, tok->amax * sizeof(*p)); + if (p == NULL) { + tok->amax -= AINCR; + return -1; + } + tok->argv = p; + } + } + tok_line_outok: + if (cc == -1 && co == -1) { + cc = (int)tok->argc; + co = (int)(tok->wptr - tok->wstart); + } + if (cursorc != NULL) + *cursorc = cc; + if (cursoro != NULL) + *cursoro = co; + FUN(tok,finish)(tok); + *argv = (const Char **)tok->argv; + *argc = (int)tok->argc; + return 0; +} + +/* FUN(tok,str)(): + * Simpler version of tok_line, taking a NUL terminated line + * and splitting into words, ignoring cursor state. + */ +int +FUN(tok,str)(TYPE(Tokenizer) *tok, const Char *line, int *argc, + const Char ***argv) +{ + TYPE(LineInfo) li; + + memset(&li, 0, sizeof(li)); + li.buffer = line; + li.cursor = li.lastchar = Strchr(line, '\0'); + return FUN(tok,line)(tok, &li, argc, argv, NULL, NULL); +} diff --git a/bin/catsh/libedit/tokenizern.c b/bin/catsh/libedit/tokenizern.c new file mode 100644 index 00000000..5846b606 --- /dev/null +++ b/bin/catsh/libedit/tokenizern.c @@ -0,0 +1,3 @@ +#include "config.h" +#define NARROWCHAR +#include "tokenizer.c" diff --git a/bin/catsh/libedit/tty.c b/bin/catsh/libedit/tty.c new file mode 100644 index 00000000..f524da47 --- /dev/null +++ b/bin/catsh/libedit/tty.c @@ -0,0 +1,1342 @@ +/* $NetBSD: tty.c,v 1.65 2016/05/09 21:46:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)tty.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: tty.c,v 1.65 2016/05/09 21:46:56 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * tty.c: tty interface stuff + */ +#include <assert.h> +#include <errno.h> +#include <stdlib.h> /* for abort */ +#include <string.h> +#include <strings.h> /* for ffs */ +#include <unistd.h> /* for isatty */ + +#include "el.h" +#include "fcns.h" +#include "parse.h" + +typedef struct ttymodes_t { + const char *m_name; + unsigned int m_value; + int m_type; +} ttymodes_t; + +typedef struct ttymap_t { + wint_t nch, och; /* Internal and termio rep of chars */ + el_action_t bind[3]; /* emacs, vi, and vi-cmd */ +} ttymap_t; + + +static const ttyperm_t ttyperm = { + { + {"iflag:", ICRNL, (INLCR | IGNCR)}, + {"oflag:", (OPOST | ONLCR), ONLRET}, + {"cflag:", 0, 0}, + {"lflag:", (ISIG | ICANON | ECHO | ECHOE | ECHOCTL | IEXTEN), + (NOFLSH | ECHONL | EXTPROC | FLUSHO)}, + {"chars:", 0, 0}, + }, + { + {"iflag:", (INLCR | ICRNL), IGNCR}, + {"oflag:", (OPOST | ONLCR), ONLRET}, + {"cflag:", 0, 0}, + {"lflag:", ISIG, + (NOFLSH | ICANON | ECHO | ECHOK | ECHONL | EXTPROC | IEXTEN | FLUSHO)}, + {"chars:", (C_SH(C_MIN) | C_SH(C_TIME) | C_SH(C_SWTCH) | C_SH(C_DSWTCH) | + C_SH(C_SUSP) | C_SH(C_DSUSP) | C_SH(C_EOL) | C_SH(C_DISCARD) | + C_SH(C_PGOFF) | C_SH(C_PAGE) | C_SH(C_STATUS)), 0} + }, + { + {"iflag:", 0, IXON | IXOFF | INLCR | ICRNL}, + {"oflag:", 0, 0}, + {"cflag:", 0, 0}, + {"lflag:", 0, ISIG | IEXTEN}, + {"chars:", 0, 0}, + } +}; + +static const ttychar_t ttychar = { + { + CINTR, CQUIT, CERASE, CKILL, + CEOF, CEOL, CEOL2, CSWTCH, + CDSWTCH, CERASE2, CSTART, CSTOP, + CWERASE, CSUSP, CDSUSP, CREPRINT, + CDISCARD, CLNEXT, CSTATUS, CPAGE, + CPGOFF, CKILL2, CBRK, CMIN, + CTIME + }, + { + CINTR, CQUIT, CERASE, CKILL, + _POSIX_VDISABLE, _POSIX_VDISABLE, _POSIX_VDISABLE, _POSIX_VDISABLE, + _POSIX_VDISABLE, CERASE2, CSTART, CSTOP, + _POSIX_VDISABLE, CSUSP, _POSIX_VDISABLE, _POSIX_VDISABLE, + CDISCARD, _POSIX_VDISABLE, _POSIX_VDISABLE, _POSIX_VDISABLE, + _POSIX_VDISABLE, _POSIX_VDISABLE, _POSIX_VDISABLE, 1, + 0 + }, + { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0 + } +}; + +static const ttymap_t tty_map[] = { +#ifdef VERASE + {C_ERASE, VERASE, + {EM_DELETE_PREV_CHAR, VI_DELETE_PREV_CHAR, ED_PREV_CHAR}}, +#endif /* VERASE */ +#ifdef VERASE2 + {C_ERASE2, VERASE2, + {EM_DELETE_PREV_CHAR, VI_DELETE_PREV_CHAR, ED_PREV_CHAR}}, +#endif /* VERASE2 */ +#ifdef VKILL + {C_KILL, VKILL, + {EM_KILL_LINE, VI_KILL_LINE_PREV, ED_UNASSIGNED}}, +#endif /* VKILL */ +#ifdef VKILL2 + {C_KILL2, VKILL2, + {EM_KILL_LINE, VI_KILL_LINE_PREV, ED_UNASSIGNED}}, +#endif /* VKILL2 */ +#ifdef VEOF + {C_EOF, VEOF, + {EM_DELETE_OR_LIST, VI_LIST_OR_EOF, ED_UNASSIGNED}}, +#endif /* VEOF */ +#ifdef VWERASE + {C_WERASE, VWERASE, + {ED_DELETE_PREV_WORD, ED_DELETE_PREV_WORD, ED_PREV_WORD}}, +#endif /* VWERASE */ +#ifdef VREPRINT + {C_REPRINT, VREPRINT, + {ED_REDISPLAY, ED_INSERT, ED_REDISPLAY}}, +#endif /* VREPRINT */ +#ifdef VLNEXT + {C_LNEXT, VLNEXT, + {ED_QUOTED_INSERT, ED_QUOTED_INSERT, ED_UNASSIGNED}}, +#endif /* VLNEXT */ + {(wint_t)-1, (wint_t)-1, + {ED_UNASSIGNED, ED_UNASSIGNED, ED_UNASSIGNED}} +}; + +static const ttymodes_t ttymodes[] = { +#ifdef IGNBRK + {"ignbrk", IGNBRK, MD_INP}, +#endif /* IGNBRK */ +#ifdef BRKINT + {"brkint", BRKINT, MD_INP}, +#endif /* BRKINT */ +#ifdef IGNPAR + {"ignpar", IGNPAR, MD_INP}, +#endif /* IGNPAR */ +#ifdef PARMRK + {"parmrk", PARMRK, MD_INP}, +#endif /* PARMRK */ +#ifdef INPCK + {"inpck", INPCK, MD_INP}, +#endif /* INPCK */ +#ifdef ISTRIP + {"istrip", ISTRIP, MD_INP}, +#endif /* ISTRIP */ +#ifdef INLCR + {"inlcr", INLCR, MD_INP}, +#endif /* INLCR */ +#ifdef IGNCR + {"igncr", IGNCR, MD_INP}, +#endif /* IGNCR */ +#ifdef ICRNL + {"icrnl", ICRNL, MD_INP}, +#endif /* ICRNL */ +#ifdef IUCLC + {"iuclc", IUCLC, MD_INP}, +#endif /* IUCLC */ +#ifdef IXON + {"ixon", IXON, MD_INP}, +#endif /* IXON */ +#ifdef IXANY + {"ixany", IXANY, MD_INP}, +#endif /* IXANY */ +#ifdef IXOFF + {"ixoff", IXOFF, MD_INP}, +#endif /* IXOFF */ +#ifdef IMAXBEL + {"imaxbel", IMAXBEL, MD_INP}, +#endif /* IMAXBEL */ + +#ifdef OPOST + {"opost", OPOST, MD_OUT}, +#endif /* OPOST */ +#ifdef OLCUC + {"olcuc", OLCUC, MD_OUT}, +#endif /* OLCUC */ +#ifdef ONLCR + {"onlcr", ONLCR, MD_OUT}, +#endif /* ONLCR */ +#ifdef OCRNL + {"ocrnl", OCRNL, MD_OUT}, +#endif /* OCRNL */ +#ifdef ONOCR + {"onocr", ONOCR, MD_OUT}, +#endif /* ONOCR */ +#ifdef ONOEOT + {"onoeot", ONOEOT, MD_OUT}, +#endif /* ONOEOT */ +#ifdef ONLRET + {"onlret", ONLRET, MD_OUT}, +#endif /* ONLRET */ +#ifdef OFILL + {"ofill", OFILL, MD_OUT}, +#endif /* OFILL */ +#ifdef OFDEL + {"ofdel", OFDEL, MD_OUT}, +#endif /* OFDEL */ +#ifdef NLDLY + {"nldly", NLDLY, MD_OUT}, +#endif /* NLDLY */ +#ifdef CRDLY + {"crdly", CRDLY, MD_OUT}, +#endif /* CRDLY */ +#ifdef TABDLY + {"tabdly", TABDLY, MD_OUT}, +#endif /* TABDLY */ +#ifdef XTABS + {"xtabs", XTABS, MD_OUT}, +#endif /* XTABS */ +#ifdef BSDLY + {"bsdly", BSDLY, MD_OUT}, +#endif /* BSDLY */ +#ifdef VTDLY + {"vtdly", VTDLY, MD_OUT}, +#endif /* VTDLY */ +#ifdef FFDLY + {"ffdly", FFDLY, MD_OUT}, +#endif /* FFDLY */ +#ifdef PAGEOUT + {"pageout", PAGEOUT, MD_OUT}, +#endif /* PAGEOUT */ +#ifdef WRAP + {"wrap", WRAP, MD_OUT}, +#endif /* WRAP */ + +#ifdef CIGNORE + {"cignore", CIGNORE, MD_CTL}, +#endif /* CBAUD */ +#ifdef CBAUD + {"cbaud", CBAUD, MD_CTL}, +#endif /* CBAUD */ +#ifdef CSTOPB + {"cstopb", CSTOPB, MD_CTL}, +#endif /* CSTOPB */ +#ifdef CREAD + {"cread", CREAD, MD_CTL}, +#endif /* CREAD */ +#ifdef PARENB + {"parenb", PARENB, MD_CTL}, +#endif /* PARENB */ +#ifdef PARODD + {"parodd", PARODD, MD_CTL}, +#endif /* PARODD */ +#ifdef HUPCL + {"hupcl", HUPCL, MD_CTL}, +#endif /* HUPCL */ +#ifdef CLOCAL + {"clocal", CLOCAL, MD_CTL}, +#endif /* CLOCAL */ +#ifdef LOBLK + {"loblk", LOBLK, MD_CTL}, +#endif /* LOBLK */ +#ifdef CIBAUD + {"cibaud", CIBAUD, MD_CTL}, +#endif /* CIBAUD */ +#ifdef CRTSCTS +#ifdef CCTS_OFLOW + {"ccts_oflow", CCTS_OFLOW, MD_CTL}, +#else + {"crtscts", CRTSCTS, MD_CTL}, +#endif /* CCTS_OFLOW */ +#endif /* CRTSCTS */ +#ifdef CRTS_IFLOW + {"crts_iflow", CRTS_IFLOW, MD_CTL}, +#endif /* CRTS_IFLOW */ +#ifdef CDTRCTS + {"cdtrcts", CDTRCTS, MD_CTL}, +#endif /* CDTRCTS */ +#ifdef MDMBUF + {"mdmbuf", MDMBUF, MD_CTL}, +#endif /* MDMBUF */ +#ifdef RCV1EN + {"rcv1en", RCV1EN, MD_CTL}, +#endif /* RCV1EN */ +#ifdef XMT1EN + {"xmt1en", XMT1EN, MD_CTL}, +#endif /* XMT1EN */ + +#ifdef ISIG + {"isig", ISIG, MD_LIN}, +#endif /* ISIG */ +#ifdef ICANON + {"icanon", ICANON, MD_LIN}, +#endif /* ICANON */ +#ifdef XCASE + {"xcase", XCASE, MD_LIN}, +#endif /* XCASE */ +#ifdef ECHO + {"echo", ECHO, MD_LIN}, +#endif /* ECHO */ +#ifdef ECHOE + {"echoe", ECHOE, MD_LIN}, +#endif /* ECHOE */ +#ifdef ECHOK + {"echok", ECHOK, MD_LIN}, +#endif /* ECHOK */ +#ifdef ECHONL + {"echonl", ECHONL, MD_LIN}, +#endif /* ECHONL */ +#ifdef NOFLSH + {"noflsh", NOFLSH, MD_LIN}, +#endif /* NOFLSH */ +#ifdef TOSTOP + {"tostop", TOSTOP, MD_LIN}, +#endif /* TOSTOP */ +#ifdef ECHOCTL + {"echoctl", ECHOCTL, MD_LIN}, +#endif /* ECHOCTL */ +#ifdef ECHOPRT + {"echoprt", ECHOPRT, MD_LIN}, +#endif /* ECHOPRT */ +#ifdef ECHOKE + {"echoke", ECHOKE, MD_LIN}, +#endif /* ECHOKE */ +#ifdef DEFECHO + {"defecho", DEFECHO, MD_LIN}, +#endif /* DEFECHO */ +#ifdef FLUSHO + {"flusho", FLUSHO, MD_LIN}, +#endif /* FLUSHO */ +#ifdef PENDIN + {"pendin", PENDIN, MD_LIN}, +#endif /* PENDIN */ +#ifdef IEXTEN + {"iexten", IEXTEN, MD_LIN}, +#endif /* IEXTEN */ +#ifdef NOKERNINFO + {"nokerninfo", NOKERNINFO, MD_LIN}, +#endif /* NOKERNINFO */ +#ifdef ALTWERASE + {"altwerase", ALTWERASE, MD_LIN}, +#endif /* ALTWERASE */ +#ifdef EXTPROC + {"extproc", EXTPROC, MD_LIN}, +#endif /* EXTPROC */ + +#if defined(VINTR) + {"intr", C_SH(C_INTR), MD_CHAR}, +#endif /* VINTR */ +#if defined(VQUIT) + {"quit", C_SH(C_QUIT), MD_CHAR}, +#endif /* VQUIT */ +#if defined(VERASE) + {"erase", C_SH(C_ERASE), MD_CHAR}, +#endif /* VERASE */ +#if defined(VKILL) + {"kill", C_SH(C_KILL), MD_CHAR}, +#endif /* VKILL */ +#if defined(VEOF) + {"eof", C_SH(C_EOF), MD_CHAR}, +#endif /* VEOF */ +#if defined(VEOL) + {"eol", C_SH(C_EOL), MD_CHAR}, +#endif /* VEOL */ +#if defined(VEOL2) + {"eol2", C_SH(C_EOL2), MD_CHAR}, +#endif /* VEOL2 */ +#if defined(VSWTCH) + {"swtch", C_SH(C_SWTCH), MD_CHAR}, +#endif /* VSWTCH */ +#if defined(VDSWTCH) + {"dswtch", C_SH(C_DSWTCH), MD_CHAR}, +#endif /* VDSWTCH */ +#if defined(VERASE2) + {"erase2", C_SH(C_ERASE2), MD_CHAR}, +#endif /* VERASE2 */ +#if defined(VSTART) + {"start", C_SH(C_START), MD_CHAR}, +#endif /* VSTART */ +#if defined(VSTOP) + {"stop", C_SH(C_STOP), MD_CHAR}, +#endif /* VSTOP */ +#if defined(VWERASE) + {"werase", C_SH(C_WERASE), MD_CHAR}, +#endif /* VWERASE */ +#if defined(VSUSP) + {"susp", C_SH(C_SUSP), MD_CHAR}, +#endif /* VSUSP */ +#if defined(VDSUSP) + {"dsusp", C_SH(C_DSUSP), MD_CHAR}, +#endif /* VDSUSP */ +#if defined(VREPRINT) + {"reprint", C_SH(C_REPRINT), MD_CHAR}, +#endif /* VREPRINT */ +#if defined(VDISCARD) + {"discard", C_SH(C_DISCARD), MD_CHAR}, +#endif /* VDISCARD */ +#if defined(VLNEXT) + {"lnext", C_SH(C_LNEXT), MD_CHAR}, +#endif /* VLNEXT */ +#if defined(VSTATUS) + {"status", C_SH(C_STATUS), MD_CHAR}, +#endif /* VSTATUS */ +#if defined(VPAGE) + {"page", C_SH(C_PAGE), MD_CHAR}, +#endif /* VPAGE */ +#if defined(VPGOFF) + {"pgoff", C_SH(C_PGOFF), MD_CHAR}, +#endif /* VPGOFF */ +#if defined(VKILL2) + {"kill2", C_SH(C_KILL2), MD_CHAR}, +#endif /* VKILL2 */ +#if defined(VBRK) + {"brk", C_SH(C_BRK), MD_CHAR}, +#endif /* VBRK */ +#if defined(VMIN) + {"min", C_SH(C_MIN), MD_CHAR}, +#endif /* VMIN */ +#if defined(VTIME) + {"time", C_SH(C_TIME), MD_CHAR}, +#endif /* VTIME */ + {NULL, 0, -1}, +}; + + + +#define tty__gettabs(td) ((((td)->c_oflag & TAB3) == TAB3) ? 0 : 1) +#define tty__geteightbit(td) (((td)->c_cflag & CSIZE) == CS8) +#define tty__cooked_mode(td) ((td)->c_lflag & ICANON) + +static int tty_getty(EditLine *, struct termios *); +static int tty_setty(EditLine *, int, const struct termios *); +static int tty__getcharindex(int); +static void tty__getchar(struct termios *, unsigned char *); +static void tty__setchar(struct termios *, unsigned char *); +static speed_t tty__getspeed(struct termios *); +static int tty_setup(EditLine *); +static void tty_setup_flags(EditLine *, struct termios *, int); + +#define t_qu t_ts + +/* tty_getty(): + * Wrapper for tcgetattr to handle EINTR + */ +static int +tty_getty(EditLine *el, struct termios *t) +{ + int rv; + while ((rv = tcgetattr(el->el_infd, t)) == -1 && errno == EINTR) + continue; + return rv; +} + +/* tty_setty(): + * Wrapper for tcsetattr to handle EINTR + */ +static int +tty_setty(EditLine *el, int action, const struct termios *t) +{ + int rv; + while ((rv = tcsetattr(el->el_infd, action, t)) == -1 && errno == EINTR) + continue; + return rv; +} + +/* tty_setup(): + * Get the tty parameters and initialize the editing state + */ +static int +tty_setup(EditLine *el) +{ + int rst = 1; + + if (el->el_flags & EDIT_DISABLED) + return 0; + + if (el->el_tty.t_initialized) + return -1; + + if (!isatty(el->el_outfd)) { +#ifdef DEBUG_TTY + (void) fprintf(el->el_errfile, "%s: isatty: %s\n", __func__, + strerror(errno)); +#endif /* DEBUG_TTY */ + return -1; + } + if (tty_getty(el, &el->el_tty.t_or) == -1) { +#ifdef DEBUG_TTY + (void) fprintf(el->el_errfile, "%s: tty_getty: %s\n", __func__, + strerror(errno)); +#endif /* DEBUG_TTY */ + return -1; + } + el->el_tty.t_ts = el->el_tty.t_ex = el->el_tty.t_ed = el->el_tty.t_or; + + el->el_tty.t_speed = tty__getspeed(&el->el_tty.t_ex); + el->el_tty.t_tabs = tty__gettabs(&el->el_tty.t_ex); + el->el_tty.t_eight = tty__geteightbit(&el->el_tty.t_ex); + + tty_setup_flags(el, &el->el_tty.t_ex, EX_IO); + + /* + * Reset the tty chars to reasonable defaults + * If they are disabled, then enable them. + */ + if (rst) { + if (tty__cooked_mode(&el->el_tty.t_ts)) { + tty__getchar(&el->el_tty.t_ts, el->el_tty.t_c[TS_IO]); + /* + * Don't affect CMIN and CTIME for the editor mode + */ + for (rst = 0; rst < C_NCC - 2; rst++) + if (el->el_tty.t_c[TS_IO][rst] != + el->el_tty.t_vdisable + && el->el_tty.t_c[ED_IO][rst] != + el->el_tty.t_vdisable) + el->el_tty.t_c[ED_IO][rst] = + el->el_tty.t_c[TS_IO][rst]; + for (rst = 0; rst < C_NCC; rst++) + if (el->el_tty.t_c[TS_IO][rst] != + el->el_tty.t_vdisable) + el->el_tty.t_c[EX_IO][rst] = + el->el_tty.t_c[TS_IO][rst]; + } + tty__setchar(&el->el_tty.t_ex, el->el_tty.t_c[EX_IO]); + if (tty_setty(el, TCSADRAIN, &el->el_tty.t_ex) == -1) { +#ifdef DEBUG_TTY + (void) fprintf(el->el_errfile, "%s: tty_setty: %s\n", + __func__, strerror(errno)); +#endif /* DEBUG_TTY */ + return -1; + } + } + + tty_setup_flags(el, &el->el_tty.t_ed, ED_IO); + + tty__setchar(&el->el_tty.t_ed, el->el_tty.t_c[ED_IO]); + tty_bind_char(el, 1); + el->el_tty.t_initialized = 1; + return 0; +} + +libedit_private int +tty_init(EditLine *el) +{ + + el->el_tty.t_mode = EX_IO; + el->el_tty.t_vdisable = _POSIX_VDISABLE; + el->el_tty.t_initialized = 0; + (void) memcpy(el->el_tty.t_t, ttyperm, sizeof(ttyperm_t)); + (void) memcpy(el->el_tty.t_c, ttychar, sizeof(ttychar_t)); + return tty_setup(el); +} + + +/* tty_end(): + * Restore the tty to its original settings + */ +libedit_private void +/*ARGSUSED*/ +tty_end(EditLine *el) +{ + if (el->el_flags & EDIT_DISABLED) + return; + + if (!el->el_tty.t_initialized) + return; + + if (tty_setty(el, TCSAFLUSH, &el->el_tty.t_or) == -1) { +#ifdef DEBUG_TTY + (void) fprintf(el->el_errfile, + "%s: tty_setty: %s\n", __func__, strerror(errno)); +#endif /* DEBUG_TTY */ + } +} + + +/* tty__getspeed(): + * Get the tty speed + */ +static speed_t +tty__getspeed(struct termios *td) +{ + speed_t spd; + + if ((spd = cfgetispeed(td)) == 0) + spd = cfgetospeed(td); + return spd; +} + +/* tty__getspeed(): + * Return the index of the asked char in the c_cc array + */ +static int +tty__getcharindex(int i) +{ + switch (i) { +#ifdef VINTR + case C_INTR: + return VINTR; +#endif /* VINTR */ +#ifdef VQUIT + case C_QUIT: + return VQUIT; +#endif /* VQUIT */ +#ifdef VERASE + case C_ERASE: + return VERASE; +#endif /* VERASE */ +#ifdef VKILL + case C_KILL: + return VKILL; +#endif /* VKILL */ +#ifdef VEOF + case C_EOF: + return VEOF; +#endif /* VEOF */ +#ifdef VEOL + case C_EOL: + return VEOL; +#endif /* VEOL */ +#ifdef VEOL2 + case C_EOL2: + return VEOL2; +#endif /* VEOL2 */ +#ifdef VSWTCH + case C_SWTCH: + return VSWTCH; +#endif /* VSWTCH */ +#ifdef VDSWTCH + case C_DSWTCH: + return VDSWTCH; +#endif /* VDSWTCH */ +#ifdef VERASE2 + case C_ERASE2: + return VERASE2; +#endif /* VERASE2 */ +#ifdef VSTART + case C_START: + return VSTART; +#endif /* VSTART */ +#ifdef VSTOP + case C_STOP: + return VSTOP; +#endif /* VSTOP */ +#ifdef VWERASE + case C_WERASE: + return VWERASE; +#endif /* VWERASE */ +#ifdef VSUSP + case C_SUSP: + return VSUSP; +#endif /* VSUSP */ +#ifdef VDSUSP + case C_DSUSP: + return VDSUSP; +#endif /* VDSUSP */ +#ifdef VREPRINT + case C_REPRINT: + return VREPRINT; +#endif /* VREPRINT */ +#ifdef VDISCARD + case C_DISCARD: + return VDISCARD; +#endif /* VDISCARD */ +#ifdef VLNEXT + case C_LNEXT: + return VLNEXT; +#endif /* VLNEXT */ +#ifdef VSTATUS + case C_STATUS: + return VSTATUS; +#endif /* VSTATUS */ +#ifdef VPAGE + case C_PAGE: + return VPAGE; +#endif /* VPAGE */ +#ifdef VPGOFF + case C_PGOFF: + return VPGOFF; +#endif /* VPGOFF */ +#ifdef VKILL2 + case C_KILL2: + return VKILL2; +#endif /* KILL2 */ +#ifdef VMIN + case C_MIN: + return VMIN; +#endif /* VMIN */ +#ifdef VTIME + case C_TIME: + return VTIME; +#endif /* VTIME */ + default: + return -1; + } +} + +/* tty__getchar(): + * Get the tty characters + */ +static void +tty__getchar(struct termios *td, unsigned char *s) +{ + +#ifdef VINTR + s[C_INTR] = td->c_cc[VINTR]; +#endif /* VINTR */ +#ifdef VQUIT + s[C_QUIT] = td->c_cc[VQUIT]; +#endif /* VQUIT */ +#ifdef VERASE + s[C_ERASE] = td->c_cc[VERASE]; +#endif /* VERASE */ +#ifdef VKILL + s[C_KILL] = td->c_cc[VKILL]; +#endif /* VKILL */ +#ifdef VEOF + s[C_EOF] = td->c_cc[VEOF]; +#endif /* VEOF */ +#ifdef VEOL + s[C_EOL] = td->c_cc[VEOL]; +#endif /* VEOL */ +#ifdef VEOL2 + s[C_EOL2] = td->c_cc[VEOL2]; +#endif /* VEOL2 */ +#ifdef VSWTCH + s[C_SWTCH] = td->c_cc[VSWTCH]; +#endif /* VSWTCH */ +#ifdef VDSWTCH + s[C_DSWTCH] = td->c_cc[VDSWTCH]; +#endif /* VDSWTCH */ +#ifdef VERASE2 + s[C_ERASE2] = td->c_cc[VERASE2]; +#endif /* VERASE2 */ +#ifdef VSTART + s[C_START] = td->c_cc[VSTART]; +#endif /* VSTART */ +#ifdef VSTOP + s[C_STOP] = td->c_cc[VSTOP]; +#endif /* VSTOP */ +#ifdef VWERASE + s[C_WERASE] = td->c_cc[VWERASE]; +#endif /* VWERASE */ +#ifdef VSUSP + s[C_SUSP] = td->c_cc[VSUSP]; +#endif /* VSUSP */ +#ifdef VDSUSP + s[C_DSUSP] = td->c_cc[VDSUSP]; +#endif /* VDSUSP */ +#ifdef VREPRINT + s[C_REPRINT] = td->c_cc[VREPRINT]; +#endif /* VREPRINT */ +#ifdef VDISCARD + s[C_DISCARD] = td->c_cc[VDISCARD]; +#endif /* VDISCARD */ +#ifdef VLNEXT + s[C_LNEXT] = td->c_cc[VLNEXT]; +#endif /* VLNEXT */ +#ifdef VSTATUS + s[C_STATUS] = td->c_cc[VSTATUS]; +#endif /* VSTATUS */ +#ifdef VPAGE + s[C_PAGE] = td->c_cc[VPAGE]; +#endif /* VPAGE */ +#ifdef VPGOFF + s[C_PGOFF] = td->c_cc[VPGOFF]; +#endif /* VPGOFF */ +#ifdef VKILL2 + s[C_KILL2] = td->c_cc[VKILL2]; +#endif /* KILL2 */ +#ifdef VMIN + s[C_MIN] = td->c_cc[VMIN]; +#endif /* VMIN */ +#ifdef VTIME + s[C_TIME] = td->c_cc[VTIME]; +#endif /* VTIME */ +} /* tty__getchar */ + + +/* tty__setchar(): + * Set the tty characters + */ +static void +tty__setchar(struct termios *td, unsigned char *s) +{ + +#ifdef VINTR + td->c_cc[VINTR] = s[C_INTR]; +#endif /* VINTR */ +#ifdef VQUIT + td->c_cc[VQUIT] = s[C_QUIT]; +#endif /* VQUIT */ +#ifdef VERASE + td->c_cc[VERASE] = s[C_ERASE]; +#endif /* VERASE */ +#ifdef VKILL + td->c_cc[VKILL] = s[C_KILL]; +#endif /* VKILL */ +#ifdef VEOF + td->c_cc[VEOF] = s[C_EOF]; +#endif /* VEOF */ +#ifdef VEOL + td->c_cc[VEOL] = s[C_EOL]; +#endif /* VEOL */ +#ifdef VEOL2 + td->c_cc[VEOL2] = s[C_EOL2]; +#endif /* VEOL2 */ +#ifdef VSWTCH + td->c_cc[VSWTCH] = s[C_SWTCH]; +#endif /* VSWTCH */ +#ifdef VDSWTCH + td->c_cc[VDSWTCH] = s[C_DSWTCH]; +#endif /* VDSWTCH */ +#ifdef VERASE2 + td->c_cc[VERASE2] = s[C_ERASE2]; +#endif /* VERASE2 */ +#ifdef VSTART + td->c_cc[VSTART] = s[C_START]; +#endif /* VSTART */ +#ifdef VSTOP + td->c_cc[VSTOP] = s[C_STOP]; +#endif /* VSTOP */ +#ifdef VWERASE + td->c_cc[VWERASE] = s[C_WERASE]; +#endif /* VWERASE */ +#ifdef VSUSP + td->c_cc[VSUSP] = s[C_SUSP]; +#endif /* VSUSP */ +#ifdef VDSUSP + td->c_cc[VDSUSP] = s[C_DSUSP]; +#endif /* VDSUSP */ +#ifdef VREPRINT + td->c_cc[VREPRINT] = s[C_REPRINT]; +#endif /* VREPRINT */ +#ifdef VDISCARD + td->c_cc[VDISCARD] = s[C_DISCARD]; +#endif /* VDISCARD */ +#ifdef VLNEXT + td->c_cc[VLNEXT] = s[C_LNEXT]; +#endif /* VLNEXT */ +#ifdef VSTATUS + td->c_cc[VSTATUS] = s[C_STATUS]; +#endif /* VSTATUS */ +#ifdef VPAGE + td->c_cc[VPAGE] = s[C_PAGE]; +#endif /* VPAGE */ +#ifdef VPGOFF + td->c_cc[VPGOFF] = s[C_PGOFF]; +#endif /* VPGOFF */ +#ifdef VKILL2 + td->c_cc[VKILL2] = s[C_KILL2]; +#endif /* VKILL2 */ +#ifdef VMIN + td->c_cc[VMIN] = s[C_MIN]; +#endif /* VMIN */ +#ifdef VTIME + td->c_cc[VTIME] = s[C_TIME]; +#endif /* VTIME */ +} /* tty__setchar */ + + +/* tty_bind_char(): + * Rebind the editline functions + */ +libedit_private void +tty_bind_char(EditLine *el, int force) +{ + + unsigned char *t_n = el->el_tty.t_c[ED_IO]; + unsigned char *t_o = el->el_tty.t_ed.c_cc; + wchar_t new[2], old[2]; + const ttymap_t *tp; + el_action_t *map, *alt; + const el_action_t *dmap, *dalt; + new[1] = old[1] = '\0'; + + map = el->el_map.key; + alt = el->el_map.alt; + if (el->el_map.type == MAP_VI) { + dmap = el->el_map.vii; + dalt = el->el_map.vic; + } else { + dmap = el->el_map.emacs; + dalt = NULL; + } + + for (tp = tty_map; tp->nch != (wint_t)-1; tp++) { + new[0] = (wchar_t)t_n[tp->nch]; + old[0] = (wchar_t)t_o[tp->och]; + if (new[0] == old[0] && !force) + continue; + /* Put the old default binding back, and set the new binding */ + keymacro_clear(el, map, old); + map[(unsigned char)old[0]] = dmap[(unsigned char)old[0]]; + keymacro_clear(el, map, new); + /* MAP_VI == 1, MAP_EMACS == 0... */ + map[(unsigned char)new[0]] = tp->bind[el->el_map.type]; + if (dalt) { + keymacro_clear(el, alt, old); + alt[(unsigned char)old[0]] = + dalt[(unsigned char)old[0]]; + keymacro_clear(el, alt, new); + alt[(unsigned char)new[0]] = + tp->bind[el->el_map.type + 1]; + } + } +} + + +static tcflag_t * +tty__get_flag(struct termios *t, int kind) { + switch (kind) { + case MD_INP: + return &t->c_iflag; + case MD_OUT: + return &t->c_oflag; + case MD_CTL: + return &t->c_cflag; + case MD_LIN: + return &t->c_lflag; + default: + abort(); + /*NOTREACHED*/ + } +} + + +static tcflag_t +tty_update_flag(EditLine *el, tcflag_t f, int mode, int kind) +{ + f &= ~el->el_tty.t_t[mode][kind].t_clrmask; + f |= el->el_tty.t_t[mode][kind].t_setmask; + return f; +} + + +static void +tty_update_flags(EditLine *el, int kind) +{ + tcflag_t *tt, *ed, *ex; + tt = tty__get_flag(&el->el_tty.t_ts, kind); + ed = tty__get_flag(&el->el_tty.t_ed, kind); + ex = tty__get_flag(&el->el_tty.t_ex, kind); + + if (*tt != *ex && (kind != MD_CTL || *tt != *ed)) { + *ed = tty_update_flag(el, *tt, ED_IO, kind); + *ex = tty_update_flag(el, *tt, EX_IO, kind); + } +} + + +static void +tty_update_char(EditLine *el, int mode, int c) { + if (!((el->el_tty.t_t[mode][MD_CHAR].t_setmask & C_SH(c))) + && (el->el_tty.t_c[TS_IO][c] != el->el_tty.t_c[EX_IO][c])) + el->el_tty.t_c[mode][c] = el->el_tty.t_c[TS_IO][c]; + if (el->el_tty.t_t[mode][MD_CHAR].t_clrmask & C_SH(c)) + el->el_tty.t_c[mode][c] = el->el_tty.t_vdisable; +} + + +/* tty_rawmode(): + * Set terminal into 1 character at a time mode. + */ +libedit_private int +tty_rawmode(EditLine *el) +{ + + if (el->el_tty.t_mode == ED_IO || el->el_tty.t_mode == QU_IO) + return 0; + + if (el->el_flags & EDIT_DISABLED) + return 0; + + if (tty_getty(el, &el->el_tty.t_ts) == -1) { +#ifdef DEBUG_TTY + (void) fprintf(el->el_errfile, "%s: tty_getty: %s\n", __func__, + strerror(errno)); +#endif /* DEBUG_TTY */ + return -1; + } + /* + * We always keep up with the eight bit setting and the speed of the + * tty. But we only believe changes that are made to cooked mode! + */ + el->el_tty.t_eight = tty__geteightbit(&el->el_tty.t_ts); + el->el_tty.t_speed = tty__getspeed(&el->el_tty.t_ts); + + if (tty__getspeed(&el->el_tty.t_ex) != el->el_tty.t_speed || + tty__getspeed(&el->el_tty.t_ed) != el->el_tty.t_speed) { + (void) cfsetispeed(&el->el_tty.t_ex, el->el_tty.t_speed); + (void) cfsetospeed(&el->el_tty.t_ex, el->el_tty.t_speed); + (void) cfsetispeed(&el->el_tty.t_ed, el->el_tty.t_speed); + (void) cfsetospeed(&el->el_tty.t_ed, el->el_tty.t_speed); + } + if (tty__cooked_mode(&el->el_tty.t_ts)) { + int i; + + for (i = MD_INP; i <= MD_LIN; i++) + tty_update_flags(el, i); + + if (tty__gettabs(&el->el_tty.t_ex) == 0) + el->el_tty.t_tabs = 0; + else + el->el_tty.t_tabs = EL_CAN_TAB ? 1 : 0; + + tty__getchar(&el->el_tty.t_ts, el->el_tty.t_c[TS_IO]); + /* + * Check if the user made any changes. + * If he did, then propagate the changes to the + * edit and execute data structures. + */ + for (i = 0; i < C_NCC; i++) + if (el->el_tty.t_c[TS_IO][i] != + el->el_tty.t_c[EX_IO][i]) + break; + + if (i != C_NCC) { + /* + * Propagate changes only to the unlibedit_private + * chars that have been modified just now. + */ + for (i = 0; i < C_NCC; i++) + tty_update_char(el, ED_IO, i); + + tty_bind_char(el, 0); + tty__setchar(&el->el_tty.t_ed, el->el_tty.t_c[ED_IO]); + + for (i = 0; i < C_NCC; i++) + tty_update_char(el, EX_IO, i); + + tty__setchar(&el->el_tty.t_ex, el->el_tty.t_c[EX_IO]); + } + } + if (tty_setty(el, TCSADRAIN, &el->el_tty.t_ed) == -1) { +#ifdef DEBUG_TTY + (void) fprintf(el->el_errfile, "%s: tty_setty: %s\n", __func__, + strerror(errno)); +#endif /* DEBUG_TTY */ + return -1; + } + el->el_tty.t_mode = ED_IO; + return 0; +} + + +/* tty_cookedmode(): + * Set the tty back to normal mode + */ +libedit_private int +tty_cookedmode(EditLine *el) +{ /* set tty in normal setup */ + + if (el->el_tty.t_mode == EX_IO) + return 0; + + if (el->el_flags & EDIT_DISABLED) + return 0; + + if (tty_setty(el, TCSADRAIN, &el->el_tty.t_ex) == -1) { +#ifdef DEBUG_TTY + (void) fprintf(el->el_errfile, "%s: tty_setty: %s\n", __func__, + strerror(errno)); +#endif /* DEBUG_TTY */ + return -1; + } + el->el_tty.t_mode = EX_IO; + return 0; +} + + +/* tty_quotemode(): + * Turn on quote mode + */ +libedit_private int +tty_quotemode(EditLine *el) +{ + if (el->el_tty.t_mode == QU_IO) + return 0; + + el->el_tty.t_qu = el->el_tty.t_ed; + + tty_setup_flags(el, &el->el_tty.t_qu, QU_IO); + + if (tty_setty(el, TCSADRAIN, &el->el_tty.t_qu) == -1) { +#ifdef DEBUG_TTY + (void) fprintf(el->el_errfile, "%s: tty_setty: %s\n", __func__, + strerror(errno)); +#endif /* DEBUG_TTY */ + return -1; + } + el->el_tty.t_mode = QU_IO; + return 0; +} + + +/* tty_noquotemode(): + * Turn off quote mode + */ +libedit_private int +tty_noquotemode(EditLine *el) +{ + + if (el->el_tty.t_mode != QU_IO) + return 0; + if (tty_setty(el, TCSADRAIN, &el->el_tty.t_ed) == -1) { +#ifdef DEBUG_TTY + (void) fprintf(el->el_errfile, "%s: tty_setty: %s\n", __func__, + strerror(errno)); +#endif /* DEBUG_TTY */ + return -1; + } + el->el_tty.t_mode = ED_IO; + return 0; +} + + +/* tty_stty(): + * Stty builtin + */ +libedit_private int +/*ARGSUSED*/ +tty_stty(EditLine *el, int argc __attribute__((__unused__)), + const wchar_t **argv) +{ + const ttymodes_t *m; + char x; + int aflag = 0; + const wchar_t *s, *d; + char name[EL_BUFSIZ]; + struct termios *tios = &el->el_tty.t_ex; + int z = EX_IO; + + if (argv == NULL) + return -1; + strncpy(name, ct_encode_string(*argv++, &el->el_scratch), sizeof(name)); + name[sizeof(name) - 1] = '\0'; + + while (argv && *argv && argv[0][0] == '-' && argv[0][2] == '\0') + switch (argv[0][1]) { + case 'a': + aflag++; + argv++; + break; + case 'd': + argv++; + tios = &el->el_tty.t_ed; + z = ED_IO; + break; + case 'x': + argv++; + tios = &el->el_tty.t_ex; + z = EX_IO; + break; + case 'q': + argv++; + tios = &el->el_tty.t_ts; + z = QU_IO; + break; + default: + (void) fprintf(el->el_errfile, + "%s: Unknown switch `%lc'.\n", + name, (wint_t)argv[0][1]); + return -1; + } + + if (!argv || !*argv) { + int i = -1; + size_t len = 0, st = 0, cu; + for (m = ttymodes; m->m_name; m++) { + if (m->m_type != i) { + (void) fprintf(el->el_outfile, "%s%s", + i != -1 ? "\n" : "", + el->el_tty.t_t[z][m->m_type].t_name); + i = m->m_type; + st = len = + strlen(el->el_tty.t_t[z][m->m_type].t_name); + } + if (i != -1) { + x = (el->el_tty.t_t[z][i].t_setmask & m->m_value) + ? '+' : '\0'; + + if (el->el_tty.t_t[z][i].t_clrmask & m->m_value) + x = '-'; + } else { + x = '\0'; + } + + if (x != '\0' || aflag) { + + cu = strlen(m->m_name) + (x != '\0') + 1; + + if (len + cu >= + (size_t)el->el_terminal.t_size.h) { + (void) fprintf(el->el_outfile, "\n%*s", + (int)st, ""); + len = st + cu; + } else + len += cu; + + if (x != '\0') + (void) fprintf(el->el_outfile, "%c%s ", + x, m->m_name); + else + (void) fprintf(el->el_outfile, "%s ", + m->m_name); + } + } + (void) fprintf(el->el_outfile, "\n"); + return 0; + } + while (argv && (s = *argv++)) { + const wchar_t *p; + switch (*s) { + case '+': + case '-': + x = (char)*s++; + break; + default: + x = '\0'; + break; + } + d = s; + p = wcschr(s, L'='); + for (m = ttymodes; m->m_name; m++) + if ((p ? strncmp(m->m_name, ct_encode_string(d, + &el->el_scratch), (size_t)(p - d)) : + strcmp(m->m_name, ct_encode_string(d, + &el->el_scratch))) == 0 && + (p == NULL || m->m_type == MD_CHAR)) + break; + + if (!m->m_name) { + (void) fprintf(el->el_errfile, + "%s: Invalid argument `%ls'.\n", name, d); + return -1; + } + if (p) { + int c = ffs((int)m->m_value); + int v = *++p ? parse__escape(&p) : + el->el_tty.t_vdisable; + assert(c != 0); + c--; + c = tty__getcharindex(c); + assert(c != -1); + tios->c_cc[c] = (cc_t)v; + continue; + } + switch (x) { + case '+': + el->el_tty.t_t[z][m->m_type].t_setmask |= m->m_value; + el->el_tty.t_t[z][m->m_type].t_clrmask &= ~m->m_value; + break; + case '-': + el->el_tty.t_t[z][m->m_type].t_setmask &= ~m->m_value; + el->el_tty.t_t[z][m->m_type].t_clrmask |= m->m_value; + break; + default: + el->el_tty.t_t[z][m->m_type].t_setmask &= ~m->m_value; + el->el_tty.t_t[z][m->m_type].t_clrmask &= ~m->m_value; + break; + } + } + + tty_setup_flags(el, tios, z); + if (el->el_tty.t_mode == z) { + if (tty_setty(el, TCSADRAIN, tios) == -1) { +#ifdef DEBUG_TTY + (void) fprintf(el->el_errfile, "%s: tty_setty: %s\n", + __func__, strerror(errno)); +#endif /* DEBUG_TTY */ + return -1; + } + } + + return 0; +} + + +#ifdef notyet +/* tty_printchar(): + * DEbugging routine to print the tty characters + */ +static void +tty_printchar(EditLine *el, unsigned char *s) +{ + ttyperm_t *m; + int i; + + for (i = 0; i < C_NCC; i++) { + for (m = el->el_tty.t_t; m->m_name; m++) + if (m->m_type == MD_CHAR && C_SH(i) == m->m_value) + break; + if (m->m_name) + (void) fprintf(el->el_errfile, "%s ^%c ", + m->m_name, s[i] + 'A' - 1); + if (i % 5 == 0) + (void) fprintf(el->el_errfile, "\n"); + } + (void) fprintf(el->el_errfile, "\n"); +} +#endif /* notyet */ + + +static void +tty_setup_flags(EditLine *el, struct termios *tios, int mode) +{ + int kind; + for (kind = MD_INP; kind <= MD_LIN; kind++) { + tcflag_t *f = tty__get_flag(tios, kind); + *f = tty_update_flag(el, *f, mode, kind); + } +} diff --git a/bin/catsh/libedit/tty.h b/bin/catsh/libedit/tty.h new file mode 100644 index 00000000..2603e1ad --- /dev/null +++ b/bin/catsh/libedit/tty.h @@ -0,0 +1,481 @@ +/* $NetBSD: tty.h,v 1.21 2016/05/09 21:46:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tty.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.tty.h: Local terminal header + */ +#ifndef _h_el_tty +#define _h_el_tty + +#include <termios.h> +#include <unistd.h> + +/* Define our own since everyone gets it wrong! */ +#define CONTROL(A) ((A) & 037) + +/* + * Aix compatible names + */ +# if defined(VWERSE) && !defined(VWERASE) +# define VWERASE VWERSE +# endif /* VWERSE && !VWERASE */ + +# if defined(VDISCRD) && !defined(VDISCARD) +# define VDISCARD VDISCRD +# endif /* VDISCRD && !VDISCARD */ + +# if defined(VFLUSHO) && !defined(VDISCARD) +# define VDISCARD VFLUSHO +# endif /* VFLUSHO && VDISCARD */ + +# if defined(VSTRT) && !defined(VSTART) +# define VSTART VSTRT +# endif /* VSTRT && ! VSTART */ + +# if defined(VSTAT) && !defined(VSTATUS) +# define VSTATUS VSTAT +# endif /* VSTAT && ! VSTATUS */ + +# ifndef ONLRET +# define ONLRET 0 +# endif /* ONLRET */ + +# ifndef TAB3 +# ifdef OXTABS +# define TAB3 OXTABS +# else +# define TAB3 0 +# endif /* OXTABS */ +# endif /* !TAB3 */ + +# if defined(OXTABS) && !defined(XTABS) +# define XTABS OXTABS +# endif /* OXTABS && !XTABS */ + +# ifndef ONLCR +# define ONLCR 0 +# endif /* ONLCR */ + +# ifndef IEXTEN +# define IEXTEN 0 +# endif /* IEXTEN */ + +# ifndef ECHOCTL +# define ECHOCTL 0 +# endif /* ECHOCTL */ + +# ifndef PARENB +# define PARENB 0 +# endif /* PARENB */ + +# ifndef EXTPROC +# define EXTPROC 0 +# endif /* EXTPROC */ + +# ifndef FLUSHO +# define FLUSHO 0 +# endif /* FLUSHO */ + + +# if defined(VDISABLE) && !defined(_POSIX_VDISABLE) +# define _POSIX_VDISABLE VDISABLE +# endif /* VDISABLE && ! _POSIX_VDISABLE */ + +/* + * Work around ISC's definition of IEXTEN which is + * XCASE! + */ +# ifdef ISC +# if defined(IEXTEN) && defined(XCASE) +# if IEXTEN == XCASE +# undef IEXTEN +# define IEXTEN 0 +# endif /* IEXTEN == XCASE */ +# endif /* IEXTEN && XCASE */ +# if defined(IEXTEN) && !defined(XCASE) +# define XCASE IEXTEN +# undef IEXTEN +# define IEXTEN 0 +# endif /* IEXTEN && !XCASE */ +# endif /* ISC */ + +/* + * Work around convex weirdness where turning off IEXTEN makes us + * lose all postprocessing! + */ +#if defined(convex) || defined(__convex__) +# if defined(IEXTEN) && IEXTEN != 0 +# undef IEXTEN +# define IEXTEN 0 +# endif /* IEXTEN != 0 */ +#endif /* convex || __convex__ */ + +/* + * So that we don't lose job control. + */ +#ifdef __SVR4 +# undef CSWTCH +#endif + +#ifndef _POSIX_VDISABLE +# define _POSIX_VDISABLE ((unsigned char) -1) +#endif /* _POSIX_VDISABLE */ + +#if !defined(CREPRINT) && defined(CRPRNT) +# define CREPRINT CRPRNT +#endif /* !CREPRINT && CRPRNT */ +#if !defined(CDISCARD) && defined(CFLUSH) +# define CDISCARD CFLUSH +#endif /* !CDISCARD && CFLUSH */ + +#ifndef CINTR +# define CINTR CONTROL('c') +#endif /* CINTR */ +#ifndef CQUIT +# define CQUIT 034 /* ^\ */ +#endif /* CQUIT */ +#ifndef CERASE +# define CERASE 0177 /* ^? */ +#endif /* CERASE */ +#ifndef CKILL +# define CKILL CONTROL('u') +#endif /* CKILL */ +#ifndef CEOF +# define CEOF CONTROL('d') +#endif /* CEOF */ +#ifndef CEOL +# define CEOL _POSIX_VDISABLE +#endif /* CEOL */ +#ifndef CEOL2 +# define CEOL2 _POSIX_VDISABLE +#endif /* CEOL2 */ +#ifndef CSWTCH +# define CSWTCH _POSIX_VDISABLE +#endif /* CSWTCH */ +#ifndef CDSWTCH +# define CDSWTCH _POSIX_VDISABLE +#endif /* CDSWTCH */ +#ifndef CERASE2 +# define CERASE2 _POSIX_VDISABLE +#endif /* CERASE2 */ +#ifndef CSTART +# define CSTART CONTROL('q') +#endif /* CSTART */ +#ifndef CSTOP +# define CSTOP CONTROL('s') +#endif /* CSTOP */ +#ifndef CSUSP +# define CSUSP CONTROL('z') +#endif /* CSUSP */ +#ifndef CDSUSP +# define CDSUSP CONTROL('y') +#endif /* CDSUSP */ + +#ifdef hpux + +# ifndef CREPRINT +# define CREPRINT _POSIX_VDISABLE +# endif /* CREPRINT */ +# ifndef CDISCARD +# define CDISCARD _POSIX_VDISABLE +# endif /* CDISCARD */ +# ifndef CLNEXT +# define CLNEXT _POSIX_VDISABLE +# endif /* CLNEXT */ +# ifndef CWERASE +# define CWERASE _POSIX_VDISABLE +# endif /* CWERASE */ + +#else /* !hpux */ + +# ifndef CREPRINT +# define CREPRINT CONTROL('r') +# endif /* CREPRINT */ +# ifndef CDISCARD +# define CDISCARD CONTROL('o') +# endif /* CDISCARD */ +# ifndef CLNEXT +# define CLNEXT CONTROL('v') +# endif /* CLNEXT */ +# ifndef CWERASE +# define CWERASE CONTROL('w') +# endif /* CWERASE */ + +#endif /* hpux */ + +#ifndef CSTATUS +# define CSTATUS CONTROL('t') +#endif /* CSTATUS */ +#ifndef CPAGE +# define CPAGE ' ' +#endif /* CPAGE */ +#ifndef CPGOFF +# define CPGOFF CONTROL('m') +#endif /* CPGOFF */ +#ifndef CKILL2 +# define CKILL2 _POSIX_VDISABLE +#endif /* CKILL2 */ +#ifndef CBRK +# ifndef masscomp +# define CBRK 0377 +# else +# define CBRK '\0' +# endif /* masscomp */ +#endif /* CBRK */ +#ifndef CMIN +# define CMIN CEOF +#endif /* CMIN */ +#ifndef CTIME +# define CTIME CEOL +#endif /* CTIME */ + +/* + * Fix for sun inconsistency. On termio VSUSP and the rest of the + * ttychars > NCC are defined. So we undefine them. + */ +#if defined(TERMIO) || defined(POSIX) +# if defined(POSIX) && defined(NCCS) +# define NUMCC NCCS +# else +# ifdef NCC +# define NUMCC NCC +# endif /* NCC */ +# endif /* POSIX && NCCS */ +# ifdef NUMCC +# ifdef VINTR +# if NUMCC <= VINTR +# undef VINTR +# endif /* NUMCC <= VINTR */ +# endif /* VINTR */ +# ifdef VQUIT +# if NUMCC <= VQUIT +# undef VQUIT +# endif /* NUMCC <= VQUIT */ +# endif /* VQUIT */ +# ifdef VERASE +# if NUMCC <= VERASE +# undef VERASE +# endif /* NUMCC <= VERASE */ +# endif /* VERASE */ +# ifdef VKILL +# if NUMCC <= VKILL +# undef VKILL +# endif /* NUMCC <= VKILL */ +# endif /* VKILL */ +# ifdef VEOF +# if NUMCC <= VEOF +# undef VEOF +# endif /* NUMCC <= VEOF */ +# endif /* VEOF */ +# ifdef VEOL +# if NUMCC <= VEOL +# undef VEOL +# endif /* NUMCC <= VEOL */ +# endif /* VEOL */ +# ifdef VEOL2 +# if NUMCC <= VEOL2 +# undef VEOL2 +# endif /* NUMCC <= VEOL2 */ +# endif /* VEOL2 */ +# ifdef VSWTCH +# if NUMCC <= VSWTCH +# undef VSWTCH +# endif /* NUMCC <= VSWTCH */ +# endif /* VSWTCH */ +# ifdef VDSWTCH +# if NUMCC <= VDSWTCH +# undef VDSWTCH +# endif /* NUMCC <= VDSWTCH */ +# endif /* VDSWTCH */ +# ifdef VERASE2 +# if NUMCC <= VERASE2 +# undef VERASE2 +# endif /* NUMCC <= VERASE2 */ +# endif /* VERASE2 */ +# ifdef VSTART +# if NUMCC <= VSTART +# undef VSTART +# endif /* NUMCC <= VSTART */ +# endif /* VSTART */ +# ifdef VSTOP +# if NUMCC <= VSTOP +# undef VSTOP +# endif /* NUMCC <= VSTOP */ +# endif /* VSTOP */ +# ifdef VWERASE +# if NUMCC <= VWERASE +# undef VWERASE +# endif /* NUMCC <= VWERASE */ +# endif /* VWERASE */ +# ifdef VSUSP +# if NUMCC <= VSUSP +# undef VSUSP +# endif /* NUMCC <= VSUSP */ +# endif /* VSUSP */ +# ifdef VDSUSP +# if NUMCC <= VDSUSP +# undef VDSUSP +# endif /* NUMCC <= VDSUSP */ +# endif /* VDSUSP */ +# ifdef VREPRINT +# if NUMCC <= VREPRINT +# undef VREPRINT +# endif /* NUMCC <= VREPRINT */ +# endif /* VREPRINT */ +# ifdef VDISCARD +# if NUMCC <= VDISCARD +# undef VDISCARD +# endif /* NUMCC <= VDISCARD */ +# endif /* VDISCARD */ +# ifdef VLNEXT +# if NUMCC <= VLNEXT +# undef VLNEXT +# endif /* NUMCC <= VLNEXT */ +# endif /* VLNEXT */ +# ifdef VSTATUS +# if NUMCC <= VSTATUS +# undef VSTATUS +# endif /* NUMCC <= VSTATUS */ +# endif /* VSTATUS */ +# ifdef VPAGE +# if NUMCC <= VPAGE +# undef VPAGE +# endif /* NUMCC <= VPAGE */ +# endif /* VPAGE */ +# ifdef VPGOFF +# if NUMCC <= VPGOFF +# undef VPGOFF +# endif /* NUMCC <= VPGOFF */ +# endif /* VPGOFF */ +# ifdef VKILL2 +# if NUMCC <= VKILL2 +# undef VKILL2 +# endif /* NUMCC <= VKILL2 */ +# endif /* VKILL2 */ +# ifdef VBRK +# if NUMCC <= VBRK +# undef VBRK +# endif /* NUMCC <= VBRK */ +# endif /* VBRK */ +# ifdef VMIN +# if NUMCC <= VMIN +# undef VMIN +# endif /* NUMCC <= VMIN */ +# endif /* VMIN */ +# ifdef VTIME +# if NUMCC <= VTIME +# undef VTIME +# endif /* NUMCC <= VTIME */ +# endif /* VTIME */ +# endif /* NUMCC */ +#endif /* !POSIX */ + +#define C_INTR 0 +#define C_QUIT 1 +#define C_ERASE 2 +#define C_KILL 3 +#define C_EOF 4 +#define C_EOL 5 +#define C_EOL2 6 +#define C_SWTCH 7 +#define C_DSWTCH 8 +#define C_ERASE2 9 +#define C_START 10 +#define C_STOP 11 +#define C_WERASE 12 +#define C_SUSP 13 +#define C_DSUSP 14 +#define C_REPRINT 15 +#define C_DISCARD 16 +#define C_LNEXT 17 +#define C_STATUS 18 +#define C_PAGE 19 +#define C_PGOFF 20 +#define C_KILL2 21 +#define C_BRK 22 +#define C_MIN 23 +#define C_TIME 24 +#define C_NCC 25 +#define C_SH(A) ((unsigned int)(1 << (A))) + +/* + * Terminal dependend data structures + */ +#define EX_IO 0 /* while we are executing */ +#define ED_IO 1 /* while we are editing */ +#define TS_IO 2 /* new mode from terminal */ +#define QU_IO 2 /* used only for quoted chars */ +#define NN_IO 3 /* The number of entries */ + +/* Don't re-order */ +#define MD_INP 0 +#define MD_OUT 1 +#define MD_CTL 2 +#define MD_LIN 3 +#define MD_CHAR 4 +#define MD_NN 5 + +typedef struct { + const char *t_name; + unsigned int t_setmask; + unsigned int t_clrmask; +} ttyperm_t[NN_IO][MD_NN]; + +typedef unsigned char ttychar_t[NN_IO][C_NCC]; + +libedit_private int tty_init(EditLine *); +libedit_private void tty_end(EditLine *); +libedit_private int tty_stty(EditLine *, int, const wchar_t **); +libedit_private int tty_rawmode(EditLine *); +libedit_private int tty_cookedmode(EditLine *); +libedit_private int tty_quotemode(EditLine *); +libedit_private int tty_noquotemode(EditLine *); +libedit_private void tty_bind_char(EditLine *, int); + +typedef struct { + ttyperm_t t_t; + ttychar_t t_c; + struct termios t_or, t_ex, t_ed, t_ts; + int t_tabs; + int t_eight; + speed_t t_speed; + unsigned char t_mode; + unsigned char t_vdisable; + unsigned char t_initialized; +} el_tty_t; + + +#endif /* _h_el_tty */ diff --git a/bin/catsh/libedit/vi.c b/bin/catsh/libedit/vi.c new file mode 100644 index 00000000..0c37bfb9 --- /dev/null +++ b/bin/catsh/libedit/vi.c @@ -0,0 +1,1157 @@ +/* $NetBSD: vi.c,v 1.62 2016/05/09 21:46:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)vi.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: vi.c,v 1.62 2016/05/09 21:46:56 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * vi.c: Vi mode commands. + */ +#include <sys/wait.h> +#include <ctype.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "el.h" +#include "common.h" +#include "emacs.h" +#include "fcns.h" +#include "vi.h" + +static el_action_t cv_action(EditLine *, wint_t); +static el_action_t cv_paste(EditLine *, wint_t); + +/* cv_action(): + * Handle vi actions. + */ +static el_action_t +cv_action(EditLine *el, wint_t c) +{ + + if (el->el_chared.c_vcmd.action != NOP) { + /* 'cc', 'dd' and (possibly) friends */ + if (c != (wint_t)el->el_chared.c_vcmd.action) + return CC_ERROR; + + if (!(c & YANK)) + cv_undo(el); + cv_yank(el, el->el_line.buffer, + (int)(el->el_line.lastchar - el->el_line.buffer)); + el->el_chared.c_vcmd.action = NOP; + el->el_chared.c_vcmd.pos = 0; + if (!(c & YANK)) { + el->el_line.lastchar = el->el_line.buffer; + el->el_line.cursor = el->el_line.buffer; + } + if (c & INSERT) + el->el_map.current = el->el_map.key; + + return CC_REFRESH; + } + el->el_chared.c_vcmd.pos = el->el_line.cursor; + el->el_chared.c_vcmd.action = c; + return CC_ARGHACK; +} + +/* cv_paste(): + * Paste previous deletion before or after the cursor + */ +static el_action_t +cv_paste(EditLine *el, wint_t c) +{ + c_kill_t *k = &el->el_chared.c_kill; + size_t len = (size_t)(k->last - k->buf); + + if (k->buf == NULL || len == 0) + return CC_ERROR; +#ifdef DEBUG_PASTE + (void) fprintf(el->el_errfile, "Paste: \"%.*ls\"\n", (int)len, + k->buf); +#endif + + cv_undo(el); + + if (!c && el->el_line.cursor < el->el_line.lastchar) + el->el_line.cursor++; + + c_insert(el, (int)len); + if (el->el_line.cursor + len > el->el_line.lastchar) + return CC_ERROR; + (void) memcpy(el->el_line.cursor, k->buf, len * + sizeof(*el->el_line.cursor)); + + return CC_REFRESH; +} + + +/* vi_paste_next(): + * Vi paste previous deletion to the right of the cursor + * [p] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_paste_next(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + return cv_paste(el, 0); +} + + +/* vi_paste_prev(): + * Vi paste previous deletion to the left of the cursor + * [P] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_paste_prev(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + return cv_paste(el, 1); +} + + +/* vi_prev_big_word(): + * Vi move to the previous space delimited word + * [B] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_prev_big_word(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + if (el->el_line.cursor == el->el_line.buffer) + return CC_ERROR; + + el->el_line.cursor = cv_prev_word(el->el_line.cursor, + el->el_line.buffer, + el->el_state.argument, + cv__isWord); + + if (el->el_chared.c_vcmd.action != NOP) { + cv_delfini(el); + return CC_REFRESH; + } + return CC_CURSOR; +} + + +/* vi_prev_word(): + * Vi move to the previous word + * [b] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_prev_word(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + if (el->el_line.cursor == el->el_line.buffer) + return CC_ERROR; + + el->el_line.cursor = cv_prev_word(el->el_line.cursor, + el->el_line.buffer, + el->el_state.argument, + cv__isword); + + if (el->el_chared.c_vcmd.action != NOP) { + cv_delfini(el); + return CC_REFRESH; + } + return CC_CURSOR; +} + + +/* vi_next_big_word(): + * Vi move to the next space delimited word + * [W] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_next_big_word(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + if (el->el_line.cursor >= el->el_line.lastchar - 1) + return CC_ERROR; + + el->el_line.cursor = cv_next_word(el, el->el_line.cursor, + el->el_line.lastchar, el->el_state.argument, cv__isWord); + + if (el->el_map.type == MAP_VI) + if (el->el_chared.c_vcmd.action != NOP) { + cv_delfini(el); + return CC_REFRESH; + } + return CC_CURSOR; +} + + +/* vi_next_word(): + * Vi move to the next word + * [w] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_next_word(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + if (el->el_line.cursor >= el->el_line.lastchar - 1) + return CC_ERROR; + + el->el_line.cursor = cv_next_word(el, el->el_line.cursor, + el->el_line.lastchar, el->el_state.argument, cv__isword); + + if (el->el_map.type == MAP_VI) + if (el->el_chared.c_vcmd.action != NOP) { + cv_delfini(el); + return CC_REFRESH; + } + return CC_CURSOR; +} + + +/* vi_change_case(): + * Vi change case of character under the cursor and advance one character + * [~] + */ +libedit_private el_action_t +vi_change_case(EditLine *el, wint_t c) +{ + int i; + + if (el->el_line.cursor >= el->el_line.lastchar) + return CC_ERROR; + cv_undo(el); + for (i = 0; i < el->el_state.argument; i++) { + + c = *el->el_line.cursor; + if (iswupper(c)) + *el->el_line.cursor = towlower(c); + else if (iswlower(c)) + *el->el_line.cursor = towupper(c); + + if (++el->el_line.cursor >= el->el_line.lastchar) { + el->el_line.cursor--; + re_fastaddc(el); + break; + } + re_fastaddc(el); + } + return CC_NORM; +} + + +/* vi_change_meta(): + * Vi change prefix command + * [c] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_change_meta(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + /* + * Delete with insert == change: first we delete and then we leave in + * insert mode. + */ + return cv_action(el, DELETE | INSERT); +} + + +/* vi_insert_at_bol(): + * Vi enter insert mode at the beginning of line + * [I] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_insert_at_bol(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + el->el_line.cursor = el->el_line.buffer; + cv_undo(el); + el->el_map.current = el->el_map.key; + return CC_CURSOR; +} + + +/* vi_replace_char(): + * Vi replace character under the cursor with the next character typed + * [r] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_replace_char(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + if (el->el_line.cursor >= el->el_line.lastchar) + return CC_ERROR; + + el->el_map.current = el->el_map.key; + el->el_state.inputmode = MODE_REPLACE_1; + cv_undo(el); + return CC_ARGHACK; +} + + +/* vi_replace_mode(): + * Vi enter replace mode + * [R] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_replace_mode(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + el->el_map.current = el->el_map.key; + el->el_state.inputmode = MODE_REPLACE; + cv_undo(el); + return CC_NORM; +} + + +/* vi_substitute_char(): + * Vi replace character under the cursor and enter insert mode + * [s] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_substitute_char(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + c_delafter(el, el->el_state.argument); + el->el_map.current = el->el_map.key; + return CC_REFRESH; +} + + +/* vi_substitute_line(): + * Vi substitute entire line + * [S] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_substitute_line(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + cv_undo(el); + cv_yank(el, el->el_line.buffer, + (int)(el->el_line.lastchar - el->el_line.buffer)); + (void) em_kill_line(el, 0); + el->el_map.current = el->el_map.key; + return CC_REFRESH; +} + + +/* vi_change_to_eol(): + * Vi change to end of line + * [C] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_change_to_eol(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + cv_undo(el); + cv_yank(el, el->el_line.cursor, + (int)(el->el_line.lastchar - el->el_line.cursor)); + (void) ed_kill_line(el, 0); + el->el_map.current = el->el_map.key; + return CC_REFRESH; +} + + +/* vi_insert(): + * Vi enter insert mode + * [i] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_insert(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + el->el_map.current = el->el_map.key; + cv_undo(el); + return CC_NORM; +} + + +/* vi_add(): + * Vi enter insert mode after the cursor + * [a] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_add(EditLine *el, wint_t c __attribute__((__unused__))) +{ + int ret; + + el->el_map.current = el->el_map.key; + if (el->el_line.cursor < el->el_line.lastchar) { + el->el_line.cursor++; + if (el->el_line.cursor > el->el_line.lastchar) + el->el_line.cursor = el->el_line.lastchar; + ret = CC_CURSOR; + } else + ret = CC_NORM; + + cv_undo(el); + + return (el_action_t)ret; +} + + +/* vi_add_at_eol(): + * Vi enter insert mode at end of line + * [A] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_add_at_eol(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + el->el_map.current = el->el_map.key; + el->el_line.cursor = el->el_line.lastchar; + cv_undo(el); + return CC_CURSOR; +} + + +/* vi_delete_meta(): + * Vi delete prefix command + * [d] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_delete_meta(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + return cv_action(el, DELETE); +} + + +/* vi_end_big_word(): + * Vi move to the end of the current space delimited word + * [E] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_end_big_word(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + if (el->el_line.cursor == el->el_line.lastchar) + return CC_ERROR; + + el->el_line.cursor = cv__endword(el->el_line.cursor, + el->el_line.lastchar, el->el_state.argument, cv__isWord); + + if (el->el_chared.c_vcmd.action != NOP) { + el->el_line.cursor++; + cv_delfini(el); + return CC_REFRESH; + } + return CC_CURSOR; +} + + +/* vi_end_word(): + * Vi move to the end of the current word + * [e] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_end_word(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + if (el->el_line.cursor == el->el_line.lastchar) + return CC_ERROR; + + el->el_line.cursor = cv__endword(el->el_line.cursor, + el->el_line.lastchar, el->el_state.argument, cv__isword); + + if (el->el_chared.c_vcmd.action != NOP) { + el->el_line.cursor++; + cv_delfini(el); + return CC_REFRESH; + } + return CC_CURSOR; +} + + +/* vi_undo(): + * Vi undo last change + * [u] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_undo(EditLine *el, wint_t c __attribute__((__unused__))) +{ + c_undo_t un = el->el_chared.c_undo; + + if (un.len == -1) + return CC_ERROR; + + /* switch line buffer and undo buffer */ + el->el_chared.c_undo.buf = el->el_line.buffer; + el->el_chared.c_undo.len = el->el_line.lastchar - el->el_line.buffer; + el->el_chared.c_undo.cursor = + (int)(el->el_line.cursor - el->el_line.buffer); + el->el_line.limit = un.buf + (el->el_line.limit - el->el_line.buffer); + el->el_line.buffer = un.buf; + el->el_line.cursor = un.buf + un.cursor; + el->el_line.lastchar = un.buf + un.len; + + return CC_REFRESH; +} + + +/* vi_command_mode(): + * Vi enter command mode (use alternative key bindings) + * [<ESC>] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_command_mode(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + /* [Esc] cancels pending action */ + el->el_chared.c_vcmd.action = NOP; + el->el_chared.c_vcmd.pos = 0; + + el->el_state.doingarg = 0; + + el->el_state.inputmode = MODE_INSERT; + el->el_map.current = el->el_map.alt; +#ifdef VI_MOVE + if (el->el_line.cursor > el->el_line.buffer) + el->el_line.cursor--; +#endif + return CC_CURSOR; +} + + +/* vi_zero(): + * Vi move to the beginning of line + * [0] + */ +libedit_private el_action_t +vi_zero(EditLine *el, wint_t c) +{ + + if (el->el_state.doingarg) + return ed_argument_digit(el, c); + + el->el_line.cursor = el->el_line.buffer; + if (el->el_chared.c_vcmd.action != NOP) { + cv_delfini(el); + return CC_REFRESH; + } + return CC_CURSOR; +} + + +/* vi_delete_prev_char(): + * Vi move to previous character (backspace) + * [^H] in insert mode only + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_delete_prev_char(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + if (el->el_line.cursor <= el->el_line.buffer) + return CC_ERROR; + + c_delbefore1(el); + el->el_line.cursor--; + return CC_REFRESH; +} + + +/* vi_list_or_eof(): + * Vi list choices for completion or indicate end of file if empty line + * [^D] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_list_or_eof(EditLine *el, wint_t c) +{ + + if (el->el_line.cursor == el->el_line.lastchar) { + if (el->el_line.cursor == el->el_line.buffer) { + terminal_writec(el, c); /* then do a EOF */ + return CC_EOF; + } else { + /* + * Here we could list completions, but it is an + * error right now + */ + terminal_beep(el); + return CC_ERROR; + } + } else { +#ifdef notyet + re_goto_bottom(el); + *el->el_line.lastchar = '\0'; /* just in case */ + return CC_LIST_CHOICES; +#else + /* + * Just complain for now. + */ + terminal_beep(el); + return CC_ERROR; +#endif + } +} + + +/* vi_kill_line_prev(): + * Vi cut from beginning of line to cursor + * [^U] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_kill_line_prev(EditLine *el, wint_t c __attribute__((__unused__))) +{ + wchar_t *kp, *cp; + + cp = el->el_line.buffer; + kp = el->el_chared.c_kill.buf; + while (cp < el->el_line.cursor) + *kp++ = *cp++; /* copy it */ + el->el_chared.c_kill.last = kp; + c_delbefore(el, (int)(el->el_line.cursor - el->el_line.buffer)); + el->el_line.cursor = el->el_line.buffer; /* zap! */ + return CC_REFRESH; +} + + +/* vi_search_prev(): + * Vi search history previous + * [?] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_search_prev(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + return cv_search(el, ED_SEARCH_PREV_HISTORY); +} + + +/* vi_search_next(): + * Vi search history next + * [/] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_search_next(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + return cv_search(el, ED_SEARCH_NEXT_HISTORY); +} + + +/* vi_repeat_search_next(): + * Vi repeat current search in the same search direction + * [n] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_repeat_search_next(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + if (el->el_search.patlen == 0) + return CC_ERROR; + else + return cv_repeat_srch(el, el->el_search.patdir); +} + + +/* vi_repeat_search_prev(): + * Vi repeat current search in the opposite search direction + * [N] + */ +/*ARGSUSED*/ +libedit_private el_action_t +vi_repeat_search_prev(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + if (el->el_search.patlen == 0) + return CC_ERROR; + else + return (cv_repeat_srch(el, + el->el_search.patdir == ED_SEARCH_PREV_HISTORY ? + ED_SEARCH_NEXT_HISTORY : ED_SEARCH_PREV_HISTORY)); +} + + +/* vi_next_char(): + * Vi move to the character specified next + * [f] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_next_char(EditLine *el, wint_t c __attribute__((__unused__))) +{ + return cv_csearch(el, CHAR_FWD, -1, el->el_state.argument, 0); +} + + +/* vi_prev_char(): + * Vi move to the character specified previous + * [F] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_prev_char(EditLine *el, wint_t c __attribute__((__unused__))) +{ + return cv_csearch(el, CHAR_BACK, -1, el->el_state.argument, 0); +} + + +/* vi_to_next_char(): + * Vi move up to the character specified next + * [t] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_to_next_char(EditLine *el, wint_t c __attribute__((__unused__))) +{ + return cv_csearch(el, CHAR_FWD, -1, el->el_state.argument, 1); +} + + +/* vi_to_prev_char(): + * Vi move up to the character specified previous + * [T] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_to_prev_char(EditLine *el, wint_t c __attribute__((__unused__))) +{ + return cv_csearch(el, CHAR_BACK, -1, el->el_state.argument, 1); +} + + +/* vi_repeat_next_char(): + * Vi repeat current character search in the same search direction + * [;] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_repeat_next_char(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + return cv_csearch(el, el->el_search.chadir, el->el_search.chacha, + el->el_state.argument, el->el_search.chatflg); +} + + +/* vi_repeat_prev_char(): + * Vi repeat current character search in the opposite search direction + * [,] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_repeat_prev_char(EditLine *el, wint_t c __attribute__((__unused__))) +{ + el_action_t r; + int dir = el->el_search.chadir; + + r = cv_csearch(el, -dir, el->el_search.chacha, + el->el_state.argument, el->el_search.chatflg); + el->el_search.chadir = dir; + return r; +} + + +/* vi_match(): + * Vi go to matching () {} or [] + * [%] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_match(EditLine *el, wint_t c __attribute__((__unused__))) +{ + const wchar_t match_chars[] = L"()[]{}"; + wchar_t *cp; + size_t delta, i, count; + wchar_t o_ch, c_ch; + + *el->el_line.lastchar = '\0'; /* just in case */ + + i = wcscspn(el->el_line.cursor, match_chars); + o_ch = el->el_line.cursor[i]; + if (o_ch == 0) + return CC_ERROR; + delta = (size_t)(wcschr(match_chars, o_ch) - match_chars); + c_ch = match_chars[delta ^ 1]; + count = 1; + delta = 1 - (delta & 1) * 2; + + for (cp = &el->el_line.cursor[i]; count; ) { + cp += delta; + if (cp < el->el_line.buffer || cp >= el->el_line.lastchar) + return CC_ERROR; + if (*cp == o_ch) + count++; + else if (*cp == c_ch) + count--; + } + + el->el_line.cursor = cp; + + if (el->el_chared.c_vcmd.action != NOP) { + /* NB posix says char under cursor should NOT be deleted + for -ve delta - this is different to netbsd vi. */ + if (delta > 0) + el->el_line.cursor++; + cv_delfini(el); + return CC_REFRESH; + } + return CC_CURSOR; +} + +/* vi_undo_line(): + * Vi undo all changes to line + * [U] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_undo_line(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + cv_undo(el); + return hist_get(el); +} + +/* vi_to_column(): + * Vi go to specified column + * [|] + * NB netbsd vi goes to screen column 'n', posix says nth character + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_to_column(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + el->el_line.cursor = el->el_line.buffer; + el->el_state.argument--; + return ed_next_char(el, 0); +} + +/* vi_yank_end(): + * Vi yank to end of line + * [Y] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_yank_end(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + cv_yank(el, el->el_line.cursor, + (int)(el->el_line.lastchar - el->el_line.cursor)); + return CC_REFRESH; +} + +/* vi_yank(): + * Vi yank + * [y] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_yank(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + return cv_action(el, YANK); +} + +/* vi_comment_out(): + * Vi comment out current command + * [#] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_comment_out(EditLine *el, wint_t c __attribute__((__unused__))) +{ + + el->el_line.cursor = el->el_line.buffer; + c_insert(el, 1); + *el->el_line.cursor = '#'; + re_refresh(el); + return ed_newline(el, 0); +} + +/* vi_alias(): + * Vi include shell alias + * [@] + * NB: posix implies that we should enter insert mode, however + * this is against historical precedent... + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_alias(EditLine *el, wint_t c __attribute__((__unused__))) +{ + char alias_name[3]; + const char *alias_text; + + if (el->el_chared.c_aliasfun == NULL) + return CC_ERROR; + + alias_name[0] = '_'; + alias_name[2] = 0; + if (el_getc(el, &alias_name[1]) != 1) + return CC_ERROR; + + alias_text = (*el->el_chared.c_aliasfun)(el->el_chared.c_aliasarg, + alias_name); + if (alias_text != NULL) + el_wpush(el, ct_decode_string(alias_text, &el->el_scratch)); + return CC_NORM; +} + +/* vi_to_history_line(): + * Vi go to specified history file line. + * [G] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_to_history_line(EditLine *el, wint_t c __attribute__((__unused__))) +{ + int sv_event_no = el->el_history.eventno; + el_action_t rval; + + + if (el->el_history.eventno == 0) { + (void) wcsncpy(el->el_history.buf, el->el_line.buffer, + EL_BUFSIZ); + el->el_history.last = el->el_history.buf + + (el->el_line.lastchar - el->el_line.buffer); + } + + /* Lack of a 'count' means oldest, not 1 */ + if (!el->el_state.doingarg) { + el->el_history.eventno = 0x7fffffff; + hist_get(el); + } else { + /* This is brain dead, all the rest of this code counts + * upwards going into the past. Here we need count in the + * other direction (to match the output of fc -l). + * I could change the world, but this seems to suffice. + */ + el->el_history.eventno = 1; + if (hist_get(el) == CC_ERROR) + return CC_ERROR; + el->el_history.eventno = 1 + el->el_history.ev.num + - el->el_state.argument; + if (el->el_history.eventno < 0) { + el->el_history.eventno = sv_event_no; + return CC_ERROR; + } + } + rval = hist_get(el); + if (rval == CC_ERROR) + el->el_history.eventno = sv_event_no; + return rval; +} + +/* vi_histedit(): + * Vi edit history line with vi + * [v] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_histedit(EditLine *el, wint_t c __attribute__((__unused__))) +{ + int fd; + pid_t pid; + ssize_t st; + int status; + char tempfile[] = "/tmp/histedit.XXXXXXXXXX"; + char *cp = NULL; + size_t len; + wchar_t *line = NULL; + + if (el->el_state.doingarg) { + if (vi_to_history_line(el, 0) == CC_ERROR) + return CC_ERROR; + } + + fd = mkstemp(tempfile); + if (fd < 0) + return CC_ERROR; + len = (size_t)(el->el_line.lastchar - el->el_line.buffer); +#define TMP_BUFSIZ (EL_BUFSIZ * MB_LEN_MAX) + cp = el_malloc(TMP_BUFSIZ * sizeof(*cp)); + if (cp == NULL) + goto error; + line = el_malloc(len * sizeof(*line) + 1); + if (line == NULL) + goto error; + wcsncpy(line, el->el_line.buffer, len); + line[len] = '\0'; + wcstombs(cp, line, TMP_BUFSIZ - 1); + cp[TMP_BUFSIZ - 1] = '\0'; + len = strlen(cp); + write(fd, cp, len); + write(fd, "\n", (size_t)1); + pid = fork(); + switch (pid) { + case -1: + goto error; + case 0: + close(fd); + execlp("vi", "vi", tempfile, (char *)NULL); + exit(0); + /*NOTREACHED*/ + default: + while (waitpid(pid, &status, 0) != pid) + continue; + lseek(fd, (off_t)0, SEEK_SET); + st = read(fd, cp, TMP_BUFSIZ - 1); + if (st > 0) { + cp[st] = '\0'; + len = (size_t)(el->el_line.limit - el->el_line.buffer); + len = mbstowcs(el->el_line.buffer, cp, len); + if (len > 0 && el->el_line.buffer[len - 1] == '\n') + --len; + } + else + len = 0; + el->el_line.cursor = el->el_line.buffer; + el->el_line.lastchar = el->el_line.buffer + len; + el_free(cp); + el_free(line); + break; + } + + close(fd); + unlink(tempfile); + /* return CC_REFRESH; */ + return ed_newline(el, 0); +error: + el_free(line); + el_free(cp); + close(fd); + unlink(tempfile); + return CC_ERROR; +} + +/* vi_history_word(): + * Vi append word from previous input line + * [_] + * Who knows where this one came from! + * '_' in vi means 'entire current line', so 'cc' is a synonym for 'c_' + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_history_word(EditLine *el, wint_t c __attribute__((__unused__))) +{ + const wchar_t *wp = HIST_FIRST(el); + const wchar_t *wep, *wsp; + int len; + wchar_t *cp; + const wchar_t *lim; + + if (wp == NULL) + return CC_ERROR; + + wep = wsp = NULL; + do { + while (iswspace(*wp)) + wp++; + if (*wp == 0) + break; + wsp = wp; + while (*wp && !iswspace(*wp)) + wp++; + wep = wp; + } while ((!el->el_state.doingarg || --el->el_state.argument > 0) + && *wp != 0); + + if (wsp == NULL || (el->el_state.doingarg && el->el_state.argument != 0)) + return CC_ERROR; + + cv_undo(el); + len = (int)(wep - wsp); + if (el->el_line.cursor < el->el_line.lastchar) + el->el_line.cursor++; + c_insert(el, len + 1); + cp = el->el_line.cursor; + lim = el->el_line.limit; + if (cp < lim) + *cp++ = ' '; + while (wsp < wep && cp < lim) + *cp++ = *wsp++; + el->el_line.cursor = cp; + + el->el_map.current = el->el_map.key; + return CC_REFRESH; +} + +/* vi_redo(): + * Vi redo last non-motion command + * [.] + */ +libedit_private el_action_t +/*ARGSUSED*/ +vi_redo(EditLine *el, wint_t c __attribute__((__unused__))) +{ + c_redo_t *r = &el->el_chared.c_redo; + + if (!el->el_state.doingarg && r->count) { + el->el_state.doingarg = 1; + el->el_state.argument = r->count; + } + + el->el_chared.c_vcmd.pos = el->el_line.cursor; + el->el_chared.c_vcmd.action = r->action; + if (r->pos != r->buf) { + if (r->pos + 1 > r->lim) + /* sanity */ + r->pos = r->lim - 1; + r->pos[0] = 0; + el_wpush(el, r->buf); + } + + el->el_state.thiscmd = r->cmd; + el->el_state.thisch = r->ch; + return (*el->el_map.func[r->cmd])(el, r->ch); +} diff --git a/bin/catsh/mail.c b/bin/catsh/mail.c new file mode 100644 index 00000000..6f8a3d39 --- /dev/null +++ b/bin/catsh/mail.c @@ -0,0 +1,120 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)mail.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/mail.c 336303 2018-07-15 09:14:30Z jilles $"); + +/* + * Routines to check for mail. (Perhaps make part of main.c?) + */ + +#include "shell.h" +#include "mail.h" +#include "var.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> + + +#define MAXMBOXES 10 + + +static int nmboxes; /* number of mailboxes */ +static time_t mailtime[MAXMBOXES]; /* times of mailboxes */ + + + +/* + * Print appropriate message(s) if mail has arrived. If the argument is + * nozero, then the value of MAIL has changed, so we just update the + * values. + */ + +void +chkmail(int silent) +{ + int i; + char *mpath; + char *p; + char *msg; + struct stackmark smark; + struct stat statb; + + if (silent) + nmboxes = 10; + if (nmboxes == 0) + return; + setstackmark(&smark); + mpath = stsavestr(mpathset()? mpathval() : mailval()); + for (i = 0 ; i < nmboxes ; i++) { + p = mpath; + if (*p == '\0') + break; + mpath = strchr(p, ':'); + if (!mpath) mpath = strchr(p, '\0'); + if (*mpath != '\0') { + *mpath++ = '\0'; + if (p == mpath - 1) + continue; + } + msg = strchr(p, '%'); + if (msg != NULL) + *msg++ = '\0'; +#ifdef notdef /* this is what the System V shell claims to do (it lies) */ + if (stat(p, &statb) < 0) + statb.st_mtime = 0; + if (statb.st_mtime > mailtime[i] && ! silent) { + out2str(msg? msg : "you have mail"); + out2c('\n'); + } + mailtime[i] = statb.st_mtime; +#else /* this is what it should do */ + if (stat(p, &statb) < 0) + statb.st_size = 0; + if (statb.st_size > mailtime[i] && ! silent) { + out2str(msg? msg : "you have mail"); + out2c('\n'); + } + mailtime[i] = statb.st_size; +#endif + } + nmboxes = i; + popstackmark(&smark); +} diff --git a/bin/catsh/mail.h b/bin/catsh/mail.h new file mode 100644 index 00000000..adeced2d --- /dev/null +++ b/bin/catsh/mail.h @@ -0,0 +1,38 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)mail.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/mail.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +void chkmail(int); diff --git a/bin/catsh/main.c b/bin/catsh/main.c new file mode 100644 index 00000000..c3b28173 --- /dev/null +++ b/bin/catsh/main.c @@ -0,0 +1,346 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)main.c 8.6 (Berkeley) 5/28/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/main.c 336320 2018-07-15 21:55:17Z jilles $"); + +#include <stdio.h> +#include <signal.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <locale.h> +#include <errno.h> + +#include "shell.h" +#include "main.h" +#include "mail.h" +#include "options.h" +#include "output.h" +#include "parser.h" +#include "nodes.h" +#include "expand.h" +#include "eval.h" +#include "jobs.h" +#include "input.h" +#include "trap.h" +#include "var.h" +#include "show.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "exec.h" +#include "cd.h" +#include "redir.h" +#include "builtins.h" + +int rootpid; +int rootshell; +struct jmploc main_handler; +int localeisutf8, initial_localeisutf8; + +static void reset(void); +static void cmdloop(int); +static void read_profile(const char *); +static char *find_dot_file(char *); + +/* + * Main routine. We initialize things, parse the arguments, execute + * profiles if we're a login shell, and then call cmdloop to execute + * commands. The setjmp call sets up the location to jump to when an + * exception occurs. When an exception occurs the variable "state" + * is used to figure out how far we had gotten. + */ + +int +main(int argc, char *argv[]) +{ + struct stackmark smark, smark2; + volatile int state; + char *shinit; + + (void) setlocale(LC_ALL, ""); + initcharset(); + state = 0; + if (setjmp(main_handler.loc)) { + switch (exception) { + case EXEXEC: + exitstatus = exerrno; + break; + + case EXERROR: + exitstatus = 2; + break; + + default: + break; + } + + if (state == 0 || iflag == 0 || ! rootshell || + exception == EXEXIT) + exitshell(exitstatus); + reset(); + if (exception == EXINT) + out2fmt_flush("\n"); + popstackmark(&smark); + FORCEINTON; /* enable interrupts */ + if (state == 1) + goto state1; + else if (state == 2) + goto state2; + else if (state == 3) + goto state3; + else + goto state4; + } + handler = &main_handler; +#ifdef DEBUG + opentrace(); + trputs("Shell args: "); trargs(argv); +#endif + rootpid = getpid(); + rootshell = 1; + INTOFF; + initvar(); + setstackmark(&smark); + setstackmark(&smark2); + procargs(argc, argv); + pwd_init(iflag); + INTON; + if (iflag) + chkmail(1); + if (argv[0] && argv[0][0] == '-') { + state = 1; + read_profile("/etc/profile"); +state1: + state = 2; + if (privileged == 0) + read_profile("${HOME-}/.profile"); + else + read_profile("/etc/suid_profile"); + } +state2: + state = 3; + if (!privileged && iflag) { + if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') { + state = 3; + read_profile(shinit); + } + } +state3: + state = 4; + popstackmark(&smark2); + if (minusc) { + evalstring(minusc, sflag ? 0 : EV_EXIT); + } +state4: + if (sflag || minusc == NULL) { + cmdloop(1); + } + exitshell(exitstatus); + /*NOTREACHED*/ + return 0; +} + +static void +reset(void) +{ + reseteval(); + resetinput(); +} + +/* + * Read and execute commands. "Top" is nonzero for the top level command + * loop; it turns on prompting if the shell is interactive. + */ + +static void +cmdloop(int top) +{ + union node *n; + struct stackmark smark; + int inter; + int numeof = 0; + + TRACE(("cmdloop(%d) called\n", top)); + setstackmark(&smark); + for (;;) { + if (pendingsig) + dotrap(); + inter = 0; + if (iflag && top) { + inter++; + showjobs(1, SHOWJOBS_DEFAULT); + chkmail(0); + flushout(&output); + } + n = parsecmd(inter); + /* showtree(n); DEBUG */ + if (n == NEOF) { + if (!top || numeof >= 50) + break; + if (!stoppedjobs()) { + if (!Iflag) + break; + out2fmt_flush("\nUse \"exit\" to leave shell.\n"); + } + numeof++; + } else if (n != NULL && nflag == 0) { + job_warning = (job_warning == 2) ? 1 : 0; + numeof = 0; + evaltree(n, 0); + } + popstackmark(&smark); + setstackmark(&smark); + if (evalskip != 0) { + if (evalskip == SKIPRETURN) + evalskip = 0; + break; + } + } + popstackmark(&smark); +} + + + +/* + * Read /etc/profile or .profile. Return on error. + */ + +static void +read_profile(const char *name) +{ + int fd; + const char *expandedname; + + expandedname = expandstr(name); + if (expandedname == NULL) + return; + INTOFF; + if ((fd = open(expandedname, O_RDONLY | O_CLOEXEC)) >= 0) + setinputfd(fd, 1); + INTON; + if (fd < 0) + return; + cmdloop(0); + popfile(); +} + + + +/* + * Read a file containing shell functions. + */ + +void +readcmdfile(const char *name) +{ + setinputfile(name, 1); + cmdloop(0); + popfile(); +} + + + +/* + * Take commands from a file. To be compatible we should do a path + * search for the file, which is necessary to find sub-commands. + */ + + +static char * +find_dot_file(char *basename) +{ + char *fullname; + const char *opt; + const char *path = pathval(); + struct stat statb; + + /* don't try this for absolute or relative paths */ + if( strchr(basename, '/')) + return basename; + + while ((fullname = padvance(&path, &opt, basename)) != NULL) { + if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { + /* + * Don't bother freeing here, since it will + * be freed by the caller. + */ + return fullname; + } + stunalloc(fullname); + } + return basename; +} + +int +dotcmd(int argc, char **argv) +{ + char *filename, *fullname; + + if (argc < 2) + error("missing filename"); + + exitstatus = 0; + + /* + * Because we have historically not supported any options, + * only treat "--" specially. + */ + filename = argc > 2 && strcmp(argv[1], "--") == 0 ? argv[2] : argv[1]; + + fullname = find_dot_file(filename); + setinputfile(fullname, 1); + commandname = fullname; + cmdloop(0); + popfile(); + return exitstatus; +} + + +int +exitcmd(int argc, char **argv) +{ + if (stoppedjobs()) + return 0; + if (argc > 1) + exitshell(number(argv[1])); + else + exitshell_savedstatus(); +} diff --git a/bin/catsh/main.h b/bin/catsh/main.h new file mode 100644 index 00000000..f37fd99e --- /dev/null +++ b/bin/catsh/main.h @@ -0,0 +1,42 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)main.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/main.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +extern int rootpid; /* pid of main shell */ +extern int rootshell; /* true if we aren't a child of the main shell */ +extern struct jmploc main_handler; /* top level exception handler */ + +void readcmdfile(const char *); diff --git a/bin/catsh/memalloc.c b/bin/catsh/memalloc.c new file mode 100644 index 00000000..e43ce4cd --- /dev/null +++ b/bin/catsh/memalloc.c @@ -0,0 +1,344 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)memalloc.c 8.3 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/memalloc.c 326025 2017-11-20 19:49:47Z pfg $"); + +#include <sys/param.h> +#include "shell.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "expand.h" +#include <stdlib.h> +#include <unistd.h> + +/* + * Like malloc, but returns an error when out of space. + */ + +pointer +ckmalloc(size_t nbytes) +{ + pointer p; + + INTOFF; + p = malloc(nbytes); + INTON; + if (p == NULL) + error("Out of space"); + return p; +} + + +/* + * Same for realloc. + */ + +pointer +ckrealloc(pointer p, int nbytes) +{ + INTOFF; + p = realloc(p, nbytes); + INTON; + if (p == NULL) + error("Out of space"); + return p; +} + +void +ckfree(pointer p) +{ + INTOFF; + free(p); + INTON; +} + + +/* + * Make a copy of a string in safe storage. + */ + +char * +savestr(const char *s) +{ + char *p; + size_t len; + + len = strlen(s); + p = ckmalloc(len + 1); + memcpy(p, s, len + 1); + return p; +} + + +/* + * Parse trees for commands are allocated in lifo order, so we use a stack + * to make this more efficient, and also to avoid all sorts of exception + * handling code to handle interrupts in the middle of a parse. + * + * The size 496 was chosen because with 16-byte alignment the total size + * for the allocated block is 512. + */ + +#define MINSIZE 496 /* minimum size of a block. */ + + +struct stack_block { + struct stack_block *prev; + /* Data follows */ +}; +#define SPACE(sp) ((char*)(sp) + ALIGN(sizeof(struct stack_block))) + +static struct stack_block *stackp; +char *stacknxt; +int stacknleft; +char *sstrend; + + +static void +stnewblock(int nbytes) +{ + struct stack_block *sp; + int allocsize; + + if (nbytes < MINSIZE) + nbytes = MINSIZE; + + allocsize = ALIGN(sizeof(struct stack_block)) + ALIGN(nbytes); + + INTOFF; + sp = ckmalloc(allocsize); + sp->prev = stackp; + stacknxt = SPACE(sp); + stacknleft = allocsize - (stacknxt - (char*)sp); + sstrend = stacknxt + stacknleft; + stackp = sp; + INTON; +} + + +pointer +stalloc(int nbytes) +{ + char *p; + + nbytes = ALIGN(nbytes); + if (nbytes > stacknleft) + stnewblock(nbytes); + p = stacknxt; + stacknxt += nbytes; + stacknleft -= nbytes; + return p; +} + + +void +stunalloc(pointer p) +{ + if (p == NULL) { /*DEBUG */ + write(STDERR_FILENO, "stunalloc\n", 10); + abort(); + } + stacknleft += stacknxt - (char *)p; + stacknxt = p; +} + + +char * +stsavestr(const char *s) +{ + char *p; + size_t len; + + len = strlen(s); + p = stalloc(len + 1); + memcpy(p, s, len + 1); + return p; +} + + +void +setstackmark(struct stackmark *mark) +{ + mark->stackp = stackp; + mark->stacknxt = stacknxt; + mark->stacknleft = stacknleft; + /* Ensure this block stays in place. */ + if (stackp != NULL && stacknxt == SPACE(stackp)) + stalloc(1); +} + + +void +popstackmark(struct stackmark *mark) +{ + struct stack_block *sp; + + INTOFF; + while (stackp != mark->stackp) { + sp = stackp; + stackp = sp->prev; + ckfree(sp); + } + stacknxt = mark->stacknxt; + stacknleft = mark->stacknleft; + sstrend = stacknxt + stacknleft; + INTON; +} + + +/* + * When the parser reads in a string, it wants to stick the string on the + * stack and only adjust the stack pointer when it knows how big the + * string is. Stackblock (defined in stack.h) returns a pointer to a block + * of space on top of the stack and stackblocklen returns the length of + * this block. Growstackblock will grow this space by at least one byte, + * possibly moving it (like realloc). Grabstackblock actually allocates the + * part of the block that has been used. + */ + +static void +growstackblock(int min) +{ + char *p; + int newlen; + char *oldspace; + int oldlen; + struct stack_block *sp; + struct stack_block *oldstackp; + + if (min < stacknleft) + min = stacknleft; + if ((unsigned int)min >= + INT_MAX / 2 - ALIGN(sizeof(struct stack_block))) + error("Out of space"); + min += stacknleft; + min += ALIGN(sizeof(struct stack_block)); + newlen = 512; + while (newlen < min) + newlen <<= 1; + oldspace = stacknxt; + oldlen = stacknleft; + + if (stackp != NULL && stacknxt == SPACE(stackp)) { + INTOFF; + oldstackp = stackp; + stackp = oldstackp->prev; + sp = ckrealloc((pointer)oldstackp, newlen); + sp->prev = stackp; + stackp = sp; + stacknxt = SPACE(sp); + stacknleft = newlen - (stacknxt - (char*)sp); + sstrend = stacknxt + stacknleft; + INTON; + } else { + newlen -= ALIGN(sizeof(struct stack_block)); + p = stalloc(newlen); + if (oldlen != 0) + memcpy(p, oldspace, oldlen); + stunalloc(p); + } +} + + + +/* + * The following routines are somewhat easier to use that the above. + * The user declares a variable of type STACKSTR, which may be declared + * to be a register. The macro STARTSTACKSTR initializes things. Then + * the user uses the macro STPUTC to add characters to the string. In + * effect, STPUTC(c, p) is the same as *p++ = c except that the stack is + * grown as necessary. When the user is done, she can just leave the + * string there and refer to it using stackblock(). Or she can allocate + * the space for it using grabstackstr(). If it is necessary to allow + * someone else to use the stack temporarily and then continue to grow + * the string, the user should use grabstack to allocate the space, and + * then call ungrabstr(p) to return to the previous mode of operation. + * + * USTPUTC is like STPUTC except that it doesn't check for overflow. + * CHECKSTACKSPACE can be called before USTPUTC to ensure that there + * is space for at least one character. + */ + +static char * +growstrstackblock(int n, int min) +{ + growstackblock(min); + return stackblock() + n; +} + +char * +growstackstr(void) +{ + int len; + + len = stackblocksize(); + return (growstrstackblock(len, 0)); +} + + +/* + * Called from CHECKSTRSPACE. + */ + +char * +makestrspace(int min, char *p) +{ + int len; + + len = p - stackblock(); + return (growstrstackblock(len, min)); +} + + +char * +stputbin(const char *data, size_t len, char *p) +{ + CHECKSTRSPACE(len, p); + memcpy(p, data, len); + return (p + len); +} + +char * +stputs(const char *data, char *p) +{ + return (stputbin(data, strlen(data), p)); +} diff --git a/bin/catsh/memalloc.h b/bin/catsh/memalloc.h new file mode 100644 index 00000000..216275d8 --- /dev/null +++ b/bin/catsh/memalloc.h @@ -0,0 +1,88 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)memalloc.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/memalloc.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +#include <string.h> + +struct stackmark { + struct stack_block *stackp; + char *stacknxt; + int stacknleft; +}; + + +extern char *stacknxt; +extern int stacknleft; +extern char *sstrend; + +pointer ckmalloc(size_t); +pointer ckrealloc(pointer, int); +void ckfree(pointer); +char *savestr(const char *); +pointer stalloc(int); +void stunalloc(pointer); +char *stsavestr(const char *); +void setstackmark(struct stackmark *); +void popstackmark(struct stackmark *); +char *growstackstr(void); +char *makestrspace(int, char *); +char *stputbin(const char *data, size_t len, char *p); +char *stputs(const char *data, char *p); + + + +#define stackblock() stacknxt +#define stackblocksize() stacknleft +#define grabstackblock(n) stalloc(n) +#define STARTSTACKSTR(p) p = stackblock() +#define STPUTC(c, p) do { if (p == sstrend) p = growstackstr(); *p++ = (c); } while(0) +#define CHECKSTRSPACE(n, p) { if ((size_t)(sstrend - p) < n) p = makestrspace(n, p); } +#define USTPUTC(c, p) (*p++ = (c)) +/* + * STACKSTRNUL's use is where we want to be able to turn a stack + * (non-sentinel, character counting string) into a C string, + * and later pretend the NUL is not there. + * Note: Because of STACKSTRNUL's semantics, STACKSTRNUL cannot be used + * on a stack that will grabstackstr()ed. + */ +#define STACKSTRNUL(p) (p == sstrend ? (p = growstackstr(), *p = '\0') : (*p = '\0')) +#define STUNPUTC(p) (--p) +#define STTOPC(p) p[-1] +#define STADJUST(amount, p) (p += (amount)) +#define grabstackstr(p) stalloc((char *)p - stackblock()) +#define ungrabstackstr(s, p) stunalloc((s)) +#define STPUTBIN(s, len, p) p = stputbin((s), (len), p) +#define STPUTS(s, p) p = stputs((s), p) diff --git a/bin/catsh/miscbltin.c b/bin/catsh/miscbltin.c new file mode 100644 index 00000000..7f0c42e7 --- /dev/null +++ b/bin/catsh/miscbltin.c @@ -0,0 +1,534 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)miscbltin.c 8.4 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/miscbltin.c 326025 2017-11-20 19:49:47Z pfg $"); + +/* + * Miscellaneous builtins. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +#include "shell.h" +#include "options.h" +#include "var.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "syntax.h" +#include "trap.h" + +#undef eflag + +int readcmd(int, char **); +int umaskcmd(int, char **); +int ulimitcmd(int, char **); + +/* + * The read builtin. The -r option causes backslashes to be treated like + * ordinary characters. + * + * This uses unbuffered input, which may be avoidable in some cases. + * + * Note that if IFS=' :' then read x y should work so that: + * 'a b' x='a', y='b' + * ' a b ' x='a', y='b' + * ':b' x='', y='b' + * ':' x='', y='' + * '::' x='', y='' + * ': :' x='', y='' + * ':::' x='', y='::' + * ':b c:' x='', y='b c:' + */ + +int +readcmd(int argc __unused, char **argv __unused) +{ + char **ap; + int backslash; + char c; + int rflag; + char *prompt; + const char *ifs; + char *p; + int startword; + int status; + int i; + int is_ifs; + int saveall = 0; + ptrdiff_t lastnonifs, lastnonifsws; + struct timeval tv; + char *tvptr; + fd_set ifds; + ssize_t nread; + int sig; + + rflag = 0; + prompt = NULL; + tv.tv_sec = -1; + tv.tv_usec = 0; + while ((i = nextopt("erp:t:")) != '\0') { + switch(i) { + case 'p': + prompt = shoptarg; + break; + case 'e': + break; + case 'r': + rflag = 1; + break; + case 't': + tv.tv_sec = strtol(shoptarg, &tvptr, 0); + if (tvptr == shoptarg) + error("timeout value"); + switch(*tvptr) { + case 0: + case 's': + break; + case 'h': + tv.tv_sec *= 60; + /* FALLTHROUGH */ + case 'm': + tv.tv_sec *= 60; + break; + default: + error("timeout unit"); + } + break; + } + } + if (prompt && isatty(0)) { + out2str(prompt); + flushall(); + } + if (*(ap = argptr) == NULL) + error("arg count"); + if ((ifs = bltinlookup("IFS", 1)) == NULL) + ifs = " \t\n"; + + if (tv.tv_sec >= 0) { + /* + * Wait for something to become available. + */ + FD_ZERO(&ifds); + FD_SET(0, &ifds); + status = select(1, &ifds, NULL, NULL, &tv); + /* + * If there's nothing ready, return an error. + */ + if (status <= 0) { + sig = pendingsig; + return (128 + (sig != 0 ? sig : SIGALRM)); + } + } + + status = 0; + startword = 2; + backslash = 0; + STARTSTACKSTR(p); + lastnonifs = lastnonifsws = -1; + for (;;) { + nread = read(STDIN_FILENO, &c, 1); + if (nread == -1) { + if (errno == EINTR) { + sig = pendingsig; + if (sig == 0) + continue; + status = 128 + sig; + break; + } + warning("read error: %s", strerror(errno)); + status = 2; + break; + } else if (nread != 1) { + status = 1; + break; + } + if (c == '\0') + continue; + CHECKSTRSPACE(1, p); + if (backslash) { + backslash = 0; + if (c != '\n') { + startword = 0; + lastnonifs = lastnonifsws = p - stackblock(); + USTPUTC(c, p); + } + continue; + } + if (!rflag && c == '\\') { + backslash++; + continue; + } + if (c == '\n') + break; + if (strchr(ifs, c)) + is_ifs = strchr(" \t\n", c) ? 1 : 2; + else + is_ifs = 0; + + if (startword != 0) { + if (is_ifs == 1) { + /* Ignore leading IFS whitespace */ + if (saveall) + USTPUTC(c, p); + continue; + } + if (is_ifs == 2 && startword == 1) { + /* Only one non-whitespace IFS per word */ + startword = 2; + if (saveall) { + lastnonifsws = p - stackblock(); + USTPUTC(c, p); + } + continue; + } + } + + if (is_ifs == 0) { + /* append this character to the current variable */ + startword = 0; + if (saveall) + /* Not just a spare terminator */ + saveall++; + lastnonifs = lastnonifsws = p - stackblock(); + USTPUTC(c, p); + continue; + } + + /* end of variable... */ + startword = is_ifs; + + if (ap[1] == NULL) { + /* Last variable needs all IFS chars */ + saveall++; + if (is_ifs == 2) + lastnonifsws = p - stackblock(); + USTPUTC(c, p); + continue; + } + + STACKSTRNUL(p); + setvar(*ap, stackblock(), 0); + ap++; + STARTSTACKSTR(p); + lastnonifs = lastnonifsws = -1; + } + STACKSTRNUL(p); + + /* + * Remove trailing IFS chars: always remove whitespace, don't remove + * non-whitespace unless it was naked + */ + if (saveall <= 1) + lastnonifsws = lastnonifs; + stackblock()[lastnonifsws + 1] = '\0'; + setvar(*ap, stackblock(), 0); + + /* Set any remaining args to "" */ + while (*++ap != NULL) + setvar(*ap, "", 0); + return status; +} + + + +int +umaskcmd(int argc __unused, char **argv __unused) +{ + char *ap; + int mask; + int i; + int symbolic_mode = 0; + + while ((i = nextopt("S")) != '\0') { + symbolic_mode = 1; + } + + INTOFF; + mask = umask(0); + umask(mask); + INTON; + + if ((ap = *argptr) == NULL) { + if (symbolic_mode) { + char u[4], g[4], o[4]; + + i = 0; + if ((mask & S_IRUSR) == 0) + u[i++] = 'r'; + if ((mask & S_IWUSR) == 0) + u[i++] = 'w'; + if ((mask & S_IXUSR) == 0) + u[i++] = 'x'; + u[i] = '\0'; + + i = 0; + if ((mask & S_IRGRP) == 0) + g[i++] = 'r'; + if ((mask & S_IWGRP) == 0) + g[i++] = 'w'; + if ((mask & S_IXGRP) == 0) + g[i++] = 'x'; + g[i] = '\0'; + + i = 0; + if ((mask & S_IROTH) == 0) + o[i++] = 'r'; + if ((mask & S_IWOTH) == 0) + o[i++] = 'w'; + if ((mask & S_IXOTH) == 0) + o[i++] = 'x'; + o[i] = '\0'; + + out1fmt("u=%s,g=%s,o=%s\n", u, g, o); + } else { + out1fmt("%.4o\n", mask); + } + } else { + if (is_digit(*ap)) { + mask = 0; + do { + if (*ap >= '8' || *ap < '0') + error("Illegal number: %s", *argptr); + mask = (mask << 3) + (*ap - '0'); + } while (*++ap != '\0'); + umask(mask); + } else { + void *set; + INTOFF; + if ((set = setmode (ap)) == NULL) + error("Illegal number: %s", ap); + + mask = getmode (set, ~mask & 0777); + umask(~mask & 0777); + free(set); + INTON; + } + } + return 0; +} + +/* + * ulimit builtin + * + * This code, originally by Doug Gwyn, Doug Kingston, Eric Gisin, and + * Michael Rendell was ripped from pdksh 5.0.8 and hacked for use with + * ash by J.T. Conklin. + * + * Public domain. + */ + +struct limits { + const char *name; + const char *units; + int cmd; + short factor; /* multiply by to get rlim_{cur,max} values */ + char option; +}; + +static const struct limits limits[] = { +#ifdef RLIMIT_CPU + { "cpu time", "seconds", RLIMIT_CPU, 1, 't' }, +#endif +#ifdef RLIMIT_FSIZE + { "file size", "512-blocks", RLIMIT_FSIZE, 512, 'f' }, +#endif +#ifdef RLIMIT_DATA + { "data seg size", "kbytes", RLIMIT_DATA, 1024, 'd' }, +#endif +#ifdef RLIMIT_STACK + { "stack size", "kbytes", RLIMIT_STACK, 1024, 's' }, +#endif +#ifdef RLIMIT_CORE + { "core file size", "512-blocks", RLIMIT_CORE, 512, 'c' }, +#endif +#ifdef RLIMIT_RSS + { "max memory size", "kbytes", RLIMIT_RSS, 1024, 'm' }, +#endif +#ifdef RLIMIT_MEMLOCK + { "locked memory", "kbytes", RLIMIT_MEMLOCK, 1024, 'l' }, +#endif +#ifdef RLIMIT_NPROC + { "max user processes", (char *)0, RLIMIT_NPROC, 1, 'u' }, +#endif +#ifdef RLIMIT_NOFILE + { "open files", (char *)0, RLIMIT_NOFILE, 1, 'n' }, +#endif +#ifdef RLIMIT_VMEM + { "virtual mem size", "kbytes", RLIMIT_VMEM, 1024, 'v' }, +#endif +#ifdef RLIMIT_SWAP + { "swap limit", "kbytes", RLIMIT_SWAP, 1024, 'w' }, +#endif +#ifdef RLIMIT_SBSIZE + { "socket buffer size", "bytes", RLIMIT_SBSIZE, 1, 'b' }, +#endif +#ifdef RLIMIT_NPTS + { "pseudo-terminals", (char *)0, RLIMIT_NPTS, 1, 'p' }, +#endif +#ifdef RLIMIT_KQUEUES + { "kqueues", (char *)0, RLIMIT_KQUEUES, 1, 'k' }, +#endif +#ifdef RLIMIT_UMTXP + { "umtx shared locks", (char *)0, RLIMIT_UMTXP, 1, 'o' }, +#endif + { (char *) 0, (char *)0, 0, 0, '\0' } +}; + +enum limithow { SOFT = 0x1, HARD = 0x2 }; + +static void +printlimit(enum limithow how, const struct rlimit *limit, + const struct limits *l) +{ + rlim_t val = 0; + + if (how & SOFT) + val = limit->rlim_cur; + else if (how & HARD) + val = limit->rlim_max; + if (val == RLIM_INFINITY) + out1str("unlimited\n"); + else + { + val /= l->factor; + out1fmt("%jd\n", (intmax_t)val); + } +} + +int +ulimitcmd(int argc __unused, char **argv __unused) +{ + rlim_t val = 0; + enum limithow how = SOFT | HARD; + const struct limits *l; + int set, all = 0; + int optc, what; + struct rlimit limit; + + what = 'f'; + while ((optc = nextopt("HSatfdsmcnuvlbpwko")) != '\0') + switch (optc) { + case 'H': + how = HARD; + break; + case 'S': + how = SOFT; + break; + case 'a': + all = 1; + break; + default: + what = optc; + } + + for (l = limits; l->name && l->option != what; l++) + ; + if (!l->name) + error("internal error (%c)", what); + + set = *argptr ? 1 : 0; + if (set) { + char *p = *argptr; + + if (all || argptr[1]) + error("too many arguments"); + if (strcmp(p, "unlimited") == 0) + val = RLIM_INFINITY; + else { + char *end; + uintmax_t uval; + + if (*p < '0' || *p > '9') + error("bad number"); + errno = 0; + uval = strtoumax(p, &end, 10); + if (errno != 0 || *end != '\0') + error("bad number"); + if (uval > UINTMAX_MAX / l->factor) + error("bad number"); + uval *= l->factor; + val = (rlim_t)uval; + if (val < 0 || (uintmax_t)val != uval || + val == RLIM_INFINITY) + error("bad number"); + } + } + if (all) { + for (l = limits; l->name; l++) { + char optbuf[40]; + if (getrlimit(l->cmd, &limit) < 0) + error("can't get limit: %s", strerror(errno)); + + if (l->units) + snprintf(optbuf, sizeof(optbuf), + "(%s, -%c) ", l->units, l->option); + else + snprintf(optbuf, sizeof(optbuf), + "(-%c) ", l->option); + out1fmt("%-18s %18s ", l->name, optbuf); + printlimit(how, &limit, l); + } + return 0; + } + + if (getrlimit(l->cmd, &limit) < 0) + error("can't get limit: %s", strerror(errno)); + if (set) { + if (how & SOFT) + limit.rlim_cur = val; + if (how & HARD) + limit.rlim_max = val; + if (setrlimit(l->cmd, &limit) < 0) + error("bad limit: %s", strerror(errno)); + } else + printlimit(how, &limit, l); + return 0; +} diff --git a/bin/catsh/mkbuiltins b/bin/catsh/mkbuiltins new file mode 100755 index 00000000..32c09ec0 --- /dev/null +++ b/bin/catsh/mkbuiltins @@ -0,0 +1,137 @@ +#!/bin/sh - + +#- +# Copyright (c) 1991, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Kenneth Almquist. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# @(#)mkbuiltins 8.2 (Berkeley) 5/4/95 +# $FreeBSD: releng/12.0/bin/sh/mkbuiltins 328934 2018-02-06 15:41:35Z arichardson $ + +temp=`mktemp -t ka` +havehist=1 +if [ "X$1" = "X-h" ]; then + havehist=0 + shift +fi +srcdir=$1 +havejobs=0 +if grep '^#define[ ]*JOBS[ ]*1' $srcdir/shell.h > /dev/null +then havejobs=1 +fi +exec > builtins.c +cat <<\! +/* + * This file was generated by the mkbuiltins program. + */ + +#include <stdlib.h> +#include "shell.h" +#include "builtins.h" + +! +awk '/^[^#]/ {if(('$havejobs' || $2 != "-j") && ('$havehist' || $2 != "-h")) \ + print $0}' $srcdir/builtins.def | sed 's/-[hj]//' > $temp +echo 'int (*const builtinfunc[])(int, char **) = {' +awk '/^[^#]/ { printf "\t%s,\n", $1}' $temp +echo '}; + +const unsigned char builtincmd[] = {' +awk '{ for (i = 2 ; i <= NF ; i++) { + if ($i == "-s") { + spc = 1; + } else if ($i == "-n") { + # Handled later for builtins.h + continue + } else { + printf "\t\"\\%03o\\%03o%s\"\n", length($i), (spc ? 128 : 0) + NR-1, $i + spc = 0; + } + }}' $temp +echo '};' + +exec > builtins.h +cat <<\! +/* + * This file was generated by the mkbuiltins program. + */ + +#include <sys/cdefs.h> +! +tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ < $temp | + awk '{ printf "#define %s %d\n", $1, NR-1}' +echo ' +#define BUILTIN_SPECIAL 0x80 + +extern int (*const builtinfunc[])(int, char **); +extern const unsigned char builtincmd[]; +' +awk '{ printf "int %s(int, char **);\n", $1}' $temp + +# Build safe_builtin_always() +cat <<EOF + +static inline int +safe_builtin_always(int idx) +{ +EOF +awk ' +BEGIN { printed = 0 } +{ + for (i = 2 ; i <= NF ; i++) { + if ($i == "-s") { + continue + } else if ($i == "-n") { + nofork = 1; + } else { + if (nofork == 0) { + continue + } + if (printed == 1) { + printf " || \n\t " + } else { + printf "\tif (" + } + printf "idx == " toupper($1) + printed = 1 + nofork = 0; + # Only need to check each once + break + } + } +}' $temp + +cat << EOF +) + return (1); + return(0); +} +EOF + +rm -f $temp diff --git a/bin/catsh/mknodes.c b/bin/catsh/mknodes.c new file mode 100644 index 00000000..cdc615fc --- /dev/null +++ b/bin/catsh/mknodes.c @@ -0,0 +1,461 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if 0 +#ifndef lint +static char const copyright[] = +"@(#) Copyright (c) 1991, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +static char sccsid[] = "@(#)mknodes.c 8.2 (Berkeley) 5/4/95"; +#endif /* not lint */ +#endif +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/mknodes.c 326025 2017-11-20 19:49:47Z pfg $"); + +/* + * This program reads the nodetypes file and nodes.c.pat file. It generates + * the files nodes.h and nodes.c. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <stdarg.h> + +#define MAXTYPES 50 /* max number of node types */ +#define MAXFIELDS 20 /* max fields in a structure */ +#define BUFLEN 100 /* size of character buffers */ + +/* field types */ +#define T_NODE 1 /* union node *field */ +#define T_NODELIST 2 /* struct nodelist *field */ +#define T_STRING 3 +#define T_INT 4 /* int field */ +#define T_OTHER 5 /* other */ +#define T_TEMP 6 /* don't copy this field */ + + +struct field { /* a structure field */ + char *name; /* name of field */ + int type; /* type of field */ + char *decl; /* declaration of field */ +}; + + +struct str { /* struct representing a node structure */ + char *tag; /* structure tag */ + int nfields; /* number of fields in the structure */ + struct field field[MAXFIELDS]; /* the fields of the structure */ + int done; /* set if fully parsed */ +}; + + +static int ntypes; /* number of node types */ +static char *nodename[MAXTYPES]; /* names of the nodes */ +static struct str *nodestr[MAXTYPES]; /* type of structure used by the node */ +static int nstr; /* number of structures */ +static struct str str[MAXTYPES]; /* the structures */ +static struct str *curstr; /* current structure */ +static char line[1024]; +static int linno; +static char *linep; + +static void parsenode(void); +static void parsefield(void); +static void output(char *); +static void outsizes(FILE *); +static void outfunc(FILE *, int); +static void indent(int, FILE *); +static int nextfield(char *); +static void skipbl(void); +static int readline(FILE *); +static void error(const char *, ...) __printf0like(1, 2) __dead2; +static char *savestr(const char *); + + +int +main(int argc, char *argv[]) +{ + FILE *infp; + + if (argc != 3) + error("usage: mknodes file"); + if ((infp = fopen(argv[1], "r")) == NULL) + error("Can't open %s: %s", argv[1], strerror(errno)); + while (readline(infp)) { + if (line[0] == ' ' || line[0] == '\t') + parsefield(); + else if (line[0] != '\0') + parsenode(); + } + fclose(infp); + output(argv[2]); + exit(0); +} + + + +static void +parsenode(void) +{ + char name[BUFLEN]; + char tag[BUFLEN]; + struct str *sp; + + if (curstr && curstr->nfields > 0) + curstr->done = 1; + nextfield(name); + if (! nextfield(tag)) + error("Tag expected"); + if (*linep != '\0') + error("Garbage at end of line"); + nodename[ntypes] = savestr(name); + for (sp = str ; sp < str + nstr ; sp++) { + if (strcmp(sp->tag, tag) == 0) + break; + } + if (sp >= str + nstr) { + sp->tag = savestr(tag); + sp->nfields = 0; + curstr = sp; + nstr++; + } + nodestr[ntypes] = sp; + ntypes++; +} + + +static void +parsefield(void) +{ + char name[BUFLEN]; + char type[BUFLEN]; + char decl[2 * BUFLEN]; + struct field *fp; + + if (curstr == NULL || curstr->done) + error("No current structure to add field to"); + if (! nextfield(name)) + error("No field name"); + if (! nextfield(type)) + error("No field type"); + fp = &curstr->field[curstr->nfields]; + fp->name = savestr(name); + if (strcmp(type, "nodeptr") == 0) { + fp->type = T_NODE; + sprintf(decl, "union node *%s", name); + } else if (strcmp(type, "nodelist") == 0) { + fp->type = T_NODELIST; + sprintf(decl, "struct nodelist *%s", name); + } else if (strcmp(type, "string") == 0) { + fp->type = T_STRING; + sprintf(decl, "char *%s", name); + } else if (strcmp(type, "int") == 0) { + fp->type = T_INT; + sprintf(decl, "int %s", name); + } else if (strcmp(type, "other") == 0) { + fp->type = T_OTHER; + } else if (strcmp(type, "temp") == 0) { + fp->type = T_TEMP; + } else { + error("Unknown type %s", type); + } + if (fp->type == T_OTHER || fp->type == T_TEMP) { + skipbl(); + fp->decl = savestr(linep); + } else { + if (*linep) + error("Garbage at end of line"); + fp->decl = savestr(decl); + } + curstr->nfields++; +} + + +static const char writer[] = "\ +/*\n\ + * This file was generated by the mknodes program.\n\ + */\n\ +\n"; + +static void +output(char *file) +{ + FILE *hfile; + FILE *cfile; + FILE *patfile; + int i; + struct str *sp; + struct field *fp; + char *p; + + if ((patfile = fopen(file, "r")) == NULL) + error("Can't open %s: %s", file, strerror(errno)); + if ((hfile = fopen("nodes.h", "w")) == NULL) + error("Can't create nodes.h: %s", strerror(errno)); + if ((cfile = fopen("nodes.c", "w")) == NULL) + error("Can't create nodes.c"); + fputs(writer, hfile); + for (i = 0 ; i < ntypes ; i++) + fprintf(hfile, "#define %s %d\n", nodename[i], i); + fputs("\n\n\n", hfile); + for (sp = str ; sp < &str[nstr] ; sp++) { + fprintf(hfile, "struct %s {\n", sp->tag); + for (i = sp->nfields, fp = sp->field ; --i >= 0 ; fp++) { + fprintf(hfile, " %s;\n", fp->decl); + } + fputs("};\n\n\n", hfile); + } + fputs("union node {\n", hfile); + fprintf(hfile, " int type;\n"); + for (sp = str ; sp < &str[nstr] ; sp++) { + fprintf(hfile, " struct %s %s;\n", sp->tag, sp->tag); + } + fputs("};\n\n\n", hfile); + fputs("struct nodelist {\n", hfile); + fputs("\tstruct nodelist *next;\n", hfile); + fputs("\tunion node *n;\n", hfile); + fputs("};\n\n\n", hfile); + fputs("struct funcdef;\n", hfile); + fputs("struct funcdef *copyfunc(union node *);\n", hfile); + fputs("union node *getfuncnode(struct funcdef *);\n", hfile); + fputs("void reffunc(struct funcdef *);\n", hfile); + fputs("void unreffunc(struct funcdef *);\n", hfile); + if (ferror(hfile)) + error("Can't write to nodes.h"); + if (fclose(hfile)) + error("Can't close nodes.h"); + + fputs(writer, cfile); + while (fgets(line, sizeof line, patfile) != NULL) { + for (p = line ; *p == ' ' || *p == '\t' ; p++); + if (strcmp(p, "%SIZES\n") == 0) + outsizes(cfile); + else if (strcmp(p, "%CALCSIZE\n") == 0) + outfunc(cfile, 1); + else if (strcmp(p, "%COPY\n") == 0) + outfunc(cfile, 0); + else + fputs(line, cfile); + } + fclose(patfile); + if (ferror(cfile)) + error("Can't write to nodes.c"); + if (fclose(cfile)) + error("Can't close nodes.c"); +} + + + +static void +outsizes(FILE *cfile) +{ + int i; + + fprintf(cfile, "static const short nodesize[%d] = {\n", ntypes); + for (i = 0 ; i < ntypes ; i++) { + fprintf(cfile, " ALIGN(sizeof (struct %s)),\n", nodestr[i]->tag); + } + fprintf(cfile, "};\n"); +} + + +static void +outfunc(FILE *cfile, int calcsize) +{ + struct str *sp; + struct field *fp; + int i; + + fputs(" if (n == NULL)\n", cfile); + if (calcsize) + fputs(" return;\n", cfile); + else + fputs(" return NULL;\n", cfile); + if (calcsize) + fputs(" result->blocksize += nodesize[n->type];\n", cfile); + else { + fputs(" new = state->block;\n", cfile); + fputs(" state->block = (char *)state->block + nodesize[n->type];\n", cfile); + } + fputs(" switch (n->type) {\n", cfile); + for (sp = str ; sp < &str[nstr] ; sp++) { + for (i = 0 ; i < ntypes ; i++) { + if (nodestr[i] == sp) + fprintf(cfile, " case %s:\n", nodename[i]); + } + for (i = sp->nfields ; --i >= 1 ; ) { + fp = &sp->field[i]; + switch (fp->type) { + case T_NODE: + if (calcsize) { + indent(12, cfile); + fprintf(cfile, "calcsize(n->%s.%s, result);\n", + sp->tag, fp->name); + } else { + indent(12, cfile); + fprintf(cfile, "new->%s.%s = copynode(n->%s.%s, state);\n", + sp->tag, fp->name, sp->tag, fp->name); + } + break; + case T_NODELIST: + if (calcsize) { + indent(12, cfile); + fprintf(cfile, "sizenodelist(n->%s.%s, result);\n", + sp->tag, fp->name); + } else { + indent(12, cfile); + fprintf(cfile, "new->%s.%s = copynodelist(n->%s.%s, state);\n", + sp->tag, fp->name, sp->tag, fp->name); + } + break; + case T_STRING: + if (calcsize) { + indent(12, cfile); + fprintf(cfile, "result->stringsize += strlen(n->%s.%s) + 1;\n", + sp->tag, fp->name); + } else { + indent(12, cfile); + fprintf(cfile, "new->%s.%s = nodesavestr(n->%s.%s, state);\n", + sp->tag, fp->name, sp->tag, fp->name); + } + break; + case T_INT: + case T_OTHER: + if (! calcsize) { + indent(12, cfile); + fprintf(cfile, "new->%s.%s = n->%s.%s;\n", + sp->tag, fp->name, sp->tag, fp->name); + } + break; + } + } + indent(12, cfile); + fputs("break;\n", cfile); + } + fputs(" };\n", cfile); + if (! calcsize) + fputs(" new->type = n->type;\n", cfile); +} + + +static void +indent(int amount, FILE *fp) +{ + while (amount >= 8) { + putc('\t', fp); + amount -= 8; + } + while (--amount >= 0) { + putc(' ', fp); + } +} + + +static int +nextfield(char *buf) +{ + char *p, *q; + + p = linep; + while (*p == ' ' || *p == '\t') + p++; + q = buf; + while (*p != ' ' && *p != '\t' && *p != '\0') + *q++ = *p++; + *q = '\0'; + linep = p; + return (q > buf); +} + + +static void +skipbl(void) +{ + while (*linep == ' ' || *linep == '\t') + linep++; +} + + +static int +readline(FILE *infp) +{ + char *p; + + if (fgets(line, 1024, infp) == NULL) + return 0; + for (p = line ; *p != '#' && *p != '\n' && *p != '\0' ; p++); + while (p > line && (p[-1] == ' ' || p[-1] == '\t')) + p--; + *p = '\0'; + linep = line; + linno++; + if (p - line > BUFLEN) + error("Line too long"); + return 1; +} + + + +static void +error(const char *msg, ...) +{ + va_list va; + va_start(va, msg); + + (void) fprintf(stderr, "line %d: ", linno); + (void) vfprintf(stderr, msg, va); + (void) fputc('\n', stderr); + + va_end(va); + + exit(2); +} + + + +static char * +savestr(const char *s) +{ + char *p; + + if ((p = malloc(strlen(s) + 1)) == NULL) + error("Out of space"); + (void) strcpy(p, s); + return p; +} diff --git a/bin/catsh/mksyntax.c b/bin/catsh/mksyntax.c new file mode 100644 index 00000000..a023da60 --- /dev/null +++ b/bin/catsh/mksyntax.c @@ -0,0 +1,332 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if 0 +#ifndef lint +static char const copyright[] = +"@(#) Copyright (c) 1991, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +static char sccsid[] = "@(#)mksyntax.c 8.2 (Berkeley) 5/4/95"; +#endif /* not lint */ +#endif +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/mksyntax.c 334008 2018-05-21 21:52:48Z jilles $"); + +/* + * This program creates syntax.h and syntax.c. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "parser.h" + + +struct synclass { + const char *name; + const char *comment; +}; + +/* Syntax classes */ +static const struct synclass synclass[] = { + { "CWORD", "character is nothing special" }, + { "CNL", "newline character" }, + { "CQNL", "newline character in quotes" }, + { "CBACK", "a backslash character" }, + { "CSBACK", "a backslash character in single quotes" }, + { "CSQUOTE", "single quote" }, + { "CDQUOTE", "double quote" }, + { "CENDQUOTE", "a terminating quote" }, + { "CBQUOTE", "backwards single quote" }, + { "CVAR", "a dollar sign" }, + { "CENDVAR", "a '}' character" }, + { "CLP", "a left paren in arithmetic" }, + { "CRP", "a right paren in arithmetic" }, + { "CEOF", "end of file" }, + { "CCTL", "like CWORD, except it must be escaped" }, + { "CSPCL", "these terminate a word" }, + { "CIGN", "character should be ignored" }, + { NULL, NULL } +}; + + +/* + * Syntax classes for is_ functions. Warning: if you add new classes + * you may have to change the definition of the is_in_name macro. + */ +static const struct synclass is_entry[] = { + { "ISDIGIT", "a digit" }, + { "ISUPPER", "an upper case letter" }, + { "ISLOWER", "a lower case letter" }, + { "ISUNDER", "an underscore" }, + { "ISSPECL", "the name of a special parameter" }, + { NULL, NULL } +}; + +static const char writer[] = "\ +/*\n\ + * This file was generated by the mksyntax program.\n\ + */\n\ +\n"; + + +static FILE *cfile; +static FILE *hfile; + +static void add_default(void); +static void finish(void); +static void init(const char *); +static void add(const char *, const char *); +static void output_type_macros(void); + +int +main(int argc __unused, char **argv __unused) +{ + int i; + char buf[80]; + int pos; + + /* Create output files */ + if ((cfile = fopen("syntax.c", "w")) == NULL) { + perror("syntax.c"); + exit(2); + } + if ((hfile = fopen("syntax.h", "w")) == NULL) { + perror("syntax.h"); + exit(2); + } + fputs(writer, hfile); + fputs(writer, cfile); + + fputs("#include <sys/cdefs.h>\n", hfile); + fputs("#include <limits.h>\n\n", hfile); + + /* Generate the #define statements in the header file */ + fputs("/* Syntax classes */\n", hfile); + for (i = 0 ; synclass[i].name ; i++) { + sprintf(buf, "#define %s %d", synclass[i].name, i); + fputs(buf, hfile); + for (pos = strlen(buf) ; pos < 32 ; pos = (pos + 8) & ~07) + putc('\t', hfile); + fprintf(hfile, "/* %s */\n", synclass[i].comment); + } + putc('\n', hfile); + fputs("/* Syntax classes for is_ functions */\n", hfile); + for (i = 0 ; is_entry[i].name ; i++) { + sprintf(buf, "#define %s %#o", is_entry[i].name, 1 << i); + fputs(buf, hfile); + for (pos = strlen(buf) ; pos < 32 ; pos = (pos + 8) & ~07) + putc('\t', hfile); + fprintf(hfile, "/* %s */\n", is_entry[i].comment); + } + putc('\n', hfile); + fputs("#define SYNBASE (1 - CHAR_MIN)\n", hfile); + fputs("#define PEOF -SYNBASE\n\n", hfile); + putc('\n', hfile); + fputs("#define BASESYNTAX (basesyntax + SYNBASE)\n", hfile); + fputs("#define DQSYNTAX (dqsyntax + SYNBASE)\n", hfile); + fputs("#define SQSYNTAX (sqsyntax + SYNBASE)\n", hfile); + fputs("#define ARISYNTAX (arisyntax + SYNBASE)\n", hfile); + putc('\n', hfile); + output_type_macros(); /* is_digit, etc. */ + putc('\n', hfile); + + /* Generate the syntax tables. */ + fputs("#include \"parser.h\"\n", cfile); + fputs("#include \"shell.h\"\n", cfile); + fputs("#include \"syntax.h\"\n\n", cfile); + + fputs("/* syntax table used when not in quotes */\n", cfile); + init("basesyntax"); + add_default(); + add("\n", "CNL"); + add("\\", "CBACK"); + add("'", "CSQUOTE"); + add("\"", "CDQUOTE"); + add("`", "CBQUOTE"); + add("$", "CVAR"); + add("}", "CENDVAR"); + add("<>();&| \t", "CSPCL"); + finish(); + + fputs("\n/* syntax table used when in double quotes */\n", cfile); + init("dqsyntax"); + add_default(); + add("\n", "CQNL"); + add("\\", "CBACK"); + add("\"", "CENDQUOTE"); + add("`", "CBQUOTE"); + add("$", "CVAR"); + add("}", "CENDVAR"); + /* ':/' for tilde expansion, '-^]' for [a\-x] pattern ranges */ + add("!*?[]=~:/-^", "CCTL"); + finish(); + + fputs("\n/* syntax table used when in single quotes */\n", cfile); + init("sqsyntax"); + add_default(); + add("\n", "CQNL"); + add("\\", "CSBACK"); + add("'", "CENDQUOTE"); + /* ':/' for tilde expansion, '-^]' for [a\-x] pattern ranges */ + add("!*?[]=~:/-^", "CCTL"); + finish(); + + fputs("\n/* syntax table used when in arithmetic */\n", cfile); + init("arisyntax"); + add_default(); + add("\n", "CQNL"); + add("\\", "CBACK"); + add("`", "CBQUOTE"); + add("\"", "CIGN"); + add("$", "CVAR"); + add("}", "CENDVAR"); + add("(", "CLP"); + add(")", "CRP"); + finish(); + + fputs("\n/* character classification table */\n", cfile); + init("is_type"); + add("0123456789", "ISDIGIT"); + add("abcdefghijklmnopqrstuvwxyz", "ISLOWER"); + add("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "ISUPPER"); + add("_", "ISUNDER"); + add("#?$!-*@", "ISSPECL"); + finish(); + + exit(0); +} + + +/* + * Output the header and declaration of a syntax table. + */ + +static void +init(const char *name) +{ + fprintf(hfile, "extern const char %s[];\n", name); + fprintf(cfile, "const char %s[SYNBASE + CHAR_MAX + 1] = {\n", name); +} + + +static void +add_one(const char *key, const char *type) +{ + fprintf(cfile, "\t[SYNBASE + %s] = %s,\n", key, type); +} + + +/* + * Add default values to the syntax table. + */ + +static void +add_default(void) +{ + add_one("PEOF", "CEOF"); + add_one("CTLESC", "CCTL"); + add_one("CTLVAR", "CCTL"); + add_one("CTLENDVAR", "CCTL"); + add_one("CTLBACKQ", "CCTL"); + add_one("CTLBACKQ + CTLQUOTE", "CCTL"); + add_one("CTLARI", "CCTL"); + add_one("CTLENDARI", "CCTL"); + add_one("CTLQUOTEMARK", "CCTL"); + add_one("CTLQUOTEEND", "CCTL"); +} + + +/* + * Output the footer of a syntax table. + */ + +static void +finish(void) +{ + fputs("};\n", cfile); +} + + +/* + * Add entries to the syntax table. + */ + +static void +add(const char *p, const char *type) +{ + for (; *p; ++p) { + char c = *p; + switch (c) { + case '\t': c = 't'; break; + case '\n': c = 'n'; break; + case '\'': c = '\''; break; + case '\\': c = '\\'; break; + + default: + fprintf(cfile, "\t[SYNBASE + '%c'] = %s,\n", c, type); + continue; + } + fprintf(cfile, "\t[SYNBASE + '\\%c'] = %s,\n", c, type); + } +} + + +/* + * Output character classification macros (e.g. is_digit). If digits are + * contiguous, we can test for them quickly. + */ + +static const char *macro[] = { + "#define is_digit(c)\t((unsigned int)((c) - '0') <= 9)", + "#define is_eof(c)\t((c) == PEOF)", + "#define is_alpha(c)\t((is_type+SYNBASE)[(int)c] & (ISUPPER|ISLOWER))", + "#define is_name(c)\t((is_type+SYNBASE)[(int)c] & (ISUPPER|ISLOWER|ISUNDER))", + "#define is_in_name(c)\t((is_type+SYNBASE)[(int)c] & (ISUPPER|ISLOWER|ISUNDER|ISDIGIT))", + "#define is_special(c)\t((is_type+SYNBASE)[(int)c] & (ISSPECL|ISDIGIT))", + "#define digit_val(c)\t((c) - '0')", + NULL +}; + +static void +output_type_macros(void) +{ + const char **pp; + + for (pp = macro ; *pp ; pp++) + fprintf(hfile, "%s\n", *pp); +} diff --git a/bin/catsh/mktokens b/bin/catsh/mktokens new file mode 100644 index 00000000..da6ae962 --- /dev/null +++ b/bin/catsh/mktokens @@ -0,0 +1,93 @@ +#!/bin/sh - + +#- +# Copyright (c) 1991, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Kenneth Almquist. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# @(#)mktokens 8.1 (Berkeley) 5/31/93 +# $FreeBSD: releng/12.0/bin/sh/mktokens 328934 2018-02-06 15:41:35Z arichardson $ + +# The following is a list of tokens. The second column is nonzero if the +# token marks the end of a list. The third column is the name to print in +# error messages. + +temp=`mktemp -t ka` +cat > $temp <<\! +TEOF 1 end of file +TNL 0 newline +TSEMI 0 ";" +TBACKGND 0 "&" +TAND 0 "&&" +TOR 0 "||" +TPIPE 0 "|" +TLP 0 "(" +TRP 1 ")" +TENDCASE 1 ";;" +TFALLTHRU 1 ";&" +TREDIR 0 redirection +TWORD 0 word +TIF 0 "if" +TTHEN 1 "then" +TELSE 1 "else" +TELIF 1 "elif" +TFI 1 "fi" +TWHILE 0 "while" +TUNTIL 0 "until" +TFOR 0 "for" +TDO 1 "do" +TDONE 1 "done" +TBEGIN 0 "{" +TEND 1 "}" +TCASE 0 "case" +TESAC 1 "esac" +TNOT 0 "!" +! +nl=`wc -l $temp` +exec > token.h +awk '{print "#define " $1 " " NR-1}' $temp +echo ' +/* Array indicating which tokens mark the end of a list */ +static const char tokendlist[] = {' +awk '{print "\t" $2 ","}' $temp +echo '}; + +static const char *const tokname[] = {' +sed -e 's/"/\\"/g' \ + -e 's/[^ ]*[ ][ ]*[^ ]*[ ][ ]*\(.*\)/ "\1",/' \ + $temp +echo '}; +' +sed 's/"//g' $temp | awk ' +/TIF/{print "#define KWDOFFSET " NR-1; print ""; print "const char *const parsekwd[] = {"} +/TIF/,/neverfound/{print " \"" $3 "\","}' +echo ' 0 +};' + +rm $temp diff --git a/bin/catsh/myhistedit.h b/bin/catsh/myhistedit.h new file mode 100644 index 00000000..6eebe521 --- /dev/null +++ b/bin/catsh/myhistedit.h @@ -0,0 +1,46 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)myhistedit.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/myhistedit.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +#include <histedit.h> + +extern History *hist; +extern EditLine *el; +extern int displayhist; + +void histedit(void); +void sethistfile(const char *); +void sethistsize(const char *); +void setpslit(const char *); +void setterm(const char *); + diff --git a/bin/catsh/mystring.c b/bin/catsh/mystring.c new file mode 100644 index 00000000..a96d4fa5 --- /dev/null +++ b/bin/catsh/mystring.c @@ -0,0 +1,100 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)mystring.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/mystring.c 326025 2017-11-20 19:49:47Z pfg $"); + +/* + * String functions. + * + * equal(s1, s2) Return true if strings are equal. + * number(s) Convert a string of digits to an integer. + * is_number(s) Return true if s is a string of digits. + */ + +#include <stdlib.h> +#include "shell.h" +#include "syntax.h" +#include "error.h" +#include "mystring.h" + + +char nullstr[1]; /* zero length string */ + +/* + * equal - #defined in mystring.h + */ + + +/* + * Convert a string of digits to an integer, printing an error message on + * failure. + */ + +int +number(const char *s) +{ + if (! is_number(s)) + error("Illegal number: %s", s); + return atoi(s); +} + + + +/* + * Check for a valid number. This should be elsewhere. + */ + +int +is_number(const char *p) +{ + const char *q; + + if (*p == '\0') + return 0; + while (*p == '0') + p++; + for (q = p; *q != '\0'; q++) + if (! is_digit(*q)) + return 0; + if (q - p > 10 || + (q - p == 10 && memcmp(p, "2147483647", 10) > 0)) + return 0; + return 1; +} diff --git a/bin/catsh/mystring.h b/bin/catsh/mystring.h new file mode 100644 index 00000000..f4fc9af4 --- /dev/null +++ b/bin/catsh/mystring.h @@ -0,0 +1,43 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)mystring.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/mystring.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +#include <string.h> + +int number(const char *); +int is_number(const char *); + +#define equal(s1, s2) (strcmp(s1, s2) == 0) diff --git a/bin/catsh/nodes.c.pat b/bin/catsh/nodes.c.pat new file mode 100644 index 00000000..eeb82737 --- /dev/null +++ b/bin/catsh/nodes.c.pat @@ -0,0 +1,193 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)nodes.c.pat 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/nodes.c.pat 314436 2017-02-28 23:42:47Z imp $ + */ + +#include <sys/param.h> +#include <stdlib.h> +#include <stddef.h> +/* + * Routine for dealing with parsed shell commands. + */ + +#include "shell.h" +#include "nodes.h" +#include "memalloc.h" +#include "mystring.h" + + +struct nodesize { + int blocksize; /* size of structures in function */ + int stringsize; /* size of strings in node */ +}; + +struct nodecopystate { + pointer block; /* block to allocate function from */ + char *string; /* block to allocate strings from */ +}; + +%SIZES + + +static void calcsize(union node *, struct nodesize *); +static void sizenodelist(struct nodelist *, struct nodesize *); +static union node *copynode(union node *, struct nodecopystate *); +static struct nodelist *copynodelist(struct nodelist *, struct nodecopystate *); +static char *nodesavestr(const char *, struct nodecopystate *); + + +struct funcdef { + unsigned int refcount; + union node n; +}; + +/* + * Make a copy of a parse tree. + */ + +struct funcdef * +copyfunc(union node *n) +{ + struct nodesize sz; + struct nodecopystate st; + struct funcdef *fn; + + if (n == NULL) + return NULL; + sz.blocksize = offsetof(struct funcdef, n); + sz.stringsize = 0; + calcsize(n, &sz); + fn = ckmalloc(sz.blocksize + sz.stringsize); + fn->refcount = 1; + st.block = (char *)fn + offsetof(struct funcdef, n); + st.string = (char *)fn + sz.blocksize; + copynode(n, &st); + return fn; +} + + +union node * +getfuncnode(struct funcdef *fn) +{ + return fn == NULL ? NULL : &fn->n; +} + + +static void +calcsize(union node *n, struct nodesize *result) +{ + %CALCSIZE +} + + + +static void +sizenodelist(struct nodelist *lp, struct nodesize *result) +{ + while (lp) { + result->blocksize += ALIGN(sizeof(struct nodelist)); + calcsize(lp->n, result); + lp = lp->next; + } +} + + + +static union node * +copynode(union node *n, struct nodecopystate *state) +{ + union node *new; + + %COPY + return new; +} + + +static struct nodelist * +copynodelist(struct nodelist *lp, struct nodecopystate *state) +{ + struct nodelist *start; + struct nodelist **lpp; + + lpp = &start; + while (lp) { + *lpp = state->block; + state->block = (char *)state->block + + ALIGN(sizeof(struct nodelist)); + (*lpp)->n = copynode(lp->n, state); + lp = lp->next; + lpp = &(*lpp)->next; + } + *lpp = NULL; + return start; +} + + + +static char * +nodesavestr(const char *s, struct nodecopystate *state) +{ + const char *p = s; + char *q = state->string; + char *rtn = state->string; + + while ((*q++ = *p++) != '\0') + continue; + state->string = q; + return rtn; +} + + +void +reffunc(struct funcdef *fn) +{ + if (fn) + fn->refcount++; +} + + +/* + * Decrement the reference count of a function definition, freeing it + * if it falls to 0. + */ + +void +unreffunc(struct funcdef *fn) +{ + if (fn) { + fn->refcount--; + if (fn->refcount > 0) + return; + ckfree(fn); + } +} diff --git a/bin/catsh/nodetypes b/bin/catsh/nodetypes new file mode 100644 index 00000000..0bb29c8e --- /dev/null +++ b/bin/catsh/nodetypes @@ -0,0 +1,145 @@ +#- +# Copyright (c) 1991, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Kenneth Almquist. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# @(#)nodetypes 8.2 (Berkeley) 5/4/95 +# $FreeBSD: releng/12.0/bin/sh/nodetypes 314436 2017-02-28 23:42:47Z imp $ + +# This file describes the nodes used in parse trees. Unindented lines +# contain a node type followed by a structure tag. Subsequent indented +# lines specify the fields of the structure. Several node types can share +# the same structure, in which case the fields of the structure should be +# specified only once. +# +# A field of a structure is described by the name of the field followed +# by a type. The currently implemented types are: +# nodeptr - a pointer to a node +# nodelist - a pointer to a list of nodes +# string - a pointer to a nul terminated string +# int - an integer +# other - any type that can be copied by assignment +# temp - a field that doesn't have to be copied when the node is copied +# The last two types should be followed by the text of a C declaration for +# the field. + +NSEMI nbinary # two commands separated by a semicolon + type int + ch1 nodeptr # the first child + ch2 nodeptr # the second child + +NCMD ncmd # a simple command + type int + args nodeptr # the arguments + redirect nodeptr # list of file redirections + +NPIPE npipe # a pipeline + type int + backgnd int # set to run pipeline in background + cmdlist nodelist # the commands in the pipeline + +NREDIR nredir # redirection (of a compex command) + type int + n nodeptr # the command + redirect nodeptr # list of file redirections + +NBACKGND nredir # run command in background +NSUBSHELL nredir # run command in a subshell + +NAND nbinary # the && operator +NOR nbinary # the || operator + +NIF nif # the if statement. Elif clauses are handled + type int # using multiple if nodes. + test nodeptr # if test + ifpart nodeptr # then ifpart + elsepart nodeptr # else elsepart + +NWHILE nbinary # the while statement. First child is the test +NUNTIL nbinary # the until statement + +NFOR nfor # the for statement + type int + args nodeptr # for var in args + body nodeptr # do body; done + var string # the for variable + +NCASE ncase # a case statement + type int + expr nodeptr # the word to switch on + cases nodeptr # the list of cases (NCLIST nodes) + +NCLIST nclist # a case ending with ;; + type int + next nodeptr # the next case in list + pattern nodeptr # list of patterns for this case + body nodeptr # code to execute for this case + +NCLISTFALLTHRU nclist # a case ending with ;& + +NDEFUN narg # define a function. The "next" field contains + # the body of the function. + +NARG narg # represents a word + type int + next nodeptr # next word in list + text string # the text of the word + backquote nodelist # list of commands in back quotes + +NTO nfile # fd> fname +NFROM nfile # fd< fname +NFROMTO nfile # fd<> fname +NAPPEND nfile # fd>> fname +NCLOBBER nfile # fd>| fname + type int + fd int # file descriptor being redirected + next nodeptr # next redirection in list + fname nodeptr # file name, in a NARG node + expfname temp char *expfname # actual file name + +NTOFD ndup # fd<&dupfd +NFROMFD ndup # fd>&dupfd + type int + fd int # file descriptor being redirected + next nodeptr # next redirection in list + dupfd int # file descriptor to duplicate + vname nodeptr # file name if fd>&$var + + +NHERE nhere # fd<<\! +NXHERE nhere # fd<<! + type int + fd int # file descriptor being redirected + next nodeptr # next redirection in list + doc nodeptr # input to command (NARG node) + expdoc temp const char *expdoc # actual document (for NXHERE) + +NNOT nnot # ! command (actually pipeline) + type int + com nodeptr diff --git a/bin/catsh/options.c b/bin/catsh/options.c new file mode 100644 index 00000000..422c0f19 --- /dev/null +++ b/bin/catsh/options.c @@ -0,0 +1,594 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)options.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/options.c 326025 2017-11-20 19:49:47Z pfg $"); + +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> + +#include "shell.h" +#define DEFINE_OPTIONS +#include "options.h" +#undef DEFINE_OPTIONS +#include "nodes.h" /* for other header files */ +#include "eval.h" +#include "jobs.h" +#include "input.h" +#include "output.h" +#include "trap.h" +#include "var.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "builtins.h" +#ifndef NO_HISTORY +#include "myhistedit.h" +#endif + +char *arg0; /* value of $0 */ +struct shparam shellparam; /* current positional parameters */ +char **argptr; /* argument list for builtin commands */ +char *shoptarg; /* set by nextopt (like getopt) */ +char *nextopt_optptr; /* used by nextopt */ + +char *minusc; /* argument to -c option */ + + +static void options(int); +static void minus_o(char *, int); +static void setoption(int, int); +static void setoptionbyindex(int, int); +static void setparam(int, char **); +static int getopts(char *, char *, char **, char ***, char **); + + +/* + * Process the shell command line arguments. + */ + +void +procargs(int argc, char **argv) +{ + int i; + char *scriptname; + + argptr = argv; + if (argc > 0) + argptr++; + for (i = 0; i < NOPTS; i++) + optval[i] = 2; + privileged = (getuid() != geteuid() || getgid() != getegid()); + options(1); + if (*argptr == NULL && minusc == NULL) + sflag = 1; + if (iflag != 0 && sflag == 1 && isatty(0) && isatty(1)) { + iflag = 1; + if (Eflag == 2) + Eflag = 1; + } + if (mflag == 2) + mflag = iflag; + for (i = 0; i < NOPTS; i++) + if (optval[i] == 2) + optval[i] = 0; + arg0 = argv[0]; + if (sflag == 0 && minusc == NULL) { + scriptname = *argptr++; + setinputfile(scriptname, 0); + commandname = arg0 = scriptname; + } + /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */ + if (argptr && minusc && *argptr) + arg0 = *argptr++; + + shellparam.p = argptr; + shellparam.reset = 1; + /* assert(shellparam.malloc == 0 && shellparam.nparam == 0); */ + while (*argptr) { + shellparam.nparam++; + argptr++; + } + optschanged(); +} + + +void +optschanged(void) +{ + setinteractive(); +#ifndef NO_HISTORY + histedit(); +#endif + setjobctl(mflag); +} + +/* + * Process shell options. The global variable argptr contains a pointer + * to the argument list; we advance it past the options. + * If cmdline is true, process the shell's argv; otherwise, process arguments + * to the set special builtin. + */ + +static void +options(int cmdline) +{ + char *kp, *p; + int val; + int c; + + if (cmdline) + minusc = NULL; + while ((p = *argptr) != NULL) { + argptr++; + if ((c = *p++) == '-') { + val = 1; + /* A "-" or "--" terminates options */ + if (p[0] == '\0') + goto end_options1; + if (p[0] == '-' && p[1] == '\0') + goto end_options2; + /** + * For the benefit of `#!' lines in shell scripts, + * treat a string of '-- *#.*' the same as '--'. + * This is needed so that a script starting with: + * #!/bin/sh -- # -*- perl -*- + * will continue to work after a change is made to + * kern/imgact_shell.c to NOT token-ize the options + * specified on a '#!' line. A bit of a kludge, + * but that trick is recommended in documentation + * for some scripting languages, and we might as + * well continue to support it. + */ + if (p[0] == '-') { + kp = p + 1; + while (*kp == ' ' || *kp == '\t') + kp++; + if (*kp == '#' || *kp == '\0') + goto end_options2; + } + } else if (c == '+') { + val = 0; + } else { + argptr--; + break; + } + while ((c = *p++) != '\0') { + if (c == 'c' && cmdline) { + char *q; + + q = *argptr++; + if (q == NULL || minusc != NULL) + error("Bad -c option"); + minusc = q; + } else if (c == 'o') { + minus_o(*argptr, val); + if (*argptr) + argptr++; + } else + setoption(c, val); + } + } + return; + + /* When processing `set', a single "-" means turn off -x and -v */ +end_options1: + if (!cmdline) { + xflag = vflag = 0; + return; + } + + /* + * When processing `set', a "--" means the remaining arguments + * replace the positional parameters in the active shell. If + * there are no remaining options, then all the positional + * parameters are cleared (equivalent to doing ``shift $#''). + */ +end_options2: + if (!cmdline) { + if (*argptr == NULL) + setparam(0, argptr); + return; + } + + /* + * At this point we are processing options given to 'sh' on a command + * line. If an end-of-options marker ("-" or "--") is followed by an + * arg of "#", then skip over all remaining arguments. Some scripting + * languages (e.g.: perl) document that /bin/sh will implement this + * behavior, and they recommend that users take advantage of it to + * solve certain issues that can come up when writing a perl script. + * Yes, this feature is in /bin/sh to help users write perl scripts. + */ + p = *argptr; + if (p != NULL && p[0] == '#' && p[1] == '\0') { + while (*argptr != NULL) + argptr++; + /* We need to keep the final argument */ + argptr--; + } +} + +static void +minus_o(char *name, int val) +{ + int i; + const unsigned char *on; + size_t len; + + if (name == NULL) { + if (val) { + /* "Pretty" output. */ + out1str("Current option settings\n"); + for (i = 0, on = optname; i < NOPTS; i++, on += *on + 1) + out1fmt("%-16.*s%s\n", *on, on + 1, + optval[i] ? "on" : "off"); + } else { + /* Output suitable for re-input to shell. */ + for (i = 0, on = optname; i < NOPTS; i++, on += *on + 1) + out1fmt("%s %co %.*s%s", + i % 6 == 0 ? "set" : "", + optval[i] ? '-' : '+', + *on, on + 1, + i % 6 == 5 || i == NOPTS - 1 ? "\n" : ""); + } + } else { + len = strlen(name); + for (i = 0, on = optname; i < NOPTS; i++, on += *on + 1) + if (*on == len && memcmp(on + 1, name, len) == 0) { + setoptionbyindex(i, val); + return; + } + error("Illegal option -o %s", name); + } +} + + +static void +setoptionbyindex(int idx, int val) +{ + if (&optval[idx] == &privileged && !val && privileged) { + if (setgid(getgid()) == -1) + error("setgid"); + if (setuid(getuid()) == -1) + error("setuid"); + } + optval[idx] = val; + if (val) { + /* #%$ hack for ksh semantics */ + if (&optval[idx] == &Vflag) + Eflag = 0; + else if (&optval[idx] == &Eflag) + Vflag = 0; + } +} + +static void +setoption(int flag, int val) +{ + int i; + + for (i = 0; i < NSHORTOPTS; i++) + if (optletter[i] == flag) { + setoptionbyindex(i, val); + return; + } + error("Illegal option -%c", flag); +} + + +/* + * Set the shell parameters. + */ + +static void +setparam(int argc, char **argv) +{ + char **newparam; + char **ap; + + ap = newparam = ckmalloc((argc + 1) * sizeof *ap); + while (*argv) { + *ap++ = savestr(*argv++); + } + *ap = NULL; + freeparam(&shellparam); + shellparam.malloc = 1; + shellparam.nparam = argc; + shellparam.p = newparam; + shellparam.optp = NULL; + shellparam.reset = 1; + shellparam.optnext = NULL; +} + + +/* + * Free the list of positional parameters. + */ + +void +freeparam(struct shparam *param) +{ + char **ap; + + if (param->malloc) { + for (ap = param->p ; *ap ; ap++) + ckfree(*ap); + ckfree(param->p); + } + if (param->optp) { + for (ap = param->optp ; *ap ; ap++) + ckfree(*ap); + ckfree(param->optp); + } +} + + + +/* + * The shift builtin command. + */ + +int +shiftcmd(int argc, char **argv) +{ + int i, n; + + n = 1; + if (argc > 1) + n = number(argv[1]); + if (n > shellparam.nparam) + return 1; + INTOFF; + shellparam.nparam -= n; + if (shellparam.malloc) + for (i = 0; i < n; i++) + ckfree(shellparam.p[i]); + memmove(shellparam.p, shellparam.p + n, + (shellparam.nparam + 1) * sizeof(shellparam.p[0])); + shellparam.reset = 1; + INTON; + return 0; +} + + + +/* + * The set builtin command. + */ + +int +setcmd(int argc, char **argv) +{ + if (argc == 1) + return showvarscmd(argc, argv); + INTOFF; + options(0); + optschanged(); + if (*argptr != NULL) { + setparam(argc - (argptr - argv), argptr); + } + INTON; + return 0; +} + + +void +getoptsreset(const char *value) +{ + while (*value == '0') + value++; + if (strcmp(value, "1") == 0) + shellparam.reset = 1; +} + +/* + * The getopts builtin. Shellparam.optnext points to the next argument + * to be processed. Shellparam.optptr points to the next character to + * be processed in the current argument. If shellparam.optnext is NULL, + * then it's the first time getopts has been called. + */ + +int +getoptscmd(int argc, char **argv) +{ + char **optbase = NULL, **ap; + int i; + + if (argc < 3) + error("usage: getopts optstring var [arg]"); + + if (shellparam.reset == 1) { + INTOFF; + if (shellparam.optp) { + for (ap = shellparam.optp ; *ap ; ap++) + ckfree(*ap); + ckfree(shellparam.optp); + shellparam.optp = NULL; + } + if (argc > 3) { + shellparam.optp = ckmalloc((argc - 2) * sizeof *ap); + memset(shellparam.optp, '\0', (argc - 2) * sizeof *ap); + for (i = 0; i < argc - 3; i++) + shellparam.optp[i] = savestr(argv[i + 3]); + } + INTON; + optbase = argc == 3 ? shellparam.p : shellparam.optp; + shellparam.optnext = optbase; + shellparam.optptr = NULL; + shellparam.reset = 0; + } else + optbase = shellparam.optp ? shellparam.optp : shellparam.p; + + return getopts(argv[1], argv[2], optbase, &shellparam.optnext, + &shellparam.optptr); +} + +static int +getopts(char *optstr, char *optvar, char **optfirst, char ***optnext, + char **optptr) +{ + char *p, *q; + char c = '?'; + int done = 0; + int ind = 0; + int err = 0; + char s[10]; + const char *newoptarg = NULL; + + if ((p = *optptr) == NULL || *p == '\0') { + /* Current word is done, advance */ + if (*optnext == NULL) + return 1; + p = **optnext; + if (p == NULL || *p != '-' || *++p == '\0') { +atend: + ind = *optnext - optfirst + 1; + *optnext = NULL; + p = NULL; + done = 1; + goto out; + } + (*optnext)++; + if (p[0] == '-' && p[1] == '\0') /* check for "--" */ + goto atend; + } + + c = *p++; + for (q = optstr; *q != c; ) { + if (*q == '\0') { + if (optstr[0] == ':') { + s[0] = c; + s[1] = '\0'; + newoptarg = s; + } + else + out2fmt_flush("Illegal option -%c\n", c); + c = '?'; + goto out; + } + if (*++q == ':') + q++; + } + + if (*++q == ':') { + if (*p == '\0' && (p = **optnext) == NULL) { + if (optstr[0] == ':') { + s[0] = c; + s[1] = '\0'; + newoptarg = s; + c = ':'; + } + else { + out2fmt_flush("No arg for -%c option\n", c); + c = '?'; + } + goto out; + } + + if (p == **optnext) + (*optnext)++; + newoptarg = p; + p = NULL; + } + +out: + if (*optnext != NULL) + ind = *optnext - optfirst + 1; + *optptr = p; + if (newoptarg != NULL) + err |= setvarsafe("OPTARG", newoptarg, 0); + else { + INTOFF; + err |= unsetvar("OPTARG"); + INTON; + } + fmtstr(s, sizeof(s), "%d", ind); + err |= setvarsafe("OPTIND", s, VNOFUNC); + s[0] = c; + s[1] = '\0'; + err |= setvarsafe(optvar, s, 0); + if (err) { + *optnext = NULL; + *optptr = NULL; + flushall(); + exraise(EXERROR); + } + return done; +} + +/* + * Standard option processing (a la getopt) for builtin routines. The + * only argument that is passed to nextopt is the option string; the + * other arguments are unnecessary. It returns the option, or '\0' on + * end of input. + */ + +int +nextopt(const char *optstring) +{ + char *p; + const char *q; + char c; + + if ((p = nextopt_optptr) == NULL || *p == '\0') { + p = *argptr; + if (p == NULL || *p != '-' || *++p == '\0') + return '\0'; + argptr++; + if (p[0] == '-' && p[1] == '\0') /* check for "--" */ + return '\0'; + } + c = *p++; + for (q = optstring ; *q != c ; ) { + if (*q == '\0') + error("Illegal option -%c", c); + if (*++q == ':') + q++; + } + if (*++q == ':') { + if (*p == '\0' && (p = *argptr++) == NULL) + error("No arg for -%c option", c); + shoptarg = p; + p = NULL; + } + nextopt_optptr = p; + return c; +} diff --git a/bin/catsh/options.h b/bin/catsh/options.h new file mode 100644 index 00000000..1242b095 --- /dev/null +++ b/bin/catsh/options.h @@ -0,0 +1,115 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)options.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/options.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +struct shparam { + int nparam; /* # of positional parameters (without $0) */ + unsigned char malloc; /* if parameter list dynamically allocated */ + unsigned char reset; /* if getopts has been reset */ + char **p; /* parameter list */ + char **optp; /* parameter list for getopts */ + char **optnext; /* next parameter to be processed by getopts */ + char *optptr; /* used by getopts */ +}; + + + +#define eflag optval[0] +#define fflag optval[1] +#define Iflag optval[2] +#define iflag optval[3] +#define mflag optval[4] +#define nflag optval[5] +#define sflag optval[6] +#define xflag optval[7] +#define vflag optval[8] +#define Vflag optval[9] +#define Eflag optval[10] +#define Cflag optval[11] +#define aflag optval[12] +#define bflag optval[13] +#define uflag optval[14] +#define privileged optval[15] +#define Tflag optval[16] +#define Pflag optval[17] +#define hflag optval[18] +#define nologflag optval[19] + +#define NSHORTOPTS 19 +#define NOPTS 20 + +extern char optval[NOPTS]; +extern const char optletter[NSHORTOPTS]; +#ifdef DEFINE_OPTIONS +char optval[NOPTS]; +const char optletter[NSHORTOPTS] = "efIimnsxvVECabupTPh"; +static const unsigned char optname[] = + "\007errexit" + "\006noglob" + "\011ignoreeof" + "\013interactive" + "\007monitor" + "\006noexec" + "\005stdin" + "\006xtrace" + "\007verbose" + "\002vi" + "\005emacs" + "\011noclobber" + "\011allexport" + "\006notify" + "\007nounset" + "\012privileged" + "\012trapsasync" + "\010physical" + "\010trackall" + "\005nolog" +; +#endif + + +extern char *minusc; /* argument to -c option */ +extern char *arg0; /* $0 */ +extern struct shparam shellparam; /* $@ */ +extern char **argptr; /* argument list for builtin commands */ +extern char *shoptarg; /* set by nextopt */ +extern char *nextopt_optptr; /* used by nextopt */ + +void procargs(int, char **); +void optschanged(void); +void freeparam(struct shparam *); +int nextopt(const char *); +void getoptsreset(const char *); diff --git a/bin/catsh/output.c b/bin/catsh/output.c new file mode 100644 index 00000000..1eb8b692 --- /dev/null +++ b/bin/catsh/output.c @@ -0,0 +1,370 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)output.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/output.c 326025 2017-11-20 19:49:47Z pfg $"); + +/* + * Shell output routines. We use our own output routines because: + * When a builtin command is interrupted we have to discard + * any pending output. + * When a builtin command appears in back quotes, we want to + * save the output of the command in a region obtained + * via malloc, rather than doing a fork and reading the + * output of the command via a pipe. + */ + +#include <stdio.h> /* defines BUFSIZ */ +#include <string.h> +#include <stdarg.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <wchar.h> +#include <wctype.h> + +#include "shell.h" +#include "syntax.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "var.h" + + +#define OUTBUFSIZ BUFSIZ +#define MEM_OUT -2 /* output to dynamically allocated memory */ +#define OUTPUT_ERR 01 /* error occurred on output */ + +static int doformat_wr(void *, const char *, int); + +struct output output = {NULL, NULL, NULL, OUTBUFSIZ, 1, 0}; +struct output errout = {NULL, NULL, NULL, 256, 2, 0}; +struct output memout = {NULL, NULL, NULL, 64, MEM_OUT, 0}; +struct output *out1 = &output; +struct output *out2 = &errout; + +void +outcslow(int c, struct output *file) +{ + outc(c, file); +} + +void +out1str(const char *p) +{ + outstr(p, out1); +} + +void +out1qstr(const char *p) +{ + outqstr(p, out1); +} + +void +out2str(const char *p) +{ + outstr(p, out2); +} + +void +out2qstr(const char *p) +{ + outqstr(p, out2); +} + +void +outstr(const char *p, struct output *file) +{ + outbin(p, strlen(p), file); +} + +static void +byteseq(int ch, struct output *file) +{ + char seq[4]; + + seq[0] = '\\'; + seq[1] = (ch >> 6 & 0x3) + '0'; + seq[2] = (ch >> 3 & 0x7) + '0'; + seq[3] = (ch & 0x7) + '0'; + outbin(seq, 4, file); +} + +static void +outdqstr(const char *p, struct output *file) +{ + const char *end; + mbstate_t mbs; + size_t clen; + wchar_t wc; + + memset(&mbs, '\0', sizeof(mbs)); + end = p + strlen(p); + outstr("$'", file); + while ((clen = mbrtowc(&wc, p, end - p + 1, &mbs)) != 0) { + if (clen == (size_t)-2) { + while (p < end) + byteseq(*p++, file); + break; + } + if (clen == (size_t)-1) { + memset(&mbs, '\0', sizeof(mbs)); + byteseq(*p++, file); + continue; + } + if (wc == L'\n') + outcslow('\n', file), p++; + else if (wc == L'\r') + outstr("\\r", file), p++; + else if (wc == L'\t') + outstr("\\t", file), p++; + else if (!iswprint(wc)) { + for (; clen > 0; clen--) + byteseq(*p++, file); + } else { + if (wc == L'\'' || wc == L'\\') + outcslow('\\', file); + outbin(p, clen, file); + p += clen; + } + } + outcslow('\'', file); +} + +/* Like outstr(), but quote for re-input into the shell. */ +void +outqstr(const char *p, struct output *file) +{ + int i; + + if (p[0] == '\0') { + outstr("''", file); + return; + } + for (i = 0; p[i] != '\0'; i++) { + if ((p[i] > '\0' && p[i] < ' ' && p[i] != '\n') || + (p[i] & 0x80) != 0 || p[i] == '\'') { + outdqstr(p, file); + return; + } + } + + if (p[strcspn(p, "|&;<>()$`\\\" \n*?[~#=")] == '\0' || + strcmp(p, "[") == 0) { + outstr(p, file); + return; + } + + outcslow('\'', file); + outstr(p, file); + outcslow('\'', file); +} + +void +outbin(const void *data, size_t len, struct output *file) +{ + const char *p; + + p = data; + while (len-- > 0) + outc(*p++, file); +} + +void +emptyoutbuf(struct output *dest) +{ + int offset, newsize; + + if (dest->buf == NULL) { + INTOFF; + dest->buf = ckmalloc(dest->bufsize); + dest->nextc = dest->buf; + dest->bufend = dest->buf + dest->bufsize; + INTON; + } else if (dest->fd == MEM_OUT) { + offset = dest->nextc - dest->buf; + newsize = dest->bufsize << 1; + INTOFF; + dest->buf = ckrealloc(dest->buf, newsize); + dest->bufsize = newsize; + dest->bufend = dest->buf + newsize; + dest->nextc = dest->buf + offset; + INTON; + } else { + flushout(dest); + } +} + + +void +flushall(void) +{ + flushout(&output); + flushout(&errout); +} + + +void +flushout(struct output *dest) +{ + + if (dest->buf == NULL || dest->nextc == dest->buf || dest->fd < 0) + return; + if (xwrite(dest->fd, dest->buf, dest->nextc - dest->buf) < 0) + dest->flags |= OUTPUT_ERR; + dest->nextc = dest->buf; +} + + +void +freestdout(void) +{ + output.nextc = output.buf; +} + + +int +outiserror(struct output *file) +{ + return (file->flags & OUTPUT_ERR); +} + + +void +outclearerror(struct output *file) +{ + file->flags &= ~OUTPUT_ERR; +} + + +void +outfmt(struct output *file, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + doformat(file, fmt, ap); + va_end(ap); +} + + +void +out1fmt(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + doformat(out1, fmt, ap); + va_end(ap); +} + +void +out2fmt_flush(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + doformat(out2, fmt, ap); + va_end(ap); + flushout(out2); +} + +void +fmtstr(char *outbuf, int length, const char *fmt, ...) +{ + va_list ap; + + INTOFF; + va_start(ap, fmt); + vsnprintf(outbuf, length, fmt, ap); + va_end(ap); + INTON; +} + +static int +doformat_wr(void *cookie, const char *buf, int len) +{ + struct output *o; + + o = (struct output *)cookie; + outbin(buf, len, o); + + return (len); +} + +void +doformat(struct output *dest, const char *f, va_list ap) +{ + FILE *fp; + + if ((fp = fwopen(dest, doformat_wr)) != NULL) { + vfprintf(fp, f, ap); + fclose(fp); + } +} + +/* + * Version of write which resumes after a signal is caught. + */ + +int +xwrite(int fd, const char *buf, int nbytes) +{ + int ntry; + int i; + int n; + + n = nbytes; + ntry = 0; + for (;;) { + i = write(fd, buf, n); + if (i > 0) { + if ((n -= i) <= 0) + return nbytes; + buf += i; + ntry = 0; + } else if (i == 0) { + if (++ntry > 10) + return nbytes - n; + } else if (errno != EINTR) { + return -1; + } + } +} diff --git a/bin/catsh/output.h b/bin/catsh/output.h new file mode 100644 index 00000000..511c7078 --- /dev/null +++ b/bin/catsh/output.h @@ -0,0 +1,85 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)output.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/output.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +#ifndef OUTPUT_INCL + +#include <stdarg.h> +#include <stddef.h> + +struct output { + char *nextc; + char *bufend; + char *buf; + int bufsize; + short fd; + short flags; +}; + +extern struct output output; /* to fd 1 */ +extern struct output errout; /* to fd 2 */ +extern struct output memout; +extern struct output *out1; /* &memout if backquote, otherwise &output */ +extern struct output *out2; /* &memout if backquote with 2>&1, otherwise + &errout */ + +void outcslow(int, struct output *); +void out1str(const char *); +void out1qstr(const char *); +void out2str(const char *); +void out2qstr(const char *); +void outstr(const char *, struct output *); +void outqstr(const char *, struct output *); +void outbin(const void *, size_t, struct output *); +void emptyoutbuf(struct output *); +void flushall(void); +void flushout(struct output *); +void freestdout(void); +int outiserror(struct output *); +void outclearerror(struct output *); +void outfmt(struct output *, const char *, ...) __printflike(2, 3); +void out1fmt(const char *, ...) __printflike(1, 2); +void out2fmt_flush(const char *, ...) __printflike(1, 2); +void fmtstr(char *, int, const char *, ...) __printflike(3, 4); +void doformat(struct output *, const char *, va_list) __printflike(2, 0); +int xwrite(int, const char *, int); + +#define outc(c, file) ((file)->nextc == (file)->bufend ? (emptyoutbuf(file), *(file)->nextc++ = (c)) : (*(file)->nextc++ = (c))) +#define out1c(c) outc(c, out1); +#define out2c(c) outcslow(c, out2); + +#define OUTPUT_INCL +#endif diff --git a/bin/catsh/parser.c b/bin/catsh/parser.c new file mode 100644 index 00000000..377ab78f --- /dev/null +++ b/bin/catsh/parser.c @@ -0,0 +1,2170 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)parser.c 8.7 (Berkeley) 5/16/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/parser.c 334008 2018-05-21 21:52:48Z jilles $"); + +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> + +#include "shell.h" +#include "parser.h" +#include "nodes.h" +#include "expand.h" /* defines rmescapes() */ +#include "syntax.h" +#include "options.h" +#include "input.h" +#include "output.h" +#include "var.h" +#include "error.h" +#include "memalloc.h" +#include "mystring.h" +#include "alias.h" +#include "show.h" +#include "eval.h" +#include "exec.h" /* to check for special builtins */ +#ifndef NO_HISTORY +#include "myhistedit.h" +#endif + +/* + * Shell command parser. + */ + +#define PROMPTLEN 128 + +/* values of checkkwd variable */ +#define CHKALIAS 0x1 +#define CHKKWD 0x2 +#define CHKNL 0x4 + +/* values returned by readtoken */ +#include "token.h" + + + +struct heredoc { + struct heredoc *next; /* next here document in list */ + union node *here; /* redirection node */ + char *eofmark; /* string indicating end of input */ + int striptabs; /* if set, strip leading tabs */ +}; + +struct parser_temp { + struct parser_temp *next; + void *data; +}; + + +static struct heredoc *heredoclist; /* list of here documents to read */ +static int doprompt; /* if set, prompt the user */ +static int needprompt; /* true if interactive and at start of line */ +static int lasttoken; /* last token read */ +static int tokpushback; /* last token pushed back */ +static char *wordtext; /* text of last word returned by readtoken */ +static int checkkwd; +static struct nodelist *backquotelist; +static union node *redirnode; +static struct heredoc *heredoc; +static int quoteflag; /* set if (part of) last token was quoted */ +static int startlinno; /* line # where last token started */ +static int funclinno; /* line # where the current function started */ +static struct parser_temp *parser_temp; + +#define NOEOFMARK ((const char *)&heredoclist) + + +static union node *list(int); +static union node *andor(void); +static union node *pipeline(void); +static union node *command(void); +static union node *simplecmd(union node **, union node *); +static union node *makename(void); +static union node *makebinary(int type, union node *n1, union node *n2); +static void parsefname(void); +static void parseheredoc(void); +static int peektoken(void); +static int readtoken(void); +static int xxreadtoken(void); +static int readtoken1(int, const char *, const char *, int); +static int noexpand(char *); +static void consumetoken(int); +static void synexpect(int) __dead2; +static void synerror(const char *) __dead2; +static void setprompt(int); +static char *expandprompt(const char *); +static int pgetc_linecont(void); + + +static void * +parser_temp_alloc(size_t len) +{ + struct parser_temp *t; + + INTOFF; + t = ckmalloc(sizeof(*t)); + t->data = NULL; + t->next = parser_temp; + parser_temp = t; + t->data = ckmalloc(len); + INTON; + return t->data; +} + + +static void * +parser_temp_realloc(void *ptr, size_t len) +{ + struct parser_temp *t; + + INTOFF; + t = parser_temp; + if (ptr != t->data) + error("bug: parser_temp_realloc misused"); + t->data = ckrealloc(t->data, len); + INTON; + return t->data; +} + + +static void +parser_temp_free_upto(void *ptr) +{ + struct parser_temp *t; + int done = 0; + + INTOFF; + while (parser_temp != NULL && !done) { + t = parser_temp; + parser_temp = t->next; + done = t->data == ptr; + ckfree(t->data); + ckfree(t); + } + INTON; + if (!done) + error("bug: parser_temp_free_upto misused"); +} + + +static void +parser_temp_free_all(void) +{ + struct parser_temp *t; + + INTOFF; + while (parser_temp != NULL) { + t = parser_temp; + parser_temp = t->next; + ckfree(t->data); + ckfree(t); + } + INTON; +} + + +/* + * Read and parse a command. Returns NEOF on end of file. (NULL is a + * valid parse tree indicating a blank line.) + */ + +union node * +parsecmd(int interact) +{ + int t; + + /* This assumes the parser is not re-entered, + * which could happen if we add command substitution on PS1/PS2. + */ + parser_temp_free_all(); + heredoclist = NULL; + + tokpushback = 0; + checkkwd = 0; + doprompt = interact; + if (doprompt) + setprompt(1); + else + setprompt(0); + needprompt = 0; + t = readtoken(); + if (t == TEOF) + return NEOF; + if (t == TNL) + return NULL; + tokpushback++; + return list(1); +} + + +/* + * Read and parse words for wordexp. + * Returns a list of NARG nodes; NULL if there are no words. + */ +union node * +parsewordexp(void) +{ + union node *n, *first = NULL, **pnext; + int t; + + /* This assumes the parser is not re-entered, + * which could happen if we add command substitution on PS1/PS2. + */ + parser_temp_free_all(); + heredoclist = NULL; + + tokpushback = 0; + checkkwd = 0; + doprompt = 0; + setprompt(0); + needprompt = 0; + pnext = &first; + while ((t = readtoken()) != TEOF) { + if (t != TWORD) + synexpect(TWORD); + n = makename(); + *pnext = n; + pnext = &n->narg.next; + } + return first; +} + + +static union node * +list(int nlflag) +{ + union node *ntop, *n1, *n2, *n3; + int tok; + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (!nlflag && tokendlist[peektoken()]) + return NULL; + ntop = n1 = NULL; + for (;;) { + n2 = andor(); + tok = readtoken(); + if (tok == TBACKGND) { + if (n2 != NULL && n2->type == NPIPE) { + n2->npipe.backgnd = 1; + } else if (n2 != NULL && n2->type == NREDIR) { + n2->type = NBACKGND; + } else { + n3 = (union node *)stalloc(sizeof (struct nredir)); + n3->type = NBACKGND; + n3->nredir.n = n2; + n3->nredir.redirect = NULL; + n2 = n3; + } + } + if (ntop == NULL) + ntop = n2; + else if (n1 == NULL) { + n1 = makebinary(NSEMI, ntop, n2); + ntop = n1; + } + else { + n3 = makebinary(NSEMI, n1->nbinary.ch2, n2); + n1->nbinary.ch2 = n3; + n1 = n3; + } + switch (tok) { + case TBACKGND: + case TSEMI: + tok = readtoken(); + /* FALLTHROUGH */ + case TNL: + if (tok == TNL) { + parseheredoc(); + if (nlflag) + return ntop; + } else if (tok == TEOF && nlflag) { + parseheredoc(); + return ntop; + } else { + tokpushback++; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (!nlflag && tokendlist[peektoken()]) + return ntop; + break; + case TEOF: + if (heredoclist) + parseheredoc(); + else + pungetc(); /* push back EOF on input */ + return ntop; + default: + if (nlflag) + synexpect(-1); + tokpushback++; + return ntop; + } + } +} + + + +static union node * +andor(void) +{ + union node *n; + int t; + + n = pipeline(); + for (;;) { + if ((t = readtoken()) == TAND) { + t = NAND; + } else if (t == TOR) { + t = NOR; + } else { + tokpushback++; + return n; + } + n = makebinary(t, n, pipeline()); + } +} + + + +static union node * +pipeline(void) +{ + union node *n1, *n2, *pipenode; + struct nodelist *lp, *prev; + int negate, t; + + negate = 0; + checkkwd = CHKNL | CHKKWD | CHKALIAS; + TRACE(("pipeline: entered\n")); + while (readtoken() == TNOT) + negate = !negate; + tokpushback++; + n1 = command(); + if (readtoken() == TPIPE) { + pipenode = (union node *)stalloc(sizeof (struct npipe)); + pipenode->type = NPIPE; + pipenode->npipe.backgnd = 0; + lp = (struct nodelist *)stalloc(sizeof (struct nodelist)); + pipenode->npipe.cmdlist = lp; + lp->n = n1; + do { + prev = lp; + lp = (struct nodelist *)stalloc(sizeof (struct nodelist)); + checkkwd = CHKNL | CHKKWD | CHKALIAS; + t = readtoken(); + tokpushback++; + if (t == TNOT) + lp->n = pipeline(); + else + lp->n = command(); + prev->next = lp; + } while (readtoken() == TPIPE); + lp->next = NULL; + n1 = pipenode; + } + tokpushback++; + if (negate) { + n2 = (union node *)stalloc(sizeof (struct nnot)); + n2->type = NNOT; + n2->nnot.com = n1; + return n2; + } else + return n1; +} + + + +static union node * +command(void) +{ + union node *n1, *n2; + union node *ap, **app; + union node *cp, **cpp; + union node *redir, **rpp; + int t; + int is_subshell; + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + is_subshell = 0; + redir = NULL; + n1 = NULL; + rpp = &redir; + + /* Check for redirection which may precede command */ + while (readtoken() == TREDIR) { + *rpp = n2 = redirnode; + rpp = &n2->nfile.next; + parsefname(); + } + tokpushback++; + + switch (readtoken()) { + case TIF: + n1 = (union node *)stalloc(sizeof (struct nif)); + n1->type = NIF; + if ((n1->nif.test = list(0)) == NULL) + synexpect(-1); + consumetoken(TTHEN); + n1->nif.ifpart = list(0); + n2 = n1; + while (readtoken() == TELIF) { + n2->nif.elsepart = (union node *)stalloc(sizeof (struct nif)); + n2 = n2->nif.elsepart; + n2->type = NIF; + if ((n2->nif.test = list(0)) == NULL) + synexpect(-1); + consumetoken(TTHEN); + n2->nif.ifpart = list(0); + } + if (lasttoken == TELSE) + n2->nif.elsepart = list(0); + else { + n2->nif.elsepart = NULL; + tokpushback++; + } + consumetoken(TFI); + checkkwd = CHKKWD | CHKALIAS; + break; + case TWHILE: + case TUNTIL: + t = lasttoken; + if ((n1 = list(0)) == NULL) + synexpect(-1); + consumetoken(TDO); + n1 = makebinary((t == TWHILE)? NWHILE : NUNTIL, n1, list(0)); + consumetoken(TDONE); + checkkwd = CHKKWD | CHKALIAS; + break; + case TFOR: + if (readtoken() != TWORD || quoteflag || ! goodname(wordtext)) + synerror("Bad for loop variable"); + n1 = (union node *)stalloc(sizeof (struct nfor)); + n1->type = NFOR; + n1->nfor.var = wordtext; + while (readtoken() == TNL) + ; + if (lasttoken == TWORD && ! quoteflag && equal(wordtext, "in")) { + app = ≈ + while (readtoken() == TWORD) { + n2 = makename(); + *app = n2; + app = &n2->narg.next; + } + *app = NULL; + n1->nfor.args = ap; + if (lasttoken != TNL && lasttoken != TSEMI) + synexpect(-1); + } else { + static char argvars[5] = { + CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' + }; + n2 = (union node *)stalloc(sizeof (struct narg)); + n2->type = NARG; + n2->narg.text = argvars; + n2->narg.backquote = NULL; + n2->narg.next = NULL; + n1->nfor.args = n2; + /* + * Newline or semicolon here is optional (but note + * that the original Bourne shell only allowed NL). + */ + if (lasttoken != TNL && lasttoken != TSEMI) + tokpushback++; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if ((t = readtoken()) == TDO) + t = TDONE; + else if (t == TBEGIN) + t = TEND; + else + synexpect(-1); + n1->nfor.body = list(0); + consumetoken(t); + checkkwd = CHKKWD | CHKALIAS; + break; + case TCASE: + n1 = (union node *)stalloc(sizeof (struct ncase)); + n1->type = NCASE; + consumetoken(TWORD); + n1->ncase.expr = makename(); + while (readtoken() == TNL); + if (lasttoken != TWORD || ! equal(wordtext, "in")) + synerror("expecting \"in\""); + cpp = &n1->ncase.cases; + checkkwd = CHKNL | CHKKWD, readtoken(); + while (lasttoken != TESAC) { + *cpp = cp = (union node *)stalloc(sizeof (struct nclist)); + cp->type = NCLIST; + app = &cp->nclist.pattern; + if (lasttoken == TLP) + readtoken(); + for (;;) { + *app = ap = makename(); + checkkwd = CHKNL | CHKKWD; + if (readtoken() != TPIPE) + break; + app = &ap->narg.next; + readtoken(); + } + ap->narg.next = NULL; + if (lasttoken != TRP) + synexpect(TRP); + cp->nclist.body = list(0); + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if ((t = readtoken()) != TESAC) { + if (t == TENDCASE) + ; + else if (t == TFALLTHRU) + cp->type = NCLISTFALLTHRU; + else + synexpect(TENDCASE); + checkkwd = CHKNL | CHKKWD, readtoken(); + } + cpp = &cp->nclist.next; + } + *cpp = NULL; + checkkwd = CHKKWD | CHKALIAS; + break; + case TLP: + n1 = (union node *)stalloc(sizeof (struct nredir)); + n1->type = NSUBSHELL; + n1->nredir.n = list(0); + n1->nredir.redirect = NULL; + consumetoken(TRP); + checkkwd = CHKKWD | CHKALIAS; + is_subshell = 1; + break; + case TBEGIN: + n1 = list(0); + consumetoken(TEND); + checkkwd = CHKKWD | CHKALIAS; + break; + /* A simple command must have at least one redirection or word. */ + case TBACKGND: + case TSEMI: + case TAND: + case TOR: + case TPIPE: + case TENDCASE: + case TFALLTHRU: + case TEOF: + case TNL: + case TRP: + if (!redir) + synexpect(-1); + case TWORD: + tokpushback++; + n1 = simplecmd(rpp, redir); + return n1; + default: + synexpect(-1); + } + + /* Now check for redirection which may follow command */ + while (readtoken() == TREDIR) { + *rpp = n2 = redirnode; + rpp = &n2->nfile.next; + parsefname(); + } + tokpushback++; + *rpp = NULL; + if (redir) { + if (!is_subshell) { + n2 = (union node *)stalloc(sizeof (struct nredir)); + n2->type = NREDIR; + n2->nredir.n = n1; + n1 = n2; + } + n1->nredir.redirect = redir; + } + + return n1; +} + + +static union node * +simplecmd(union node **rpp, union node *redir) +{ + union node *args, **app; + union node **orig_rpp = rpp; + union node *n = NULL; + int special; + int savecheckkwd; + + /* If we don't have any redirections already, then we must reset */ + /* rpp to be the address of the local redir variable. */ + if (redir == NULL) + rpp = &redir; + + args = NULL; + app = &args; + /* + * We save the incoming value, because we need this for shell + * functions. There can not be a redirect or an argument between + * the function name and the open parenthesis. + */ + orig_rpp = rpp; + + savecheckkwd = CHKALIAS; + + for (;;) { + checkkwd = savecheckkwd; + if (readtoken() == TWORD) { + n = makename(); + *app = n; + app = &n->narg.next; + if (savecheckkwd != 0 && !isassignment(wordtext)) + savecheckkwd = 0; + } else if (lasttoken == TREDIR) { + *rpp = n = redirnode; + rpp = &n->nfile.next; + parsefname(); /* read name of redirection file */ + } else if (lasttoken == TLP && app == &args->narg.next + && rpp == orig_rpp) { + /* We have a function */ + consumetoken(TRP); + funclinno = plinno; + /* + * - Require plain text. + * - Functions with '/' cannot be called. + * - Reject name=(). + * - Reject ksh extended glob patterns. + */ + if (!noexpand(n->narg.text) || quoteflag || + strchr(n->narg.text, '/') || + strchr("!%*+-=?@}~", + n->narg.text[strlen(n->narg.text) - 1])) + synerror("Bad function name"); + rmescapes(n->narg.text); + if (find_builtin(n->narg.text, &special) >= 0 && + special) + synerror("Cannot override a special builtin with a function"); + n->type = NDEFUN; + n->narg.next = command(); + funclinno = 0; + return n; + } else { + tokpushback++; + break; + } + } + *app = NULL; + *rpp = NULL; + n = (union node *)stalloc(sizeof (struct ncmd)); + n->type = NCMD; + n->ncmd.args = args; + n->ncmd.redirect = redir; + return n; +} + +static union node * +makename(void) +{ + union node *n; + + n = (union node *)stalloc(sizeof (struct narg)); + n->type = NARG; + n->narg.next = NULL; + n->narg.text = wordtext; + n->narg.backquote = backquotelist; + return n; +} + +static union node * +makebinary(int type, union node *n1, union node *n2) +{ + union node *n; + + n = (union node *)stalloc(sizeof (struct nbinary)); + n->type = type; + n->nbinary.ch1 = n1; + n->nbinary.ch2 = n2; + return (n); +} + +void +forcealias(void) +{ + checkkwd |= CHKALIAS; +} + +void +fixredir(union node *n, const char *text, int err) +{ + TRACE(("Fix redir %s %d\n", text, err)); + if (!err) + n->ndup.vname = NULL; + + if (is_digit(text[0]) && text[1] == '\0') + n->ndup.dupfd = digit_val(text[0]); + else if (text[0] == '-' && text[1] == '\0') + n->ndup.dupfd = -1; + else { + + if (err) + synerror("Bad fd number"); + else + n->ndup.vname = makename(); + } +} + + +static void +parsefname(void) +{ + union node *n = redirnode; + + consumetoken(TWORD); + if (n->type == NHERE) { + struct heredoc *here = heredoc; + struct heredoc *p; + + if (quoteflag == 0) + n->type = NXHERE; + TRACE(("Here document %d\n", n->type)); + if (here->striptabs) { + while (*wordtext == '\t') + wordtext++; + } + if (! noexpand(wordtext)) + synerror("Illegal eof marker for << redirection"); + rmescapes(wordtext); + here->eofmark = wordtext; + here->next = NULL; + if (heredoclist == NULL) + heredoclist = here; + else { + for (p = heredoclist ; p->next ; p = p->next); + p->next = here; + } + } else if (n->type == NTOFD || n->type == NFROMFD) { + fixredir(n, wordtext, 0); + } else { + n->nfile.fname = makename(); + } +} + + +/* + * Input any here documents. + */ + +static void +parseheredoc(void) +{ + struct heredoc *here; + union node *n; + + while (heredoclist) { + here = heredoclist; + heredoclist = here->next; + if (needprompt) { + setprompt(2); + needprompt = 0; + } + readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX, + here->eofmark, here->striptabs); + n = makename(); + here->here->nhere.doc = n; + } +} + +static int +peektoken(void) +{ + int t; + + t = readtoken(); + tokpushback++; + return (t); +} + +static int +readtoken(void) +{ + int t; + struct alias *ap; +#ifdef DEBUG + int alreadyseen = tokpushback; +#endif + + top: + t = xxreadtoken(); + + /* + * eat newlines + */ + if (checkkwd & CHKNL) { + while (t == TNL) { + parseheredoc(); + t = xxreadtoken(); + } + } + + /* + * check for keywords and aliases + */ + if (t == TWORD && !quoteflag) + { + const char * const *pp; + + if (checkkwd & CHKKWD) + for (pp = parsekwd; *pp; pp++) { + if (**pp == *wordtext && equal(*pp, wordtext)) + { + lasttoken = t = pp - parsekwd + KWDOFFSET; + TRACE(("keyword %s recognized\n", tokname[t])); + goto out; + } + } + if (checkkwd & CHKALIAS && + (ap = lookupalias(wordtext, 1)) != NULL) { + pushstring(ap->val, strlen(ap->val), ap); + goto top; + } + } +out: + if (t != TNOT) + checkkwd = 0; + +#ifdef DEBUG + if (!alreadyseen) + TRACE(("token %s %s\n", tokname[t], t == TWORD ? wordtext : "")); + else + TRACE(("reread token %s %s\n", tokname[t], t == TWORD ? wordtext : "")); +#endif + return (t); +} + + +/* + * Read the next input token. + * If the token is a word, we set backquotelist to the list of cmds in + * backquotes. We set quoteflag to true if any part of the word was + * quoted. + * If the token is TREDIR, then we set redirnode to a structure containing + * the redirection. + * In all cases, the variable startlinno is set to the number of the line + * on which the token starts. + * + * [Change comment: here documents and internal procedures] + * [Readtoken shouldn't have any arguments. Perhaps we should make the + * word parsing code into a separate routine. In this case, readtoken + * doesn't need to have any internal procedures, but parseword does. + * We could also make parseoperator in essence the main routine, and + * have parseword (readtoken1?) handle both words and redirection.] + */ + +#define RETURN(token) return lasttoken = token + +static int +xxreadtoken(void) +{ + int c; + + if (tokpushback) { + tokpushback = 0; + return lasttoken; + } + if (needprompt) { + setprompt(2); + needprompt = 0; + } + startlinno = plinno; + for (;;) { /* until token or start of word found */ + c = pgetc_macro(); + switch (c) { + case ' ': case '\t': + continue; + case '#': + while ((c = pgetc()) != '\n' && c != PEOF); + pungetc(); + continue; + case '\\': + if (pgetc() == '\n') { + startlinno = ++plinno; + if (doprompt) + setprompt(2); + else + setprompt(0); + continue; + } + pungetc(); + /* FALLTHROUGH */ + default: + return readtoken1(c, BASESYNTAX, (char *)NULL, 0); + case '\n': + plinno++; + needprompt = doprompt; + RETURN(TNL); + case PEOF: + RETURN(TEOF); + case '&': + if (pgetc_linecont() == '&') + RETURN(TAND); + pungetc(); + RETURN(TBACKGND); + case '|': + if (pgetc_linecont() == '|') + RETURN(TOR); + pungetc(); + RETURN(TPIPE); + case ';': + c = pgetc_linecont(); + if (c == ';') + RETURN(TENDCASE); + else if (c == '&') + RETURN(TFALLTHRU); + pungetc(); + RETURN(TSEMI); + case '(': + RETURN(TLP); + case ')': + RETURN(TRP); + } + } +#undef RETURN +} + + +#define MAXNEST_static 8 +struct tokenstate +{ + const char *syntax; /* *SYNTAX */ + int parenlevel; /* levels of parentheses in arithmetic */ + enum tokenstate_category + { + TSTATE_TOP, + TSTATE_VAR_OLD, /* ${var+-=?}, inherits dquotes */ + TSTATE_VAR_NEW, /* other ${var...}, own dquote state */ + TSTATE_ARITH + } category; +}; + + +/* + * Check to see whether we are at the end of the here document. When this + * is called, c is set to the first character of the next input line. If + * we are at the end of the here document, this routine sets the c to PEOF. + * The new value of c is returned. + */ + +static int +checkend(int c, const char *eofmark, int striptabs) +{ + if (striptabs) { + while (c == '\t') + c = pgetc(); + } + if (c == *eofmark) { + int c2; + const char *q; + + for (q = eofmark + 1; c2 = pgetc(), *q != '\0' && c2 == *q; q++) + ; + if ((c2 == PEOF || c2 == '\n') && *q == '\0') { + c = PEOF; + if (c2 == '\n') { + plinno++; + needprompt = doprompt; + } + } else { + pungetc(); + pushstring(eofmark + 1, q - (eofmark + 1), NULL); + } + } else if (c == '\n' && *eofmark == '\0') { + c = PEOF; + plinno++; + needprompt = doprompt; + } + return (c); +} + + +/* + * Parse a redirection operator. The variable "out" points to a string + * specifying the fd to be redirected. The variable "c" contains the + * first character of the redirection operator. + */ + +static void +parseredir(char *out, int c) +{ + char fd = *out; + union node *np; + + np = (union node *)stalloc(sizeof (struct nfile)); + if (c == '>') { + np->nfile.fd = 1; + c = pgetc_linecont(); + if (c == '>') + np->type = NAPPEND; + else if (c == '&') + np->type = NTOFD; + else if (c == '|') + np->type = NCLOBBER; + else { + np->type = NTO; + pungetc(); + } + } else { /* c == '<' */ + np->nfile.fd = 0; + c = pgetc_linecont(); + if (c == '<') { + if (sizeof (struct nfile) != sizeof (struct nhere)) { + np = (union node *)stalloc(sizeof (struct nhere)); + np->nfile.fd = 0; + } + np->type = NHERE; + heredoc = (struct heredoc *)stalloc(sizeof (struct heredoc)); + heredoc->here = np; + if ((c = pgetc_linecont()) == '-') { + heredoc->striptabs = 1; + } else { + heredoc->striptabs = 0; + pungetc(); + } + } else if (c == '&') + np->type = NFROMFD; + else if (c == '>') + np->type = NFROMTO; + else { + np->type = NFROM; + pungetc(); + } + } + if (fd != '\0') + np->nfile.fd = digit_val(fd); + redirnode = np; +} + +/* + * Called to parse command substitutions. + */ + +static char * +parsebackq(char *out, struct nodelist **pbqlist, + int oldstyle, int dblquote, int quoted) +{ + struct nodelist **nlpp; + union node *n; + char *volatile str; + struct jmploc jmploc; + struct jmploc *const savehandler = handler; + size_t savelen; + int saveprompt; + const int bq_startlinno = plinno; + char *volatile ostr = NULL; + struct parsefile *const savetopfile = getcurrentfile(); + struct heredoc *const saveheredoclist = heredoclist; + struct heredoc *here; + + str = NULL; + if (setjmp(jmploc.loc)) { + popfilesupto(savetopfile); + if (str) + ckfree(str); + if (ostr) + ckfree(ostr); + heredoclist = saveheredoclist; + handler = savehandler; + if (exception == EXERROR) { + startlinno = bq_startlinno; + synerror("Error in command substitution"); + } + longjmp(handler->loc, 1); + } + INTOFF; + savelen = out - stackblock(); + if (savelen > 0) { + str = ckmalloc(savelen); + memcpy(str, stackblock(), savelen); + } + handler = &jmploc; + heredoclist = NULL; + INTON; + if (oldstyle) { + /* We must read until the closing backquote, giving special + treatment to some slashes, and then push the string and + reread it as input, interpreting it normally. */ + char *oout; + int c; + int olen; + + + STARTSTACKSTR(oout); + for (;;) { + if (needprompt) { + setprompt(2); + needprompt = 0; + } + CHECKSTRSPACE(2, oout); + c = pgetc_linecont(); + if (c == '`') + break; + switch (c) { + case '\\': + c = pgetc(); + if (c != '\\' && c != '`' && c != '$' + && (!dblquote || c != '"')) + USTPUTC('\\', oout); + break; + + case '\n': + plinno++; + needprompt = doprompt; + break; + + case PEOF: + startlinno = plinno; + synerror("EOF in backquote substitution"); + break; + + default: + break; + } + USTPUTC(c, oout); + } + USTPUTC('\0', oout); + olen = oout - stackblock(); + INTOFF; + ostr = ckmalloc(olen); + memcpy(ostr, stackblock(), olen); + setinputstring(ostr, 1); + INTON; + } + nlpp = pbqlist; + while (*nlpp) + nlpp = &(*nlpp)->next; + *nlpp = (struct nodelist *)stalloc(sizeof (struct nodelist)); + (*nlpp)->next = NULL; + + if (oldstyle) { + saveprompt = doprompt; + doprompt = 0; + } + + n = list(0); + + if (oldstyle) { + if (peektoken() != TEOF) + synexpect(-1); + doprompt = saveprompt; + } else + consumetoken(TRP); + + (*nlpp)->n = n; + if (oldstyle) { + /* + * Start reading from old file again, ignoring any pushed back + * tokens left from the backquote parsing + */ + popfile(); + tokpushback = 0; + } + STARTSTACKSTR(out); + CHECKSTRSPACE(savelen + 1, out); + INTOFF; + if (str) { + memcpy(out, str, savelen); + STADJUST(savelen, out); + ckfree(str); + str = NULL; + } + if (ostr) { + ckfree(ostr); + ostr = NULL; + } + here = saveheredoclist; + if (here != NULL) { + while (here->next != NULL) + here = here->next; + here->next = heredoclist; + heredoclist = saveheredoclist; + } + handler = savehandler; + INTON; + if (quoted) + USTPUTC(CTLBACKQ | CTLQUOTE, out); + else + USTPUTC(CTLBACKQ, out); + return out; +} + + +/* + * Called to parse a backslash escape sequence inside $'...'. + * The backslash has already been read. + */ +static char * +readcstyleesc(char *out) +{ + int c, vc, i, n; + unsigned int v; + + c = pgetc(); + switch (c) { + case '\0': + synerror("Unterminated quoted string"); + case '\n': + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + return out; + case '\\': + case '\'': + case '"': + v = c; + break; + case 'a': v = '\a'; break; + case 'b': v = '\b'; break; + case 'e': v = '\033'; break; + case 'f': v = '\f'; break; + case 'n': v = '\n'; break; + case 'r': v = '\r'; break; + case 't': v = '\t'; break; + case 'v': v = '\v'; break; + case 'x': + v = 0; + for (;;) { + c = pgetc(); + if (c >= '0' && c <= '9') + v = (v << 4) + c - '0'; + else if (c >= 'A' && c <= 'F') + v = (v << 4) + c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + v = (v << 4) + c - 'a' + 10; + else + break; + } + pungetc(); + break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + v = c - '0'; + c = pgetc(); + if (c >= '0' && c <= '7') { + v <<= 3; + v += c - '0'; + c = pgetc(); + if (c >= '0' && c <= '7') { + v <<= 3; + v += c - '0'; + } else + pungetc(); + } else + pungetc(); + break; + case 'c': + c = pgetc(); + if (c < 0x3f || c > 0x7a || c == 0x60) + synerror("Bad escape sequence"); + if (c == '\\' && pgetc() != '\\') + synerror("Bad escape sequence"); + if (c == '?') + v = 127; + else + v = c & 0x1f; + break; + case 'u': + case 'U': + n = c == 'U' ? 8 : 4; + v = 0; + for (i = 0; i < n; i++) { + c = pgetc(); + if (c >= '0' && c <= '9') + v = (v << 4) + c - '0'; + else if (c >= 'A' && c <= 'F') + v = (v << 4) + c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + v = (v << 4) + c - 'a' + 10; + else + synerror("Bad escape sequence"); + } + if (v == 0 || (v >= 0xd800 && v <= 0xdfff)) + synerror("Bad escape sequence"); + /* We really need iconv here. */ + if (initial_localeisutf8 && v > 127) { + CHECKSTRSPACE(4, out); + /* + * We cannot use wctomb() as the locale may have + * changed. + */ + if (v <= 0x7ff) { + USTPUTC(0xc0 | v >> 6, out); + USTPUTC(0x80 | (v & 0x3f), out); + return out; + } else if (v <= 0xffff) { + USTPUTC(0xe0 | v >> 12, out); + USTPUTC(0x80 | ((v >> 6) & 0x3f), out); + USTPUTC(0x80 | (v & 0x3f), out); + return out; + } else if (v <= 0x10ffff) { + USTPUTC(0xf0 | v >> 18, out); + USTPUTC(0x80 | ((v >> 12) & 0x3f), out); + USTPUTC(0x80 | ((v >> 6) & 0x3f), out); + USTPUTC(0x80 | (v & 0x3f), out); + return out; + } + } + if (v > 127) + v = '?'; + break; + default: + synerror("Bad escape sequence"); + } + vc = (char)v; + /* + * We can't handle NUL bytes. + * POSIX says we should skip till the closing quote. + */ + if (vc == '\0') { + while ((c = pgetc()) != '\'') { + if (c == '\\') + c = pgetc(); + if (c == PEOF) + synerror("Unterminated quoted string"); + if (c == '\n') { + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + } + } + pungetc(); + return out; + } + if (SQSYNTAX[vc] == CCTL) + USTPUTC(CTLESC, out); + USTPUTC(vc, out); + return out; +} + + +/* + * If eofmark is NULL, read a word or a redirection symbol. If eofmark + * is not NULL, read a here document. In the latter case, eofmark is the + * word which marks the end of the document and striptabs is true if + * leading tabs should be stripped from the document. The argument firstc + * is the first character of the input token or document. + * + * Because C does not have internal subroutines, I have simulated them + * using goto's to implement the subroutine linkage. The following macros + * will run code that appears at the end of readtoken1. + */ + +#define PARSESUB() {goto parsesub; parsesub_return:;} +#define PARSEARITH() {goto parsearith; parsearith_return:;} + +static int +readtoken1(int firstc, char const *initialsyntax, const char *eofmark, + int striptabs) +{ + int c = firstc; + char *out; + int len; + struct nodelist *bqlist; + int quotef; + int newvarnest; + int level; + int synentry; + struct tokenstate state_static[MAXNEST_static]; + int maxnest = MAXNEST_static; + struct tokenstate *state = state_static; + int sqiscstyle = 0; + + startlinno = plinno; + quotef = 0; + bqlist = NULL; + newvarnest = 0; + level = 0; + state[level].syntax = initialsyntax; + state[level].parenlevel = 0; + state[level].category = TSTATE_TOP; + + STARTSTACKSTR(out); + loop: { /* for each line, until end of word */ + if (eofmark && eofmark != NOEOFMARK) + /* set c to PEOF if at end of here document */ + c = checkend(c, eofmark, striptabs); + for (;;) { /* until end of line or end of word */ + CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ + + synentry = state[level].syntax[c]; + + switch(synentry) { + case CNL: /* '\n' */ + if (level == 0) + goto endword; /* exit outer loop */ + /* FALLTHROUGH */ + case CQNL: + USTPUTC(c, out); + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + c = pgetc(); + goto loop; /* continue outer loop */ + case CSBACK: + if (sqiscstyle) { + out = readcstyleesc(out); + break; + } + /* FALLTHROUGH */ + case CWORD: + USTPUTC(c, out); + break; + case CCTL: + if (eofmark == NULL || initialsyntax != SQSYNTAX) + USTPUTC(CTLESC, out); + USTPUTC(c, out); + break; + case CBACK: /* backslash */ + c = pgetc(); + if (c == PEOF) { + USTPUTC('\\', out); + pungetc(); + } else if (c == '\n') { + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + } else { + if (state[level].syntax == DQSYNTAX && + c != '\\' && c != '`' && c != '$' && + (c != '"' || (eofmark != NULL && + newvarnest == 0)) && + (c != '}' || state[level].category != TSTATE_VAR_OLD)) + USTPUTC('\\', out); + if ((eofmark == NULL || + newvarnest > 0) && + state[level].syntax == BASESYNTAX) + USTPUTC(CTLQUOTEMARK, out); + if (SQSYNTAX[c] == CCTL) + USTPUTC(CTLESC, out); + USTPUTC(c, out); + if ((eofmark == NULL || + newvarnest > 0) && + state[level].syntax == BASESYNTAX && + state[level].category == TSTATE_VAR_OLD) + USTPUTC(CTLQUOTEEND, out); + quotef++; + } + break; + case CSQUOTE: + USTPUTC(CTLQUOTEMARK, out); + state[level].syntax = SQSYNTAX; + sqiscstyle = 0; + break; + case CDQUOTE: + USTPUTC(CTLQUOTEMARK, out); + state[level].syntax = DQSYNTAX; + break; + case CENDQUOTE: + if (eofmark != NULL && newvarnest == 0) + USTPUTC(c, out); + else { + if (state[level].category == TSTATE_VAR_OLD) + USTPUTC(CTLQUOTEEND, out); + state[level].syntax = BASESYNTAX; + quotef++; + } + break; + case CVAR: /* '$' */ + PARSESUB(); /* parse substitution */ + break; + case CENDVAR: /* '}' */ + if (level > 0 && + ((state[level].category == TSTATE_VAR_OLD && + state[level].syntax == + state[level - 1].syntax) || + (state[level].category == TSTATE_VAR_NEW && + state[level].syntax == BASESYNTAX))) { + if (state[level].category == TSTATE_VAR_NEW) + newvarnest--; + level--; + USTPUTC(CTLENDVAR, out); + } else { + USTPUTC(c, out); + } + break; + case CLP: /* '(' in arithmetic */ + state[level].parenlevel++; + USTPUTC(c, out); + break; + case CRP: /* ')' in arithmetic */ + if (state[level].parenlevel > 0) { + USTPUTC(c, out); + --state[level].parenlevel; + } else { + if (pgetc_linecont() == ')') { + if (level > 0 && + state[level].category == TSTATE_ARITH) { + level--; + USTPUTC(CTLENDARI, out); + } else + USTPUTC(')', out); + } else { + /* + * unbalanced parens + * (don't 2nd guess - no error) + */ + pungetc(); + USTPUTC(')', out); + } + } + break; + case CBQUOTE: /* '`' */ + out = parsebackq(out, &bqlist, 1, + state[level].syntax == DQSYNTAX && + (eofmark == NULL || newvarnest > 0), + state[level].syntax == DQSYNTAX || state[level].syntax == ARISYNTAX); + break; + case CEOF: + goto endword; /* exit outer loop */ + case CIGN: + break; + default: + if (level == 0) + goto endword; /* exit outer loop */ + USTPUTC(c, out); + } + c = pgetc_macro(); + } + } +endword: + if (state[level].syntax == ARISYNTAX) + synerror("Missing '))'"); + if (state[level].syntax != BASESYNTAX && eofmark == NULL) + synerror("Unterminated quoted string"); + if (state[level].category == TSTATE_VAR_OLD || + state[level].category == TSTATE_VAR_NEW) { + startlinno = plinno; + synerror("Missing '}'"); + } + if (state != state_static) + parser_temp_free_upto(state); + USTPUTC('\0', out); + len = out - stackblock(); + out = stackblock(); + if (eofmark == NULL) { + if ((c == '>' || c == '<') + && quotef == 0 + && len <= 2 + && (*out == '\0' || is_digit(*out))) { + parseredir(out, c); + return lasttoken = TREDIR; + } else { + pungetc(); + } + } + quoteflag = quotef; + backquotelist = bqlist; + grabstackblock(len); + wordtext = out; + return lasttoken = TWORD; +/* end of readtoken routine */ + + +/* + * Parse a substitution. At this point, we have read the dollar sign + * and nothing else. + */ + +parsesub: { + int subtype; + int typeloc; + int flags; + char *p; + static const char types[] = "}-+?="; + int linno; + int length; + int c1; + + c = pgetc_linecont(); + if (c == '(') { /* $(command) or $((arith)) */ + if (pgetc_linecont() == '(') { + PARSEARITH(); + } else { + pungetc(); + out = parsebackq(out, &bqlist, 0, + state[level].syntax == DQSYNTAX && + (eofmark == NULL || newvarnest > 0), + state[level].syntax == DQSYNTAX || + state[level].syntax == ARISYNTAX); + } + } else if (c == '{' || is_name(c) || is_special(c)) { + USTPUTC(CTLVAR, out); + typeloc = out - stackblock(); + USTPUTC(VSNORMAL, out); + subtype = VSNORMAL; + flags = 0; + if (c == '{') { + c = pgetc_linecont(); + subtype = 0; + } +varname: + if (!is_eof(c) && is_name(c)) { + length = 0; + do { + STPUTC(c, out); + c = pgetc_linecont(); + length++; + } while (!is_eof(c) && is_in_name(c)); + if (length == 6 && + strncmp(out - length, "LINENO", length) == 0) { + /* Replace the variable name with the + * current line number. */ + STADJUST(-6, out); + CHECKSTRSPACE(11, out); + linno = plinno; + if (funclinno != 0) + linno -= funclinno - 1; + length = snprintf(out, 11, "%d", linno); + if (length > 10) + length = 10; + out += length; + flags |= VSLINENO; + } + } else if (is_digit(c)) { + if (subtype != VSNORMAL) { + do { + STPUTC(c, out); + c = pgetc_linecont(); + } while (is_digit(c)); + } else { + USTPUTC(c, out); + c = pgetc_linecont(); + } + } else if (is_special(c)) { + c1 = c; + c = pgetc_linecont(); + if (subtype == 0 && c1 == '#') { + subtype = VSLENGTH; + if (strchr(types, c) == NULL && c != ':' && + c != '#' && c != '%') + goto varname; + c1 = c; + c = pgetc_linecont(); + if (c1 != '}' && c == '}') { + pungetc(); + c = c1; + goto varname; + } + pungetc(); + c = c1; + c1 = '#'; + subtype = 0; + } + USTPUTC(c1, out); + } else { + subtype = VSERROR; + if (c == '}') + pungetc(); + else if (c == '\n' || c == PEOF) + synerror("Unexpected end of line in substitution"); + else if (BASESYNTAX[c] != CCTL) + USTPUTC(c, out); + } + if (subtype == 0) { + switch (c) { + case ':': + flags |= VSNUL; + c = pgetc_linecont(); + /*FALLTHROUGH*/ + default: + p = strchr(types, c); + if (p == NULL) { + if (c == '\n' || c == PEOF) + synerror("Unexpected end of line in substitution"); + if (flags == VSNUL) + STPUTC(':', out); + if (BASESYNTAX[c] != CCTL) + STPUTC(c, out); + subtype = VSERROR; + } else + subtype = p - types + VSNORMAL; + break; + case '%': + case '#': + { + int cc = c; + subtype = c == '#' ? VSTRIMLEFT : + VSTRIMRIGHT; + c = pgetc_linecont(); + if (c == cc) + subtype++; + else + pungetc(); + break; + } + } + } else if (subtype != VSERROR) { + if (subtype == VSLENGTH && c != '}') + subtype = VSERROR; + pungetc(); + } + STPUTC('=', out); + if (state[level].syntax == DQSYNTAX || + state[level].syntax == ARISYNTAX) + flags |= VSQUOTE; + *(stackblock() + typeloc) = subtype | flags; + if (subtype != VSNORMAL) { + if (level + 1 >= maxnest) { + maxnest *= 2; + if (state == state_static) { + state = parser_temp_alloc( + maxnest * sizeof(*state)); + memcpy(state, state_static, + MAXNEST_static * sizeof(*state)); + } else + state = parser_temp_realloc(state, + maxnest * sizeof(*state)); + } + level++; + state[level].parenlevel = 0; + if (subtype == VSMINUS || subtype == VSPLUS || + subtype == VSQUESTION || subtype == VSASSIGN) { + /* + * For operators that were in the Bourne shell, + * inherit the double-quote state. + */ + state[level].syntax = state[level - 1].syntax; + state[level].category = TSTATE_VAR_OLD; + } else { + /* + * The other operators take a pattern, + * so go to BASESYNTAX. + * Also, ' and " are now special, even + * in here documents. + */ + state[level].syntax = BASESYNTAX; + state[level].category = TSTATE_VAR_NEW; + newvarnest++; + } + } + } else if (c == '\'' && state[level].syntax == BASESYNTAX) { + /* $'cstylequotes' */ + USTPUTC(CTLQUOTEMARK, out); + state[level].syntax = SQSYNTAX; + sqiscstyle = 1; + } else { + USTPUTC('$', out); + pungetc(); + } + goto parsesub_return; +} + + +/* + * Parse an arithmetic expansion (indicate start of one and set state) + */ +parsearith: { + + if (level + 1 >= maxnest) { + maxnest *= 2; + if (state == state_static) { + state = parser_temp_alloc( + maxnest * sizeof(*state)); + memcpy(state, state_static, + MAXNEST_static * sizeof(*state)); + } else + state = parser_temp_realloc(state, + maxnest * sizeof(*state)); + } + level++; + state[level].syntax = ARISYNTAX; + state[level].parenlevel = 0; + state[level].category = TSTATE_ARITH; + USTPUTC(CTLARI, out); + if (state[level - 1].syntax == DQSYNTAX) + USTPUTC('"',out); + else + USTPUTC(' ',out); + goto parsearith_return; +} + +} /* end of readtoken */ + + +/* + * Returns true if the text contains nothing to expand (no dollar signs + * or backquotes). + */ + +static int +noexpand(char *text) +{ + char *p; + char c; + + p = text; + while ((c = *p++) != '\0') { + if ( c == CTLQUOTEMARK) + continue; + if (c == CTLESC) + p++; + else if (BASESYNTAX[(int)c] == CCTL) + return 0; + } + return 1; +} + + +/* + * Return true if the argument is a legal variable name (a letter or + * underscore followed by zero or more letters, underscores, and digits). + */ + +int +goodname(const char *name) +{ + const char *p; + + p = name; + if (! is_name(*p)) + return 0; + while (*++p) { + if (! is_in_name(*p)) + return 0; + } + return 1; +} + + +int +isassignment(const char *p) +{ + if (!is_name(*p)) + return 0; + p++; + for (;;) { + if (*p == '=') + return 1; + else if (!is_in_name(*p)) + return 0; + p++; + } +} + + +static void +consumetoken(int token) +{ + if (readtoken() != token) + synexpect(token); +} + + +/* + * Called when an unexpected token is read during the parse. The argument + * is the token that is expected, or -1 if more than one type of token can + * occur at this point. + */ + +static void +synexpect(int token) +{ + char msg[64]; + + if (token >= 0) { + fmtstr(msg, 64, "%s unexpected (expecting %s)", + tokname[lasttoken], tokname[token]); + } else { + fmtstr(msg, 64, "%s unexpected", tokname[lasttoken]); + } + synerror(msg); +} + + +static void +synerror(const char *msg) +{ + if (commandname) + outfmt(out2, "%s: %d: ", commandname, startlinno); + else if (arg0) + outfmt(out2, "%s: ", arg0); + outfmt(out2, "Syntax error: %s\n", msg); + error((char *)NULL); +} + +static void +setprompt(int which) +{ + whichprompt = which; + if (which == 0) + return; + + if (which == 1 && *ps0val()) { + out2str(expandprompt(ps0val())); + flushout(out2); + } + +#ifndef NO_HISTORY + if (!el) +#endif + { + out2str(getprompt(NULL)); + flushout(out2); + } +} + +static int +pgetc_linecont(void) +{ + int c; + + while ((c = pgetc_macro()) == '\\') { + c = pgetc(); + if (c == '\n') { + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + } else { + pungetc(); + /* Allow the backslash to be pushed back. */ + pushstring("\\", 1, NULL); + return (pgetc()); + } + } + return (c); +} + +static char * +expandprompt(const char *fmt) { + static char ps[PROMPTLEN]; + const char *pwd, *home; + int i, trim; + + /* + * Format prompt string. + */ + for (i = 0; (i < PROMPTLEN - 1) && (*fmt != '\0'); i++, fmt++) + if (*fmt == '\\') + switch (*++fmt) { + + /* + * Hostname. + * + * \h specifies just the local hostname, + * \H specifies fully-qualified hostname. + */ + case 'h': + case 'H': + ps[i] = '\0'; + gethostname(&ps[i], PROMPTLEN - i - 1); + ps[PROMPTLEN - 1] = '\0'; + /* Skip to end of hostname. */ + trim = (*fmt == 'h') ? '.' : '\0'; + while ((ps[i] != '\0') && (ps[i] != trim)) + i++; + --i; + break; + + /* + * Working directory. + * + * \W specifies just the final component, + * \w specifies the entire path. + */ + case 'W': + case 'w': + pwd = lookupvar("PWD"); + home = lookupvar("HOME"); + if (pwd == NULL || *pwd == '\0') + pwd = "?"; + if (home == NULL || *home == '\0') + home = "?"; + if (*fmt == 'W' && + *pwd == '/' && pwd[1] != '\0' && + strcmp(pwd, home) != 0) { + strlcpy(&ps[i], strrchr(pwd, '/') + 1, + PROMPTLEN - i); + } else { + if (strncmp(pwd, home, strlen(home)) == 0) { + ps[i++] = '~'; + pwd += strlen(home); + } + strlcpy(&ps[i], pwd, PROMPTLEN - i); + } + /* Skip to end of path. */ + while (ps[i] != '\0') + i++; + --i; + break; + + /* + * Superuser status. + * + * '$' for normal users, '#' for root. + */ + case '$': + ps[i] = (geteuid() != 0) ? '$' : '#'; + break; + + /* + * A literal \. + */ + case '\\': + ps[i] = '\\'; + break; + + /* + * Emit unrecognized formats verbatim. + */ + default: + ps[i] = '\\'; + if (i < PROMPTLEN - 2) + ps[++i] = *fmt; + break; + } + else + ps[i] = *fmt; + ps[i] = '\0'; + return (ps); +} + +/* + * called by editline -- any expansions to the prompt + * should be added here. + */ +char * +getprompt(void *unused __unused) +{ + const char *fmt; + static char internal_error[] = "??"; + + /* + * Select prompt format. + */ + switch (whichprompt) { + case 0: + fmt = ""; + break; + case 1: + fmt = ps1val(); + break; + case 2: + fmt = ps2val(); + break; + default: + return internal_error; + } + + return expandprompt(fmt); +} + +char * +getrprompt(void *unused __unused) +{ + const char *fmt; + static char internal_error[] = "??"; + + /* + * Select prompt format. + */ + switch (whichprompt) { + case 0: + fmt = ""; + break; + case 1: + fmt = rps1val(); + break; + case 2: + fmt = rps2val(); + break; + default: + return internal_error; + } + + return expandprompt(fmt); +} + + +const char * +expandstr(const char *ps) +{ + union node n; + struct jmploc jmploc; + struct jmploc *const savehandler = handler; + const int saveprompt = doprompt; + struct parsefile *const savetopfile = getcurrentfile(); + struct parser_temp *const saveparser_temp = parser_temp; + const char *result = NULL; + + if (!setjmp(jmploc.loc)) { + handler = &jmploc; + parser_temp = NULL; + setinputstring(ps, 1); + doprompt = 0; + readtoken1(pgetc(), DQSYNTAX, NOEOFMARK, 0); + if (backquotelist != NULL) + error("Command substitution not allowed here"); + + n.narg.type = NARG; + n.narg.next = NULL; + n.narg.text = wordtext; + n.narg.backquote = backquotelist; + + expandarg(&n, NULL, 0); + result = stackblock(); + INTOFF; + } + handler = savehandler; + doprompt = saveprompt; + popfilesupto(savetopfile); + if (parser_temp != saveparser_temp) { + parser_temp_free_all(); + parser_temp = saveparser_temp; + } + if (result != NULL) { + INTON; + } else if (exception == EXINT) + raise(SIGINT); + return result; +} diff --git a/bin/catsh/parser.h b/bin/catsh/parser.h new file mode 100644 index 00000000..bc86f7e4 --- /dev/null +++ b/bin/catsh/parser.h @@ -0,0 +1,88 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)parser.h 8.3 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/parser.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +/* control characters in argument strings */ +#define CTLESC '\300' +#define CTLVAR '\301' +#define CTLENDVAR '\371' +#define CTLBACKQ '\372' +#define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */ +/* CTLBACKQ | CTLQUOTE == '\373' */ +#define CTLARI '\374' +#define CTLENDARI '\375' +#define CTLQUOTEMARK '\376' +#define CTLQUOTEEND '\377' /* only for ${v+-...} */ + +/* variable substitution byte (follows CTLVAR) */ +#define VSTYPE 0x0f /* type of variable substitution */ +#define VSNUL 0x10 /* colon--treat the empty string as unset */ +#define VSLINENO 0x20 /* expansion of $LINENO, the line number \ + follows immediately */ +#define VSQUOTE 0x80 /* inside double quotes--suppress splitting */ + +/* values of VSTYPE field */ +#define VSNORMAL 0x1 /* normal variable: $var or ${var} */ +#define VSMINUS 0x2 /* ${var-text} */ +#define VSPLUS 0x3 /* ${var+text} */ +#define VSQUESTION 0x4 /* ${var?message} */ +#define VSASSIGN 0x5 /* ${var=text} */ +#define VSTRIMLEFT 0x6 /* ${var#pattern} */ +#define VSTRIMLEFTMAX 0x7 /* ${var##pattern} */ +#define VSTRIMRIGHT 0x8 /* ${var%pattern} */ +#define VSTRIMRIGHTMAX 0x9 /* ${var%%pattern} */ +#define VSLENGTH 0xa /* ${#var} */ +#define VSERROR 0xb /* Syntax error, issue when expanded */ + + +/* + * NEOF is returned by parsecmd when it encounters an end of file. It + * must be distinct from NULL. + */ +#define NEOF ((union node *)-1) +extern int whichprompt; /* 1 == PS1, 2 == PS2 */ +extern const char *const parsekwd[]; + + +union node *parsecmd(int); +union node *parsewordexp(void); +void forcealias(void); +void fixredir(union node *, const char *, int); +int goodname(const char *); +int isassignment(const char *); +char *getprompt(void *); +char *getrprompt(void *); +const char *expandstr(const char *); diff --git a/bin/catsh/printf.c b/bin/catsh/printf.c new file mode 100644 index 00000000..e698c417 --- /dev/null +++ b/bin/catsh/printf.c @@ -0,0 +1,688 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> + * Copyright 2014 Garrett D'Amore <garrett@damore.org> + * Copyright 2010 Nexenta Systems, Inc. All rights reserved. + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/* + * Important: This file is used both as a standalone program /usr/bin/printf + * and as a builtin for /bin/sh (#define SHELL). + */ + +#ifndef SHELL +#ifndef lint +static char const copyright[] = +"@(#) Copyright (c) 1989, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ +#endif + +#ifndef lint +#if 0 +static char const sccsid[] = "@(#)printf.c 8.1 (Berkeley) 7/20/93"; +#endif +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/usr.bin/printf/printf.c 337618 2018-08-11 11:13:34Z jilles $"); +#endif /* not lint */ + +#include <sys/types.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wchar.h> + +#ifdef SHELL +#define main printfcmd +#include "bltin.h" +#include "options.h" +#endif + +#define PF(f, func) do { \ + if (havewidth) \ + if (haveprec) \ + (void)printf(f, fieldwidth, precision, func); \ + else \ + (void)printf(f, fieldwidth, func); \ + else if (haveprec) \ + (void)printf(f, precision, func); \ + else \ + (void)printf(f, func); \ +} while (0) + +static int asciicode(void); +static char *printf_doformat(char *, int *); +static int escape(char *, int, size_t *); +static int getchr(void); +static int getfloating(long double *, int); +static int getint(int *); +static int getnum(intmax_t *, uintmax_t *, int); +static const char + *getstr(void); +static char *mknum(char *, char); +static void usage(void); + +static const char digits[] = "0123456789"; + +static char end_fmt[1]; + +static int myargc; +static char **myargv; +static char **gargv; +static char **maxargv; + +int +main(int argc, char *argv[]) +{ + size_t len; + int end, rval; + char *format, *fmt, *start; +#ifndef SHELL + int ch; + + (void) setlocale(LC_ALL, ""); +#endif + +#ifdef SHELL + nextopt(""); + argc -= argptr - argv; + argv = argptr; +#else + while ((ch = getopt(argc, argv, "")) != -1) + switch (ch) { + case '?': + default: + usage(); + return (1); + } + argc -= optind; + argv += optind; +#endif + + if (argc < 1) { + usage(); + return (1); + } + +#ifdef SHELL + INTOFF; +#endif + /* + * Basic algorithm is to scan the format string for conversion + * specifications -- once one is found, find out if the field + * width or precision is a '*'; if it is, gather up value. Note, + * format strings are reused as necessary to use up the provided + * arguments, arguments of zero/null string are provided to use + * up the format string. + */ + fmt = format = *argv; + escape(fmt, 1, &len); /* backslash interpretation */ + rval = end = 0; + gargv = ++argv; + + for (;;) { + maxargv = gargv; + + myargv = gargv; + for (myargc = 0; gargv[myargc]; myargc++) + /* nop */; + start = fmt; + while (fmt < format + len) { + if (fmt[0] == '%') { + fwrite(start, 1, fmt - start, stdout); + if (fmt[1] == '%') { + /* %% prints a % */ + putchar('%'); + fmt += 2; + } else { + fmt = printf_doformat(fmt, &rval); + if (fmt == NULL || fmt == end_fmt) { +#ifdef SHELL + INTON; +#endif + return (fmt == NULL ? 1 : rval); + } + end = 0; + } + start = fmt; + } else + fmt++; + if (gargv > maxargv) + maxargv = gargv; + } + gargv = maxargv; + + if (end == 1) { + warnx("missing format character"); +#ifdef SHELL + INTON; +#endif + return (1); + } + fwrite(start, 1, fmt - start, stdout); + if (!*gargv) { +#ifdef SHELL + INTON; +#endif + return (rval); + } + /* Restart at the beginning of the format string. */ + fmt = format; + end = 1; + } + /* NOTREACHED */ +} + + +static char * +printf_doformat(char *fmt, int *rval) +{ + static const char skip1[] = "#'-+ 0"; + int fieldwidth, haveprec, havewidth, mod_ldbl, precision; + char convch, nextch; + char start[strlen(fmt) + 1]; + char **fargv; + char *dptr; + int l; + + dptr = start; + *dptr++ = '%'; + *dptr = 0; + + fmt++; + + /* look for "n$" field index specifier */ + l = strspn(fmt, digits); + if ((l > 0) && (fmt[l] == '$')) { + int idx = atoi(fmt); + if (idx <= myargc) { + gargv = &myargv[idx - 1]; + } else { + gargv = &myargv[myargc]; + } + if (gargv > maxargv) + maxargv = gargv; + fmt += l + 1; + + /* save format argument */ + fargv = gargv; + } else { + fargv = NULL; + } + + /* skip to field width */ + while (*fmt && strchr(skip1, *fmt) != NULL) { + *dptr++ = *fmt++; + *dptr = 0; + } + + if (*fmt == '*') { + + fmt++; + l = strspn(fmt, digits); + if ((l > 0) && (fmt[l] == '$')) { + int idx = atoi(fmt); + if (fargv == NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + if (idx <= myargc) { + gargv = &myargv[idx - 1]; + } else { + gargv = &myargv[myargc]; + } + fmt += l + 1; + } else if (fargv != NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + + if (getint(&fieldwidth)) + return (NULL); + if (gargv > maxargv) + maxargv = gargv; + havewidth = 1; + + *dptr++ = '*'; + *dptr = 0; + } else { + havewidth = 0; + + /* skip to possible '.', get following precision */ + while (isdigit(*fmt)) { + *dptr++ = *fmt++; + *dptr = 0; + } + } + + if (*fmt == '.') { + /* precision present? */ + fmt++; + *dptr++ = '.'; + + if (*fmt == '*') { + + fmt++; + l = strspn(fmt, digits); + if ((l > 0) && (fmt[l] == '$')) { + int idx = atoi(fmt); + if (fargv == NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + if (idx <= myargc) { + gargv = &myargv[idx - 1]; + } else { + gargv = &myargv[myargc]; + } + fmt += l + 1; + } else if (fargv != NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + + if (getint(&precision)) + return (NULL); + if (gargv > maxargv) + maxargv = gargv; + haveprec = 1; + *dptr++ = '*'; + *dptr = 0; + } else { + haveprec = 0; + + /* skip to conversion char */ + while (isdigit(*fmt)) { + *dptr++ = *fmt++; + *dptr = 0; + } + } + } else + haveprec = 0; + if (!*fmt) { + warnx("missing format character"); + return (NULL); + } + *dptr++ = *fmt; + *dptr = 0; + + /* + * Look for a length modifier. POSIX doesn't have these, so + * we only support them for floating-point conversions, which + * are extensions. This is useful because the L modifier can + * be used to gain extra range and precision, while omitting + * it is more likely to produce consistent results on different + * architectures. This is not so important for integers + * because overflow is the only bad thing that can happen to + * them, but consider the command printf %a 1.1 + */ + if (*fmt == 'L') { + mod_ldbl = 1; + fmt++; + if (!strchr("aAeEfFgG", *fmt)) { + warnx("bad modifier L for %%%c", *fmt); + return (NULL); + } + } else { + mod_ldbl = 0; + } + + /* save the current arg offset, and set to the format arg */ + if (fargv != NULL) { + gargv = fargv; + } + + convch = *fmt; + nextch = *++fmt; + + *fmt = '\0'; + switch (convch) { + case 'b': { + size_t len; + char *p; + int getout; + + /* Convert "b" to "s" for output. */ + start[strlen(start) - 1] = 's'; + if ((p = strdup(getstr())) == NULL) { + warnx("%s", strerror(ENOMEM)); + return (NULL); + } + getout = escape(p, 0, &len); + PF(start, p); + /* Restore format for next loop. */ + + free(p); + if (getout) + return (end_fmt); + break; + } + case 'c': { + char p; + + p = getchr(); + if (p != '\0') + PF(start, p); + break; + } + case 's': { + const char *p; + + p = getstr(); + PF(start, p); + break; + } + case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': { + char *f; + intmax_t val; + uintmax_t uval; + int signedconv; + + signedconv = (convch == 'd' || convch == 'i'); + if ((f = mknum(start, convch)) == NULL) + return (NULL); + if (getnum(&val, &uval, signedconv)) + *rval = 1; + if (signedconv) + PF(f, val); + else + PF(f, uval); + break; + } + case 'e': case 'E': + case 'f': case 'F': + case 'g': case 'G': + case 'a': case 'A': { + long double p; + + if (getfloating(&p, mod_ldbl)) + *rval = 1; + if (mod_ldbl) + PF(start, p); + else + PF(start, (double)p); + break; + } + default: + warnx("illegal format character %c", convch); + return (NULL); + } + *fmt = nextch; + /* return the gargv to the next element */ + return (fmt); +} + +static char * +mknum(char *str, char ch) +{ + static char *copy; + static size_t copy_size; + char *newcopy; + size_t len, newlen; + + len = strlen(str) + 2; + if (len > copy_size) { + newlen = ((len + 1023) >> 10) << 10; + if ((newcopy = realloc(copy, newlen)) == NULL) { + warnx("%s", strerror(ENOMEM)); + return (NULL); + } + copy = newcopy; + copy_size = newlen; + } + + memmove(copy, str, len - 3); + copy[len - 3] = 'j'; + copy[len - 2] = ch; + copy[len - 1] = '\0'; + return (copy); +} + +static int +escape(char *fmt, int percent, size_t *len) +{ + char *save, *store, c; + int value; + + for (save = store = fmt; ((c = *fmt) != 0); ++fmt, ++store) { + if (c != '\\') { + *store = c; + continue; + } + switch (*++fmt) { + case '\0': /* EOS, user error */ + *store = '\\'; + *++store = '\0'; + *len = store - save; + return (0); + case '\\': /* backslash */ + case '\'': /* single quote */ + *store = *fmt; + break; + case 'a': /* bell/alert */ + *store = '\a'; + break; + case 'b': /* backspace */ + *store = '\b'; + break; + case 'c': + if (!percent) { + *store = '\0'; + *len = store - save; + return (1); + } + *store = 'c'; + break; + case 'f': /* form-feed */ + *store = '\f'; + break; + case 'n': /* newline */ + *store = '\n'; + break; + case 'r': /* carriage-return */ + *store = '\r'; + break; + case 't': /* horizontal tab */ + *store = '\t'; + break; + case 'v': /* vertical tab */ + *store = '\v'; + break; + /* octal constant */ + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + c = (!percent && *fmt == '0') ? 4 : 3; + for (value = 0; + c-- && *fmt >= '0' && *fmt <= '7'; ++fmt) { + value <<= 3; + value += *fmt - '0'; + } + --fmt; + if (percent && value == '%') { + *store++ = '%'; + *store = '%'; + } else + *store = (char)value; + break; + default: + *store = *fmt; + break; + } + } + *store = '\0'; + *len = store - save; + return (0); +} + +static int +getchr(void) +{ + if (!*gargv) + return ('\0'); + return ((int)**gargv++); +} + +static const char * +getstr(void) +{ + if (!*gargv) + return (""); + return (*gargv++); +} + +static int +getint(int *ip) +{ + intmax_t val; + uintmax_t uval; + int rval; + + if (getnum(&val, &uval, 1)) + return (1); + rval = 0; + if (val < INT_MIN || val > INT_MAX) { + warnx("%s: %s", *gargv, strerror(ERANGE)); + rval = 1; + } + *ip = (int)val; + return (rval); +} + +static int +getnum(intmax_t *ip, uintmax_t *uip, int signedconv) +{ + char *ep; + int rval; + + if (!*gargv) { + *ip = *uip = 0; + return (0); + } + if (**gargv == '"' || **gargv == '\'') { + if (signedconv) + *ip = asciicode(); + else + *uip = asciicode(); + return (0); + } + rval = 0; + errno = 0; + if (signedconv) + *ip = strtoimax(*gargv, &ep, 0); + else + *uip = strtoumax(*gargv, &ep, 0); + if (ep == *gargv) { + warnx("%s: expected numeric value", *gargv); + rval = 1; + } + else if (*ep != '\0') { + warnx("%s: not completely converted", *gargv); + rval = 1; + } + if (errno == ERANGE) { + warnx("%s: %s", *gargv, strerror(ERANGE)); + rval = 1; + } + ++gargv; + return (rval); +} + +static int +getfloating(long double *dp, int mod_ldbl) +{ + char *ep; + int rval; + + if (!*gargv) { + *dp = 0.0; + return (0); + } + if (**gargv == '"' || **gargv == '\'') { + *dp = asciicode(); + return (0); + } + rval = 0; + errno = 0; + if (mod_ldbl) + *dp = strtold(*gargv, &ep); + else + *dp = strtod(*gargv, &ep); + if (ep == *gargv) { + warnx("%s: expected numeric value", *gargv); + rval = 1; + } else if (*ep != '\0') { + warnx("%s: not completely converted", *gargv); + rval = 1; + } + if (errno == ERANGE) { + warnx("%s: %s", *gargv, strerror(ERANGE)); + rval = 1; + } + ++gargv; + return (rval); +} + +static int +asciicode(void) +{ + int ch; + wchar_t wch; + mbstate_t mbs; + + ch = (unsigned char)**gargv; + if (ch == '\'' || ch == '"') { + memset(&mbs, 0, sizeof(mbs)); + switch (mbrtowc(&wch, *gargv + 1, MB_LEN_MAX, &mbs)) { + case (size_t)-2: + case (size_t)-1: + wch = (unsigned char)gargv[0][1]; + break; + case 0: + wch = 0; + break; + } + ch = wch; + } + ++gargv; + return (ch); +} + +static void +usage(void) +{ + (void)fprintf(stderr, "usage: printf format [arguments ...]\n"); +} diff --git a/bin/catsh/redir.c b/bin/catsh/redir.c new file mode 100644 index 00000000..616053cf --- /dev/null +++ b/bin/catsh/redir.c @@ -0,0 +1,365 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)redir.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/redir.c 326025 2017-11-20 19:49:47Z pfg $"); + +#include <sys/types.h> +#include <sys/stat.h> +#include <signal.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> + +/* + * Code for dealing with input/output redirection. + */ + +#include "shell.h" +#include "nodes.h" +#include "jobs.h" +#include "expand.h" +#include "redir.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "options.h" + + +#define EMPTY -2 /* marks an unused slot in redirtab */ +#define CLOSED -1 /* fd was not open before redir */ + + +struct redirtab { + struct redirtab *next; + int renamed[10]; + int fd0_redirected; + unsigned int empty_redirs; +}; + + +static struct redirtab *redirlist; + +/* + * We keep track of whether or not fd0 has been redirected. This is for + * background commands, where we want to redirect fd0 to /dev/null only + * if it hasn't already been redirected. +*/ +static int fd0_redirected = 0; + +/* Number of redirtabs that have not been allocated. */ +static unsigned int empty_redirs = 0; + +static void openredirect(union node *, char[10 ]); +static int openhere(union node *); + + +/* + * Process a list of redirection commands. If the REDIR_PUSH flag is set, + * old file descriptors are stashed away so that the redirection can be + * undone by calling popredir. If the REDIR_BACKQ flag is set, then the + * standard output, and the standard error if it becomes a duplicate of + * stdout, is saved in memory. +* + * We suppress interrupts so that we won't leave open file + * descriptors around. Because the signal handler remains + * installed and we do not use system call restart, interrupts + * will still abort blocking opens such as fifos (they will fail + * with EINTR). There is, however, a race condition if an interrupt + * arrives after INTOFF and before open blocks. + */ + +void +redirect(union node *redir, int flags) +{ + union node *n; + struct redirtab *sv = NULL; + int i; + int fd; + char memory[10]; /* file descriptors to write to memory */ + + INTOFF; + for (i = 10 ; --i >= 0 ; ) + memory[i] = 0; + memory[1] = flags & REDIR_BACKQ; + if (flags & REDIR_PUSH) { + empty_redirs++; + if (redir != NULL) { + sv = ckmalloc(sizeof (struct redirtab)); + for (i = 0 ; i < 10 ; i++) + sv->renamed[i] = EMPTY; + sv->fd0_redirected = fd0_redirected; + sv->empty_redirs = empty_redirs - 1; + sv->next = redirlist; + redirlist = sv; + empty_redirs = 0; + } + } + for (n = redir ; n ; n = n->nfile.next) { + fd = n->nfile.fd; + if (fd == 0) + fd0_redirected = 1; + if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) && + n->ndup.dupfd == fd) + continue; /* redirect from/to same file descriptor */ + + if ((flags & REDIR_PUSH) && sv->renamed[fd] == EMPTY) { + INTOFF; + if ((i = fcntl(fd, F_DUPFD_CLOEXEC, 10)) == -1) { + switch (errno) { + case EBADF: + i = CLOSED; + break; + default: + INTON; + error("%d: %s", fd, strerror(errno)); + break; + } + } + sv->renamed[fd] = i; + INTON; + } + openredirect(n, memory); + INTON; + INTOFF; + } + if (memory[1]) + out1 = &memout; + if (memory[2]) + out2 = &memout; + INTON; +} + + +static void +openredirect(union node *redir, char memory[10]) +{ + struct stat sb; + int fd = redir->nfile.fd; + const char *fname; + int f; + int e; + + memory[fd] = 0; + switch (redir->nfile.type) { + case NFROM: + fname = redir->nfile.expfname; + if ((f = open(fname, O_RDONLY)) < 0) + error("cannot open %s: %s", fname, strerror(errno)); + break; + case NFROMTO: + fname = redir->nfile.expfname; + if ((f = open(fname, O_RDWR|O_CREAT, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + break; + case NTO: + if (Cflag) { + fname = redir->nfile.expfname; + if (stat(fname, &sb) == -1) { + if ((f = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + } else if (!S_ISREG(sb.st_mode)) { + if ((f = open(fname, O_WRONLY, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + if (fstat(f, &sb) != -1 && S_ISREG(sb.st_mode)) { + close(f); + error("cannot create %s: %s", fname, + strerror(EEXIST)); + } + } else + error("cannot create %s: %s", fname, + strerror(EEXIST)); + break; + } + /* FALLTHROUGH */ + case NCLOBBER: + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + break; + case NAPPEND: + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + break; + case NTOFD: + case NFROMFD: + if (redir->ndup.dupfd >= 0) { /* if not ">&-" */ + if (memory[redir->ndup.dupfd]) + memory[fd] = 1; + else { + if (dup2(redir->ndup.dupfd, fd) < 0) + error("%d: %s", redir->ndup.dupfd, + strerror(errno)); + } + } else { + close(fd); + } + return; + case NHERE: + case NXHERE: + f = openhere(redir); + break; + default: + abort(); + } + if (f != fd) { + if (dup2(f, fd) == -1) { + e = errno; + close(f); + error("%d: %s", fd, strerror(e)); + } + close(f); + } +} + + +/* + * Handle here documents. Normally we fork off a process to write the + * data to a pipe. If the document is short, we can stuff the data in + * the pipe without forking. + */ + +static int +openhere(union node *redir) +{ + const char *p; + int pip[2]; + size_t len = 0; + int flags; + ssize_t written = 0; + + if (pipe(pip) < 0) + error("Pipe call failed: %s", strerror(errno)); + + if (redir->type == NXHERE) + p = redir->nhere.expdoc; + else + p = redir->nhere.doc->narg.text; + len = strlen(p); + if (len == 0) + goto out; + flags = fcntl(pip[1], F_GETFL, 0); + if (flags != -1 && fcntl(pip[1], F_SETFL, flags | O_NONBLOCK) != -1) { + written = write(pip[1], p, len); + if (written < 0) + written = 0; + if ((size_t)written == len) + goto out; + fcntl(pip[1], F_SETFL, flags); + } + + if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) { + close(pip[0]); + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGHUP, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + signal(SIGPIPE, SIG_DFL); + xwrite(pip[1], p + written, len - written); + _exit(0); + } +out: + close(pip[1]); + return pip[0]; +} + + + +/* + * Undo the effects of the last redirection. + */ + +void +popredir(void) +{ + struct redirtab *rp = redirlist; + int i; + + INTOFF; + if (empty_redirs > 0) { + empty_redirs--; + INTON; + return; + } + for (i = 0 ; i < 10 ; i++) { + if (rp->renamed[i] != EMPTY) { + if (rp->renamed[i] >= 0) { + dup2(rp->renamed[i], i); + close(rp->renamed[i]); + } else { + close(i); + } + } + } + fd0_redirected = rp->fd0_redirected; + empty_redirs = rp->empty_redirs; + redirlist = rp->next; + ckfree(rp); + INTON; +} + +/* Return true if fd 0 has already been redirected at least once. */ +int +fd0_redirected_p(void) +{ + return fd0_redirected != 0; +} + +/* + * Discard all saved file descriptors. + */ + +void +clearredir(void) +{ + struct redirtab *rp; + int i; + + for (rp = redirlist ; rp ; rp = rp->next) { + for (i = 0 ; i < 10 ; i++) { + if (rp->renamed[i] >= 0) { + close(rp->renamed[i]); + } + rp->renamed[i] = EMPTY; + } + } +} diff --git a/bin/catsh/redir.h b/bin/catsh/redir.h new file mode 100644 index 00000000..8c171423 --- /dev/null +++ b/bin/catsh/redir.h @@ -0,0 +1,47 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)redir.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/redir.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +/* flags passed to redirect */ +#define REDIR_PUSH 01 /* save previous values of file descriptors */ +#define REDIR_BACKQ 02 /* save the command output in memory */ + +union node; +void redirect(union node *, int); +void popredir(void); +int fd0_redirected_p(void); +void clearredir(void); + diff --git a/bin/catsh/shell.h b/bin/catsh/shell.h new file mode 100644 index 00000000..5e3d60dc --- /dev/null +++ b/bin/catsh/shell.h @@ -0,0 +1,79 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)shell.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/shell.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +#ifndef SHELL_H_ +#define SHELL_H_ + +#include <inttypes.h> + +/* + * The follow should be set to reflect the type of system you have: + * JOBS -> 1 if you have Berkeley job control, 0 otherwise. + * define DEBUG=1 to compile in debugging (set global "debug" to turn on) + * define DEBUG=2 to compile in and turn on debugging. + * + * When debugging is on, debugging info will be written to ./trace and + * a quit signal will generate a core dump. + */ + + +#define JOBS 1 +/* #define DEBUG 1 */ + +/* + * Type of used arithmetics. SUSv3 requires us to have at least signed long. + */ +typedef intmax_t arith_t; +#define ARITH_FORMAT_STR "%" PRIdMAX +#define atoarith_t(arg) strtoimax(arg, NULL, 0) +#define strtoarith_t(nptr, endptr, base) strtoimax(nptr, endptr, base) +#define ARITH_MIN INTMAX_MIN +#define ARITH_MAX INTMAX_MAX + +typedef void *pointer; + +#include <sys/cdefs.h> + +extern char nullstr[1]; /* null string */ + +#ifdef DEBUG +#define TRACE(param) sh_trace param +#else +#define TRACE(param) +#endif + +#endif /* !SHELL_H_ */ diff --git a/bin/catsh/show.c b/bin/catsh/show.c new file mode 100644 index 00000000..e8a55886 --- /dev/null +++ b/bin/catsh/show.c @@ -0,0 +1,410 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)show.c 8.3 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/show.c 326025 2017-11-20 19:49:47Z pfg $"); + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <errno.h> + +#include "shell.h" +#include "parser.h" +#include "nodes.h" +#include "mystring.h" +#include "show.h" + + +#ifdef DEBUG +static void shtree(union node *, int, char *, FILE*); +static void shcmd(union node *, FILE *); +static void sharg(union node *, FILE *); +static void indent(int, char *, FILE *); +static void trstring(char *); + + +void +showtree(union node *n) +{ + trputs("showtree called\n"); + shtree(n, 1, NULL, stdout); +} + + +static void +shtree(union node *n, int ind, char *pfx, FILE *fp) +{ + struct nodelist *lp; + char *s; + + if (n == NULL) + return; + + indent(ind, pfx, fp); + switch(n->type) { + case NSEMI: + s = "; "; + goto binop; + case NAND: + s = " && "; + goto binop; + case NOR: + s = " || "; +binop: + shtree(n->nbinary.ch1, ind, NULL, fp); + /* if (ind < 0) */ + fputs(s, fp); + shtree(n->nbinary.ch2, ind, NULL, fp); + break; + case NCMD: + shcmd(n, fp); + if (ind >= 0) + putc('\n', fp); + break; + case NPIPE: + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + shcmd(lp->n, fp); + if (lp->next) + fputs(" | ", fp); + } + if (n->npipe.backgnd) + fputs(" &", fp); + if (ind >= 0) + putc('\n', fp); + break; + default: + fprintf(fp, "<node type %d>", n->type); + if (ind >= 0) + putc('\n', fp); + break; + } +} + + + +static void +shcmd(union node *cmd, FILE *fp) +{ + union node *np; + int first; + char *s; + int dftfd; + + first = 1; + for (np = cmd->ncmd.args ; np ; np = np->narg.next) { + if (! first) + putchar(' '); + sharg(np, fp); + first = 0; + } + for (np = cmd->ncmd.redirect ; np ; np = np->nfile.next) { + if (! first) + putchar(' '); + switch (np->nfile.type) { + case NTO: s = ">"; dftfd = 1; break; + case NAPPEND: s = ">>"; dftfd = 1; break; + case NTOFD: s = ">&"; dftfd = 1; break; + case NCLOBBER: s = ">|"; dftfd = 1; break; + case NFROM: s = "<"; dftfd = 0; break; + case NFROMTO: s = "<>"; dftfd = 0; break; + case NFROMFD: s = "<&"; dftfd = 0; break; + case NHERE: s = "<<"; dftfd = 0; break; + case NXHERE: s = "<<"; dftfd = 0; break; + default: s = "*error*"; dftfd = 0; break; + } + if (np->nfile.fd != dftfd) + fprintf(fp, "%d", np->nfile.fd); + fputs(s, fp); + if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) { + if (np->ndup.dupfd >= 0) + fprintf(fp, "%d", np->ndup.dupfd); + else + fprintf(fp, "-"); + } else if (np->nfile.type == NHERE) { + fprintf(fp, "HERE"); + } else if (np->nfile.type == NXHERE) { + fprintf(fp, "XHERE"); + } else { + sharg(np->nfile.fname, fp); + } + first = 0; + } +} + + + +static void +sharg(union node *arg, FILE *fp) +{ + char *p; + struct nodelist *bqlist; + int subtype; + + if (arg->type != NARG) { + printf("<node type %d>\n", arg->type); + fflush(stdout); + abort(); + } + bqlist = arg->narg.backquote; + for (p = arg->narg.text ; *p ; p++) { + switch (*p) { + case CTLESC: + putc(*++p, fp); + break; + case CTLVAR: + putc('$', fp); + putc('{', fp); + subtype = *++p; + if (subtype == VSLENGTH) + putc('#', fp); + + while (*p != '=') + putc(*p++, fp); + + if (subtype & VSNUL) + putc(':', fp); + + switch (subtype & VSTYPE) { + case VSNORMAL: + putc('}', fp); + break; + case VSMINUS: + putc('-', fp); + break; + case VSPLUS: + putc('+', fp); + break; + case VSQUESTION: + putc('?', fp); + break; + case VSASSIGN: + putc('=', fp); + break; + case VSTRIMLEFT: + putc('#', fp); + break; + case VSTRIMLEFTMAX: + putc('#', fp); + putc('#', fp); + break; + case VSTRIMRIGHT: + putc('%', fp); + break; + case VSTRIMRIGHTMAX: + putc('%', fp); + putc('%', fp); + break; + case VSLENGTH: + break; + default: + printf("<subtype %d>", subtype); + } + break; + case CTLENDVAR: + putc('}', fp); + break; + case CTLBACKQ: + case CTLBACKQ|CTLQUOTE: + putc('$', fp); + putc('(', fp); + shtree(bqlist->n, -1, NULL, fp); + putc(')', fp); + break; + default: + putc(*p, fp); + break; + } + } +} + + +static void +indent(int amount, char *pfx, FILE *fp) +{ + int i; + + for (i = 0 ; i < amount ; i++) { + if (pfx && i == amount - 1) + fputs(pfx, fp); + putc('\t', fp); + } +} + + +/* + * Debugging stuff. + */ + + +FILE *tracefile; + +#if DEBUG >= 2 +int debug = 1; +#else +int debug = 0; +#endif + + +void +trputc(int c) +{ + if (tracefile == NULL) + return; + putc(c, tracefile); + if (c == '\n') + fflush(tracefile); +} + + +void +sh_trace(const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + if (tracefile != NULL) { + (void) vfprintf(tracefile, fmt, va); + if (strchr(fmt, '\n')) + (void) fflush(tracefile); + } + va_end(va); +} + + +void +trputs(const char *s) +{ + if (tracefile == NULL) + return; + fputs(s, tracefile); + if (strchr(s, '\n')) + fflush(tracefile); +} + + +static void +trstring(char *s) +{ + char *p; + char c; + + if (tracefile == NULL) + return; + putc('"', tracefile); + for (p = s ; *p ; p++) { + switch (*p) { + case '\n': c = 'n'; goto backslash; + case '\t': c = 't'; goto backslash; + case '\r': c = 'r'; goto backslash; + case '"': c = '"'; goto backslash; + case '\\': c = '\\'; goto backslash; + case CTLESC: c = 'e'; goto backslash; + case CTLVAR: c = 'v'; goto backslash; + case CTLVAR+CTLQUOTE: c = 'V'; goto backslash; + case CTLBACKQ: c = 'q'; goto backslash; + case CTLBACKQ+CTLQUOTE: c = 'Q'; goto backslash; +backslash: putc('\\', tracefile); + putc(c, tracefile); + break; + default: + if (*p >= ' ' && *p <= '~') + putc(*p, tracefile); + else { + putc('\\', tracefile); + putc(*p >> 6 & 03, tracefile); + putc(*p >> 3 & 07, tracefile); + putc(*p & 07, tracefile); + } + break; + } + } + putc('"', tracefile); +} + + +void +trargs(char **ap) +{ + if (tracefile == NULL) + return; + while (*ap) { + trstring(*ap++); + if (*ap) + putc(' ', tracefile); + else + putc('\n', tracefile); + } + fflush(tracefile); +} + + +void +opentrace(void) +{ + char s[100]; + int flags; + + if (!debug) + return; +#ifdef not_this_way + { + char *p; + if ((p = getenv("HOME")) == NULL) { + if (geteuid() == 0) + p = "/"; + else + p = "/tmp"; + } + strcpy(s, p); + strcat(s, "/trace"); + } +#else + strcpy(s, "./trace"); +#endif /* not_this_way */ + if ((tracefile = fopen(s, "a")) == NULL) { + fprintf(stderr, "Can't open %s: %s\n", s, strerror(errno)); + return; + } + if ((flags = fcntl(fileno(tracefile), F_GETFL, 0)) >= 0) + fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND); + fputs("\nTracing started.\n", tracefile); + fflush(tracefile); +} +#endif /* DEBUG */ diff --git a/bin/catsh/show.h b/bin/catsh/show.h new file mode 100644 index 00000000..587c5ada --- /dev/null +++ b/bin/catsh/show.h @@ -0,0 +1,42 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1995 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)show.h 1.1 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/show.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +void showtree(union node *); +#ifdef DEBUG +void sh_trace(const char *, ...) __printflike(1, 2); +void trargs(char **); +void trputc(int); +void trputs(const char *); +void opentrace(void); +#endif diff --git a/bin/catsh/test.c b/bin/catsh/test.c new file mode 100644 index 00000000..a153cdb3 --- /dev/null +++ b/bin/catsh/test.c @@ -0,0 +1,630 @@ +/* $NetBSD: test.c,v 1.21 1999/04/05 09:48:38 kleink Exp $ */ + +/*- + * test(1); version 7-like -- author Erik Baalbergen + * modified by Eric Gisin to be used as built-in. + * modified by Arnold Robbins to add SVR3 compatibility + * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). + * modified by J.T. Conklin for NetBSD. + * + * This program is in the Public Domain. + */ +/* + * Important: This file is used both as a standalone program /bin/test and + * as a builtin for /bin/sh (#define SHELL). + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/test/test.c 298232 2016-04-19 00:38:07Z araujo $"); + +#include <sys/types.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef SHELL +#define main testcmd +#include "bltin.h" +#else +#include <locale.h> + +static void error(const char *, ...) __dead2 __printf0like(1, 2); + +static void +error(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + verrx(2, msg, ap); + /*NOTREACHED*/ + va_end(ap); +} +#endif + +/* test(1) accepts the following grammar: + oexpr ::= aexpr | aexpr "-o" oexpr ; + aexpr ::= nexpr | nexpr "-a" aexpr ; + nexpr ::= primary | "!" primary + primary ::= unary-operator operand + | operand binary-operator operand + | operand + | "(" oexpr ")" + ; + unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| + "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; + + binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| + "-nt"|"-ot"|"-ef"; + operand ::= <any legal UNIX file name> +*/ + +enum token_types { + UNOP = 0x100, + BINOP = 0x200, + BUNOP = 0x300, + BBINOP = 0x400, + PAREN = 0x500 +}; + +enum token { + EOI, + OPERAND, + FILRD = UNOP + 1, + FILWR, + FILEX, + FILEXIST, + FILREG, + FILDIR, + FILCDEV, + FILBDEV, + FILFIFO, + FILSOCK, + FILSYM, + FILGZ, + FILTT, + FILSUID, + FILSGID, + FILSTCK, + STREZ, + STRNZ, + FILUID, + FILGID, + FILNT = BINOP + 1, + FILOT, + FILEQ, + STREQ, + STRNE, + STRLT, + STRGT, + INTEQ, + INTNE, + INTGE, + INTGT, + INTLE, + INTLT, + UNOT = BUNOP + 1, + BAND = BBINOP + 1, + BOR, + LPAREN = PAREN + 1, + RPAREN +}; + +#define TOKEN_TYPE(token) ((token) & 0xff00) + +static const struct t_op { + char op_text[2]; + short op_num; +} ops1[] = { + {"=", STREQ}, + {"<", STRLT}, + {">", STRGT}, + {"!", UNOT}, + {"(", LPAREN}, + {")", RPAREN}, +}, opsm1[] = { + {"r", FILRD}, + {"w", FILWR}, + {"x", FILEX}, + {"e", FILEXIST}, + {"f", FILREG}, + {"d", FILDIR}, + {"c", FILCDEV}, + {"b", FILBDEV}, + {"p", FILFIFO}, + {"u", FILSUID}, + {"g", FILSGID}, + {"k", FILSTCK}, + {"s", FILGZ}, + {"t", FILTT}, + {"z", STREZ}, + {"n", STRNZ}, + {"h", FILSYM}, /* for backwards compat */ + {"O", FILUID}, + {"G", FILGID}, + {"L", FILSYM}, + {"S", FILSOCK}, + {"a", BAND}, + {"o", BOR}, +}, ops2[] = { + {"==", STREQ}, + {"!=", STRNE}, +}, opsm2[] = { + {"eq", INTEQ}, + {"ne", INTNE}, + {"ge", INTGE}, + {"gt", INTGT}, + {"le", INTLE}, + {"lt", INTLT}, + {"nt", FILNT}, + {"ot", FILOT}, + {"ef", FILEQ}, +}; + +static int nargc; +static char **t_wp; +static int parenlevel; + +static int aexpr(enum token); +static int binop(enum token); +static int equalf(const char *, const char *); +static int filstat(char *, enum token); +static int getn(const char *); +static intmax_t getq(const char *); +static int intcmp(const char *, const char *); +static int isunopoperand(void); +static int islparenoperand(void); +static int isrparenoperand(void); +static int newerf(const char *, const char *); +static int nexpr(enum token); +static int oexpr(enum token); +static int olderf(const char *, const char *); +static int primary(enum token); +static void syntax(const char *, const char *); +static enum token t_lex(char *); + +int +main(int argc, char **argv) +{ + int res; + char *p; + + if ((p = strrchr(argv[0], '/')) == NULL) + p = argv[0]; + else + p++; + if (strcmp(p, "[") == 0) { + if (strcmp(argv[--argc], "]") != 0) + error("missing ]"); + argv[argc] = NULL; + } + + /* no expression => false */ + if (--argc <= 0) + return 1; + +#ifndef SHELL + (void)setlocale(LC_CTYPE, ""); +#endif + nargc = argc; + t_wp = &argv[1]; + parenlevel = 0; + if (nargc == 4 && strcmp(*t_wp, "!") == 0) { + /* Things like ! "" -o x do not fit in the normal grammar. */ + --nargc; + ++t_wp; + res = oexpr(t_lex(*t_wp)); + } else + res = !oexpr(t_lex(*t_wp)); + + if (--nargc > 0) + syntax(*t_wp, "unexpected operator"); + + return res; +} + +static void +syntax(const char *op, const char *msg) +{ + + if (op && *op) + error("%s: %s", op, msg); + else + error("%s", msg); +} + +static int +oexpr(enum token n) +{ + int res; + + res = aexpr(n); + if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BOR) + return oexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) || + res; + t_wp--; + nargc++; + return res; +} + +static int +aexpr(enum token n) +{ + int res; + + res = nexpr(n); + if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BAND) + return aexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) && + res; + t_wp--; + nargc++; + return res; +} + +static int +nexpr(enum token n) +{ + if (n == UNOT) + return !nexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)); + return primary(n); +} + +static int +primary(enum token n) +{ + enum token nn; + int res; + + if (n == EOI) + return 0; /* missing expression */ + if (n == LPAREN) { + parenlevel++; + if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) == + RPAREN) { + parenlevel--; + return 0; /* missing expression */ + } + res = oexpr(nn); + if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN) + syntax(NULL, "closing paren expected"); + parenlevel--; + return res; + } + if (TOKEN_TYPE(n) == UNOP) { + /* unary expression */ + if (--nargc == 0) + syntax(NULL, "argument expected"); /* impossible */ + switch (n) { + case STREZ: + return strlen(*++t_wp) == 0; + case STRNZ: + return strlen(*++t_wp) != 0; + case FILTT: + return isatty(getn(*++t_wp)); + default: + return filstat(*++t_wp, n); + } + } + + nn = t_lex(nargc > 0 ? t_wp[1] : NULL); + if (TOKEN_TYPE(nn) == BINOP) + return binop(nn); + + return strlen(*t_wp) > 0; +} + +static int +binop(enum token n) +{ + const char *opnd1, *op, *opnd2; + + opnd1 = *t_wp; + op = nargc > 0 ? (--nargc, *++t_wp) : NULL; + + if ((opnd2 = nargc > 0 ? (--nargc, *++t_wp) : NULL) == NULL) + syntax(op, "argument expected"); + + switch (n) { + case STREQ: + return strcmp(opnd1, opnd2) == 0; + case STRNE: + return strcmp(opnd1, opnd2) != 0; + case STRLT: + return strcmp(opnd1, opnd2) < 0; + case STRGT: + return strcmp(opnd1, opnd2) > 0; + case INTEQ: + return intcmp(opnd1, opnd2) == 0; + case INTNE: + return intcmp(opnd1, opnd2) != 0; + case INTGE: + return intcmp(opnd1, opnd2) >= 0; + case INTGT: + return intcmp(opnd1, opnd2) > 0; + case INTLE: + return intcmp(opnd1, opnd2) <= 0; + case INTLT: + return intcmp(opnd1, opnd2) < 0; + case FILNT: + return newerf (opnd1, opnd2); + case FILOT: + return olderf (opnd1, opnd2); + case FILEQ: + return equalf (opnd1, opnd2); + default: + abort(); + /* NOTREACHED */ + } +} + +static int +filstat(char *nm, enum token mode) +{ + struct stat s; + + if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) + return 0; + + switch (mode) { + case FILRD: + return (faccessat(AT_FDCWD, nm, R_OK, AT_EACCESS) == 0); + case FILWR: + return (faccessat(AT_FDCWD, nm, W_OK, AT_EACCESS) == 0); + case FILEX: + /* XXX work around eaccess(2) false positives for superuser */ + if (faccessat(AT_FDCWD, nm, X_OK, AT_EACCESS) != 0) + return 0; + if (S_ISDIR(s.st_mode) || geteuid() != 0) + return 1; + return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0; + case FILEXIST: + return (faccessat(AT_FDCWD, nm, F_OK, AT_EACCESS) == 0); + case FILREG: + return S_ISREG(s.st_mode); + case FILDIR: + return S_ISDIR(s.st_mode); + case FILCDEV: + return S_ISCHR(s.st_mode); + case FILBDEV: + return S_ISBLK(s.st_mode); + case FILFIFO: + return S_ISFIFO(s.st_mode); + case FILSOCK: + return S_ISSOCK(s.st_mode); + case FILSYM: + return S_ISLNK(s.st_mode); + case FILSUID: + return (s.st_mode & S_ISUID) != 0; + case FILSGID: + return (s.st_mode & S_ISGID) != 0; + case FILSTCK: + return (s.st_mode & S_ISVTX) != 0; + case FILGZ: + return s.st_size > (off_t)0; + case FILUID: + return s.st_uid == geteuid(); + case FILGID: + return s.st_gid == getegid(); + default: + return 1; + } +} + +static int +find_op_1char(const struct t_op *op, const struct t_op *end, const char *s) +{ + char c; + + c = s[0]; + while (op != end) { + if (c == *op->op_text) + return op->op_num; + op++; + } + return OPERAND; +} + +static int +find_op_2char(const struct t_op *op, const struct t_op *end, const char *s) +{ + while (op != end) { + if (s[0] == op->op_text[0] && s[1] == op->op_text[1]) + return op->op_num; + op++; + } + return OPERAND; +} + +static int +find_op(const char *s) +{ + if (s[0] == '\0') + return OPERAND; + else if (s[1] == '\0') + return find_op_1char(ops1, (&ops1)[1], s); + else if (s[2] == '\0') + return s[0] == '-' ? find_op_1char(opsm1, (&opsm1)[1], s + 1) : + find_op_2char(ops2, (&ops2)[1], s); + else if (s[3] == '\0') + return s[0] == '-' ? find_op_2char(opsm2, (&opsm2)[1], s + 1) : + OPERAND; + else + return OPERAND; +} + +static enum token +t_lex(char *s) +{ + int num; + + if (s == NULL) { + return EOI; + } + num = find_op(s); + if (((TOKEN_TYPE(num) == UNOP || TOKEN_TYPE(num) == BUNOP) + && isunopoperand()) || + (num == LPAREN && islparenoperand()) || + (num == RPAREN && isrparenoperand())) + return OPERAND; + return num; +} + +static int +isunopoperand(void) +{ + char *s; + char *t; + int num; + + if (nargc == 1) + return 1; + s = *(t_wp + 1); + if (nargc == 2) + return parenlevel == 1 && strcmp(s, ")") == 0; + t = *(t_wp + 2); + num = find_op(s); + return TOKEN_TYPE(num) == BINOP && + (parenlevel == 0 || t[0] != ')' || t[1] != '\0'); +} + +static int +islparenoperand(void) +{ + char *s; + int num; + + if (nargc == 1) + return 1; + s = *(t_wp + 1); + if (nargc == 2) + return parenlevel == 1 && strcmp(s, ")") == 0; + if (nargc != 3) + return 0; + num = find_op(s); + return TOKEN_TYPE(num) == BINOP; +} + +static int +isrparenoperand(void) +{ + char *s; + + if (nargc == 1) + return 0; + s = *(t_wp + 1); + if (nargc == 2) + return parenlevel == 1 && strcmp(s, ")") == 0; + return 0; +} + +/* atoi with error detection */ +static int +getn(const char *s) +{ + char *p; + long r; + + errno = 0; + r = strtol(s, &p, 10); + + if (s == p) + error("%s: bad number", s); + + if (errno != 0) + error((errno == EINVAL) ? "%s: bad number" : + "%s: out of range", s); + + while (isspace((unsigned char)*p)) + p++; + + if (*p) + error("%s: bad number", s); + + return (int) r; +} + +/* atoi with error detection and 64 bit range */ +static intmax_t +getq(const char *s) +{ + char *p; + intmax_t r; + + errno = 0; + r = strtoimax(s, &p, 10); + + if (s == p) + error("%s: bad number", s); + + if (errno != 0) + error((errno == EINVAL) ? "%s: bad number" : + "%s: out of range", s); + + while (isspace((unsigned char)*p)) + p++; + + if (*p) + error("%s: bad number", s); + + return r; +} + +static int +intcmp (const char *s1, const char *s2) +{ + intmax_t q1, q2; + + + q1 = getq(s1); + q2 = getq(s2); + + if (q1 > q2) + return 1; + + if (q1 < q2) + return -1; + + return 0; +} + +static int +newerf (const char *f1, const char *f2) +{ + struct stat b1, b2; + + if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0) + return 0; + + if (b1.st_mtimespec.tv_sec > b2.st_mtimespec.tv_sec) + return 1; + if (b1.st_mtimespec.tv_sec < b2.st_mtimespec.tv_sec) + return 0; + + return (b1.st_mtimespec.tv_nsec > b2.st_mtimespec.tv_nsec); +} + +static int +olderf (const char *f1, const char *f2) +{ + return (newerf(f2, f1)); +} + +static int +equalf (const char *f1, const char *f2) +{ + struct stat b1, b2; + + return (stat (f1, &b1) == 0 && + stat (f2, &b2) == 0 && + b1.st_dev == b2.st_dev && + b1.st_ino == b2.st_ino); +} diff --git a/bin/catsh/trap.c b/bin/catsh/trap.c new file mode 100644 index 00000000..93c063e1 --- /dev/null +++ b/bin/catsh/trap.c @@ -0,0 +1,553 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)trap.c 8.5 (Berkeley) 6/5/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/trap.c 326025 2017-11-20 19:49:47Z pfg $"); + +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> + +#include "shell.h" +#include "main.h" +#include "nodes.h" /* for other headers */ +#include "eval.h" +#include "jobs.h" +#include "show.h" +#include "options.h" +#include "syntax.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "trap.h" +#include "mystring.h" +#include "builtins.h" +#include "myhistedit.h" + + +/* + * Sigmode records the current value of the signal handlers for the various + * modes. A value of zero means that the current handler is not known. + * S_HARD_IGN indicates that the signal was ignored on entry to the shell, + */ + +#define S_DFL 1 /* default signal handling (SIG_DFL) */ +#define S_CATCH 2 /* signal is caught */ +#define S_IGN 3 /* signal is ignored (SIG_IGN) */ +#define S_HARD_IGN 4 /* signal is ignored permanently */ +#define S_RESET 5 /* temporary - to reset a hard ignored sig */ + + +static char sigmode[NSIG]; /* current value of signal */ +volatile sig_atomic_t pendingsig; /* indicates some signal received */ +volatile sig_atomic_t pendingsig_waitcmd; /* indicates wait builtin should be interrupted */ +static int in_dotrap; /* do we execute in a trap handler? */ +static char *volatile trap[NSIG]; /* trap handler commands */ +static volatile sig_atomic_t gotsig[NSIG]; + /* indicates specified signal received */ +static int ignore_sigchld; /* Used while handling SIGCHLD traps. */ +static int last_trapsig; + +static int exiting; /* exitshell() has been called */ +static int exiting_exitstatus; /* value passed to exitshell() */ + +static int getsigaction(int, sig_t *); + + +/* + * Map a string to a signal number. + * + * Note: the signal number may exceed NSIG. + */ +static int +sigstring_to_signum(char *sig) +{ + + if (is_number(sig)) { + int signo; + + signo = atoi(sig); + return ((signo >= 0 && signo < NSIG) ? signo : (-1)); + } else if (strcasecmp(sig, "EXIT") == 0) { + return (0); + } else { + int n; + + if (strncasecmp(sig, "SIG", 3) == 0) + sig += 3; + for (n = 1; n < NSIG; n++) + if (sys_signame[n] && + strcasecmp(sys_signame[n], sig) == 0) + return (n); + } + return (-1); +} + + +/* + * Print a list of valid signal names. + */ +static void +printsignals(void) +{ + int n, outlen; + + outlen = 0; + for (n = 1; n < NSIG; n++) { + if (sys_signame[n]) { + out1fmt("%s", sys_signame[n]); + outlen += strlen(sys_signame[n]); + } else { + out1fmt("%d", n); + outlen += 3; /* good enough */ + } + ++outlen; + if (outlen > 71 || n == NSIG - 1) { + out1str("\n"); + outlen = 0; + } else { + out1c(' '); + } + } +} + + +/* + * The trap builtin. + */ +int +trapcmd(int argc __unused, char **argv) +{ + char *action; + int signo; + int errors = 0; + int i; + + while ((i = nextopt("l")) != '\0') { + switch (i) { + case 'l': + printsignals(); + return (0); + } + } + argv = argptr; + + if (*argv == NULL) { + for (signo = 0 ; signo < NSIG ; signo++) { + if (signo < NSIG && trap[signo] != NULL) { + out1str("trap -- "); + out1qstr(trap[signo]); + if (signo == 0) { + out1str(" EXIT\n"); + } else if (sys_signame[signo]) { + out1fmt(" %s\n", sys_signame[signo]); + } else { + out1fmt(" %d\n", signo); + } + } + } + return 0; + } + action = NULL; + if (*argv && !is_number(*argv)) { + if (strcmp(*argv, "-") == 0) + argv++; + else { + action = *argv; + argv++; + } + } + for (; *argv; argv++) { + if ((signo = sigstring_to_signum(*argv)) == -1) { + warning("bad signal %s", *argv); + errors = 1; + continue; + } + INTOFF; + if (action) + action = savestr(action); + if (trap[signo]) + ckfree(trap[signo]); + trap[signo] = action; + if (signo != 0) + setsignal(signo); + INTON; + } + return errors; +} + + +/* + * Clear traps on a fork. + */ +void +clear_traps(void) +{ + char *volatile *tp; + + for (tp = trap ; tp <= &trap[NSIG - 1] ; tp++) { + if (*tp && **tp) { /* trap not NULL or SIG_IGN */ + INTOFF; + ckfree(*tp); + *tp = NULL; + if (tp != &trap[0]) + setsignal(tp - trap); + INTON; + } + } +} + + +/* + * Check if we have any traps enabled. + */ +int +have_traps(void) +{ + char *volatile *tp; + + for (tp = trap ; tp <= &trap[NSIG - 1] ; tp++) { + if (*tp && **tp) /* trap not NULL or SIG_IGN */ + return 1; + } + return 0; +} + +/* + * Set the signal handler for the specified signal. The routine figures + * out what it should be set to. + */ +void +setsignal(int signo) +{ + int action; + sig_t sigact = SIG_DFL; + struct sigaction sa; + char *t; + + if ((t = trap[signo]) == NULL) + action = S_DFL; + else if (*t != '\0') + action = S_CATCH; + else + action = S_IGN; + if (action == S_DFL) { + switch (signo) { + case SIGINT: + action = S_CATCH; + break; + case SIGQUIT: +#ifdef DEBUG + { + extern int debug; + + if (debug) + break; + } +#endif + action = S_CATCH; + break; + case SIGTERM: + if (rootshell && iflag) + action = S_IGN; + break; +#if JOBS + case SIGTSTP: + case SIGTTOU: + if (rootshell && mflag) + action = S_IGN; + break; +#endif + } + } + + t = &sigmode[signo]; + if (*t == 0) { + /* + * current setting unknown + */ + if (!getsigaction(signo, &sigact)) { + /* + * Pretend it worked; maybe we should give a warning + * here, but other shells don't. We don't alter + * sigmode, so that we retry every time. + */ + return; + } + if (sigact == SIG_IGN) { + if (mflag && (signo == SIGTSTP || + signo == SIGTTIN || signo == SIGTTOU)) { + *t = S_IGN; /* don't hard ignore these */ + } else + *t = S_HARD_IGN; + } else { + *t = S_RESET; /* force to be set */ + } + } + if (*t == S_HARD_IGN || *t == action) + return; + switch (action) { + case S_DFL: sigact = SIG_DFL; break; + case S_CATCH: sigact = onsig; break; + case S_IGN: sigact = SIG_IGN; break; + } + *t = action; + sa.sa_handler = sigact; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(signo, &sa, NULL); +} + + +/* + * Return the current setting for sig w/o changing it. + */ +static int +getsigaction(int signo, sig_t *sigact) +{ + struct sigaction sa; + + if (sigaction(signo, (struct sigaction *)0, &sa) == -1) + return 0; + *sigact = (sig_t) sa.sa_handler; + return 1; +} + + +/* + * Ignore a signal. + */ +void +ignoresig(int signo) +{ + + if (sigmode[signo] == 0) + setsignal(signo); + if (sigmode[signo] != S_IGN && sigmode[signo] != S_HARD_IGN) { + signal(signo, SIG_IGN); + sigmode[signo] = S_IGN; + } +} + + +int +issigchldtrapped(void) +{ + + return (trap[SIGCHLD] != NULL && *trap[SIGCHLD] != '\0'); +} + + +/* + * Signal handler. + */ +void +onsig(int signo) +{ + + if (signo == SIGINT && trap[SIGINT] == NULL) { + /* + * The !in_dotrap here is safe. The only way we can arrive + * here with in_dotrap set is that a trap handler set SIGINT to + * SIG_DFL and killed itself. + */ + if (suppressint && !in_dotrap) + SET_PENDING_INT; + else + onint(); + return; + } + + /* If we are currently in a wait builtin, prepare to break it */ + if (signo == SIGINT || signo == SIGQUIT) + pendingsig_waitcmd = signo; + + if (trap[signo] != NULL && trap[signo][0] != '\0' && + (signo != SIGCHLD || !ignore_sigchld)) { + gotsig[signo] = 1; + pendingsig = signo; + pendingsig_waitcmd = signo; + } +} + + +/* + * Called to execute a trap. Perhaps we should avoid entering new trap + * handlers while we are executing a trap handler. + */ +void +dotrap(void) +{ + struct stackmark smark; + int i; + int savestatus, prev_evalskip, prev_skipcount; + + in_dotrap++; + for (;;) { + pendingsig = 0; + pendingsig_waitcmd = 0; + for (i = 1; i < NSIG; i++) { + if (gotsig[i]) { + gotsig[i] = 0; + if (trap[i]) { + /* + * Ignore SIGCHLD to avoid infinite + * recursion if the trap action does + * a fork. + */ + if (i == SIGCHLD) + ignore_sigchld++; + + /* + * Backup current evalskip + * state and reset it before + * executing a trap, so that the + * trap is not disturbed by an + * ongoing break/continue/return + * statement. + */ + prev_evalskip = evalskip; + prev_skipcount = skipcount; + evalskip = 0; + + last_trapsig = i; + savestatus = exitstatus; + setstackmark(&smark); + evalstring(stsavestr(trap[i]), 0); + popstackmark(&smark); + + /* + * If such a command was not + * already in progress, allow a + * break/continue/return in the + * trap action to have an effect + * outside of it. + */ + if (evalskip == 0 || + prev_evalskip != 0) { + evalskip = prev_evalskip; + skipcount = prev_skipcount; + exitstatus = savestatus; + } + + if (i == SIGCHLD) + ignore_sigchld--; + } + break; + } + } + if (i >= NSIG) + break; + } + in_dotrap--; +} + + +/* + * Controls whether the shell is interactive or not based on iflag. + */ +void +setinteractive(void) +{ + setsignal(SIGINT); + setsignal(SIGQUIT); + setsignal(SIGTERM); +} + + +/* + * Called to exit the shell. + */ +void +exitshell(int status) +{ + TRACE(("exitshell(%d) pid=%d\n", status, getpid())); + exiting = 1; + exiting_exitstatus = status; + exitshell_savedstatus(); +} + +void +exitshell_savedstatus(void) +{ + struct jmploc loc1, loc2; + char *p; + int sig = 0; + sigset_t sigs; + + if (!exiting) { + if (in_dotrap && last_trapsig) { + sig = last_trapsig; + exiting_exitstatus = sig + 128; + } else + exiting_exitstatus = oexitstatus; + } + exitstatus = oexitstatus = exiting_exitstatus; + if (!setjmp(loc1.loc)) { + handler = &loc1; + if ((p = trap[0]) != NULL && *p != '\0') { + /* + * Reset evalskip, or the trap on EXIT could be + * interrupted if the last command was a "return". + */ + evalskip = 0; + trap[0] = NULL; + FORCEINTON; + evalstring(p, 0); + } + } + if (!setjmp(loc2.loc)) { + handler = &loc2; /* probably unnecessary */ + FORCEINTON; + flushall(); +#if JOBS + setjobctl(0); +#endif + } + if (sig != 0 && sig != SIGSTOP && sig != SIGTSTP && sig != SIGTTIN && + sig != SIGTTOU) { + signal(sig, SIG_DFL); + sigemptyset(&sigs); + sigaddset(&sigs, sig); + sigprocmask(SIG_UNBLOCK, &sigs, NULL); + kill(getpid(), sig); + /* If the default action is to ignore, fall back to _exit(). */ + } + _exit(exiting_exitstatus); +} diff --git a/bin/catsh/trap.h b/bin/catsh/trap.h new file mode 100644 index 00000000..2898c594 --- /dev/null +++ b/bin/catsh/trap.h @@ -0,0 +1,50 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)trap.h 8.3 (Berkeley) 6/5/95 + * $FreeBSD: releng/12.0/bin/sh/trap.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +extern volatile sig_atomic_t pendingsig; +extern volatile sig_atomic_t pendingsig_waitcmd; + +void clear_traps(void); +int have_traps(void); +void setsignal(int); +void ignoresig(int); +int issigchldtrapped(void); +void onsig(int); +void dotrap(void); +void setinteractive(void); +void exitshell(int) __dead2; +void exitshell_savedstatus(void) __dead2; diff --git a/bin/catsh/var.c b/bin/catsh/var.c new file mode 100644 index 00000000..4e486e64 --- /dev/null +++ b/bin/catsh/var.c @@ -0,0 +1,995 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.0/bin/sh/var.c 329221 2018-02-13 16:48:57Z bdrewery $"); + +#include <unistd.h> +#include <stdlib.h> +#include <paths.h> + +/* + * Shell variables. + */ + +#include <locale.h> +#include <langinfo.h> + +#include "shell.h" +#include "output.h" +#include "expand.h" +#include "nodes.h" /* for other headers */ +#include "eval.h" /* defines cmdenviron */ +#include "exec.h" +#include "syntax.h" +#include "options.h" +#include "mail.h" +#include "var.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "parser.h" +#include "builtins.h" +#ifndef NO_HISTORY +#include "myhistedit.h" +#endif + + +#ifndef VTABSIZE +#define VTABSIZE 39 +#endif + + +struct varinit { + struct var *var; + int flags; + const char *text; + void (*func)(const char *); +}; + + +#ifndef NO_HISTORY +struct var vhistfile; +struct var vhistsize; +struct var vpslit; +struct var vterm; +#endif +struct var venv; +struct var vifs; +struct var vmail; +struct var vmpath; +struct var vpath; +struct var vps0; +struct var vps1; +struct var vps2; +struct var vps4; +struct var vrps1; +struct var vrps2; +static struct var voptind; +struct var vdisvfork; + +struct localvar *localvars; +int forcelocal; + +static const struct varinit varinit[] = { + { + &venv, 0, + "ENV=${XDG_CONFIG_HOME:-${HOME}/.config}/catsh/env.sh", + NULL, + }, +#ifndef NO_HISTORY + { + &vhistfile, 0, + "HISTFILE=${XDG_DATA_HOME:-${HOME}/.local/share}/catsh/history", + sethistfile, + }, + { &vhistsize, VUNSET, "HISTSIZE=", + sethistsize }, +#endif + { &vifs, 0, "IFS= \t\n", + NULL }, + { &vmail, VUNSET, "MAIL=", + NULL }, + { &vmpath, VUNSET, "MAILPATH=", + NULL }, + { &vpath, 0, "PATH=" _PATH_DEFPATH, + changepath }, + { &vps0, VUNSET, "PS0=", + NULL }, + /* + * vps1 depends on uid + */ + { &vps2, 0, "PS2=> ", + NULL }, + { &vps4, 0, "PS4=+ ", + NULL }, + { &vrps1, VUNSET, "RPS1=", + NULL }, + { &vrps2, VUNSET, "RPS2=", + NULL }, +#ifndef NO_HISTORY + { &vpslit, VUNSET, "PSlit=", + setpslit }, + { &vterm, VUNSET, "TERM=", + setterm }, +#endif + { &voptind, 0, "OPTIND=1", + getoptsreset }, + { &vdisvfork, VUNSET, "SH_DISABLE_VFORK=", + NULL }, + { NULL, 0, NULL, + NULL } +}; + +static struct var *vartab[VTABSIZE]; + +static const char *const locale_names[7] = { + "LC_COLLATE", "LC_CTYPE", "LC_MONETARY", + "LC_NUMERIC", "LC_TIME", "LC_MESSAGES", NULL +}; +static const int locale_categories[7] = { + LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME, LC_MESSAGES, 0 +}; + +static int varequal(const char *, const char *); +static struct var *find_var(const char *, struct var ***, int *); +static int localevar(const char *); +static void setvareq_const(const char *s, int flags); + +extern char **environ; + +/* + * This routine initializes the builtin variables and imports the environment. + * It is called when the shell is initialized. + */ + +void +initvar(void) +{ + char ppid[20]; + const struct varinit *ip; + struct var *vp; + struct var **vpp; + char **envp; + + for (ip = varinit ; (vp = ip->var) != NULL ; ip++) { + if (find_var(ip->text, &vpp, &vp->name_len) != NULL) + continue; + vp->next = *vpp; + *vpp = vp; + vp->text = __DECONST(char *, ip->text); + vp->flags = ip->flags | VSTRFIXED | VTEXTFIXED; + vp->func = ip->func; + } + /* + * PS1 depends on uid + */ + if (find_var("PS1", &vpp, &vps1.name_len) == NULL) { + vps1.next = *vpp; + *vpp = &vps1; + vps1.text = __DECONST(char *, geteuid() ? "PS1=$ " : "PS1=# "); + vps1.flags = VSTRFIXED|VTEXTFIXED; + } + fmtstr(ppid, sizeof(ppid), "%d", (int)getppid()); + setvarsafe("PPID", ppid, 0); + for (envp = environ ; *envp ; envp++) { + if (strchr(*envp, '=')) { + setvareq(*envp, VEXPORT|VTEXTFIXED); + } + } + setvareq_const("OPTIND=1", 0); + setvareq_const("IFS= \t\n", 0); +} + +/* + * Safe version of setvar, returns 1 on success 0 on failure. + */ + +int +setvarsafe(const char *name, const char *val, int flags) +{ + struct jmploc jmploc; + struct jmploc *const savehandler = handler; + int err = 0; + int inton; + + inton = is_int_on(); + if (setjmp(jmploc.loc)) + err = 1; + else { + handler = &jmploc; + setvar(name, val, flags); + } + handler = savehandler; + SETINTON(inton); + return err; +} + +/* + * Set the value of a variable. The flags argument is stored with the + * flags of the variable. If val is NULL, the variable is unset. + */ + +void +setvar(const char *name, const char *val, int flags) +{ + const char *p; + size_t len; + size_t namelen; + size_t vallen; + char *nameeq; + int isbad; + + isbad = 0; + p = name; + if (! is_name(*p)) + isbad = 1; + p++; + for (;;) { + if (! is_in_name(*p)) { + if (*p == '\0' || *p == '=') + break; + isbad = 1; + } + p++; + } + namelen = p - name; + if (isbad) + error("%.*s: bad variable name", (int)namelen, name); + len = namelen + 2; /* 2 is space for '=' and '\0' */ + if (val == NULL) { + flags |= VUNSET; + vallen = 0; + } else { + vallen = strlen(val); + len += vallen; + } + INTOFF; + nameeq = ckmalloc(len); + memcpy(nameeq, name, namelen); + nameeq[namelen] = '='; + if (val) + memcpy(nameeq + namelen + 1, val, vallen + 1); + else + nameeq[namelen + 1] = '\0'; + setvareq(nameeq, flags); + INTON; +} + +static int +localevar(const char *s) +{ + const char *const *ss; + + if (*s != 'L') + return 0; + if (varequal(s + 1, "ANG")) + return 1; + if (strncmp(s + 1, "C_", 2) != 0) + return 0; + if (varequal(s + 3, "ALL")) + return 1; + for (ss = locale_names; *ss ; ss++) + if (varequal(s + 3, *ss + 3)) + return 1; + return 0; +} + + +/* + * Sets/unsets an environment variable from a pointer that may actually be a + * pointer into environ where the string should not be manipulated. + */ +static void +change_env(const char *s, int set) +{ + char *eqp; + char *ss; + + INTOFF; + ss = savestr(s); + if ((eqp = strchr(ss, '=')) != NULL) + *eqp = '\0'; + if (set && eqp != NULL) + (void) setenv(ss, eqp + 1, 1); + else + (void) unsetenv(ss); + ckfree(ss); + INTON; + + return; +} + + +/* + * Same as setvar except that the variable and value are passed in + * the first argument as name=value. Since the first argument will + * be actually stored in the table, it should not be a string that + * will go away. + */ + +void +setvareq(char *s, int flags) +{ + struct var *vp, **vpp; + int nlen; + + if (aflag) + flags |= VEXPORT; + if (forcelocal && !(flags & (VNOSET | VNOLOCAL))) + mklocal(s); + vp = find_var(s, &vpp, &nlen); + if (vp != NULL) { + if (vp->flags & VREADONLY) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + error("%.*s: is read only", vp->name_len, vp->text); + } + if (flags & VNOSET) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + return; + } + INTOFF; + + if (vp->func && (flags & VNOFUNC) == 0) + (*vp->func)(s + vp->name_len + 1); + + if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(vp->text); + + vp->flags &= ~(VTEXTFIXED|VSTACK|VUNSET); + vp->flags |= flags; + vp->text = s; + + /* + * We could roll this to a function, to handle it as + * a regular variable function callback, but why bother? + * + * Note: this assumes iflag is not set to 1 initially. + * As part of initvar(), this is called before arguments + * are looked at. + */ + if ((vp == &vmpath || (vp == &vmail && ! mpathset())) && + iflag == 1) + chkmail(1); + if ((vp->flags & VEXPORT) && localevar(s)) { + change_env(s, 1); + (void) setlocale(LC_ALL, ""); + updatecharset(); + } + INTON; + return; + } + /* not found */ + if (flags & VNOSET) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + return; + } + INTOFF; + vp = ckmalloc(sizeof (*vp)); + vp->flags = flags; + vp->text = s; + vp->name_len = nlen; + vp->next = *vpp; + vp->func = NULL; + *vpp = vp; + if ((vp->flags & VEXPORT) && localevar(s)) { + change_env(s, 1); + (void) setlocale(LC_ALL, ""); + updatecharset(); + } + INTON; +} + + +static void +setvareq_const(const char *s, int flags) +{ + setvareq(__DECONST(char *, s), flags | VTEXTFIXED); +} + + +/* + * Process a linked list of variable assignments. + */ + +void +listsetvar(struct arglist *list, int flags) +{ + int i; + + INTOFF; + for (i = 0; i < list->count; i++) + setvareq(savestr(list->args[i]), flags); + INTON; +} + + + +/* + * Find the value of a variable. Returns NULL if not set. + */ + +char * +lookupvar(const char *name) +{ + struct var *v; + + v = find_var(name, NULL, NULL); + if (v == NULL || v->flags & VUNSET) + return NULL; + return v->text + v->name_len + 1; +} + + + +/* + * Search the environment of a builtin command. If the second argument + * is nonzero, return the value of a variable even if it hasn't been + * exported. + */ + +char * +bltinlookup(const char *name, int doall) +{ + struct var *v; + char *result; + int i; + + result = NULL; + if (cmdenviron) for (i = 0; i < cmdenviron->count; i++) { + if (varequal(cmdenviron->args[i], name)) + result = strchr(cmdenviron->args[i], '=') + 1; + } + if (result != NULL) + return result; + + v = find_var(name, NULL, NULL); + if (v == NULL || v->flags & VUNSET || + (!doall && (v->flags & VEXPORT) == 0)) + return NULL; + return v->text + v->name_len + 1; +} + + +/* + * Set up locale for a builtin (LANG/LC_* assignments). + */ +void +bltinsetlocale(void) +{ + int act = 0; + char *loc, *locdef; + int i; + + if (cmdenviron) for (i = 0; i < cmdenviron->count; i++) { + if (localevar(cmdenviron->args[i])) { + act = 1; + break; + } + } + if (!act) + return; + loc = bltinlookup("LC_ALL", 0); + INTOFF; + if (loc != NULL) { + setlocale(LC_ALL, loc); + INTON; + updatecharset(); + return; + } + locdef = bltinlookup("LANG", 0); + for (i = 0; locale_names[i] != NULL; i++) { + loc = bltinlookup(locale_names[i], 0); + if (loc == NULL) + loc = locdef; + if (loc != NULL) + setlocale(locale_categories[i], loc); + } + INTON; + updatecharset(); +} + +/* + * Undo the effect of bltinlocaleset(). + */ +void +bltinunsetlocale(void) +{ + int i; + + INTOFF; + if (cmdenviron) for (i = 0; i < cmdenviron->count; i++) { + if (localevar(cmdenviron->args[i])) { + setlocale(LC_ALL, ""); + updatecharset(); + break; + } + } + INTON; +} + +/* + * Update the localeisutf8 flag. + */ +void +updatecharset(void) +{ + char *charset; + + charset = nl_langinfo(CODESET); + localeisutf8 = !strcmp(charset, "UTF-8"); +} + +void +initcharset(void) +{ + updatecharset(); + initial_localeisutf8 = localeisutf8; +} + +/* + * Generate a list of exported variables. This routine is used to construct + * the third argument to execve when executing a program. + */ + +char ** +environment(void) +{ + int nenv; + struct var **vpp; + struct var *vp; + char **env, **ep; + + nenv = 0; + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) + if (vp->flags & VEXPORT) + nenv++; + } + ep = env = stalloc((nenv + 1) * sizeof *env); + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) + if (vp->flags & VEXPORT) + *ep++ = vp->text; + } + *ep = NULL; + return env; +} + + +static int +var_compare(const void *a, const void *b) +{ + const char *const *sa, *const *sb; + + sa = a; + sb = b; + /* + * This compares two var=value strings which creates a different + * order from what you would probably expect. POSIX is somewhat + * ambiguous on what should be sorted exactly. + */ + return strcoll(*sa, *sb); +} + + +/* + * Command to list all variables which are set. This is invoked from the + * set command when it is called without any options or operands. + */ + +int +showvarscmd(int argc __unused, char **argv __unused) +{ + struct var **vpp; + struct var *vp; + const char *s; + const char **vars; + int i, n; + + /* + * POSIX requires us to sort the variables. + */ + n = 0; + for (vpp = vartab; vpp < vartab + VTABSIZE; vpp++) { + for (vp = *vpp; vp; vp = vp->next) { + if (!(vp->flags & VUNSET)) + n++; + } + } + + INTOFF; + vars = ckmalloc(n * sizeof(*vars)); + i = 0; + for (vpp = vartab; vpp < vartab + VTABSIZE; vpp++) { + for (vp = *vpp; vp; vp = vp->next) { + if (!(vp->flags & VUNSET)) + vars[i++] = vp->text; + } + } + + qsort(vars, n, sizeof(*vars), var_compare); + for (i = 0; i < n; i++) { + /* + * Skip improper variable names so the output remains usable as + * shell input. + */ + if (!isassignment(vars[i])) + continue; + s = strchr(vars[i], '='); + s++; + outbin(vars[i], s - vars[i], out1); + out1qstr(s); + out1c('\n'); + } + ckfree(vars); + INTON; + + return 0; +} + + + +/* + * The export and readonly commands. + */ + +int +exportcmd(int argc __unused, char **argv) +{ + struct var **vpp; + struct var *vp; + char **ap; + char *name; + char *p; + char *cmdname; + int ch, values; + int flag = argv[0][0] == 'r'? VREADONLY : VEXPORT; + + cmdname = argv[0]; + values = 0; + while ((ch = nextopt("p")) != '\0') { + switch (ch) { + case 'p': + values = 1; + break; + } + } + + if (values && *argptr != NULL) + error("-p requires no arguments"); + if (*argptr != NULL) { + for (ap = argptr; (name = *ap) != NULL; ap++) { + if ((p = strchr(name, '=')) != NULL) { + p++; + } else { + vp = find_var(name, NULL, NULL); + if (vp != NULL) { + vp->flags |= flag; + if ((vp->flags & VEXPORT) && localevar(vp->text)) { + change_env(vp->text, 1); + (void) setlocale(LC_ALL, ""); + updatecharset(); + } + continue; + } + } + setvar(name, p, flag); + } + } else { + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) { + if (vp->flags & flag) { + if (values) { + /* + * Skip improper variable names + * so the output remains usable + * as shell input. + */ + if (!isassignment(vp->text)) + continue; + out1str(cmdname); + out1c(' '); + } + if (values && !(vp->flags & VUNSET)) { + outbin(vp->text, + vp->name_len + 1, out1); + out1qstr(vp->text + + vp->name_len + 1); + } else + outbin(vp->text, vp->name_len, + out1); + out1c('\n'); + } + } + } + } + return 0; +} + + +/* + * The "local" command. + */ + +int +localcmd(int argc __unused, char **argv __unused) +{ + char *name; + + nextopt(""); + if (! in_function()) + error("Not in a function"); + while ((name = *argptr++) != NULL) { + mklocal(name); + } + return 0; +} + + +/* + * Make a variable a local variable. When a variable is made local, it's + * value and flags are saved in a localvar structure. The saved values + * will be restored when the shell function returns. We handle the name + * "-" as a special case. + */ + +void +mklocal(char *name) +{ + struct localvar *lvp; + struct var **vpp; + struct var *vp; + + INTOFF; + lvp = ckmalloc(sizeof (struct localvar)); + if (name[0] == '-' && name[1] == '\0') { + lvp->text = ckmalloc(sizeof optval); + memcpy(lvp->text, optval, sizeof optval); + vp = NULL; + } else { + vp = find_var(name, &vpp, NULL); + if (vp == NULL) { + if (strchr(name, '=')) + setvareq(savestr(name), VSTRFIXED | VNOLOCAL); + else + setvar(name, NULL, VSTRFIXED | VNOLOCAL); + vp = *vpp; /* the new variable */ + lvp->text = NULL; + lvp->flags = VUNSET; + } else { + lvp->text = vp->text; + lvp->flags = vp->flags; + vp->flags |= VSTRFIXED|VTEXTFIXED; + if (name[vp->name_len] == '=') + setvareq(savestr(name), VNOLOCAL); + } + } + lvp->vp = vp; + lvp->next = localvars; + localvars = lvp; + INTON; +} + + +/* + * Called after a function returns. + */ + +void +poplocalvars(void) +{ + struct localvar *lvp; + struct var *vp; + int islocalevar; + + INTOFF; + while ((lvp = localvars) != NULL) { + localvars = lvp->next; + vp = lvp->vp; + if (vp == NULL) { /* $- saved */ + memcpy(optval, lvp->text, sizeof optval); + ckfree(lvp->text); + optschanged(); + } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { + vp->flags &= ~VREADONLY; + (void)unsetvar(vp->text); + } else { + islocalevar = (vp->flags | lvp->flags) & VEXPORT && + localevar(lvp->text); + if ((vp->flags & VTEXTFIXED) == 0) + ckfree(vp->text); + vp->flags = lvp->flags; + vp->text = lvp->text; + if (vp->func) + (*vp->func)(vp->text + vp->name_len + 1); + if (islocalevar) { + change_env(vp->text, vp->flags & VEXPORT && + (vp->flags & VUNSET) == 0); + setlocale(LC_ALL, ""); + updatecharset(); + } + } + ckfree(lvp); + } + INTON; +} + + +int +setvarcmd(int argc, char **argv) +{ + if (argc <= 2) + return unsetcmd(argc, argv); + else if (argc == 3) + setvar(argv[1], argv[2], 0); + else + error("too many arguments"); + return 0; +} + + +/* + * The unset builtin command. + */ + +int +unsetcmd(int argc __unused, char **argv __unused) +{ + char **ap; + int i; + int flg_func = 0; + int flg_var = 0; + int ret = 0; + + while ((i = nextopt("vf")) != '\0') { + if (i == 'f') + flg_func = 1; + else + flg_var = 1; + } + if (flg_func == 0 && flg_var == 0) + flg_var = 1; + + INTOFF; + for (ap = argptr; *ap ; ap++) { + if (flg_func) + ret |= unsetfunc(*ap); + if (flg_var) + ret |= unsetvar(*ap); + } + INTON; + return ret; +} + + +/* + * Unset the specified variable. + * Called with interrupts off. + */ + +int +unsetvar(const char *s) +{ + struct var **vpp; + struct var *vp; + + vp = find_var(s, &vpp, NULL); + if (vp == NULL) + return (0); + if (vp->flags & VREADONLY) + return (1); + if (vp->text[vp->name_len + 1] != '\0') + setvar(s, "", 0); + if ((vp->flags & VEXPORT) && localevar(vp->text)) { + change_env(s, 0); + setlocale(LC_ALL, ""); + updatecharset(); + } + vp->flags &= ~VEXPORT; + vp->flags |= VUNSET; + if ((vp->flags & VSTRFIXED) == 0) { + if ((vp->flags & VTEXTFIXED) == 0) + ckfree(vp->text); + *vpp = vp->next; + ckfree(vp); + } + return (0); +} + + + +/* + * Returns true if the two strings specify the same variable. The first + * variable name is terminated by '='; the second may be terminated by + * either '=' or '\0'. + */ + +static int +varequal(const char *p, const char *q) +{ + while (*p == *q++) { + if (*p++ == '=') + return 1; + } + if (*p == '=' && *(q - 1) == '\0') + return 1; + return 0; +} + +/* + * Search for a variable. + * 'name' may be terminated by '=' or a NUL. + * vppp is set to the pointer to vp, or the list head if vp isn't found + * lenp is set to the number of characters in 'name' + */ + +static struct var * +find_var(const char *name, struct var ***vppp, int *lenp) +{ + unsigned int hashval; + int len; + struct var *vp, **vpp; + const char *p = name; + + hashval = 0; + while (*p && *p != '=') + hashval = 2 * hashval + (unsigned char)*p++; + len = p - name; + + if (lenp) + *lenp = len; + vpp = &vartab[hashval % VTABSIZE]; + if (vppp) + *vppp = vpp; + + for (vp = *vpp ; vp ; vpp = &vp->next, vp = *vpp) { + if (vp->name_len != len) + continue; + if (memcmp(vp->text, name, len) != 0) + continue; + if (vppp) + *vppp = vpp; + return vp; + } + return NULL; +} diff --git a/bin/catsh/var.h b/bin/catsh/var.h new file mode 100644 index 00000000..1e6420fd --- /dev/null +++ b/bin/catsh/var.h @@ -0,0 +1,140 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)var.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.0/bin/sh/var.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +/* + * Shell variables. + */ + +/* flags */ +#define VEXPORT 0x01 /* variable is exported */ +#define VREADONLY 0x02 /* variable cannot be modified */ +#define VSTRFIXED 0x04 /* variable struct is statically allocated */ +#define VTEXTFIXED 0x08 /* text is statically allocated */ +#define VSTACK 0x10 /* text is allocated on the stack */ +#define VUNSET 0x20 /* the variable is not set */ +#define VNOFUNC 0x40 /* don't call the callback function */ +#define VNOSET 0x80 /* do not set variable - just readonly test */ +#define VNOLOCAL 0x100 /* ignore forcelocal */ + + +struct var { + struct var *next; /* next entry in hash list */ + int flags; /* flags are defined above */ + int name_len; /* length of name */ + char *text; /* name=value */ + void (*func)(const char *); + /* function to be called when */ + /* the variable gets set/unset */ +}; + + +struct localvar { + struct localvar *next; /* next local variable in list */ + struct var *vp; /* the variable that was made local */ + int flags; /* saved flags */ + char *text; /* saved text */ +}; + + +extern struct localvar *localvars; +extern int forcelocal; + +extern struct var vifs; +extern struct var vmail; +extern struct var vmpath; +extern struct var vpath; +extern struct var vps0; +extern struct var vps1; +extern struct var vps2; +extern struct var vps4; +extern struct var vrps1; +extern struct var vrps2; +extern struct var vdisvfork; +#ifndef NO_HISTORY +extern struct var vhistfile; +extern struct var vhistsize; +extern struct var vterm; +#endif + +extern int localeisutf8; +/* The parser uses the locale that was in effect at startup. */ +extern int initial_localeisutf8; + +/* + * The following macros access the values of the above variables. + * They have to skip over the name. They return the null string + * for unset variables. + */ + +#define ifsval() (vifs.text + 4) +#define ifsset() ((vifs.flags & VUNSET) == 0) +#define mailval() (vmail.text + 5) +#define mpathval() (vmpath.text + 9) +#define pathval() (vpath.text + 5) +#define ps0val() (vps0.text + 4) +#define ps1val() (vps1.text + 4) +#define ps2val() (vps2.text + 4) +#define ps4val() (vps4.text + 4) +#define rps1val() (vrps1.text + 5) +#define rps2val() (vrps2.text + 5) +#define optindval() (voptind.text + 7) +#ifndef NO_HISTORY +#define histfileval() (vhistfile.text + 9) +#define histsizeval() (vhistsize.text + 9) +#define termval() (vterm.text + 5) +#endif + +#define mpathset() ((vmpath.flags & VUNSET) == 0) +#define disvforkset() ((vdisvfork.flags & VUNSET) == 0) + +void initvar(void); +void setvar(const char *, const char *, int); +void setvareq(char *, int); +struct arglist; +void listsetvar(struct arglist *, int); +char *lookupvar(const char *); +char *bltinlookup(const char *, int); +void bltinsetlocale(void); +void bltinunsetlocale(void); +void updatecharset(void); +void initcharset(void); +char **environment(void); +int showvarscmd(int, char **); +void mklocal(char *); +void poplocalvars(void); +int unsetvar(const char *); +int setvarsafe(const char *, const char *, int); |