summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--GNUmakefile72
-rw-r--r--README.md8
-rw-r--r--cdefs.h10
-rw-r--r--compat.c20
-rw-r--r--compat.h2
-rw-r--r--histedit.c20
-rw-r--r--namespace.h3
-rw-r--r--sys/cdefs.h10
-rw-r--r--termcap.c311
-rw-r--r--termcap.h11
-rw-r--r--tests/linux/bind-no-history.21
-rw-r--r--tests/linux/bind-no-history.2.stderr1
-rw-r--r--tests/linux/fc-no-history.21
-rw-r--r--tests/linux/fc-no-history.2.stderr1
-rw-r--r--tests/test.sh3
15 files changed, 452 insertions, 22 deletions
diff --git a/GNUmakefile b/GNUmakefile
index 14814b9624..871b18c5ef 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -6,13 +6,15 @@ export LANG := C
CC ?= cc
AWK ?= awk
-CPPFLAGS += -D_GNU_SOURCE -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=700 -DNO_HISTORY \
+CPPFLAGS += -D_GNU_SOURCE -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=700 \
-I"$(CURDIR)" -I"$(CURDIR)/build/gen" -I"$(CURDIR)/../kill" \
-I"$(CURDIR)/../../usr.bin/printf" -I"$(CURDIR)/../../bin/test"
CFLAGS ?= -O2
CFLAGS += -g -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare
LDFLAGS ?=
LDLIBS ?=
+EDITLINE_LIBS ?=
+EDITLINE_CPPFLAGS ?=
OBJDIR := $(CURDIR)/build/obj
TOOLDIR := $(CURDIR)/build/tools
@@ -20,11 +22,17 @@ GENDIR := $(CURDIR)/build/gen
OUTDIR := $(CURDIR)/out
TARGET := $(OUTDIR)/sh
TEST_RUNNER := $(TOOLDIR)/run-default-sigpipe
+LIBEDITDIR := $(CURDIR)/../../contrib/libedit
+LIBEDIT_GENDIR := $(GENDIR)/libedit
+VISDIR := $(CURDIR)/../../contrib/libc-vis
KILLDIR := $(CURDIR)/../kill
TESTDIR := $(CURDIR)/../../bin/test
PRINTFDIR := $(CURDIR)/../../usr.bin/printf
+CPPFLAGS += -I"$(LIBEDIT_GENDIR)" -I"$(LIBEDITDIR)"
+LIBEDIT_CPPFLAGS := $(CPPFLAGS) $(EDITLINE_CPPFLAGS) -include "$(CURDIR)/compat.h" -I"$(VISDIR)"
+
LOCAL_SRCS := \
alias.c \
arith_yacc.c \
@@ -50,14 +58,46 @@ LOCAL_SRCS := \
redir.c \
show.c \
signames.c \
+ termcap.c \
trap.c \
var.c
BUILTIN_SRCS := bltin/echo.c
EXTERNAL_SRCS := $(KILLDIR)/kill.c $(TESTDIR)/test.c $(PRINTFDIR)/printf.c
GEN_SRCS := $(GENDIR)/builtins.c $(GENDIR)/nodes.c $(GENDIR)/syntax.c
GEN_HDRS := $(GENDIR)/builtins.h $(GENDIR)/nodes.h $(GENDIR)/syntax.h $(GENDIR)/token.h
+LIBEDIT_SRCS := \
+ chared.c \
+ chartype.c \
+ common.c \
+ el.c \
+ eln.c \
+ emacs.c \
+ filecomplete.c \
+ hist.c \
+ history.c \
+ historyn.c \
+ keymacro.c \
+ literal.c \
+ map.c \
+ parse.c \
+ prompt.c \
+ read.c \
+ refresh.c \
+ search.c \
+ sig.c \
+ terminal.c \
+ tokenizer.c \
+ tokenizern.c \
+ tty.c \
+ vi.c
+LIBEDIT_GEN_HDRS := $(LIBEDIT_GENDIR)/vi.h $(LIBEDIT_GENDIR)/emacs.h \
+ $(LIBEDIT_GENDIR)/common.h $(LIBEDIT_GENDIR)/fcns.h \
+ $(LIBEDIT_GENDIR)/func.h $(LIBEDIT_GENDIR)/help.h
+VIS_SRCS := vis.c unvis.c
SRCS := $(LOCAL_SRCS) $(BUILTIN_SRCS) $(EXTERNAL_SRCS) $(GEN_SRCS)
OBJS := $(addprefix $(OBJDIR)/,$(notdir $(SRCS:.c=.o)))
+LIBEDIT_OBJS := $(addprefix $(OBJDIR)/libedit-,$(LIBEDIT_SRCS:.c=.o))
+VIS_OBJS := $(addprefix $(OBJDIR)/vis-,$(VIS_SRCS:.c=.o))
vpath %.c $(CURDIR) $(CURDIR)/bltin $(KILLDIR) $(TESTDIR) $(PRINTFDIR) $(GENDIR)
@@ -66,10 +106,10 @@ vpath %.c $(CURDIR) $(CURDIR)/bltin $(KILLDIR) $(TESTDIR) $(PRINTFDIR) $(GENDIR)
all: $(TARGET)
dirs:
- @mkdir -p "$(OBJDIR)" "$(TOOLDIR)" "$(GENDIR)" "$(OUTDIR)"
+ @mkdir -p "$(OBJDIR)" "$(TOOLDIR)" "$(GENDIR)" "$(OUTDIR)" "$(LIBEDIT_GENDIR)"
-$(TARGET): $(OBJS) | dirs
- $(CC) $(LDFLAGS) -o "$@" $(OBJS) $(LDLIBS)
+$(TARGET): $(OBJS) $(LIBEDIT_OBJS) $(VIS_OBJS) | dirs
+ $(CC) $(LDFLAGS) -o "$@" $(OBJS) $(LIBEDIT_OBJS) $(VIS_OBJS) $(LDLIBS) $(EDITLINE_LIBS)
$(OBJDIR)/%.o: %.c $(GEN_HDRS) | dirs
$(CC) $(CPPFLAGS) $(CFLAGS) -DSHELL -c "$<" -o "$@"
@@ -83,6 +123,12 @@ $(OBJDIR)/mode.o: $(CURDIR)/mode.c $(CURDIR)/mode.h | dirs
$(OBJDIR)/signames.o: $(CURDIR)/signames.c $(CURDIR)/signames.h | dirs
$(CC) $(CPPFLAGS) $(CFLAGS) -c "$<" -o "$@"
+$(OBJDIR)/libedit-%.o: $(LIBEDITDIR)/%.c $(LIBEDIT_GEN_HDRS) $(LIBEDITDIR)/histedit.h $(LIBEDITDIR)/filecomplete.h | dirs
+ $(CC) $(LIBEDIT_CPPFLAGS) $(CFLAGS) -c "$<" -o "$@"
+
+$(OBJDIR)/vis-%.o: $(VISDIR)/%.c $(CURDIR)/namespace.h $(VISDIR)/vis.h | dirs
+ $(CC) $(LIBEDIT_CPPFLAGS) $(CFLAGS) -c "$<" -o "$@"
+
$(TOOLDIR)/mknodes: $(CURDIR)/mknodes.c | dirs
$(CC) $(CPPFLAGS) $(CFLAGS) "$<" -o "$@"
@@ -104,6 +150,24 @@ $(GENDIR)/syntax.c $(GENDIR)/syntax.h: $(TOOLDIR)/mksyntax $(CURDIR)/parser.h |
$(GENDIR)/token.h: $(CURDIR)/mktokens | dirs
cd "$(GENDIR)" && sh "$(CURDIR)/mktokens"
+$(LIBEDIT_GENDIR)/vi.h: $(LIBEDITDIR)/vi.c $(LIBEDITDIR)/makelist | dirs
+ cd "$(LIBEDIT_GENDIR)" && sh "$(LIBEDITDIR)/makelist" -h "$(LIBEDITDIR)/vi.c" > vi.h
+
+$(LIBEDIT_GENDIR)/emacs.h: $(LIBEDITDIR)/emacs.c $(LIBEDITDIR)/makelist | dirs
+ cd "$(LIBEDIT_GENDIR)" && sh "$(LIBEDITDIR)/makelist" -h "$(LIBEDITDIR)/emacs.c" > emacs.h
+
+$(LIBEDIT_GENDIR)/common.h: $(LIBEDITDIR)/common.c $(LIBEDITDIR)/makelist | dirs
+ cd "$(LIBEDIT_GENDIR)" && sh "$(LIBEDITDIR)/makelist" -h "$(LIBEDITDIR)/common.c" > common.h
+
+$(LIBEDIT_GENDIR)/fcns.h: $(LIBEDIT_GENDIR)/vi.h $(LIBEDIT_GENDIR)/emacs.h $(LIBEDIT_GENDIR)/common.h $(LIBEDITDIR)/makelist | dirs
+ cd "$(LIBEDIT_GENDIR)" && sh "$(LIBEDITDIR)/makelist" -fh vi.h emacs.h common.h > fcns.h
+
+$(LIBEDIT_GENDIR)/func.h: $(LIBEDIT_GENDIR)/vi.h $(LIBEDIT_GENDIR)/emacs.h $(LIBEDIT_GENDIR)/common.h $(LIBEDITDIR)/makelist | dirs
+ cd "$(LIBEDIT_GENDIR)" && sh "$(LIBEDITDIR)/makelist" -fc vi.h emacs.h common.h > func.h
+
+$(LIBEDIT_GENDIR)/help.h: $(LIBEDITDIR)/vi.c $(LIBEDITDIR)/emacs.c $(LIBEDITDIR)/common.c $(LIBEDITDIR)/makelist | dirs
+ cd "$(LIBEDIT_GENDIR)" && sh "$(LIBEDITDIR)/makelist" -bh "$(LIBEDITDIR)/vi.c" "$(LIBEDITDIR)/emacs.c" "$(LIBEDITDIR)/common.c" > help.h
+
test: $(TARGET) $(TEST_RUNNER)
SH_BIN="$(TARGET)" SH_RUNNER="$(TEST_RUNNER)" sh "$(CURDIR)/tests/test.sh"
diff --git a/README.md b/README.md
index 1fdc58bda3..65abe8f50f 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# sh
-Standalone Linux-native port of FreeBSD `sh` for Project Tick BSD/Linux Distribution.
+Standalone Linux-native port of FreeBSD `sh` for Project Tick BSD/Linux Distribution, with in-tree `libedit`, `vis`, and a local ANSI/termcap backend for history and line editing.
## Build
@@ -26,6 +26,8 @@ gmake -f GNUmakefile test CC=musl-gcc
- The FreeBSD `bsd.prog.mk` build is replaced with a standalone `GNUmakefile` that regenerates `builtins.c`, `builtins.h`, `nodes.c`, `nodes.h`, `syntax.c`, `syntax.h`, and `token.h`.
- No shared BSD compatibility shim is introduced. FreeBSD-only assumptions are either rewritten directly for Linux or rejected with explicit runtime errors.
+- History and editline support are built from the repository's `contrib/libedit` sources rather than depending on a host `libedit` package.
+- Terminal capability handling is provided by a local Linux-oriented ANSI/termcap implementation, avoiding host `ncurses`/`tinfo` ABI dependencies and keeping musl builds runnable.
- `wait3(2)` is replaced with Linux `wait4(-1, ...)` for job accounting and child collection.
- `CLOCK_UPTIME`-based `read -t` timing is mapped to `CLOCK_MONOTONIC`, preserving monotonic timeout behavior on Linux.
- BSD `sys_signame` / `sys_nsig` usage is replaced with a local Linux signal table that covers standard signals plus `SIGRTMIN`/`SIGRTMAX`.
@@ -37,6 +39,8 @@ gmake -f GNUmakefile test CC=musl-gcc
## Supported / Unsupported Linux Semantics
- Supported: POSIX shell scripts, pipelines, expansions, traps, job control, `wait`, `kill %job`, `command -p`, symbolic `umask`, and interactive prompting.
+- Supported: interactive history, `fc`, and editline bindings through vendored `libedit`.
+- Supported with Linux-native mapping: interactive editing targets ANSI/xterm-style terminals through the local termcap backend instead of relying on external `ncurses`/`termcap` libraries.
- Supported with Linux-native mapping: `read -t` uses monotonic time, job waiting uses `wait4(2)`, and shell self-reexec falls back through `/proc/self/exe`.
- Unsupported: `set -o verify` / `set +o verify` enabling is rejected with an explicit error because Linux has no `O_VERIFY` equivalent.
-- Unsupported in this standalone build: line editing and history support are disabled (`NO_HISTORY`). `fc` and `bind` remain present but fail with explicit runtime errors instead of pretending to work.
+- Unsupported: non-ANSI terminal databases are not consulted. Exotic `TERM` entries outside the built-in ANSI capability set may lose advanced editing behavior even though non-interactive shell semantics remain supported.
diff --git a/cdefs.h b/cdefs.h
index 4c204eb1f6..cfb25696f2 100644
--- a/cdefs.h
+++ b/cdefs.h
@@ -5,6 +5,16 @@
#define __dead2 __attribute__((__noreturn__))
#endif
+#ifndef __BEGIN_DECLS
+#ifdef __cplusplus
+#define __BEGIN_DECLS extern "C" {
+#define __END_DECLS }
+#else
+#define __BEGIN_DECLS
+#define __END_DECLS
+#endif
+#endif
+
#ifndef __printflike
#define __printflike(fmtarg, firstvararg) \
__attribute__((__format__(__printf__, fmtarg, firstvararg)))
diff --git a/compat.c b/compat.c
index 79f07a386a..44e7108770 100644
--- a/compat.c
+++ b/compat.c
@@ -1,7 +1,27 @@
+#include <errno.h>
#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#if defined(__linux__)
+#include <sys/auxv.h>
+#endif
#include "compat.h"
+int
+sh_issetugid(void)
+{
+#if defined(__linux__) && defined(AT_SECURE)
+ errno = 0;
+ if (getauxval(AT_SECURE) != 0)
+ return (1);
+ if (errno == 0)
+ return (0);
+#endif
+ return (getuid() != geteuid() || getgid() != getegid());
+}
+
size_t
sh_strlcpy(char *dst, const char *src, size_t dstsize)
{
diff --git a/compat.h b/compat.h
index a7af5eab32..4990c8cc7a 100644
--- a/compat.h
+++ b/compat.h
@@ -56,8 +56,10 @@
((a)->tv_sec cmp (b)->tv_sec))
#endif
+int sh_issetugid(void);
size_t sh_strlcpy(char *dst, const char *src, size_t dstsize);
+#define issetugid sh_issetugid
#define strlcpy sh_strlcpy
#endif
diff --git a/histedit.c b/histedit.c
index d41f4b2e02..900e488208 100644
--- a/histedit.c
+++ b/histedit.c
@@ -71,11 +71,12 @@ int displayhist;
static int savehist;
static FILE *el_in, *el_out;
static bool in_command_completion;
+static int completion_compare_curpos;
static char *fc_replace(const char *, char *, char *);
static int not_fcnumber(const char *);
static int str_to_event(const char *, int);
-static int comparator(const void *, const void *, void *);
+static int comparator(const void *, const void *);
static char **sh_matches(const char *, int, int);
static const char *append_char_function(const char *);
static unsigned char sh_complete(EditLine *, int);
@@ -582,9 +583,9 @@ bindcmd(int argc, char **argv)
* characters that we already know to compare equal (common prefix).
*/
static int
-comparator(const void *a, const void *b, void *thunk)
+comparator(const void *a, const void *b)
{
- size_t curpos = (intptr_t)thunk;
+ size_t curpos = (size_t)completion_compare_curpos;
return (strcmp(*(char *const *)a + curpos,
*(char *const *)b + curpos));
@@ -665,7 +666,8 @@ static char
for (const unsigned char *bp = builtincmd; *bp != 0; bp += 2 + bp[0]) {
if (curpos > bp[0] || memcmp(bp + 2, text, curpos) != 0)
continue;
- rmatches = add_match(matches, ++i, &size, strndup(bp + 2, bp[0]));
+ rmatches = add_match(matches, ++i, &size,
+ strndup((const char *)bp + 2, bp[0]));
if (rmatches == NULL)
goto out;
matches = rmatches;
@@ -694,11 +696,11 @@ out:
free(matches);
return (NULL);
}
- uniq = 1;
- if (i > 1) {
- qsort_s(matches + 1, i, sizeof(matches[0]), comparator,
- (void *)(intptr_t)curpos);
- for (size_t k = 2; k <= i; k++) {
+ uniq = 1;
+ if (i > 1) {
+ completion_compare_curpos = (int)curpos;
+ qsort(matches + 1, i, sizeof(matches[0]), comparator);
+ for (size_t k = 2; k <= i; k++) {
const char *l = matches[uniq] + curpos;
const char *r = matches[k] + curpos;
size_t common = 0;
diff --git a/namespace.h b/namespace.h
new file mode 100644
index 0000000000..0ed042f265
--- /dev/null
+++ b/namespace.h
@@ -0,0 +1,3 @@
+#ifndef SH_NAMESPACE_H
+#define SH_NAMESPACE_H
+#endif
diff --git a/sys/cdefs.h b/sys/cdefs.h
new file mode 100644
index 0000000000..8671995871
--- /dev/null
+++ b/sys/cdefs.h
@@ -0,0 +1,10 @@
+#ifndef SH_SYS_CDEFS_H
+#define SH_SYS_CDEFS_H
+
+#if defined(__GLIBC__)
+#include_next <sys/cdefs.h>
+#else
+#include "../cdefs.h"
+#endif
+
+#endif
diff --git a/termcap.c b/termcap.c
new file mode 100644
index 0000000000..ae2f0c8ca8
--- /dev/null
+++ b/termcap.c
@@ -0,0 +1,311 @@
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "termcap.h"
+
+struct capability {
+ const char *name;
+ const char *value;
+};
+
+struct flag_capability {
+ const char *name;
+ int value;
+};
+
+struct num_capability {
+ const char *name;
+ int value;
+};
+
+static const struct capability ansi_caps[] = {
+ { "al", "\033[L" },
+ { "bl", "\a" },
+ { "cd", "\033[J" },
+ { "ce", "\033[K" },
+ { "ch", "\033[%i%dG" },
+ { "cl", "\033[H\033[2J" },
+ { "dc", "\033[P" },
+ { "dl", "\033[M" },
+ { "dm", "" },
+ { "ed", "" },
+ { "ei", "" },
+ { "fs", "" },
+ { "ho", "\033[H" },
+ { "ic", "\033[@" },
+ { "im", "" },
+ { "ip", "" },
+ { "kd", "\033[B" },
+ { "kl", "\033[D" },
+ { "kr", "\033[C" },
+ { "ku", "\033[A" },
+ { "md", "\033[1m" },
+ { "me", "\033[0m" },
+ { "nd", "\033[C" },
+ { "se", "\033[0m" },
+ { "so", "\033[7m" },
+ { "ts", "" },
+ { "up", "\033[A" },
+ { "us", "\033[4m" },
+ { "ue", "\033[0m" },
+ { "vb", "\a" },
+ { "DC", "\033[%dP" },
+ { "DO", "\033[%dB" },
+ { "IC", "\033[%d@" },
+ { "LE", "\033[%dD" },
+ { "RI", "\033[%dC" },
+ { "UP", "\033[%dA" },
+ { "kh", "\033[H" },
+ { "@7", "\033[F" },
+ { "kD", "\033[3~" },
+ { NULL, NULL }
+};
+
+static const struct flag_capability flag_caps[] = {
+ { "am", 1 },
+ { "km", 1 },
+ { "pt", 1 },
+ { "xt", 0 },
+ { "xn", 0 },
+ { "MT", 0 },
+ { NULL, 0 }
+};
+
+static char tgoto_buf[64];
+static int term_columns = 80;
+static int term_lines = 24;
+
+static const char *
+find_capability(const char *id)
+{
+ size_t i;
+
+ for (i = 0; ansi_caps[i].name != NULL; i++) {
+ if (strcmp(ansi_caps[i].name, id) == 0)
+ return ansi_caps[i].value;
+ }
+ return NULL;
+}
+
+static void
+update_terminal_size(void)
+{
+ struct winsize ws;
+ const char *env;
+ char *end;
+ long value;
+
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) {
+ if (ws.ws_col > 0)
+ term_columns = ws.ws_col;
+ if (ws.ws_row > 0)
+ term_lines = ws.ws_row;
+ }
+
+ env = getenv("COLUMNS");
+ if (env != NULL && *env != '\0') {
+ errno = 0;
+ value = strtol(env, &end, 10);
+ if (errno == 0 && *end == '\0' && value > 0 && value <= INT_MAX)
+ term_columns = (int)value;
+ }
+
+ env = getenv("LINES");
+ if (env != NULL && *env != '\0') {
+ errno = 0;
+ value = strtol(env, &end, 10);
+ if (errno == 0 && *end == '\0' && value > 0 && value <= INT_MAX)
+ term_lines = (int)value;
+ }
+}
+
+int
+tgetent(char *bp, const char *name)
+{
+ (void)bp;
+ (void)name;
+ update_terminal_size();
+ return 1;
+}
+
+int
+tgetflag(const char *id)
+{
+ size_t i;
+
+ for (i = 0; flag_caps[i].name != NULL; i++) {
+ if (strcmp(flag_caps[i].name, id) == 0)
+ return flag_caps[i].value;
+ }
+ return 0;
+}
+
+int
+tgetnum(const char *id)
+{
+ if (strcmp(id, "co") == 0)
+ return term_columns;
+ if (strcmp(id, "li") == 0)
+ return term_lines;
+ return -1;
+}
+
+char *
+tgetstr(const char *id, char **area)
+{
+ const char *value;
+ size_t len;
+ char *dst;
+
+ value = find_capability(id);
+ if (value == NULL)
+ return NULL;
+ if (area == NULL)
+ return (char *)value;
+
+ len = strlen(value) + 1;
+ dst = *area;
+ memcpy(dst, value, len);
+ *area += len;
+ return dst;
+}
+
+int
+tputs(const char *str, int affcnt, int (*putc_fn)(int))
+{
+ const unsigned char *p;
+
+ (void)affcnt;
+ if (str == NULL || putc_fn == NULL)
+ return -1;
+ for (p = (const unsigned char *)str; *p != '\0'; p++) {
+ if (putc_fn(*p) == EOF)
+ return -1;
+ }
+ return 0;
+}
+
+static int
+next_param(const int params[2], int *index)
+{
+ int value;
+
+ if (*index >= 2)
+ return 0;
+ value = params[*index];
+ (*index)++;
+ if (value < 0)
+ value = -value;
+ return value;
+}
+
+char *
+tgoto(const char *cap, int col, int row)
+{
+ const unsigned char *src;
+ char *dst;
+ int params[2];
+ int param_index;
+
+ if (cap == NULL)
+ return NULL;
+
+ params[0] = col;
+ params[1] = row;
+ param_index = 0;
+ dst = tgoto_buf;
+ src = (const unsigned char *)cap;
+
+ while (*src != '\0' && (size_t)(dst - tgoto_buf) < sizeof(tgoto_buf) - 1) {
+ if (*src != '%') {
+ *dst++ = (char)*src++;
+ continue;
+ }
+
+ src++;
+ switch (*src) {
+ case '%':
+ *dst++ = '%';
+ src++;
+ break;
+ case 'd': {
+ int value;
+ int written;
+
+ value = next_param(params, &param_index);
+ written = snprintf(dst,
+ sizeof(tgoto_buf) - (size_t)(dst - tgoto_buf),
+ "%d", value);
+ if (written < 0)
+ return NULL;
+ dst += written;
+ src++;
+ break;
+ }
+ case '2':
+ case '3': {
+ int width;
+ int value;
+ int written;
+
+ width = *src - '0';
+ value = next_param(params, &param_index);
+ written = snprintf(dst,
+ sizeof(tgoto_buf) - (size_t)(dst - tgoto_buf),
+ "%0*d", width, value);
+ if (written < 0)
+ return NULL;
+ dst += written;
+ src++;
+ break;
+ }
+ case '.': {
+ int value;
+
+ value = next_param(params, &param_index);
+ if (value == 0)
+ value = ' ';
+ *dst++ = (char)value;
+ src++;
+ break;
+ }
+ case '+': {
+ int value;
+
+ src++;
+ value = next_param(params, &param_index);
+ *dst++ = (char)(value + *src++);
+ break;
+ }
+ case 'i':
+ params[0]++;
+ params[1]++;
+ src++;
+ break;
+ case 'r': {
+ int tmp;
+
+ tmp = params[0];
+ params[0] = params[1];
+ params[1] = tmp;
+ src++;
+ break;
+ }
+ default:
+ *dst++ = '%';
+ if (*src != '\0')
+ *dst++ = (char)*src++;
+ break;
+ }
+ }
+
+ *dst = '\0';
+ return tgoto_buf;
+}
diff --git a/termcap.h b/termcap.h
new file mode 100644
index 0000000000..17f98b4b9c
--- /dev/null
+++ b/termcap.h
@@ -0,0 +1,11 @@
+#ifndef SH_TERMCAP_H
+#define SH_TERMCAP_H
+
+int tgetent(char *bp, const char *name);
+int tgetflag(const char *id);
+int tgetnum(const char *id);
+char *tgetstr(const char *id, char **area);
+int tputs(const char *str, int affcnt, int (*putc_fn)(int));
+char *tgoto(const char *cap, int col, int row);
+
+#endif
diff --git a/tests/linux/bind-no-history.2 b/tests/linux/bind-no-history.2
deleted file mode 100644
index 4eb66e263c..0000000000
--- a/tests/linux/bind-no-history.2
+++ /dev/null
@@ -1 +0,0 @@
-bind '^I' self-insert
diff --git a/tests/linux/bind-no-history.2.stderr b/tests/linux/bind-no-history.2.stderr
deleted file mode 100644
index 009b41f04f..0000000000
--- a/tests/linux/bind-no-history.2.stderr
+++ /dev/null
@@ -1 +0,0 @@
-bind: not compiled with line editing support
diff --git a/tests/linux/fc-no-history.2 b/tests/linux/fc-no-history.2
deleted file mode 100644
index 6bfcc02b19..0000000000
--- a/tests/linux/fc-no-history.2
+++ /dev/null
@@ -1 +0,0 @@
-fc -l
diff --git a/tests/linux/fc-no-history.2.stderr b/tests/linux/fc-no-history.2.stderr
deleted file mode 100644
index 8b2fe95717..0000000000
--- a/tests/linux/fc-no-history.2.stderr
+++ /dev/null
@@ -1 +0,0 @@
-fc: not compiled with history support
diff --git a/tests/test.sh b/tests/test.sh
index c0acb5ae46..2ba944951c 100644
--- a/tests/test.sh
+++ b/tests/test.sh
@@ -12,9 +12,6 @@ trap 'rm -rf "$WORKDIR"' EXIT INT TERM
SKIP_CASES='
parameters/mail1.0
parameters/mail2.0
-builtins/fc1.0
-builtins/fc2.0
-builtins/fc3.0
'
fail() {