Snap for 9875146 from 71a7c1787d458f91eed6348c807ed0af9446654f to udc-release

Change-Id: I87ebabf3d4d7005db239acf49305bd18e872f634
diff --git a/Android.bp b/Android.bp
index dcb534e..a300947 100644
--- a/Android.bp
+++ b/Android.bp
@@ -38,6 +38,7 @@
         "src/shf.c",
         "src/syn.c",
         "src/tree.c",
+        "src/ulimit.c",
         "src/var.c",
     ],
 
@@ -125,7 +126,7 @@
         "-DHAVE_SYS_ERRLIST_DECL=0",
         "-DHAVE_SYS_SIGLIST_DECL=1",
         "-DHAVE_PERSISTENT_HISTORY=0",
-        "-DMKSH_BUILD_R=592",
+        "-DMKSH_BUILD_R=593",
 
         // Additional flags
         "-DMKSH_DEFAULT_PROFILEDIR=\"/system/etc\"",
diff --git a/Android.patch.txt b/Android.patch.txt
index eefcd5c..72a9db5 100644
--- a/Android.patch.txt
+++ b/Android.patch.txt
@@ -1,8 +1,7 @@
-Only in src: FAQ.htm
-diff -u mksh-R59b/funcs.c src/funcs.c
---- mksh-R59b/funcs.c	2020-05-16 15:38:48.000000000 -0700
-+++ src/funcs.c	2020-05-20 17:14:19.588510856 -0700
-@@ -104,7 +104,9 @@
+diff -u mksh-R59c/mksh/funcs.c src/funcs.c
+--- mksh-R59c/mksh/funcs.c	2020-08-27 19:53:11.000000000 +0000
++++ src/funcs.c	2023-03-31 18:02:12.376044783 +0000
+@@ -98,7 +98,9 @@
  	{Tsgbreak, c_brkcont},
  	{T__builtin, c_builtin},
  	{Tbuiltin, c_builtin},
@@ -12,7 +11,7 @@
  	{Tcd, c_cd},
  	/* dash compatibility hack */
  	{"chdir", c_cd},
-@@ -125,7 +127,9 @@
+@@ -119,7 +121,9 @@
  	{"pwd", c_pwd},
  	{Tread, c_read},
  	{Tdsgreadonly, c_typeset},
@@ -22,7 +21,7 @@
  	{"~rename", c_rename},
  	{"*=return", c_exitreturn},
  	{Tsghset, c_set},
-@@ -159,8 +163,10 @@
+@@ -153,8 +157,10 @@
  	{"~printf", c_printf},
  #endif
  #if HAVE_SELECT
@@ -33,25 +32,22 @@
  #ifdef __MirBSD__
  	/* alias to "true" for historical reasons */
  	{"domainname", c_true},
-diff -u mksh-R59b/main.c src/main.c
---- mksh-R59b/main.c	2020-05-16 15:51:51.000000000 -0700
-+++ src/main.c	2020-05-20 17:14:19.588510856 -0700
-@@ -414,6 +414,12 @@
+diff -u mksh-R59c/mksh/main.c src/main.c
+--- mksh-R59c/mksh/main.c	2020-10-01 20:29:21.000000000 +0000
++++ src/main.c	2023-03-31 18:09:32.827660886 +0000
+@@ -413,6 +413,12 @@
+
  	/* import environment */
  	init_environ();
- 
++
 +	/* override default PATH regardless of environment */
 +#ifdef MKSH_DEFPATH_OVERRIDE
 +	vp = global(TPATH);
 +	setstr(vp, MKSH_DEFPATH_OVERRIDE, KSH_RETURN_ERROR);
 +#endif
-+
+ 
  	/* for security */
  	typeset(TinitIFS, 0, 0, 0, 0);
- 
-Only in src: main.c.orig
-Only in src: Rebuild.sh
-Only in src: rlimits.gen
-Only in src: sh_flags.gen
-Only in src: signames.inc
-Only in src: test.sh
+Only in src/: rlimits.gen
+Only in src/: sh_flags.gen
+Only in src/: ulimits.gen
diff --git a/METADATA b/METADATA
index d97975c..c6745e1 100644
--- a/METADATA
+++ b/METADATA
@@ -1,3 +1,18 @@
+name: "mksh"
 third_party {
+  url {
+    type: HOMEPAGE
+    value: "http://www.mirbsd.org/mksh"
+  }
+  url {
+    type: ARCHIVE
+    value: "http://www.mirbsd.org/MirOS/dist/mir/mksh/mksh-R59c.tgz"
+  }
+  version: "mksh-R59c"
   license_type: NOTICE
+  last_upgrade_date {
+    year: 2023
+    month: 3
+    day: 31
+  }
 }
diff --git a/src/Build.sh b/src/Build.sh
index 1d30045..597d8ca 100644
--- a/src/Build.sh
+++ b/src/Build.sh
@@ -1,5 +1,5 @@
 #!/bin/sh
-srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.756 2020/05/16 22:53:03 tg Exp $'
+srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.763 2020/09/04 21:01:37 tg Exp $'
 #-
 # Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
 #		2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019,
@@ -721,6 +721,13 @@
 	add_cppflags -DMKSH_NO_SIGSETJMP
 	add_cppflags -DMKSH_TYPEDEF_SIG_ATOMIC_T=int
 	;;
+A/UX)
+	add_cppflags -D_POSIX_SOURCE
+	: "${CC=gcc}"
+	: "${LIBS=-lposix}"
+	# GCC defines AUX but cc nothing
+	add_cppflags -D__A_UX__
+	;;
 AIX)
 	add_cppflags -D_ALL_SOURCE
 	: "${HAVE_SETLOCALE_CTYPE=0}"
@@ -862,10 +869,9 @@
 	;;
 Minix3)
 	add_cppflags -DMKSH_UNEMPLOYED
-	add_cppflags -DMKSH_NO_LIMITS
 	add_cppflags -D_POSIX_SOURCE -D_POSIX_1_SOURCE=2 -D_MINIX
 	oldish_ed=no-stderr-ed		# /usr/bin/ed(!) is broken
-	: "${HAVE_SETLOCALE_CTYPE=0}"
+	: "${HAVE_SETLOCALE_CTYPE=0}${MKSH_UNLIMITED=1}" #XXX recheck ulimit
 	;;
 MirBSD)
 	;;
@@ -898,7 +904,7 @@
 Ninix3)
 	# similar to Minix3
 	add_cppflags -DMKSH_UNEMPLOYED
-	add_cppflags -DMKSH_NO_LIMITS
+	: "${MKSH_UNLIMITED=1}" #XXX recheck ulimit
 	# but no idea what else could be needed
 	oswarn="; it has unknown issues"
 	;;
@@ -911,15 +917,14 @@
 	HAVE_ISOFF_MKSH_ASSUME_UTF8=1
 	HAVE_TERMIOS_H=0
 	HAVE_MKNOD=0	# setmode() incompatible
-	oswarn="; it is being ported"
 	check_categories="$check_categories nosymlink"
 	: "${CC=gcc}"
 	: "${SIZE=: size}"
 	SRCS="$SRCS os2.c"
 	add_cppflags -DMKSH_UNEMPLOYED
 	add_cppflags -DMKSH_NOPROSPECTOFWORK
-	add_cppflags -DMKSH_NO_LIMITS
 	add_cppflags -DMKSH_DOSPATH
+	: "${MKSH_UNLIMITED=1}"
 	if test $textmode = 0; then
 		x='dis'
 		y='standard OS/2 tools'
@@ -1363,7 +1368,7 @@
 etd=" on $et"
 case $et in
 klibc)
-	add_cppflags -DMKSH_NO_LIMITS
+	: "${MKSH_UNLIMITED=1}"
 	;;
 unknown)
 	# nothing special detected, don’t worry
@@ -1898,23 +1903,6 @@
 	int main(int ac, char *av[]) { return ((u_int8_t)(size_t)av[ac]); }
 EOF
 
-ac_test rlim_t <<-'EOF'
-	#include <sys/types.h>
-	#if HAVE_BOTH_TIME_H
-	#include <sys/time.h>
-	#include <time.h>
-	#elif HAVE_SYS_TIME_H
-	#include <sys/time.h>
-	#elif HAVE_TIME_H
-	#include <time.h>
-	#endif
-	#if HAVE_SYS_RESOURCE_H
-	#include <sys/resource.h>
-	#endif
-	#include <unistd.h>
-	int main(void) { return (((int)(rlim_t)0) + isatty(0)); }
-EOF
-
 # only testn: added later below
 ac_testn sig_t <<-'EOF'
 	#include <sys/types.h>
@@ -2067,6 +2055,35 @@
 	}
 EOF
 
+ac_test rlimit '' 'getrlimit and setrlimit' <<-'EOF'
+	#define MKSH_INCLUDES_ONLY
+	#include "sh.h"
+	int main(void) {
+		struct rlimit l;
+		if (getrlimit(0, &l)) return 1;
+		l.rlim_max = l.rlim_cur;
+		l.rlim_cur = RLIM_INFINITY;
+		return (setrlimit(0, &l));
+	}
+EOF
+
+ac_test rlim_t rlimit 0 <<-'EOF'
+	#include <sys/types.h>
+	#if HAVE_BOTH_TIME_H
+	#include <sys/time.h>
+	#include <time.h>
+	#elif HAVE_SYS_TIME_H
+	#include <sys/time.h>
+	#elif HAVE_TIME_H
+	#include <time.h>
+	#endif
+	#if HAVE_SYS_RESOURCE_H
+	#include <sys/resource.h>
+	#endif
+	#include <unistd.h>
+	int main(void) { return (((int)(rlim_t)0) + isatty(0)); }
+EOF
+
 ac_test getrusage <<-'EOF'
 	#define MKSH_INCLUDES_ONLY
 	#include "sh.h"
@@ -2457,11 +2474,24 @@
 	$e done.
 fi
 
+if test 1 = "$MKSH_UNLIMITED"; then
+	add_cppflags -DMKSH_UNLIMITED
+else
+	MKSH_UNLIMITED=0
+fi
+
+if test 1 = "$USE_PRINTF_BUILTIN"; then
+	add_cppflags -DMKSH_PRINTF_BUILTIN
+else
+	USE_PRINTF_BUILTIN=0
+fi
+
 addsrcs '!' HAVE_STRLCPY strlcpy.c
 addsrcs USE_PRINTF_BUILTIN printf.c
-test 1 = "$USE_PRINTF_BUILTIN" && add_cppflags -DMKSH_PRINTF_BUILTIN
+addsrcs '!' MKSH_UNLIMITED ulimit.c
+
 test 1 = "$HAVE_CAN_VERB" && CFLAGS="$CFLAGS -verbose"
-add_cppflags -DMKSH_BUILD_R=592
+add_cppflags -DMKSH_BUILD_R=593
 
 $e $bi$me: Finished configuration testing, now producing output.$ao
 
@@ -2544,7 +2574,7 @@
 		args[\${#args[*]}]=\$TMPDIR
 	fi
 	print Testing mksh for conformance:
-	grep -F -e Mir''OS: -e MIRBSD "\$sflag"
+	grep -F -e 'KSH R' -e Mir''OS: "\$sflag" | sed '/KSH/s/^./&           /'
 	print "This shell is actually:\\n\\t\$KSH_VERSION"
 	print 'test.sh built for mksh $dstversion'
 	cstr='\$os = defined \$^O ? \$^O : "unknown";'
@@ -2604,7 +2634,6 @@
 	op=`echo x"$file" | sed 's/^x\(.*\)\.c$/\1./'`
 	test -f $file || file=$srcdir/$file
 	files="$files$sp$file"
-	sp=' '
 	echo "$CC $CFLAGS $CPPFLAGS $emitbc $file || exit 1" >>Rebuild.sh
 	if test $cm = dragonegg; then
 		echo "mv ${op}s ${op}ll" >>Rebuild.sh
@@ -2613,6 +2642,7 @@
 	else
 		objs="$objs$sp${op}o"
 	fi
+	sp=' '
 done
 case $cm in
 dragonegg|llvm)
@@ -2628,7 +2658,7 @@
 echo "$CC $CFLAGS $LDFLAGS -o \$tcfn $lobjs $LIBS $ccpr" >>Rebuild.sh
 echo "test -f \$tcfn || exit 1; $SIZE \$tcfn" >>Rebuild.sh
 if test $cm = makefile; then
-	extras='emacsfn.h exprtok.h rlimits.opt sh.h sh_flags.opt var_spec.h'
+	extras='emacsfn.h exprtok.h rlimits.opt sh.h sh_flags.opt ulimits.opt var_spec.h'
 	test 0 = $HAVE_SYS_SIGNAME && extras="$extras signames.inc"
 	gens= genq=
 	for file in $optfiles; do
@@ -2734,11 +2764,11 @@
 fi
 $e
 $e Installing the manual:
-if test -e FAQ.htm; then
+if test -f FAQ.htm; then
 	$e "# $i -c -o root -g bin -m 444 FAQ.htm /usr/share/doc/mksh/"
 fi
 if test -f mksh.cat1; then
-	if test -e FAQ.htm; then
+	if test -f FAQ.htm; then
 		$e plus either
 	fi
 	$e "# $i -c -o root -g bin -m 444 lksh.cat1" \
@@ -2751,7 +2781,7 @@
 $e
 $e Run the regression test suite: ./test.sh
 $e Please also read the sample file dot.mkshrc and the fine manual.
-test -e FAQ.htm || \
+test -f FAQ.htm || \
     $e Run FAQ2HTML.sh and place FAQ.htm into a suitable location as well.
 exit 0
 
@@ -2775,6 +2805,7 @@
 TARGET_OSREV			[QNX] default: $(uname -r)
 
 ==== feature selectors ====
+MKSH_UNLIMITED			1 to omit ulimit builtin completely
 USE_PRINTF_BUILTIN		1 to include (unsupported) printf(1) as builtin
 ===== general format =====
 HAVE_STRLEN			ac_test
@@ -2782,7 +2813,7 @@
 HAVE_CAN_FSTACKPROTECTORALL	ac_flags
 
 ==== cpp definitions ====
-DEBUG				dont use in production, wants gcc, implies:
+DEBUG				don’t use in production, wants gcc, implies:
 DEBUG_LEAKS			enable freeing resources before exiting
 KSH_VERSIONNAME_VENDOR_EXT	when patching; space+plus+word (e.g. " +SuSE")
 MKSHRC_PATH			"~/.mkshrc" (do not change)
@@ -2804,7 +2835,6 @@
 MKSH_NOPWNAM			skip PAM calls, for -static on glibc or Solaris
 MKSH_NO_CMDLINE_EDITING		disable command line editing code entirely
 MKSH_NO_DEPRECATED_WARNING	omit warning when deprecated stuff is run
-MKSH_NO_LIMITS			omit ulimit code
 MKSH_NO_SIGSETJMP		define if sigsetjmp is broken or not available
 MKSH_NO_SIGSUSPEND		use sigprocmask+pause instead of sigsuspend
 MKSH_SMALL			omit some code, optimise hard for size (slower)
diff --git a/src/FAQ2HTML.sh b/src/FAQ2HTML.sh
index 180ed98..ecff077 100644
--- a/src/FAQ2HTML.sh
+++ b/src/FAQ2HTML.sh
@@ -1,5 +1,5 @@
-#!/bin/mksh
-rcsid='$MirOS: src/bin/mksh/FAQ2HTML.sh,v 1.1 2020/02/03 22:23:33 tg Exp $'
+#!/bin/sh
+rcsid='$MirOS: src/bin/mksh/FAQ2HTML.sh,v 1.2 2020/10/31 04:17:36 tg Exp $'
 #-
 # Copyright © 2020
 #	mirabilos <m@mirbsd.org>
@@ -37,7 +37,7 @@
 src_id=$(sed $p -n '/^RCSID: /s///p' "$srcdir"/mksh.faq)
 # sanity check
 case $src_id in
-(*"$nl"*)
+*"$nl"*)
 	echo >&2 "E: more than one RCSID in mksh.faq?"
 	exit 1 ;;
 esac
diff --git a/src/check.pl b/src/check.pl
index c08e287..8e3efe9 100644
--- a/src/check.pl
+++ b/src/check.pl
@@ -1,4 +1,4 @@
-# $MirOS: src/bin/mksh/check.pl,v 1.50 2019/08/01 20:05:55 tg Exp $
+# $MirOS: src/bin/mksh/check.pl,v 1.51 2020/06/22 17:10:59 tg Exp $
 # $OpenBSD: th,v 1.1 2013/12/02 20:39:44 millert Exp $
 #-
 # Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011,
@@ -55,7 +55,7 @@
 #					default is no arguments.
 #	script			m	Value is written to a file which
 #					is passed as an argument to the program
-#					(after the arguments arguments)
+#					(after the arguments from arguments)
 #	stdin			m	Value is written to a file which is
 #					used as standard-input for the program;
 #					default is to use /dev/null.
@@ -195,7 +195,7 @@
 		the path (kludge option)
 	-p p	Use p as the program to test
 	-s s	Read tests from file s; if s is a directory, it is recursively
-		scaned for test files (which end in .t).
+		scanned for test files (which end in .t).
 	-T dir	Use dir instead of /tmp to hold temporary files
 	-t t	Use t as default time limit for tests (default is unlimited)
 	-U lcl	Use lcl as UTF-8 locale (e.g. C.UTF-8) instead of the default
diff --git a/src/check.t b/src/check.t
index 8818822..311f5c1 100644
--- a/src/check.t
+++ b/src/check.t
Binary files differ
diff --git a/src/edit.c b/src/edit.c
index 8c1c2c8..a98f5dd 100644
--- a/src/edit.c
+++ b/src/edit.c
@@ -29,7 +29,7 @@
 
 #ifndef MKSH_NO_CMDLINE_EDITING
 
-__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.351 2020/04/15 20:16:19 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.357 2020/10/31 05:02:17 tg Exp $");
 
 /*
  * in later versions we might use libtermcap for this, but since external
@@ -41,6 +41,10 @@
 #define MKSH_CLS_STRING		KSH_ESC_STRING "[;H" KSH_ESC_STRING "[J"
 #endif
 
+#if !defined(MKSH_SMALL) || !MKSH_S_NOVI
+static const char ctrl_x_e[] = "fc -e \"${VISUAL:-${EDITOR:-vi}}\" --";
+#endif
+
 /* tty driver characters we are interested in */
 #define EDCHAR_DISABLED	0xFFFFU
 #define EDCHAR_INITIAL	0xFFFEU
@@ -153,7 +157,7 @@
 	char c;
 	ssize_t n;
 
-	while ((n = blocking_read(STDIN_FILENO, &c, 1)) < 0 && errno == EINTR)
+	while ((n = blocking_read(0, &c, 1)) < 0 && errno == EINTR)
 		if (trap) {
 			x_mode(false);
 			runtraps(0);
@@ -970,7 +974,6 @@
 static int wbuf_len;		/* length of window buffers (x_cols - 3) */
 static int win;			/* window buffer in use */
 static char morec;		/* more character at right of window */
-static int lastref;		/* argument to last refresh() */
 static int holdlen;		/* length of holdbuf */
 #endif
 static int pwidth;		/* width of prompt */
@@ -989,11 +992,11 @@
 static void x_zotc3(char **);
 static void x_vi_zotc(int);
 static void x_load_hist(char **);
-static int x_search(char *, int, int);
+static int x_search(const char *, int, int);
 #ifndef MKSH_SMALL
 static int x_search_dir(int);
 #endif
-static int x_match(char *, char *);
+static int x_match(const char *, const char *);
 static void x_redraw(int);
 static void x_push(size_t);
 static void x_bind_showone(int, int);
@@ -1946,7 +1949,7 @@
 				offset = x_match(xbuf, pat);
 				if (offset >= 0) {
 					x_goto(xbuf + offset + (p - pat) -
-					    (*pat == '^'));
+					    (*pat == '^' ? 1 : 0));
 					continue;
 				}
 			}
@@ -1968,18 +1971,21 @@
 
 /* search backward from current line */
 static int
-x_search(char *pat, int sameline, int offset)
+x_search(const char *pat, int sameline, int offset)
 {
 	char **hp;
 	int i;
+	size_t patlen = strlen(pat);
 
+	if (*pat == '^')
+		--patlen;
 	for (hp = x_histp - (sameline ? 0 : 1); hp >= history; --hp) {
 		i = x_match(*hp, pat);
 		if (i >= 0) {
 			if (offset < 0)
 				x_e_putc2('\n');
 			x_load_hist(hp);
-			x_goto(xbuf + i + strlen(pat) - (*pat == '^'));
+			x_goto(xbuf + i + patlen);
 			return (i);
 		}
 	}
@@ -2024,7 +2030,7 @@
 
 /* return position of first match of pattern in string, else -1 */
 static int
-x_match(char *str, char *pat)
+x_match(const char *str, const char *pat)
 {
 	if (*pat == '^') {
 		return ((strncmp(str, pat + 1, strlen(pat + 1)) == 0) ? 0 : -1);
@@ -3125,10 +3131,6 @@
 x_edit_line(int c MKSH_A_UNUSED)
 {
 	if (x_arg_defaulted) {
-		if (xep == xbuf) {
-			x_e_putc2(KSH_BEL);
-			return (KSTD);
-		}
 		if (modified) {
 			*xep = '\0';
 			histsave(&source->line, xbuf, HIST_STORE, true);
@@ -3137,10 +3139,9 @@
 			x_arg = source->line - (histptr - x_histp);
 	}
 	if (x_arg)
-		shf_snprintf(xbuf, xend - xbuf, Tf_sd,
-		    "fc -e ${VISUAL:-${EDITOR:-vi}} --", x_arg);
+		shf_snprintf(xbuf, xend - xbuf, Tf_sd, ctrl_x_e, x_arg);
 	else
-		strlcpy(xbuf, "fc -e ${VISUAL:-${EDITOR:-vi}} --", xend - xbuf);
+		strlcpy(xbuf, ctrl_x_e, xend - xbuf);
 	xep = strnul(xbuf);
 	return (x_newline('\n'));
 }
@@ -3433,13 +3434,13 @@
 static int Backword(int);
 static int Endword(int);
 static int grabhist(int, int);
-static int grabsearch(int, int, int, const char *);
+static int grabsearch(const char *, int, int, bool);
 static void redraw_line(bool);
-static void refresh(int);
+static void refresh(bool);
 static int outofwin(void);
 static void rewindow(void);
 static int newcol(unsigned char, int);
-static void display(char *, char *, int);
+static void display(char *, char *, bool);
 static void ed_mov_opt(int, char *);
 static int expand_word(int);
 static int complete_word(int, int);
@@ -3610,7 +3611,6 @@
 	winwidth = x_cols - pwidth - 3;
 	win = 0;
 	morec = ' ';
-	lastref = 1;
 	holdlen = 0;
 
 	editmode = 2;
@@ -3712,7 +3712,7 @@
 			case 0:
 				if (state == VLIT) {
 					vs->cursor--;
-					refresh(0);
+					refresh(false);
 				} else
 					refresh(insert != 0);
 				break;
@@ -3738,7 +3738,7 @@
 					if (putbuf(ord(ch) == ORD('/') ?
 					    "/" : "?", 1, false) != 0)
 						return (-1);
-					refresh(0);
+					refresh(false);
 				}
 				if (state == VVERSION) {
 					save_cbuf();
@@ -3746,7 +3746,7 @@
 					vs->linelen = 0;
 					putbuf(KSH_VERSION,
 					    strlen(KSH_VERSION), false);
-					refresh(0);
+					refresh(false);
 				}
 			}
 		}
@@ -3758,14 +3758,14 @@
 			vi_error();
 		} else
 			vs->cbuf[vs->cursor++] = ch;
-		refresh(1);
+		refresh(true);
 		state = VNORMAL;
 		break;
 
 	case VVERSION:
 		restore_cbuf();
 		state = VNORMAL;
-		refresh(0);
+		refresh(false);
 		break;
 
 	case VARG1:
@@ -3829,7 +3829,7 @@
 				if (!srchpat[0]) {
 					vi_error();
 					state = VNORMAL;
-					refresh(0);
+					refresh(false);
 					return (0);
 				}
 			} else {
@@ -3842,17 +3842,17 @@
 				srchlen--;
 				vs->linelen -= char_len(locpat[srchlen]);
 				vs->cursor = vs->linelen;
-				refresh(0);
+				refresh(false);
 				return (0);
 			}
 			restore_cbuf();
 			state = VNORMAL;
-			refresh(0);
+			refresh(false);
 		} else if (isched(ch, edchars.kill)) {
 			srchlen = 0;
 			vs->linelen = 1;
 			vs->cursor = 1;
-			refresh(0);
+			refresh(false);
 			return (0);
 		} else if (isched(ch, edchars.werase)) {
 			unsigned int i, n;
@@ -3871,7 +3871,7 @@
 				vs->linelen -= char_len(locpat[i]);
 			srchlen = (int)n;
 			vs->cursor = vs->linelen;
-			refresh(0);
+			refresh(false);
 			return (0);
 		} else {
 			if (srchlen == SRCHLEN - 1)
@@ -3890,7 +3890,7 @@
 					vs->cbuf[vs->linelen++] = ch;
 				}
 				vs->cursor = vs->linelen;
-				refresh(0);
+				refresh(false);
 			}
 			return (0);
 		}
@@ -3931,7 +3931,7 @@
 		switch (vi_cmd(argc1, curcmd)) {
 		case -1:
 			vi_error();
-			refresh(0);
+			refresh(false);
 			break;
 		case 0:
 			if (insert != 0)
@@ -3939,7 +3939,7 @@
 			refresh(insert != 0);
 			break;
 		case 1:
-			refresh(0);
+			refresh(false);
 			return (1);
 		case 2:
 			/* back from a 'v' command - don't redraw the screen */
@@ -3954,7 +3954,7 @@
 		switch (vi_cmd(lastac, lastcmd)) {
 		case -1:
 			vi_error();
-			refresh(0);
+			refresh(false);
 			break;
 		case 0:
 			if (insert != 0) {
@@ -3967,10 +3967,10 @@
 						vi_error();
 				}
 			}
-			refresh(0);
+			refresh(false);
 			break;
 		case 1:
-			refresh(0);
+			refresh(false);
 			return (1);
 		case 2:
 			/* back from a 'v' command - can't happen */
@@ -4135,8 +4135,9 @@
 vi_cmd(int argcnt, const char *cmd)
 {
 	int ncursor;
-	int cur, c1, c2, c3 = 0;
+	int cur, c1, c2;
 	int any;
+	bool b;
 	struct edstate *t;
 
 	if (argcnt == 0 && !is_zerocount(*cmd))
@@ -4407,8 +4408,6 @@
 
 		case ORD('v'):
 			if (!argcnt) {
-				if (vs->linelen == 0)
-					return (-1);
 				if (modified) {
 					vs->cbuf[vs->linelen] = '\0';
 					histsave(&source->line, vs->cbuf,
@@ -4419,12 +4418,9 @@
 			}
 			if (argcnt)
 				shf_snprintf(vs->cbuf, vs->cbufsize, Tf_sd,
-				    "fc -e ${VISUAL:-${EDITOR:-vi}} --",
-				    argcnt);
+				    ctrl_x_e, argcnt);
 			else
-				strlcpy(vs->cbuf,
-				    "fc -e ${VISUAL:-${EDITOR:-vi}} --",
-				    vs->cbufsize);
+				strlcpy(vs->cbuf, ctrl_x_e, vs->cbufsize);
 			vs->linelen = strlen(vs->cbuf);
 			return (2);
 
@@ -4474,25 +4470,23 @@
 
 			/* FALLTHROUGH */
 		case ORD('/'):
-			c3 = 1;
+			c1 = 1;
 			srchlen = 0;
 			lastsearch = *cmd;
-			/* FALLTHROUGH */
+			if (0)
+				/* FALLTHROUGH */
 		case ORD('n'):
 		case ORD('N'):
+			  c1 = 0;
 			if (lastsearch == ORD(' '))
 				return (-1);
-			if (lastsearch == ORD('?'))
-				c1 = 1;
-			else
-				c1 = 0;
+			b = (lastsearch == ORD('?'));
 			if (*cmd == 'N')
-				c1 = !c1;
-			if ((c2 = grabsearch(modified, hnum,
-			    c1, srchpat)) < 0) {
-				if (c3) {
+				b = !b;
+			if ((c2 = grabsearch(srchpat, modified, hnum, b)) < 0) {
+				if (c1) {
 					restore_cbuf();
-					refresh(0);
+					refresh(false);
 				}
 				return (-1);
 			} else {
@@ -5117,20 +5111,20 @@
 }
 
 static int
-grabsearch(int save, int start, int fwd, const char *pat)
+grabsearch(const char *pat, int save, int start, bool fwd)
 {
 	char *hptr;
 	int hist;
 	bool anchored;
 
-	if ((start == 0 && fwd == 0) || (start >= hlast - 1 && fwd == 1))
+	if ((start == 0 && !fwd) || (start >= hlast - 1 && fwd))
 		return (-1);
 	if (fwd)
 		start++;
 	else
 		start--;
 	anchored = *pat == '^' ? (++pat, true) : false;
-	if ((hist = findhist(start, fwd, pat, anchored)) < 0) {
+	if ((hist = findhist(start, pat, fwd, anchored)) < 0) {
 		/* (start != 0 && fwd && match(holdbufp, pat) >= 0) */
 		if (start != 0 && fwd && strcmp(holdbufp, pat) >= 0) {
 			restore_cbuf();
@@ -5163,12 +5157,8 @@
 }
 
 static void
-refresh(int leftside)
+refresh(bool leftside)
 {
-	if (leftside < 0)
-		leftside = lastref;
-	else
-		lastref = leftside;
 	if (outofwin())
 		rewindow();
 	display(wbuf[1 - win], wbuf[win], leftside);
@@ -5224,7 +5214,7 @@
 }
 
 static void
-display(char *wb1, char *wb2, int leftside)
+display(char *wb1, char *wb2, bool leftside)
 {
 	unsigned char ch;
 	char *twb1, *twb2, mc;
@@ -5379,7 +5369,7 @@
 	hnum = hlast;
 	insert = INSERT;
 	lastac = 0;
-	refresh(0);
+	refresh(false);
 	return (rval);
 }
 
@@ -5494,7 +5484,7 @@
 	insert = INSERT;
 	/* prevent this from being redone... */
 	lastac = 0;
-	refresh(0);
+	refresh(false);
 
 	return (rval);
 }
diff --git a/src/exec.c b/src/exec.c
index 9d1b69b..447d441 100644
--- a/src/exec.c
+++ b/src/exec.c
@@ -24,7 +24,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.223 2020/04/07 23:14:41 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.224 2020/08/27 19:52:43 tg Exp $");
 
 #ifndef MKSH_DEFAULT_EXECSHELL
 #define MKSH_DEFAULT_EXECSHELL	MKSH_UNIXROOT "/bin/sh"
@@ -1213,7 +1213,7 @@
 	}
 
  Search:
-	if ((!tp || (tp->type == CTALIAS && !(tp->flag&ISSET))) &&
+	if ((!tp || (tp->type == CTALIAS && !(tp->flag & ISSET))) &&
 	    (flags & FC_PATH)) {
 		if (!tp) {
 			if (insert && !(flags & FC_DEFPATH)) {
@@ -1351,10 +1351,8 @@
 			XcheckN(xs, xp, p - sp);
 			memcpy(xp, sp, p - sp);
 			xp += p - sp;
-#ifdef __OS2__
-			if (xp > Xstring(xs, xp) && mksh_cdirsep(xp[-1]))
+			if (mksh_cdirsep(xp[-1]))
 				xp--;
-#endif
 			*xp++ = '/';
 		}
 		sp = p;
diff --git a/src/expr.c b/src/expr.c
index 6764279..cb389c1 100644
--- a/src/expr.c
+++ b/src/expr.c
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.107 2020/03/27 02:49:40 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.108 2020/06/20 02:27:50 tg Exp $");
 
 #define EXPRTOK_DEFNS
 #include "exprtok.h"
@@ -886,8 +886,8 @@
     unsigned int val) MKSH_A_PURE;
 
 /*
- * Generated from the UCD 13.0.0 by
- * MirOS: contrib/code/Snippets/eawparse,v 1.14 2020/03/27 01:33:21 tg Exp $
+ * Generated from the UCD 13.0.0 - see /usr/share/doc/legal/LICENCE-BSD - by
+ * MirOS: contrib/code/Snippets/eawparse,v 1.15 2020/06/15 20:31:13 tg Exp $
  */
 
 /*-
@@ -1136,6 +1136,7 @@
 	{ 0xABE5, 0xABE5 },
 	{ 0xABE8, 0xABE8 },
 	{ 0xABED, 0xABED },
+	{ 0xD7B0, 0xD7FF },
 	{ 0xFB1E, 0xFB1E },
 	{ 0xFE00, 0xFE0F },
 	{ 0xFE20, 0xFE2F },
diff --git a/src/funcs.c b/src/funcs.c
index d3427d0..ff6506d 100644
--- a/src/funcs.c
+++ b/src/funcs.c
@@ -1,7 +1,6 @@
 /*	$OpenBSD: c_ksh.c,v 1.37 2015/09/10 22:48:58 nicm Exp $	*/
 /*	$OpenBSD: c_sh.c,v 1.46 2015/07/20 20:46:24 guenther Exp $	*/
 /*	$OpenBSD: c_test.c,v 1.18 2009/03/01 20:11:06 otto Exp $	*/
-/*	$OpenBSD: c_ulimit.c,v 1.19 2013/11/28 10:33:37 sobrado Exp $	*/
 
 /*-
  * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
@@ -39,7 +38,7 @@
 #endif
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.373 2020/05/16 22:38:21 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.379 2020/08/27 19:52:44 tg Exp $");
 
 #if HAVE_KILLPG
 /*
@@ -52,12 +51,7 @@
 #define mksh_kill	kill
 #endif
 
-/* XXX conditions correct? */
-#if !defined(RLIM_INFINITY) && !defined(MKSH_NO_LIMITS)
-#define MKSH_NO_LIMITS	1
-#endif
-
-#ifdef MKSH_NO_LIMITS
+#ifdef MKSH_UNLIMITED
 #define c_ulimit	c_true
 #endif
 
@@ -713,7 +707,23 @@
 						    "exported " : "",
 						    Talias);
 				}
-				shf_puts(tp->val.s, shl_stdout);
+				if (!mksh_abspath(tp->val.s)) {
+					const char *xcwd = current_wd[0] ?
+					    current_wd : ".";
+					size_t xlen = strlen(xcwd);
+					size_t clen = strlen(tp->val.s) + 1;
+					char *xp = alloc(xlen + 1 + clen, ATEMP);
+
+					memcpy(xp, xcwd, xlen);
+					if (mksh_cdirsep(xp[xlen - 1]))
+						--xlen;
+					xp[xlen++] = '/';
+					memcpy(xp + xlen, tp->val.s, clen);
+					simplify_path(xp);
+					shf_puts(xp, shl_stdout);
+					afree(xp, ATEMP);
+				} else
+					shf_puts(tp->val.s, shl_stdout);
 			} else {
 				if (vflag)
 					shprintf(Tnot_found_s, id);
@@ -3219,214 +3229,6 @@
 		bi_errorf(Tf_s, msg);
 }
 
-#ifndef MKSH_NO_LIMITS
-#define SOFT	0x1
-#define HARD	0x2
-
-/* Magic to divine the 'm' and 'v' limits */
-
-#ifdef RLIMIT_AS
-#if !defined(RLIMIT_VMEM) || (RLIMIT_VMEM == RLIMIT_AS) || \
-    !defined(RLIMIT_RSS) || (RLIMIT_VMEM == RLIMIT_RSS)
-#define ULIMIT_V_IS_AS
-#elif defined(RLIMIT_VMEM)
-#if !defined(RLIMIT_RSS) || (RLIMIT_RSS == RLIMIT_AS)
-#define ULIMIT_V_IS_AS
-#else
-#define ULIMIT_V_IS_VMEM
-#endif
-#endif
-#endif
-
-#ifdef RLIMIT_RSS
-#ifdef ULIMIT_V_IS_VMEM
-#define ULIMIT_M_IS_RSS
-#elif defined(RLIMIT_VMEM) && (RLIMIT_VMEM == RLIMIT_RSS)
-#define ULIMIT_M_IS_VMEM
-#else
-#define ULIMIT_M_IS_RSS
-#endif
-#if defined(ULIMIT_M_IS_RSS) && defined(RLIMIT_AS) && (RLIMIT_RSS == RLIMIT_AS)
-#undef ULIMIT_M_IS_RSS
-#endif
-#endif
-
-#if !defined(RLIMIT_AS) && !defined(ULIMIT_M_IS_VMEM) && defined(RLIMIT_VMEM)
-#define ULIMIT_V_IS_VMEM
-#endif
-
-#if !defined(ULIMIT_V_IS_VMEM) && defined(RLIMIT_VMEM) && \
-    (!defined(RLIMIT_RSS) || (defined(RLIMIT_AS) && (RLIMIT_RSS == RLIMIT_AS)))
-#define ULIMIT_M_IS_VMEM
-#endif
-
-#if defined(ULIMIT_M_IS_VMEM) && defined(RLIMIT_AS) && \
-    (RLIMIT_VMEM == RLIMIT_AS)
-#undef ULIMIT_M_IS_VMEM
-#endif
-
-#if defined(ULIMIT_M_IS_RSS) && defined(ULIMIT_M_IS_VMEM)
-# error nonsensical m ulimit
-#endif
-
-#if defined(ULIMIT_V_IS_VMEM) && defined(ULIMIT_V_IS_AS)
-# error nonsensical v ulimit
-#endif
-
-struct limits {
-	/* limit resource */
-	int resource;
-	/* multiply by to get rlim_{cur,max} values */
-	unsigned int factor;
-	/* getopts char */
-	char optchar;
-	/* limit name */
-	char name[1];
-};
-
-#define RLIMITS_DEFNS
-#define FN(lname,lid,lfac,lopt)				\
-	static const struct {				\
-		int resource;				\
-		unsigned int factor;			\
-		char optchar;				\
-		char name[sizeof(lname)];		\
-	} rlimits_ ## lid = {				\
-		lid, lfac, lopt, lname			\
-	};
-#include "rlimits.gen"
-
-static void print_ulimit(const struct limits *, int);
-static int set_ulimit(const struct limits *, const char *, int);
-
-static const struct limits * const rlimits[] = {
-#define RLIMITS_ITEMS
-#include "rlimits.gen"
-};
-
-static const char rlimits_opts[] =
-#define RLIMITS_OPTCS
-#include "rlimits.gen"
-    ;
-
-int
-c_ulimit(const char **wp)
-{
-	size_t i = 0;
-	int how = SOFT | HARD, optc, what = 'f';
-	bool all = false;
-
-	while ((optc = ksh_getopt(wp, &builtin_opt, rlimits_opts)) != -1)
-		switch (optc) {
-		case 'H':
-			how = HARD;
-			break;
-		case 'S':
-			how = SOFT;
-			break;
-		case 'a':
-			all = true;
-			break;
-		case '?':
-			bi_errorf("usage: ulimit [-%s] [value]", rlimits_opts);
-			return (1);
-		default:
-			what = optc;
-		}
-
-	while (i < NELEM(rlimits)) {
-		if (rlimits[i]->optchar == what)
-			goto found;
-		++i;
-	}
-	internal_warningf("ulimit: %c", what);
-	return (1);
- found:
-	if (wp[builtin_opt.optind]) {
-		if (all || wp[builtin_opt.optind + 1]) {
-			bi_errorf(Ttoo_many_args);
-			return (1);
-		}
-		return (set_ulimit(rlimits[i], wp[builtin_opt.optind], how));
-	}
-	if (!all)
-		print_ulimit(rlimits[i], how);
-	else for (i = 0; i < NELEM(rlimits); ++i) {
-		shprintf("-%c: %-20s  ", rlimits[i]->optchar, rlimits[i]->name);
-		print_ulimit(rlimits[i], how);
-	}
-	return (0);
-}
-
-static int
-set_ulimit(const struct limits *l, const char *v, int how)
-{
-	rlim_t val = (rlim_t)0;
-	struct rlimit limit;
-
-	if (strcmp(v, "unlimited") == 0)
-		val = (rlim_t)RLIM_INFINITY;
-	else {
-		mksh_uari_t rval;
-
-		if (!evaluate(v, (mksh_ari_t *)&rval, KSH_RETURN_ERROR, false))
-			return (1);
-		/*
-		 * Avoid problems caused by typos that evaluate misses due
-		 * to evaluating unset parameters to 0...
-		 * If this causes problems, will have to add parameter to
-		 * evaluate() to control if unset params are 0 or an error.
-		 */
-		if (!rval && !ctype(v[0], C_DIGIT)) {
-			bi_errorf("invalid %s limit: %s", l->name, v);
-			return (1);
-		}
-		val = (rlim_t)((rlim_t)rval * l->factor);
-	}
-
-	if (getrlimit(l->resource, &limit) < 0) {
-#ifndef MKSH_SMALL
-		bi_errorf("limit %s could not be read, contact the mksh developers: %s",
-		    l->name, cstrerror(errno));
-#endif
-		/* some can't be read */
-		limit.rlim_cur = RLIM_INFINITY;
-		limit.rlim_max = RLIM_INFINITY;
-	}
-	if (how & SOFT)
-		limit.rlim_cur = val;
-	if (how & HARD)
-		limit.rlim_max = val;
-	if (!setrlimit(l->resource, &limit))
-		return (0);
-	if (errno == EPERM)
-		bi_errorf("%s exceeds allowable %s limit", v, l->name);
-	else
-		bi_errorf("bad %s limit: %s", l->name, cstrerror(errno));
-	return (1);
-}
-
-static void
-print_ulimit(const struct limits *l, int how)
-{
-	rlim_t val = (rlim_t)0;
-	struct rlimit limit;
-
-	if (getrlimit(l->resource, &limit)) {
-		shf_puts("unknown\n", shl_stdout);
-		return;
-	}
-	if (how & SOFT)
-		val = limit.rlim_cur;
-	else if (how & HARD)
-		val = limit.rlim_max;
-	if (val == (rlim_t)RLIM_INFINITY)
-		shf_puts("unlimited\n", shl_stdout);
-	else
-		shprintf("%lu\n", (unsigned long)(val / l->factor));
-}
-#endif
-
 int
 c_rename(const char **wp)
 {
@@ -3483,7 +3285,7 @@
 int
 c_cat(const char **wp)
 {
-	int fd = STDIN_FILENO, rv;
+	int fd = 0, rv;
 	ssize_t n, w;
 	const char *fn = "<stdin>";
 	char *buf, *cp;
@@ -3516,7 +3318,7 @@
 		if (*wp) {
 			fn = *wp++;
 			if (ksh_isdash(fn))
-				fd = STDIN_FILENO;
+				fd = 0;
 			else if ((fd = binopen2(fn, O_RDONLY)) < 0) {
 				bi_errorf(Tf_sD_s, fn, cstrerror(errno));
 				rv = 1;
@@ -3535,7 +3337,7 @@
 					opipe = block_pipe();
 					continue;
 				}
-				/* an error occured during reading */
+				/* an error occurred during reading */
 				bi_errorf(Tf_sD_s, fn, cstrerror(errno));
 				rv = 1;
 				break;
@@ -3545,7 +3347,7 @@
 			while (n) {
 				if (intrsig)
 					goto has_intrsig;
-				if ((w = write(STDOUT_FILENO, cp, n)) != -1) {
+				if ((w = write(1, cp, n)) != -1) {
 					n -= w;
 					cp += w;
 					continue;
@@ -3564,17 +3366,17 @@
 					/* fake receiving signal */
 					rv = ksh_sigmask(SIGPIPE);
 				} else {
-					/* an error occured during writing */
+					/* an error occurred during writing */
 					bi_errorf(Tf_sD_s, "<stdout>",
 					    cstrerror(errno));
 					rv = 1;
 				}
-				if (fd != STDIN_FILENO)
+				if (fd != 0)
 					close(fd);
 				goto out;
 			}
 		}
-		if (fd != STDIN_FILENO)
+		if (fd != 0)
 			close(fd);
 	} while (*wp);
 
diff --git a/src/histrap.c b/src/histrap.c
index 84211c6..7f46201 100644
--- a/src/histrap.c
+++ b/src/histrap.c
@@ -27,7 +27,7 @@
 #include <sys/file.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.169 2019/09/16 21:10:33 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.170 2020/10/01 22:53:20 tg Exp $");
 
 Trap sigtraps[ksh_NSIG + 1];
 static struct sigaction Sigact_ign;
@@ -415,7 +415,8 @@
 		bool anchored = *str == '?' ? (++str, false) : true;
 
 		/* the -1 is to avoid the current fc command */
-		if ((n = findhist(histptr - history - 1, 0, str, anchored)) < 0)
+		if ((n = findhist(histptr - history - 1, str, false,
+		    anchored)) < 0)
 			bi_errorf(Tf_sD_s, str, Tnot_in_history);
 		else
 			hp = &history[n];
@@ -479,7 +480,7 @@
  * direction.
  */
 int
-findhist(int start, int fwd, const char *str, bool anchored)
+findhist(int start, const char *str, bool fwd, bool anchored)
 {
 	char **hp;
 	int maxhist = histptr - history;
diff --git a/src/lksh.1 b/src/lksh.1
index 0d6b226..7965156 100644
--- a/src/lksh.1
+++ b/src/lksh.1
@@ -1,4 +1,4 @@
-.\" $MirOS: src/bin/mksh/lksh.1,v 1.25 2018/12/25 19:38:08 tg Exp $
+.\" $MirOS: src/bin/mksh/lksh.1,v 1.26 2020/09/04 22:37:01 tg Exp $
 .\"-
 .\" Copyright (c) 2008, 2009, 2010, 2012, 2013, 2015, 2016, 2017, 2018
 .\"	mirabilos <m@mirbsd.org>
@@ -25,7 +25,8 @@
 .\"   thus use - for hyphens and \- for minus signs and option dashes
 .\" * ~ is size-reduced and placed atop in groff, so use \*(TI
 .\" * ^ is size-reduced and placed atop in groff, so use \*(ha
-.\" * \(en does not work in nroff, so use \*(en
+.\" * \(en does not work in nroff, so use \*(en for a solo en dash
+.\" *   and \*(EM for a correctly spaced em dash
 .\" * <>| are problematic, so redefine and use \*(Lt\*(Gt\*(Ba
 .\" Also make sure to use \& *before* a punctuation char that is to not
 .\" be interpreted as punctuation, and especially with two-letter words
@@ -59,6 +60,12 @@
 .	ds ha ^
 .	ds en \(em
 .\}
+.ie n \{\
+.	ds EM \ \*(en\ \&
+.\}
+.el \{\
+.	ds EM \f(TR\^\(em\^\fP
+.\}
 .\"
 .\" Implement .Dd with the Mdocdate RCS keyword
 .\"
@@ -74,7 +81,7 @@
 .\" with -mandoc, it might implement .Mx itself, but we want to
 .\" use our own definition. And .Dd must come *first*, always.
 .\"
-.Dd $Mdocdate: December 25 2018 $
+.Dd $Mdocdate: September 4 2020 $
 .\"
 .\" Check which macro package we use, and do other -mdoc setup.
 .\"
diff --git a/src/main.c b/src/main.c
index 1328045..e17ac75 100644
--- a/src/main.c
+++ b/src/main.c
@@ -35,7 +35,7 @@
 #include <locale.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/main.c,v 1.372 2020/05/16 22:51:24 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/main.c,v 1.374 2020/10/01 20:28:54 tg Exp $");
 
 #ifndef MKSHRC_PATH
 #define MKSHRC_PATH	"~/.mkshrc"
@@ -1203,7 +1203,7 @@
 
 /*
  * Initialise tty_fd. Used for tracking the size of the terminal,
- * saving/resetting tty modes upon forground job completion, and
+ * saving/resetting tty modes upon foreground job completion, and
  * for setting up the tty process group. Return values:
  *	0 = got controlling tty
  *	1 = got terminal but no controlling tty
@@ -1435,7 +1435,8 @@
 	/* Avoid foo: foo[2]: ... */
 	if (!fileline || !source || !source->file ||
 	    strcmp(source->file, kshname) != 0)
-		shf_fprintf(shl_out, Tf_sD_, kshname + (*kshname == '-'));
+		shf_fprintf(shl_out, Tf_sD_, kshname +
+		    (*kshname == '-' ? 1 : 0));
 	if (fileline && source && source->file != NULL) {
 		shf_fprintf(shl_out, "%s[%lu]: ", source->file,
 		    (unsigned long)(source->errline ?
diff --git a/src/misc.c b/src/misc.c
index b19a253..f9f0201 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -33,7 +33,7 @@
 #include <grp.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.299 2020/05/16 22:19:58 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.302 2020/08/27 19:52:45 tg Exp $");
 
 #define KSH_CHVT_FLAG
 #ifdef MKSH_SMALL
@@ -1328,7 +1328,7 @@
 			if (go->flags & GF_ERROR)
 				bi_errorfz();
 		}
-		return ('?');
+		return (ORD('?'));
 	}
 	/**
 	 * : means argument must be present, may be part of option argument
@@ -1347,7 +1347,7 @@
 			if (optionsp[0] == ':') {
 				go->buf[0] = c;
 				go->optarg = go->buf;
-				return (':');
+				return (ORD(':'));
 			}
 			warningf(true, Tf_optfoo,
 			    (go->flags & GF_NONAME) ? "" : argv[0],
@@ -1355,7 +1355,7 @@
 			    c, Treq_arg);
 			if (go->flags & GF_ERROR)
 				bi_errorfz();
-			return ('?');
+			return (ORD('?'));
 		}
 		go->p = 0;
 	} else if (*o == ',') {
@@ -1383,7 +1383,7 @@
 				go->optarg = NULL;
 		}
 	}
-	return (c);
+	return (ord(c));
 }
 
 /*
@@ -1927,7 +1927,7 @@
  *	- if file starts with '/', append file to result & set cdpathp to NULL
  *	- if file starts with ./ or ../ append cwd and file to result
  *	  and set cdpathp to NULL
- *	- if the first element of cdpathp doesnt start with a '/' xx or '.' xx
+ *	- if the first element of cdpathp doesn't start with a '/' xx or '.' xx
  *	  then cwd is appended to result.
  *	- the first element of cdpathp is appended to result
  *	- file is appended to result
@@ -1983,16 +1983,18 @@
 			XcheckN(*xsp, xp, len);
 			memcpy(xp, cwd, len);
 			xp += len;
-			if (!mksh_cdirsep(cwd[len - 1]))
-				Xput(*xsp, xp, '/');
+			if (mksh_cdirsep(xp[-1]))
+				xp--;
+			*xp++ = '/';
 		}
 		*phys_pathp = Xlength(*xsp, xp);
 		if (use_cdpath && plen) {
 			XcheckN(*xsp, xp, plen);
 			memcpy(xp, plist, plen);
 			xp += plen;
-			if (!mksh_cdirsep(plist[plen - 1]))
-				Xput(*xsp, xp, '/');
+			if (mksh_cdirsep(xp[-1]))
+				xp--;
+			*xp++ = '/';
 			rval = 1;
 		}
 	}
@@ -2056,9 +2058,14 @@
 	case '\\':
 #endif
 		/* exactly two leading slashes? (SUSv4 3.266) */
-		if (p[1] == p[0] && !mksh_cdirsep(p[2]))
+		if (p[1] == p[0] && !mksh_cdirsep(p[2])) {
 			/* keep them, e.g. for UNC pathnames */
+#ifdef MKSH_DOSPATH
+			*p++ = '/';
+#else
 			++p;
+#endif
+		}
 		needslash = true;
 		break;
 	default:
@@ -2189,26 +2196,26 @@
 	oldpwd_s = global(TOLDPWD);
 
 	if (!wp[0]) {
-		/* No arguments - go home */
+		/* no arguments; go home */
 		if ((dir = str_val(global("HOME"))) == null) {
 			bi_errorf("no home directory (HOME not set)");
 			return (2);
 		}
 	} else if (!wp[1]) {
-		/* One argument: - or dir */
-		strdupx(allocd, wp[0], ATEMP);
-		if (ksh_isdash((dir = allocd))) {
-			afree(allocd, ATEMP);
-			allocd = NULL;
+		/* one argument: - or dir */
+		if (ksh_isdash(wp[0])) {
 			dir = str_val(oldpwd_s);
 			if (dir == null) {
 				bi_errorf(Tno_OLDPWD);
 				return (2);
 			}
 			printpath = true;
+		} else {
+			strdupx(allocd, wp[0], ATEMP);
+			dir = allocd;
 		}
 	} else if (!wp[2]) {
-		/* Two arguments - substitute arg1 in PWD for arg2 */
+		/* two arguments; substitute arg1 in PWD for arg2 */
 		size_t ilen, olen, nlen, elen;
 		char *cp;
 
@@ -2217,10 +2224,9 @@
 			return (2);
 		}
 		/*
-		 * substitute arg1 for arg2 in current path.
-		 * if the first substitution fails because the cd fails
-		 * we could try to find another substitution. For now
-		 * we don't
+		 * Substitute arg1 for arg2 in current path. If the first
+		 * substitution fails because the cd fails we could try to
+		 * find another substitution. For now, we don't.
 		 */
 		if ((cp = strstr(current_wd, wp[0])) == NULL) {
 			bi_errorf(Tbadsubst);
diff --git a/src/mksh.1 b/src/mksh.1
index 1f1a121..6cfb70c 100644
--- a/src/mksh.1
+++ b/src/mksh.1
@@ -1,4 +1,4 @@
-.\" $MirOS: src/bin/mksh/mksh.1,v 1.491 2020/05/16 22:12:36 tg Exp $
+.\" $MirOS: src/bin/mksh/mksh.1,v 1.494 2020/10/01 22:39:57 tg Exp $
 .\" $OpenBSD: ksh.1,v 1.160 2015/07/04 13:27:04 feinerer Exp $
 .\"-
 .\" Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
@@ -84,7 +84,7 @@
 .\" with -mandoc, it might implement .Mx itself, but we want to
 .\" use our own definition. And .Dd must come *first*, always.
 .\"
-.Dd $Mdocdate: May 16 2020 $
+.Dd $Mdocdate: October 1 2020 $
 .\"
 .\" Check which macro package we use, and do other -mdoc setup.
 .\"
@@ -1414,7 +1414,7 @@
 where
 .Ar name
 is a parameter name.
-Substitutions of an an array in scalar context, i.e. without an
+Substitutions of an array in scalar context, i.e. without an
 .Ar expr
 in the latter form mentioned above, expand the element with the key
 .Dq 0 .
@@ -5333,10 +5333,16 @@
 Impose a size limit of
 .Ar n
 blocks on the size of core dumps.
+Silently ignored if the system does not support this limit.
 .It Fl d Ar n
 Limit the size of the data area to
 .Ar n
 kibibytes.
+.br
+On some systems, read-only maximum
+.Xr brk 2
+size minus
+.Li etext .
 .It Fl e Ar n
 Set the maximum niceness to
 .Ar n .
@@ -5369,6 +5375,7 @@
 Impose a limit of
 .Ar n
 file descriptors that can be open at once.
+On some systems attempts to set are silently ignored.
 .It Fl O Ar n
 Set the number of AIO operations to
 .Ar n .
@@ -6064,11 +6071,18 @@
 .Op Ar n
 .No \*(haXe
 .Xc
-Edit line
+Internally run the command
+.Ic fc \-e \&"${VISUAL:\-${EDITOR:\-vi}}" Fl \- Ar n
+.br
+on a temporary script file to interactively edit line
 .Ar n
-or the current line, if not specified, interactively.
-The actual command executed is
-.Ic fc \-e ${VISUAL:\-${EDITOR:\-vi}} Ar n .
+(if
+.Ar n
+is not specified, the current line); then, unless the editor invoked
+exits nonzero but even if the script was not changed, execute the
+resulting script as if typed on the command line; both the edited
+.Pq resulting
+and original lines are added onto history.
 .It end\-of\-history: \*(ha[\*(Gt
 Moves to the end of the history.
 .It end\-of\-line: \*(haE, ANSI-End, PC-End
@@ -6426,15 +6440,18 @@
 .It Xo
 .Oo Ar n Oc Ns v
 .Xc
-Edit line
+Internally run the command
+.Ic fc \-e \&"${VISUAL:\-${EDITOR:\-vi}}" Fl \- Ar n
+.br
+on a temporary script file to interactively edit line
 .Ar n
-using the
-.Xr vi 1
-editor; if
+(if
 .Ar n
-is not specified, the current line is edited.
-The actual command executed is
-.Ic fc \-e ${VISUAL:\-${EDITOR:\-vi}} Ar n .
+is not specified, the current line); then, unless the editor invoked
+exits nonzero but even if the script was not changed, execute the
+resulting script as if typed on the command line; both the edited
+.Pq resulting
+and original lines are added onto history.
 .It * and \*(haX
 Command or file name expansion is applied to the current big-word (with an
 appended
@@ -7072,7 +7089,7 @@
 .Xr memmove 3 .
 .Pp
 This document attempts to describe
-.Nm mksh\ R59b
+.Nm mksh\ R59c
 and up,
 .\" with vendor patches from insert-your-name-here,
 compiled without any options impacting functionality, such as
diff --git a/src/mksh.faq b/src/mksh.faq
index 83e0191..6634d60 100644
--- a/src/mksh.faq
+++ b/src/mksh.faq
@@ -1,4 +1,4 @@
-RCSID: $MirOS: src/bin/mksh/mksh.faq,v 1.7 2020/04/25 12:09:55 tg Exp $
+RCSID: $MirOS: src/bin/mksh/mksh.faq,v 1.10 2020/10/01 22:59:12 tg Exp $
 ToC: spelling
 Title: How do you spell <tt>mksh</tt>? How do you pronounce it?
 
@@ -207,7 +207,7 @@
 ToC: oldbsd
 Title: Why do other BSDs and QNX still use pdksh instead of mksh?
 
-<p>Some systems are resistent to change, mostly due to bikeshedding
+<p>Some systems are resistant to change, mostly due to bikeshedding
  (some people would, for example, rather see all shells banned to
  ports/pkgsrc®) and hysterial raisins (historical reasons ☻). Most
  BSDs have mksh packages available, and it works on all of them and
@@ -297,6 +297,29 @@
 (yash), <tt>$ZSH_VERSION</tt> (or if <tt>$VERSION</tt> begins with “zsh”); a <a
 href="@@RELPATH@@ksh-chan.htm#which-shell">list of more approaches</a> exists.
 ----
+ToC: ctrl-x-e
+Title: Multiline command editing
+
+<p>mksh is very independent of the terminal and external libraries and
+ databases, such as termcap, and therefore is conservative in which ANSI
+ control codes are sent to the terminal.</p>
+<p>For this reason, mksh’s input line editing uses a “windowed one-line”
+ concept: the line the cursor is on is a “window” into the whole input,
+ horizontally scrolled. Some other shells (that are much larger and have
+ more dependencies on external tooling) use a “multi-line” editing mode,
+ and users occasionally wish for this. It is on the long-term TODO, but
+ (due to the aforementioned implications) this is not trivial.</p>
+<p>One way to achieve multi-line editing is to <em>dis</em>able input
+ line editing: <tt>set +o emacs +o vi</tt><br />This will, however, lose
+ you all editing features: tab completion, cursor keys, history, etc.</p>
+<p>Another way, if you don’t need it all the time, is to use a function
+ that spawns your editor on the input line: press <tt>^Xe</tt> in the
+ default emacs mode or <tt>Esc + v</tt> in vi mode. Once you exit the
+ editor, whatever was written there is run; this includes the original
+ command line if you quit without saving, so request the editor to exit
+ nōn-zero (e.g. using jupp’s “abendjoe” command) to prevent execution.
+ This is <em>really</em> useful to write ad-hōc scripts as well.</p>
+----
 ToC: ctrl-l-cls
 Title: ^L (Ctrl-L) does not clear the screen
 
@@ -439,6 +462,15 @@
  … becomes…<br />
  <tt>[[ foo = *@(foo|bar)*baz* ]]</tt></p>
 ----
+ToC: trim-vector
+Title: ${@?}: bad substitution
+
+<p>In mksh, you cannot assign to or trim a vector (yet). For most
+ cases it is possible to write the affected code in a way avoiding
+ this extension; for example, trimming <tt>${@#foo}</tt> could be
+ applied to <tt>$1</tt> only and <tt>${@?}</tt> can be replaced
+ with a test whether <tt>$# -eq 0</tt>.</p>
+----
 ToC: extensions-to-avoid
 Title: Are there any extensions to avoid?
 
diff --git a/src/os2.c b/src/os2.c
index 8a80782..ed31eff 100644
--- a/src/os2.c
+++ b/src/os2.c
@@ -1,7 +1,7 @@
 /*-
  * Copyright (c) 2015, 2017, 2020
  *	KO Myung-Hun <komh@chollian.net>
- * Copyright (c) 2017
+ * Copyright (c) 2017, 2020
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -32,11 +32,23 @@
 #include <unistd.h>
 #include <process.h>
 
-__RCSID("$MirOS: src/bin/mksh/os2.c,v 1.10 2020/04/07 11:13:45 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/os2.c,v 1.11 2020/10/01 21:13:45 tg Exp $");
 
-static char *remove_trailing_dots(char *);
-static int access_stat_ex(int (*)(), const char *, void *);
-static int test_exec_exist(const char *, char *);
+struct a_s_arg {
+	union {
+		int (*i)(const char *, int);
+		int (*p)(const char *, void *);
+	} fn;
+	union {
+		int i;
+		void *p;
+	} arg;
+	bool isint;
+};
+
+static void remove_trailing_dots(char *, size_t);
+static int access_stat_ex(const char *, struct a_s_arg *);
+static int test_exec_exist(const char *, void *);
 static void response(int *, const char ***);
 static char *make_response_file(char * const *);
 static void add_temp(const char *);
@@ -223,23 +235,18 @@
 }
 
 /* remove trailing dots */
-static char *
-remove_trailing_dots(char *name)
+static void
+remove_trailing_dots(char *name, size_t namelen)
 {
-	char *p = strnul(name);
+	char *p = name + namelen;
 
 	while (--p > name && *p == '.')
 		/* nothing */;
 
 	if (*p != '.' && *p != '/' && *p != '\\' && *p != ':')
 		p[1] = '\0';
-
-	return (name);
 }
 
-#define REMOVE_TRAILING_DOTS(name)	\
-	remove_trailing_dots(memcpy(alloca(strlen(name) + 1), name, strlen(name) + 1))
-
 /* alias of stat() */
 extern int _std_stat(const char *, struct stat *);
 
@@ -247,7 +254,12 @@
 int
 stat(const char *name, struct stat *buffer)
 {
-	return (_std_stat(REMOVE_TRAILING_DOTS(name), buffer));
+	size_t namelen = strlen(name) + 1;
+	char nodots[namelen];
+
+	memcpy(nodots, name, namelen);
+	remove_trailing_dots(nodots, namelen);
+	return (_std_stat(nodots, buffer));
 }
 
 /* alias of access() */
@@ -257,6 +269,9 @@
 int
 access(const char *name, int mode)
 {
+	size_t namelen = strlen(name) + 1;
+	char nodots[namelen];
+
 	/*
 	 * On OS/2 kLIBC, X_OK is set only for executable files.
 	 * This prevents scripts from being executed.
@@ -264,7 +279,9 @@
 	if (mode & X_OK)
 		mode = (mode & ~X_OK) | R_OK;
 
-	return (_std_access(REMOVE_TRAILING_DOTS(name), mode));
+	memcpy(nodots, name, namelen);
+	remove_trailing_dots(nodots, namelen);
+	return (_std_access(nodots, mode));
 }
 
 #define MAX_X_SUFFIX_LEN	4
@@ -274,7 +291,7 @@
 
 /* call fn() by appending executable extensions */
 static int
-access_stat_ex(int (*fn)(), const char *name, void *arg)
+access_stat_ex(const char *name, struct a_s_arg *action)
 {
 	char *x_name;
 	const char **x_suffix;
@@ -288,7 +305,8 @@
 		strlcpy(x_name, name, x_namelen);
 		strlcat(x_name, *x_suffix, x_namelen);
 
-		rc = fn(x_name, arg);
+		rc = action->isint ? action->fn.i(x_name, action->arg.i) :
+		    action->fn.p(x_name, action->arg.p);
 	}
 
 	afree(x_name, ATEMP);
@@ -300,8 +318,12 @@
 int
 access_ex(int (*fn)(const char *, int), const char *name, int mode)
 {
-	/*XXX this smells fishy --mirabilos */
-	return (access_stat_ex(fn, name, (void *)mode));
+	struct a_s_arg arg;
+
+	arg.fn.i = fn;
+	arg.arg.i = mode;
+	arg.isint = true;
+	return (access_stat_ex(name, &arg));
 }
 
 /* stat()/lstat() version */
@@ -309,34 +331,39 @@
 stat_ex(int (*fn)(const char *, struct stat *),
     const char *name, struct stat *buffer)
 {
-	return (access_stat_ex(fn, name, buffer));
+	struct a_s_arg arg;
+
+	arg.fn.p = fn;
+	arg.arg.p = buffer;
+	arg.isint = false;
+	return (access_stat_ex(name, &arg));
 }
 
 static int
-test_exec_exist(const char *name, char *real_name)
+test_exec_exist(const char *name, void *arg)
 {
 	struct stat sb;
+	char *real_name;
 
 	if (stat(name, &sb) < 0 || !S_ISREG(sb.st_mode))
 		return (-1);
 
-	/* safe due to calculations in real_exec_name() */
-	memcpy(real_name, name, strlen(name) + 1);
-
+	/*XXX memory leak */
+	strdupx(real_name, name, ATEMP);
+	*((char **)arg) = real_name;
 	return (0);
 }
 
 const char *
 real_exec_name(const char *name)
 {
-	char x_name[strlen(name) + MAX_X_SUFFIX_LEN + 1];
-	const char *real_name = name;
+	struct a_s_arg arg;
+	char *real_name;
 
-	if (access_stat_ex(test_exec_exist, real_name, x_name) != -1)
-		/*XXX memory leak */
-		strdupx(real_name, x_name, ATEMP);
-
-	return (real_name);
+	arg.fn.p = &test_exec_exist;
+	arg.arg.p = (void *)(&real_name);
+	arg.isint = false;
+	return (access_stat_ex(name, &arg) ? name : real_name);
 }
 
 /* make a response file to pass a very long command line */
diff --git a/src/rlimits.gen b/src/rlimits.gen
index 5ccd007..cf684b4 100644
--- a/src/rlimits.gen
+++ b/src/rlimits.gen
@@ -1,6 +1,6 @@
 /* +++ GENERATED FILE +++ DO NOT EDIT +++ */
 /*-
- * Copyright (c) 2013, 2015
+ * Copyright (c) 2013, 2015, 2019
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -17,11 +17,13 @@
  * of dealing in the work, even if advised of the possibility of such
  * damage or existence of a defect, except proven that it results out
  * of said person's immediate fault when using the work as intended.
+ *-
+ * Keep {r,u}limits.opt in sync with each other!
  */
 
 #ifndef RLIMITS_OPTCS
 #if defined(RLIMITS_DEFNS)
-__RCSID("$MirOS: src/bin/mksh/rlimits.opt,v 1.4 2019/04/24 20:56:31 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/rlimits.opt,v 1.5 2020/07/24 20:11:18 tg Exp $");
 #elif defined(RLIMITS_ITEMS)
 #define FN(lname,lid,lfac,lopt) (const struct limits *)(&rlimits_ ## lid),
 #endif
diff --git a/src/rlimits.opt b/src/rlimits.opt
index db2a531..b806b40 100644
--- a/src/rlimits.opt
+++ b/src/rlimits.opt
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2013, 2015
+ * Copyright (c) 2013, 2015, 2019
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -16,10 +16,12 @@
  * of dealing in the work, even if advised of the possibility of such
  * damage or existence of a defect, except proven that it results out
  * of said person's immediate fault when using the work as intended.
+ *-
+ * Keep {r,u}limits.opt in sync with each other!
  */
 
 @RLIMITS_DEFNS
-__RCSID("$MirOS: src/bin/mksh/rlimits.opt,v 1.4 2019/04/24 20:56:31 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/rlimits.opt,v 1.5 2020/07/24 20:11:18 tg Exp $");
 @RLIMITS_ITEMS
 #define FN(lname,lid,lfac,lopt) (const struct limits *)(&rlimits_ ## lid),
 @@
diff --git a/src/sh.h b/src/sh.h
index 8394c89..575f9bf 100644
--- a/src/sh.h
+++ b/src/sh.h
@@ -78,7 +78,9 @@
 #if HAVE_PATHS_H
 #include <paths.h>
 #endif
+#ifndef MKSH_NOPWNAM
 #include <pwd.h>
+#endif
 #include <setjmp.h>
 #include <signal.h>
 #include <stdarg.h>
@@ -191,9 +193,9 @@
 #endif
 
 #ifdef EXTERN
-__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.898 2020/05/16 22:38:23 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.904 2020/10/31 03:53:06 tg Exp $");
 #endif
-#define MKSH_VERSION "R59 2020/05/16"
+#define MKSH_VERSION "R59 2020/10/31"
 
 /* arithmetic types: C implementation */
 #if !HAVE_CAN_INTTYPES
@@ -247,10 +249,6 @@
 
 /* other standard types */
 
-#if !HAVE_RLIM_T
-typedef unsigned long rlim_t;
-#endif
-
 #if !HAVE_SIG_T
 #undef sig_t
 typedef void (*sig_t)(int);
@@ -668,7 +666,7 @@
 #endif
 #endif
 
-#if (!defined(MKSH_BUILDMAKEFILE4BSD) && !defined(MKSH_BUILDSH)) || (MKSH_BUILD_R != 592)
+#if (!defined(MKSH_BUILDMAKEFILE4BSD) && !defined(MKSH_BUILDSH)) || (MKSH_BUILD_R != 593)
 #error Must run Build.sh to compile this.
 extern void thiswillneverbedefinedIhope(void);
 int
@@ -2522,7 +2520,7 @@
 char **histpos(void) MKSH_A_PURE;
 int histnum(int);
 #endif
-int findhist(int, int, const char *, bool) MKSH_A_PURE;
+int findhist(int, const char *, bool, bool) MKSH_A_PURE;
 char **hist_get_newest(bool);
 void inittraps(void);
 void alarm_init(void);
diff --git a/src/shf.c b/src/shf.c
index 8e95fc6..ec7fc1f 100644
--- a/src/shf.c
+++ b/src/shf.c
@@ -27,7 +27,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.101 2019/12/11 17:56:58 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.102 2020/06/22 17:11:03 tg Exp $");
 
 /* flags to shf_emptybuf() */
 #define EB_READSW	0x01	/* about to switch to reading */
@@ -666,7 +666,7 @@
 	if (nbytes < 0)
 		internal_errorf(Tf_szs, Tshf_write, nbytes, Tbytes);
 
-	/* Don't buffer if buffer is empty and we're writting a large amount. */
+	/* don't buffer if buffer is empty and we're writing a large amount */
 	if ((ncopy = shf->wnleft) &&
 	    (shf->wp != shf->buf || nbytes < shf->wnleft)) {
 		if (ncopy > nbytes)
@@ -782,7 +782,7 @@
 #define FL_ZERO		0x040	/* '0' seen */
 #define FL_DOT		0x080	/* '.' seen */
 #define FL_UPPER	0x100	/* format character was uppercase */
-#define FL_NUMBER	0x200	/* a number was formated %[douxefg] */
+#define FL_NUMBER	0x200	/* a number was formatted %[douxefg] */
 #define FL_SIZET	0x400	/* 'z' seen */
 #define FM_SIZES	0x430	/* h/l/z mask */
 
diff --git a/src/syn.c b/src/syn.c
index 3387cf5..bcb5a5a 100644
--- a/src/syn.c
+++ b/src/syn.c
@@ -24,7 +24,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.128 2020/03/31 00:30:05 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.129 2020/10/31 01:21:58 tg Exp $");
 
 struct nesting_state {
 	int start_token;	/* token than began nesting (eg, FOR) */
@@ -268,13 +268,12 @@
 {
 	struct op *t;
 	int c, iopn = 0, syniocf, lno;
-	struct ioword *iop, **iops;
+	struct ioword *iop;
 	XPtrV args, vars;
 	struct nesting_state old_nesting;
 	bool check_decl_utility;
+	static struct ioword *iops[NUFILE + 1];
 
-	/* NUFILE is small enough to leave this addition unchecked */
-	iops = alloc2((NUFILE + 1), sizeof(struct ioword *), ATEMP);
 	XPinit(args, 16);
 	XPinit(vars, 16);
 
@@ -282,7 +281,6 @@
 	switch (c = token(cf|KEYWORD|sALIAS|CMDASN)) {
 	default:
 		REJECT;
-		afree(iops, ATEMP);
 		XPfree(args);
 		XPfree(vars);
 		/* empty line */
@@ -510,12 +508,11 @@
 	}
 
 	if (iopn == 0) {
-		afree(iops, ATEMP);
 		t->ioact = NULL;
 	} else {
 		iops[iopn++] = NULL;
-		iops = aresize2(iops, iopn, sizeof(struct ioword *), ATEMP);
-		t->ioact = iops;
+		t->ioact = alloc2(iopn, sizeof(struct ioword *), ATEMP);
+		memcpy(t->ioact, iops, iopn * sizeof(struct ioword *));
 	}
 
 	if (t->type == TCOM || t->type == TDBRACKET) {
diff --git a/src/tree.c b/src/tree.c
index 335e3fb..85fa0d6 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.97 2018/10/20 18:46:00 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.100 2020/10/31 04:28:54 tg Exp $");
 
 #define INDENT	8
 
@@ -37,6 +37,19 @@
 /* "foo& ; bar" and "foo |& ; bar" are invalid */
 static bool prevent_semicolon;
 
+/* here document diversion */
+static unsigned short ptree_nest;
+static bool ptree_hashere;
+static struct shf ptree_heredoc;
+#define ptree_outhere(shf) do {					\
+	if (ptree_hashere) {					\
+		shf_puts(shf_sclose(&ptree_heredoc), (shf));	\
+		shf_putc('\n', (shf));				\
+		ptree_hashere = false;				\
+		/*prevent_semicolon = true;*/			\
+	}							\
+} while (/* CONSTCOND */ 0)
+
 static const char Telif_pT[] = "elif %T";
 
 /*
@@ -82,8 +95,11 @@
 			w = (const char **)t->vars;
 			while (*w)
 				fptreef(shf, indent, Tf_S_, *w++);
-		} else
+		}
+#ifndef MKSH_SMALL
+		  else
 			shf_puts("#no-vars# ", shf);
+#endif
 		if (t->args) {
 			w = t->args;
 			if (*w && **w == CHAR) {
@@ -97,8 +113,11 @@
 			}
 			while (*w)
 				fptreef(shf, indent, Tf_S_, *w++);
-		} else
+		}
+#ifndef MKSH_SMALL
+		  else
 			shf_puts("#no-args# ", shf);
+#endif
 		break;
 	case TEXEC:
 		t = t->left;
@@ -221,36 +240,9 @@
 		prevent_semicolon = false;
 		break;
 	}
-	if ((ioact = t->ioact) != NULL) {
-		bool need_nl = false;
-
+	if ((ioact = t->ioact) != NULL)
 		while (*ioact != NULL)
 			pioact(shf, *ioact++);
-		/* Print here documents after everything else... */
-		ioact = t->ioact;
-		while (*ioact != NULL) {
-			struct ioword *iop = *ioact++;
-
-			/* heredoc is NULL when tracing (set -x) */
-			if ((iop->ioflag & (IOTYPE | IOHERESTR)) == IOHERE &&
-			    iop->heredoc) {
-				shf_putc('\n', shf);
-				shf_puts(iop->heredoc, shf);
-				fptreef(shf, indent, Tf_s,
-				    evalstr(iop->delim, 0));
-				need_nl = true;
-			}
-		}
-		/*
-		 * Last delimiter must be followed by a newline (this
-		 * often leads to an extra blank line, but it's not
-		 * worth worrying about)
-		 */
-		if (need_nl) {
-			shf_putc('\n', shf);
-			prevent_semicolon = true;
-		}
-	}
 }
 
 static void
@@ -272,11 +264,29 @@
 		shf_putc('<', shf);
 		break;
 	case IOHERE:
+		if (flag & IOHERESTR) {
+			shf_puts("<<<", shf);
+			goto ioheredelim;
+		}
 		shf_puts("<<", shf);
 		if (flag & IOSKIP)
 			shf_putc('-', shf);
-		else if (flag & IOHERESTR)
-			shf_putc('<', shf);
+		if (iop->heredoc /* nil when tracing */) {
+			/* here document diversion */
+			if (!ptree_hashere) {
+				shf_sopen(NULL, 0, SHF_WR | SHF_DYNAMIC,
+				    &ptree_heredoc);
+				ptree_hashere = true;
+			}
+			shf_putc('\n', &ptree_heredoc);
+			shf_puts(iop->heredoc, &ptree_heredoc);
+			/* iop->delim is set before iop->heredoc */
+			shf_puts(evalstr(iop->delim, 0), &ptree_heredoc);
+		}
+ ioheredelim:
+		/* delim is NULL during syntax error printing */
+		if (iop->delim && !(iop->ioflag & IONDELIM))
+			wdvarput(shf, iop->delim, 0, WDS_TPUTS);
 		break;
 	case IOCAT:
 		shf_puts(">>", shf);
@@ -293,11 +303,8 @@
 		shf_puts(flag & IORDUP ? "<&" : ">&", shf);
 		break;
 	}
-	/* name/delim are NULL when printing syntax errors */
-	if (type == IOHERE) {
-		if (iop->delim && !(iop->ioflag & IONDELIM))
-			wdvarput(shf, iop->delim, 0, WDS_TPUTS);
-	} else if (iop->ioname) {
+	/* name is NULL for IOHERE or when printing syntax errors */
+	if (iop->ioname) {
 		if (flag & IONAMEXP)
 			print_value_quoted(shf, iop->ioname);
 		else
@@ -342,13 +349,6 @@
 			c = ord(*wp++);
 			if (opmode & WDS_TPUTS)
 				switch (c) {
-				case ORD('\n'):
-					if (quotelevel == 0) {
-						c = ORD('\'');
-						shf_putc(c, shf);
-						shf_putc(ORD('\n'), shf);
-					}
-					break;
 				default:
 					if (quotelevel == 0)
 						/* FALLTHROUGH */
@@ -467,6 +467,9 @@
 {
 	int c;
 
+	if (!ptree_nest++)
+		ptree_hashere = false;
+
 	while ((c = ord(*fmt++))) {
 		if (c == '%') {
 			switch ((c = ord(*fmt++))) {
@@ -504,10 +507,10 @@
 						shf_putc(';', shf);
 					shf_putc(' ', shf);
 				} else {
-					int i;
+					int i = indent;
 
+					ptree_outhere(shf);
 					shf_putc('\n', shf);
-					i = indent;
 					while (i >= 8) {
 						shf_putc('\t', shf);
 						i -= 8;
@@ -530,6 +533,9 @@
  dont_trash_prevent_semicolon:
 		;
 	}
+
+	if (!--ptree_nest)
+		ptree_outhere(shf);
 }
 
 /*
diff --git a/src/ulimit.c b/src/ulimit.c
new file mode 100644
index 0000000..5bdfdd1
--- /dev/null
+++ b/src/ulimit.c
@@ -0,0 +1,360 @@
+/*	$OpenBSD: c_ulimit.c,v 1.19 2013/11/28 10:33:37 sobrado Exp $	*/
+
+/*-
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
+ *		 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
+ *		 2019, 2020
+ *	mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/ulimit.c,v 1.3 2020/07/24 21:08:26 tg Exp $");
+
+#define SOFT	0x1
+#define HARD	0x2
+
+#if HAVE_RLIMIT
+
+#if !HAVE_RLIM_T
+typedef unsigned long rlim_t;
+#endif
+
+/* Magic to divine the 'm' and 'v' limits */
+
+#ifdef RLIMIT_AS
+#if !defined(RLIMIT_VMEM) || (RLIMIT_VMEM == RLIMIT_AS) || \
+    !defined(RLIMIT_RSS) || (RLIMIT_VMEM == RLIMIT_RSS)
+#define ULIMIT_V_IS_AS
+#elif defined(RLIMIT_VMEM)
+#if !defined(RLIMIT_RSS) || (RLIMIT_RSS == RLIMIT_AS)
+#define ULIMIT_V_IS_AS
+#else
+#define ULIMIT_V_IS_VMEM
+#endif
+#endif
+#endif
+
+#ifdef RLIMIT_RSS
+#ifdef ULIMIT_V_IS_VMEM
+#define ULIMIT_M_IS_RSS
+#elif defined(RLIMIT_VMEM) && (RLIMIT_VMEM == RLIMIT_RSS)
+#define ULIMIT_M_IS_VMEM
+#else
+#define ULIMIT_M_IS_RSS
+#endif
+#if defined(ULIMIT_M_IS_RSS) && defined(RLIMIT_AS) && \
+    !defined(__APPLE__) && (RLIMIT_RSS == RLIMIT_AS)
+/* On Mac OSX keep -m as -v alias for pkgsrc and other software expecting it */
+#undef ULIMIT_M_IS_RSS
+#endif
+#endif
+
+#if !defined(RLIMIT_AS) && !defined(ULIMIT_M_IS_VMEM) && defined(RLIMIT_VMEM)
+#define ULIMIT_V_IS_VMEM
+#endif
+
+#if !defined(ULIMIT_V_IS_VMEM) && defined(RLIMIT_VMEM) && \
+    (!defined(RLIMIT_RSS) || (defined(RLIMIT_AS) && (RLIMIT_RSS == RLIMIT_AS)))
+#define ULIMIT_M_IS_VMEM
+#endif
+
+#if defined(ULIMIT_M_IS_VMEM) && defined(RLIMIT_AS) && \
+    (RLIMIT_VMEM == RLIMIT_AS)
+#undef ULIMIT_M_IS_VMEM
+#endif
+
+#if defined(ULIMIT_M_IS_RSS) && defined(ULIMIT_M_IS_VMEM)
+# error nonsensical m ulimit
+#endif
+
+#if defined(ULIMIT_V_IS_VMEM) && defined(ULIMIT_V_IS_AS)
+# error nonsensical v ulimit
+#endif
+
+#define LIMITS_GEN	"rlimits.gen"
+
+#else /* !HAVE_RLIMIT */
+
+#undef RLIMIT_CORE	/* just in case */
+
+#if defined(UL_GETFSIZE)
+#define KSH_UL_GFIL	UL_GETFSIZE
+#elif defined(UL_GFILLIM)
+#define KSH_UL_GFIL	UL_GFILLIM
+#elif defined(__A_UX__) || defined(KSH_ULIMIT2_TEST)
+#define KSH_UL_GFIL	1
+#endif
+
+#if defined(UL_SETFSIZE)
+#define KSH_UL_SFIL	UL_SETFSIZE
+#elif defined(UL_SFILLIM)
+#define KSH_UL_SFIL	UL_SFILLIM
+#elif defined(__A_UX__) || defined(KSH_ULIMIT2_TEST)
+#define KSH_UL_SFIL	2
+#endif
+
+#if defined(KSH_UL_SFIL)
+#define KSH_UL_WFIL	true
+#else
+#define KSH_UL_WFIL	false
+#define KSH_UL_SFIL	0
+#endif
+
+#if defined(UL_GETMAXBRK)
+#define KSH_UL_GBRK	UL_GETMAXBRK
+#elif defined(UL_GMEMLIM)
+#define KSH_UL_GBRK	UL_GMEMLIM
+#elif defined(__A_UX__) || defined(KSH_ULIMIT2_TEST)
+#define KSH_UL_GBRK	3
+#endif
+
+#if defined(UL_GDESLIM)
+#define KSH_UL_GDES	UL_GDESLIM
+#elif defined(__GLIBC__) || defined(KSH_ULIMIT2_TEST)
+#define KSH_UL_GDES	4
+#endif
+
+extern char etext;
+extern long ulimit(int, long);
+
+#define LIMITS_GEN	"ulimits.gen"
+
+#endif /* !HAVE_RLIMIT */
+
+struct limits {
+	/* limit resource / read command */
+	int resource;
+#if HAVE_RLIMIT
+	/* multiply by to get rlim_{cur,max} values */
+	unsigned int factor;
+#else
+	/* write command */
+	int wesource;
+	/* writable? */
+	bool writable;
+#endif
+	/* getopts char */
+	char optchar;
+	/* limit name */
+	char name[1];
+};
+
+#define RLIMITS_DEFNS
+#if HAVE_RLIMIT
+#define FN(lname,lid,lfac,lopt)				\
+	static const struct {				\
+		int resource;				\
+		unsigned int factor;			\
+		char optchar;				\
+		char name[sizeof(lname)];		\
+	} rlimits_ ## lid = {				\
+		lid, lfac, lopt, lname			\
+	};
+#else
+#define FN(lname,lg,ls,lw,lopt)				\
+	static const struct {				\
+		int rcmd;				\
+		int wcmd;				\
+		bool writable;				\
+		char optchar;				\
+		char name[sizeof(lname)];		\
+	} rlimits_ ## lg = {				\
+		lg, ls, lw, lopt, lname			\
+	};
+#endif
+#include LIMITS_GEN
+
+static void print_ulimit(const struct limits *, int);
+static int set_ulimit(const struct limits *, const char *, int);
+
+static const struct limits * const rlimits[] = {
+#define RLIMITS_ITEMS
+#include LIMITS_GEN
+};
+
+static const char rlimits_opts[] =
+#define RLIMITS_OPTCS
+#include LIMITS_GEN
+#ifndef RLIMIT_CORE
+	"c"
+#endif
+    ;
+
+int
+c_ulimit(const char **wp)
+{
+	size_t i = 0;
+	int how = SOFT | HARD, optc;
+	char what = 'f';
+	bool all = false;
+
+	while ((optc = ksh_getopt(wp, &builtin_opt, rlimits_opts)) != -1)
+		switch (optc) {
+		case ORD('H'):
+			how = HARD;
+			break;
+		case ORD('S'):
+			how = SOFT;
+			break;
+		case ORD('a'):
+			all = true;
+			break;
+		case ORD('?'):
+			bi_errorf("usage: ulimit [-%s] [value]", rlimits_opts);
+			return (1);
+		default:
+			what = optc;
+		}
+
+	while (i < NELEM(rlimits)) {
+		if (rlimits[i]->optchar == what)
+			goto found;
+		++i;
+	}
+#ifndef RLIMIT_CORE
+	if (what == ORD('c'))
+		/* silently accept */
+		return 0;
+#endif
+	internal_warningf("ulimit: %c", what);
+	return (1);
+ found:
+	if (wp[builtin_opt.optind]) {
+		if (all || wp[builtin_opt.optind + 1]) {
+			bi_errorf(Ttoo_many_args);
+			return (1);
+		}
+		return (set_ulimit(rlimits[i], wp[builtin_opt.optind], how));
+	}
+	if (!all)
+		print_ulimit(rlimits[i], how);
+	else for (i = 0; i < NELEM(rlimits); ++i) {
+		shprintf("-%c: %-20s  ", rlimits[i]->optchar, rlimits[i]->name);
+		print_ulimit(rlimits[i], how);
+	}
+	return (0);
+}
+
+#if HAVE_RLIMIT
+#define RL_T rlim_t
+#define RL_U (rlim_t)RLIM_INFINITY
+#else
+#define RL_T long
+#define RL_U LONG_MAX
+#endif
+
+static int
+set_ulimit(const struct limits *l, const char *v, int how MKSH_A_UNUSED)
+{
+	RL_T val = (RL_T)0;
+#if HAVE_RLIMIT
+	struct rlimit limit;
+#endif
+
+	if (strcmp(v, "unlimited") == 0)
+		val = RL_U;
+	else {
+		mksh_uari_t rval;
+
+		if (!evaluate(v, (mksh_ari_t *)&rval, KSH_RETURN_ERROR, false))
+			return (1);
+		/*
+		 * Avoid problems caused by typos that evaluate misses due
+		 * to evaluating unset parameters to 0...
+		 * If this causes problems, will have to add parameter to
+		 * evaluate() to control if unset params are 0 or an error.
+		 */
+		if (!rval && !ctype(v[0], C_DIGIT)) {
+			bi_errorf("invalid %s limit: %s", l->name, v);
+			return (1);
+		}
+#if HAVE_RLIMIT
+		val = (rlim_t)((rlim_t)rval * l->factor);
+#else
+		val = (RL_T)rval;
+#endif
+	}
+
+#if HAVE_RLIMIT
+	if (getrlimit(l->resource, &limit) < 0) {
+#ifndef MKSH_SMALL
+		bi_errorf("limit %s could not be read, contact the mksh developers: %s",
+		    l->name, cstrerror(errno));
+#endif
+		/* some can't be read */
+		limit.rlim_cur = RLIM_INFINITY;
+		limit.rlim_max = RLIM_INFINITY;
+	}
+	if (how & SOFT)
+		limit.rlim_cur = val;
+	if (how & HARD)
+		limit.rlim_max = val;
+	if (!setrlimit(l->resource, &limit))
+		return (0);
+#else
+	if (l->writable == false) {
+	    /* check.t:ulimit-2 fails if we return 1 and/or do:
+		bi_errorf(Tf_ro, l->name);
+	    */
+		return (0);
+	}
+	if (ulimit(l->wesource, val) != -1L)
+		return (0);
+#endif
+	if (errno == EPERM)
+		bi_errorf("%s exceeds allowable %s limit", v, l->name);
+	else
+		bi_errorf("bad %s limit: %s", l->name, cstrerror(errno));
+	return (1);
+}
+
+static void
+print_ulimit(const struct limits *l, int how MKSH_A_UNUSED)
+{
+	RL_T val = (RL_T)0;
+#if HAVE_RLIMIT
+	struct rlimit limit;
+
+	if (getrlimit(l->resource, &limit))
+#else
+	if ((val = ulimit(l->resource, 0)) < 0)
+#endif
+	    {
+		shf_puts("unknown\n", shl_stdout);
+		return;
+	}
+#if HAVE_RLIMIT
+	if (how & SOFT)
+		val = limit.rlim_cur;
+	else if (how & HARD)
+		val = limit.rlim_max;
+#endif
+	if (val == RL_U)
+		shf_puts("unlimited\n", shl_stdout);
+	else {
+#if HAVE_RLIMIT
+		val /= l->factor;
+#elif defined(KSH_UL_GBRK)
+		if (l->resource == KSH_UL_GBRK)
+			val = (RL_T)(((size_t)val - (size_t)&etext) /
+			    (size_t)1024);
+#endif
+		shprintf("%lu\n", (unsigned long)val);
+	}
+}
diff --git a/src/ulimits.gen b/src/ulimits.gen
new file mode 100644
index 0000000..374df60
--- /dev/null
+++ b/src/ulimits.gen
@@ -0,0 +1,60 @@
+/* +++ GENERATED FILE +++ DO NOT EDIT +++ */
+/*-
+ * Copyright (c) 2013, 2015, 2020
+ *	mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ *-
+ * Keep {r,u}limits.opt in sync with each other!
+ */
+
+#ifndef RLIMITS_OPTCS
+#if defined(RLIMITS_DEFNS)
+__RCSID("$MirOS: src/bin/mksh/ulimits.opt,v 1.2 2020/07/24 20:50:11 tg Exp $");
+#elif defined(RLIMITS_ITEMS)
+#define FN(lname,lg,ls,lw,lopt) (const struct limits *)(&rlimits_ ## lg),
+#endif
+#ifndef F0
+#define F0 FN
+#endif
+#ifdef KSH_UL_GFIL
+FN("file(blocks)", KSH_UL_GFIL, KSH_UL_SFIL, KSH_UL_WFIL, 'f')
+#endif
+#ifdef KSH_UL_GBRK
+FN("data(KiB)", KSH_UL_GBRK, 0, false, 'd')
+#endif
+#ifdef KSH_UL_GDES
+FN("nofiles(descriptors)", KSH_UL_GDES, 0, false, 'n')
+#endif
+#undef F0
+#undef FN
+#undef RLIMITS_DEFNS
+#undef RLIMITS_ITEMS
+#else
+"a"
+#ifdef KSH_UL_GBRK
+"d"
+#endif
+#ifdef KSH_UL_GFIL
+"f"
+#endif
+"H"
+#ifdef KSH_UL_GDES
+"n"
+#endif
+"S"
+#undef RLIMITS_OPTCS
+#endif
diff --git a/src/ulimits.opt b/src/ulimits.opt
new file mode 100644
index 0000000..aad64be
--- /dev/null
+++ b/src/ulimits.opt
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2013, 2015, 2020
+ *	mirabilos <m@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ *-
+ * Keep {r,u}limits.opt in sync with each other!
+ */
+
+@RLIMITS_DEFNS
+__RCSID("$MirOS: src/bin/mksh/ulimits.opt,v 1.2 2020/07/24 20:50:11 tg Exp $");
+@RLIMITS_ITEMS
+#define FN(lname,lg,ls,lw,lopt) (const struct limits *)(&rlimits_ ## lg),
+@@
+
+/* generic options for the ulimit builtin */
+
+<a|
+<H|
+<S|
+
+/* do not use options -H, -S or -a or change the order */
+
+>f|KSH_UL_GFIL
+FN("file(blocks)", KSH_UL_GFIL, KSH_UL_SFIL, KSH_UL_WFIL
+
+>d|KSH_UL_GBRK
+FN("data(KiB)", KSH_UL_GBRK, 0, false
+
+>n|KSH_UL_GDES
+FN("nofiles(descriptors)", KSH_UL_GDES, 0, false
+
+|RLIMITS_OPTCS
diff --git a/src/var.c b/src/var.c
index ade60c5..e3c92eb 100644
--- a/src/var.c
+++ b/src/var.c
@@ -29,7 +29,7 @@
 #include <sys/sysctl.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/var.c,v 1.236 2020/04/13 16:29:34 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/var.c,v 1.237 2020/06/22 17:11:03 tg Exp $");
 
 /*-
  * Variables
@@ -1900,7 +1900,7 @@
 		break;
 	}
 
-	/* see comment below regarding possible opions */
+	/* see comment below regarding possible options */
 	opts = istset ? "L#R#UZ#afgi#lnprtux" : "p";
 
 	builtin_opt.flags |= GF_PLUSOPT;