about summary refs log tree commit diff
diff options
context:
space:
mode:
authorDaniel Shahaf <danielsh@apache.org>2020-03-07 21:36:46 +0000
committerDaniel Shahaf <danielsh@apache.org>2020-03-07 21:36:46 +0000
commit6fc8e8628f9c3da6e4b83c3de67e44376708cbcb (patch)
treec711b1d53564bcca757799e1d734e24859c5f32e
parent0d3d07c56f75064310271bf4469c5c9c13145d58 (diff)
parentdd50f125b5eb65896642d2ff664adefd33f1004c (diff)
downloadzsh-6fc8e8628f9c3da6e4b83c3de67e44376708cbcb.tar.gz
zsh-6fc8e8628f9c3da6e4b83c3de67e44376708cbcb.tar.xz
zsh-6fc8e8628f9c3da6e4b83c3de67e44376708cbcb.zip
Merge remote-tracking branch 'origin/master' into 5.9
* Test/D02glob.ztst:
  On the "unreadable directories can be globbed (users/24619, users/24626)"
  test, resolve conflicts by removing the Cygwin-only skip that has been added
  in master, since the test is passing on this branch.  This effectively reverts
  workers/45492.  See discussion starting in workers/45504.

* origin/master:
  unposted: Remove 'sgi', as that OpenBSD port has been discontinued.
  45509: fix typos in B01cd.ztst
  45490 (+45495 and a test): refactor rlimits.c
  github #49: Fix typo: longson should be loongson
  users/24710: Fix job control problem with sudo.
  45492: skip test added by users/24633 on Cygwin
  45488: COMP_WORDS for bash need "$@"-style quoting
  45487: Missing mod_export declarations for AIX
  45447: Complete vcs_info_hookadd and vcs_info_hookdel. Expose _vcs_info_hooks as a top-level helper function.
  45463: test: kill: Document why we use SIGURG
  45453: builtins: kill: Do not signal current process group when pid is empty
  45452: builtins: kill: Add `kill ''` regression test with explicit sigspec
  45451: builtins: kill: Add basic test suite
  github #48/0002: vcs_info git: properly detect bare repositories
  github #48/0001: vcs_info git: avoid warnings in bare repositories
  unposted: Post-release version bump
  unposted: Release 5.8
  CVE-2019-20044: Update change log for preceding commits
  Update NEWS/README
  Add unsetopt/PRIVILEGED tests
  Clean up error-message white space
  Improve PRIVILEGED fixes (again)
  Improve PRIVILEGED fixes
  Drop privileges securely
  unposted: V01zmodload: Fix failing test from workers/45385
  45423: _su: Improve arg handling, shell look-ups
  unposted: _zip: Recognise '--'
  45385: Add a test for 'zmodload -Fa' preemptively disabling ("blacklisting"?) features.
  unposted: Test release: 5.7.1-test-3
  zsh/system: Fix infinite loop in sysread
  _diff_options: Restore -w completion lost in workers/43351
  unposted: Fix ChangeLog typo.
  45368: Add tests for workers/45367's issue about double slashes in 'cd -P' and /home/daniel/in/zsh.
  45373: Fix ERR_EXIT bug in else branch of if.
  45372: Record a symlink loop bug involving :P
  45365: _git: Fix __git_recent_branches for the case when a commit has an empty message
  45343: Queue signals around arithmetic evaluations
  45344: Document where third-party completion functions should be installed.
  45345: internal: ztst.vim: Fix highlighting of zsh comments in test payload
  unposted: internal: Add some comments and fix indentation.  No functional change.
  45340: internal: Document the difference between paramtab and realparamtab.
  45332: _git: add completion for git-version
  _brace_parameter: add missing \

Conflicts:
	ChangeLog
	Test/D02glob.ztst
	Test/V01zmodload.ztst
-rw-r--r--.gitignore1
-rw-r--r--ChangeLog163
-rw-r--r--Completion/BSD/Type/_obsd_architectures2
-rw-r--r--Completion/Unix/Command/_git13
-rw-r--r--Completion/Unix/Command/_su45
-rw-r--r--Completion/Unix/Command/_zip6
-rw-r--r--Completion/Unix/Type/_diff_options1
-rw-r--r--Completion/Zsh/Command/_zstyle3
-rw-r--r--Completion/Zsh/Context/_brace_parameter2
-rw-r--r--Completion/Zsh/Function/_vcs_info31
-rw-r--r--Completion/Zsh/Type/_vcs_info_hooks2
-rw-r--r--Completion/bashcompinit2
-rw-r--r--Config/version.mk4
-rw-r--r--Etc/BUGS2
-rw-r--r--Functions/VCS_Info/Backends/VCS_INFO_get_data_git6
-rw-r--r--INSTALL10
-rw-r--r--NEWS18
-rw-r--r--README11
-rw-r--r--Src/Builtins/rlimits.awk116
-rw-r--r--Src/Builtins/rlimits.c692
-rw-r--r--Src/Builtins/rlimits.mdd15
-rw-r--r--Src/Modules/system.c2
-rw-r--r--Src/Zle/zle_keymap.c2
-rw-r--r--Src/Zle/zle_main.c2
-rw-r--r--Src/Zle/zle_move.c2
-rw-r--r--Src/builtin.c2
-rw-r--r--Src/compat.c2
-rw-r--r--Src/exec.c6
-rw-r--r--Src/init.c15
-rw-r--r--Src/jobs.c19
-rw-r--r--Src/loop.c2
-rw-r--r--Src/math.c15
-rw-r--r--Src/openssh_bsd_setres_id.c129
-rw-r--r--Src/options.c141
-rw-r--r--Src/params.c8
-rw-r--r--Src/signals.c3
-rw-r--r--Src/utils.c2
-rw-r--r--Src/zsh.mdd3
-rw-r--r--Src/zsh_system.h94
-rw-r--r--Test/B01cd.ztst10
-rw-r--r--Test/B11kill.ztst86
-rw-r--r--Test/B12limit.ztst10
-rw-r--r--Test/C03traps.ztst9
-rw-r--r--Test/D02glob.ztst4
-rw-r--r--Test/E01options.ztst25
-rw-r--r--Test/P01privileged.ztst197
-rw-r--r--Test/README1
-rw-r--r--Test/V01zmodload.ztst16
-rw-r--r--Util/ztst-syntax.vim8
-rw-r--r--configure.ac2
50 files changed, 1287 insertions, 675 deletions
diff --git a/.gitignore b/.gitignore
index e46f8517e..ec2f56642 100644
--- a/.gitignore
+++ b/.gitignore
@@ -123,7 +123,6 @@ Src/Builtins/*.mdh
 Src/Builtins/*.mdhi
 Src/Builtins/*.mdhs
 Src/Builtins/*.mdh.tmp
-Src/Builtins/rlimits.h
 
 Src/Modules/Makefile.in
 Src/Modules/*.export
diff --git a/ChangeLog b/ChangeLog
index a1b3b2d75..b26340e43 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -57,6 +57,169 @@
 	* 45131: Src/exec.c, Test/E02xtrace.ztst: Make a function that
 	redefines itself preserve its tracedness.
 
+2020-03-06  Daniel Shahaf  <danielsh@apache.org>
+
+	* unposted: Completion/BSD/Type/_obsd_architectures: Remove
+	'sgi', as that OpenBSD port has been discontinued.
+
+2020-03-06  Jun-ichi Takimoto  <takimoto-j@kba.biglobe.ne.jp>
+
+	* 45509: Test/B01cd.ztst: Fix typos.
+
+	* 45490 (+45495 and a test): .gitignore,
+	Src/Builtins/rlimits.awk, Src/Builtins/rlimits.c,
+	Src/Builtins/rlimits.mdd, Test/B12limit.ztst, configure.ac:
+	Refactor rlimits.c.
+
+2020-03-05  Bryan Irvine  <sparctacus@gmail.com>
+
+	* github #49: Completion/BSD/Type/_obsd_architectures: Fix typo:
+	longson should be loongson
+
+2020-02-27  Peter Stephenson  <p.w.stephenson@ntlworld.com>
+
+	* users/24710: Src/exec.c, Src/jobs.c, Src/signals.c: when using
+	kill or killpg to test for continued existince of a process
+	group, check errono is ESRCH on failure as EPERM indicates
+	processes exist but under a different UID.
+
+2020-02-27  Jun-ichi Takimoto  <takimoto-j@kba.biglobe.ne.jp>
+
+	* 45492: Test/D02glob.ztst: skip test added by users/24633 
+	on Cygwin.
+
+2020-02-25  Peter Stephenson  <p.stephenson@samsung.com>
+
+	* 45488: Marc Cornella: Completion/bashcompinit: Need "$@"
+	quoting for empty words in COMP_WORDS for bash completion.
+
+2020-02-24  Peter Stephenson  <p.stephenson@samsung.com>
+
+	* 45487: Src/Zle/zle_keymap.c, Src/Zle/zle_main.c,
+	Src/Zle/zle_move.c, Src/builtin.c, Src/compat.c, Src/utils.c:
+	Add missing mod_export for AIX compilation.
+
+2020-02-19  Daniel Shahaf  <danielsh@apache.org>
+
+	* 45447: Completion/Zsh/Command/_zstyle,
+	Completion/Zsh/Function/_vcs_info,
+	Completion/Zsh/Type/_vcs_info_hooks: Complete vcs_info_hookadd
+	and vcs_info_hookdel. Expose _vcs_info_hooks as a top-level
+	helper function.
+
+2020-02-18  Chris Down  <chris@chrisdown.name>
+
+	* 45463: Test/B11kill.ztst: test: kill: Document why we use
+	SIGURG
+
+	* 45453: Src/jobs.c, Test/B11kill.ztst: builtins: kill: Do not
+	signal current process group when pid is empty
+
+	* 45452: Test/B11kill.ztst: builtins: kill: Add `kill ''`
+	regression test with explicit sigspec
+
+	* 45451: Test/B11kill.ztst: builtins: kill: Add basic test suite
+
+2020-02-17  brian m. carlson  <sandals@crustytoothpaste.net>
+
+	* github #48/0002:
+	Functions/VCS_Info/Backends/VCS_INFO_get_data_git: vcs_info git:
+	properly detect bare repositories
+
+	* github #48/0001:
+	Functions/VCS_Info/Backends/VCS_INFO_get_data_git: vcs_info git:
+	avoid warnings in bare repositories
+
+2020-02-14  dana  <dana@dana.is>
+
+	* unposted: Config/version.mk: Post-release version bump
+
+	* unposted: Config/version.mk: Update for 5.8
+
+	* Sam Foxman, Daniel Shahaf, dana: CVE-2019-20044: NEWS,
+	README, Src/openssh_bsd_setres_id.c, Src/options.c, Src/zsh.mdd,
+	Src/zsh_system.h, Test/E01options.ztst, Test/P01privileged.ztst,
+	Test/README, configure.ac: Fix insecure dropping of privileges
+	when unsetting PRIVILEGED option
+
+	* unposted: Test/V01zmodload.ztst: Fix failing test from
+	workers/45385
+
+	* 45423 (tweaked): Completion/Unix/Command/_su: Improve arg
+	handling, shell look-ups
+
+2020-02-07  dana  <dana@dana.is>
+
+	* unposted: Completion/Unix/Command/_zip: Recognise '--'
+
+2020-02-06  Daniel Shahaf  <danielsh@apache.org>
+
+	* 45385: Test/V01zmodload.ztst: Add a test for 'zmodload -Fa'
+	preemptively disabling ("blacklisting"?) features.
+
+2020-02-06  dana  <dana@dana.is>
+
+	* unposted: Config/version.mk: Update for 5.7.1-test-3
+
+	* Roman Perepelitsa: 45382: Src/Modules/system.c: Fix infinite
+	loop in sysread
+
+	* Martin von Wittich: 45388 (tweaked):
+	Completion/Unix/Type/_diff_options: Restore -w completion lost
+	in workers/43351
+
+2020-02-03  Daniel Shahaf  <danielsh@apache.org>
+
+	* 45368: Test/B01cd.ztst, Test/D02glob.ztst: Add tests for
+	workers/45367's issue about double slashes in 'cd -P' and
+	$PWD.
+
+2020-02-02  Peter Stephenson  <p.w.stephenson@ntlworld.com>
+
+	* 45373: Src/loop.c, Test/C03traps.ztst: ERR_EXIT failed on
+	command substitution in else branch.
+
+2020-02-02  Daniel Shahaf  <danielsh@apache.org>
+
+	* 45372: Etc/BUGS: Record a symlink loop bug involving :P
+
+2020-02-02  WGH  <wgh@torlan.ru>
+
+	* 45365: Completion/Unix/Command/_git: Fix __git_recent_branches
+	for the case when a commit has an empty message
+
+2020-01-29  Daniel Shahaf  <danielsh@apache.org>
+
+	* 45343: Src/exec.c, Src/math.c: Queue signals around arithmetic
+	evaluations
+
+	* 45344: INSTALL: Document where third-party completion functions
+	should be installed.
+
+2020-01-28  Daniel Shahaf  <danielsh@apache.org>
+
+	* 45345: Util/ztst-syntax.vim: internal: ztst.vim: Fix
+	highlighting of zsh comments in test payload
+
+2020-01-26  Daniel Shahaf  <danielsh@apache.org>
+
+	* unposted: Src/init.c: internal: Add some comments and fix
+	indentation.  No functional change.
+
+2020-01-23  Daniel Shahaf  <danielsh@apache.org>
+
+	* 45340: Src/params.c: internal: Document the difference between
+	paramtab and realparamtab.
+
+2020-01-19  Eitan Adler  <lists@eitanadler.com>
+
+	* 45332: Completion/Unix/Command/_git: add completion for
+	git-version
+
+2020-01-19  Mikael Magnusson  <mikachu@gmail.com>
+
+	* unposted: _brace_parameter: add missing \
+
 2020-01-16  Daniel Shahaf  <danielsh@apache.org>
 
 	* 45305: Test/A01grammar.ztst: Add an XFail test: The
diff --git a/Completion/BSD/Type/_obsd_architectures b/Completion/BSD/Type/_obsd_architectures
index ca3e0e12f..cec000a08 100644
--- a/Completion/BSD/Type/_obsd_architectures
+++ b/Completion/BSD/Type/_obsd_architectures
@@ -3,4 +3,4 @@
 local expl
 
 _description architectures expl 'architecture'
-compadd "$@" "$expl[@]" alpha amd64 arm64 armv7 hppa i386 landisk longson luna88k macppc octeon sgi sparc64
+compadd "$@" "$expl[@]" alpha amd64 arm64 armv7 hppa i386 landisk loongson luna88k macppc octeon sparc64
diff --git a/Completion/Unix/Command/_git b/Completion/Unix/Command/_git
index 92b72b936..8487ebc1a 100644
--- a/Completion/Unix/Command/_git
+++ b/Completion/Unix/Command/_git
@@ -407,6 +407,12 @@ _git-bundle () {
   return ret
 }
 
+(( $+functions[_git-version] )) ||
+_git-version () {
+  _arguments -S $endopt \
+    '--build-options[also print build options]'
+}
+
 (( $+functions[_git-check-ignore] )) ||
 _git-check-ignore () {
   _arguments -s -S $endopt \
@@ -5951,7 +5957,8 @@ _git_commands () {
     show-branch:'show branches and their commits'
     verify-commit:'check GPG signature of commits'
     verify-tag:'check GPG signature of tags'
-    whatchanged:'show commit-logs and differences they introduce')
+    whatchanged:'show commit-logs and differences they introduce'
+    version:'show git version')
 
   interaction_commands=(
     archimport:'import an Arch repository into git'
@@ -6655,8 +6662,8 @@ __git_recent_branches() {
 
   # 4. Obtain log messages for all of them in one shot.
   # TODO: we'd really like --sort=none here...  but git doesn't support such a thing.
-  # The \n removal is because for-each-ref prints a \n after each entry.
-  descriptions=( ${(0)"$(_call_program all-descriptions "git --no-pager for-each-ref --format='%(refname)%00%(subject)%00'" refs/heads/${(q)^branches} "--")"//$'\n'} )
+  local z=$'\0'
+  descriptions=( "${(0)"$(_call_program all-descriptions "git --no-pager for-each-ref --format='%(refname)%00%(subject)'" refs/heads/${(q)^branches} "--")"//$'\n'/$z}" )
 
   # 5. Synthesize the data structure _describe wants.
   local -a branches_colon_descriptions
diff --git a/Completion/Unix/Command/_su b/Completion/Unix/Command/_su
index 900905632..032f867f4 100644
--- a/Completion/Unix/Command/_su
+++ b/Completion/Unix/Command/_su
@@ -9,36 +9,44 @@ local shell usr
 (( $words[(i)-(l|-login)] < CURRENT )) || args=( '-[use a login shell]' )
 case $OSTYPE in
   linux*)
+    # Some of these options only apply to util-linux, not shadow-utils
     args=( -S $args
-      '(-c --command --session-command *)'{-c,--command=}'[pass command to shell]:command string:_cmdstring'
+      '(-c --command --session-command *)'{-c+,--command=}'[pass command to shell]:command string:_cmdstring'
       "(-c --command *)--session-command=[pass command to shell and don't create a new session]:command string:_cmdstring"
       '(--fast -f)'{-f,--fast}'[pass -f to shell]'
       '(-l --login -m -p --preserve-environment)'{-l,--login}'[use a login shell]'
       '(-l --login -m -p --preserve-environment)'{-m,-p,--preserve-environment}"[don't reset environment]"
-      '(-s --shell)'{-s,--shell=}'[run the specified shell]:shell:->shells'
+      '(-s --shell)'{-s+,--shell=}'[run the specified shell]:shell:->shells'
       '(-)--help[display help information]'
       '(-)--version[display version information]'
     )
-    (( EUID )) || args+=(
-      '(-g --group)'{-g,--group=}'[specify primary group]:group:_groups'
-      \*{-G,--supp-group=}'[specify supplemental group]:group:_groups'
+    (( $#_comp_priv_prefix || EUID == 0 )) && args+=(
+      '(-g --group)'{-g+,--group=}'[specify primary group]:group:_groups'
+      \*{-G+,--supp-group=}'[specify supplemental group]:group:_groups'
     )
     first="(--help --version)${first#???}"
   ;;
   *bsd*|darwin*|dragonfly*)
     args+=(
-      '-c[use settings from specified login class]:class'
       '-f[if the invoked shell is csh, prevent it from reading .cshrc]'
       '(-m)-l[use a login shell]'
       "(-l)-m[don't reset environment]"
     )
   ;|
+  *bsd*|dragonfly*)
+    args+=(
+      '-c+[use settings from specified login class]:class'
+    )
+  ;|
   freebsd*) args+=( '-s[set the MAC label]' ) ;;
   openbsd*)
     args+=(
-      '(-K)-a[specify authentication type]:authentication type'
+      # See login.conf(5)
+      '(-K)-a+[specify authentication type]:authentication type:(
+        activ chpass crypto lchpass passwd radius reject skey snk token yubikey
+      )'
       '(-a)-K[shorthand for -a passwd]'
-      '-s[run the specified shell]:shell:->shells'
+      '-s+[run the specified shell]:shell:->shells'
       '-L[loop until login succeeds]'
     )
   ;;
@@ -57,13 +65,26 @@ fi
 
 _arguments $args ${(e)first} "*:shell arguments:= ->rest" && return
 
-usr=${line[norm]/--/root}
-if (( $#opt_args[(i)-(s|-shell)] )); then
+usr=${${(Q)line[norm]}/--/root}
+# OpenBSD supports appending a log-in method to the user name, as in usr:radius
+[[ $OSTYPE == openbsd* ]] && usr=${usr%:*}
+
+# Normal users generally don't appear in passwd on macOS; try the Directory
+# Service first
+if [[ $OSTYPE == darwin* ]] && (( $+commands[dscl] )); then
+  shell=${"$(
+    _call_program shells dscl . -read /Users/${(q)usr} UserShell
+  )"#UserShell: }
+fi
+
+if [[ -n $shell ]]; then
+  : # Found above
+elif (( ${#${(@M)args:#*-s[+\[]*:*}} && $#opt_args[(i)-(s|-shell)] )); then
   shell=${(v)opt_args[(i)-(s|-shell)]}
 elif (( ${+commands[getent]} )); then
-  shell="${$(_call_program shells getent passwd $usr)##*:}"
+  shell="${$(_call_program shells getent passwd ${(q)usr})##*:}"
 else
-  shell="${${(M@)${(@f)$(</etc/passwd)}:#$usr*}##*:}"
+  shell="${${(M@)${(@f)$(</etc/passwd)}:#${usr}:*}##*:}"
 fi
 
 case $state in
diff --git a/Completion/Unix/Command/_zip b/Completion/Unix/Command/_zip
index 1b1b6c315..bc9aab1a5 100644
--- a/Completion/Unix/Command/_zip
+++ b/Completion/Unix/Command/_zip
@@ -82,7 +82,7 @@ case $service in
       '*:file:->files' && ret=0
   ;;
   unzip)
-    _arguments -C -s \
+    _arguments -C -s -S \
       '(-Z)-M[page output]' \
       - unzip \
       '(-f -u -l -t -z -d -p)-c[extract files to stdout including file names]' \
@@ -130,7 +130,7 @@ esac
 [[ $state == zipinfo ]] && uzi="-Z[zipinfo mode]"
 
 if [[ $service == zipinfo ]] || [[ -n $uzi ]]; then
-    _arguments -C -s \
+    _arguments -C -s -S \
       $uzi \
       '(-2 -s -m -l -v -h -t -T -z)-1[filenames only]' \
       '(-1 -s -m -l -v -T)-2[just filenames but allow -h/-t/-z]' \
@@ -170,7 +170,7 @@ case $state in
       fi 2>/dev/null
       if [[ $zipfile !=  $_zip_cache_name ]]; then
 	_zip_cache_name="$zipfile"
-	_zip_cache_list=( ${(f)"$(zipinfo -1 $_zip_cache_name)"} )
+	_zip_cache_list=( ${(f)"$(zipinfo -1 -- $_zip_cache_name)"} )
       fi
      _wanted files expl 'file from archive' \
 	 _multi_parts / _zip_cache_list && return
diff --git a/Completion/Unix/Type/_diff_options b/Completion/Unix/Type/_diff_options
index 4fd27442e..440913dff 100644
--- a/Completion/Unix/Type/_diff_options
+++ b/Completion/Unix/Type/_diff_options
@@ -92,6 +92,7 @@ if _pick_variant -r variant -c $cmd gnu=GNU unix -v || [[ $OSTYPE = freebsd<12->
     '--ignore-file-name-case[ignore case when comparing file names]' \
     '!(--ignore-file-name-case)--no-ignore-file-name-case' \
     '(-b --ignore-space-change)'{-b,--ignore-space-change}'[ignore changes in the amount of white space]' \
+    '(-w --ignore-all-space)'{-w,--ignore-all-space}'[ignore all white space]' \
     '(-B --ignore-blank-lines)'{-B,--ignore-blank-lines}'[ignore lines that are all blank]' \
     '(-I --ignore-matching-lines)'{-I+,--ignore-matching-lines=}'[ignore lines that match regex]:line exclusion regex:' \
     '--strip-trailing-cr[strip trailing carriage return on input]' \
diff --git a/Completion/Zsh/Command/_zstyle b/Completion/Zsh/Command/_zstyle
index 7db73c0c0..07b60605f 100644
--- a/Completion/Zsh/Command/_zstyle
+++ b/Completion/Zsh/Command/_zstyle
@@ -5,9 +5,6 @@ local nm=$compstate[nmatches] taglist patterns contexts MATCH
 integer MBEGIN MEND
 typeset -A opt_args styles
 
-_vcs_info_hooks() {
-  compadd - ${functions[(I)+vi-*]#+vi-}
-}
 # Assoc array of styles; the values give the possible top-level
 # contexts:
 #   c   completion
diff --git a/Completion/Zsh/Context/_brace_parameter b/Completion/Zsh/Context/_brace_parameter
index c6e74bf7b..6960cec9b 100644
--- a/Completion/Zsh/Context/_brace_parameter
+++ b/Completion/Zsh/Context/_brace_parameter
@@ -38,7 +38,7 @@ if [[ $PREFIX = *'${('[^\)]# ]]; then
 	case $char in
 	  (g)
 	  compset -P '*'
-	  flags=('o:octal escapes' 'c:expand ^X etc.' 'e:expand \M-t etc.')
+	  flags=('o:octal escapes' 'c:expand ^X etc.' 'e:expand \\M-t etc.')
 	  _describe -t format 'format option' flags -Q -S ''
 	  ;;
 
diff --git a/Completion/Zsh/Function/_vcs_info b/Completion/Zsh/Function/_vcs_info
new file mode 100644
index 000000000..fdb28de6a
--- /dev/null
+++ b/Completion/Zsh/Function/_vcs_info
@@ -0,0 +1,31 @@
+#compdef vcs_info_hookadd vcs_info_hookdel
+
+local -a hook_types=(
+  gen-applied-string
+  gen-hg-bookmark-string
+  gen-mqguards-string
+  gen-unapplied-string
+  no-vcs
+  post-backend
+  post-quilt
+  pre-addon-quilt
+  pre-get-data
+  set-branch-format
+  set-hgrev-format
+  set-message
+  set-patch-format
+  start-up
+)
+
+local -a specs
+case $service in
+  (vcs_info_hookdel)
+    specs=( '-a[remove all occurrences, not just the first]' )
+    ;;
+esac
+
+# TODO: for vcs_info_hookdel complete only functions installed for that hook
+_arguments : \
+  $specs \
+  ":hook type:($hook_types)" \
+  '*:hook function:_vcs_info_hooks'
diff --git a/Completion/Zsh/Type/_vcs_info_hooks b/Completion/Zsh/Type/_vcs_info_hooks
new file mode 100644
index 000000000..bad915000
--- /dev/null
+++ b/Completion/Zsh/Type/_vcs_info_hooks
@@ -0,0 +1,2 @@
+#autoload
+compadd - ${functions[(I)+vi-*]#+vi-}
diff --git a/Completion/bashcompinit b/Completion/bashcompinit
index 02290a16f..b278ac8f4 100644
--- a/Completion/bashcompinit
+++ b/Completion/bashcompinit
@@ -10,7 +10,7 @@ _bash_complete() {
 
   (( COMP_POINT = 1 + ${#${(j. .)words[1,CURRENT-1]}} + $#QIPREFIX + $#IPREFIX + $#PREFIX ))
   (( COMP_CWORD = CURRENT - 1))
-  COMP_WORDS=( $words )
+  COMP_WORDS=( "${words[@]}" )
   BASH_VERSINFO=( 2 05b 0 1 release )
 
   savejobstates=( ${(kv)jobstates} )
diff --git a/Config/version.mk b/Config/version.mk
index 99d8f965c..6540e4b98 100644
--- a/Config/version.mk
+++ b/Config/version.mk
@@ -27,5 +27,5 @@
 # This must also serve as a shell script, so do not add spaces around the
 # `=' signs.
 
-VERSION=5.7.1-test-2
-VERSION_DATE='December 21, 2019'
+VERSION=5.8.0.1-dev
+VERSION_DATE='February 15, 2020'
diff --git a/Etc/BUGS b/Etc/BUGS
index 8112299f5..99a0d9753 100644
--- a/Etc/BUGS
+++ b/Etc/BUGS
@@ -29,3 +29,5 @@ skipped when STTY=... is set for that command
 44007 - Martijn - exit in trap executes rest of function
 See test case in Test/C03traps.ztst.
 ------------------------------------------------------------------------
+45282: ${${:-foo}:P} where foo is a symlink that points to itself segfaults
+------------------------------------------------------------------------
diff --git a/Functions/VCS_Info/Backends/VCS_INFO_get_data_git b/Functions/VCS_Info/Backends/VCS_INFO_get_data_git
index ceb4f978a..5ddce72a6 100644
--- a/Functions/VCS_Info/Backends/VCS_INFO_get_data_git
+++ b/Functions/VCS_Info/Backends/VCS_INFO_get_data_git
@@ -138,7 +138,11 @@ VCS_INFO_git_handle_patches () {
 
 gitdir=${vcs_comm[gitdir]}
 VCS_INFO_git_getbranch ${gitdir}
-gitbase=$( ${vcs_comm[cmd]} rev-parse --show-toplevel )
+gitbase=$( ${vcs_comm[cmd]} rev-parse --show-toplevel 2> /dev/null )
+if [[ -z ${gitbase} ]]; then
+    # Bare repository
+    gitbase=${gitdir:P}
+fi
 rrn=${gitbase:t}
 if zstyle -t ":vcs_info:${vcs}:${usercontext}:${rrn}" get-revision ; then
     gitsha1=$(${vcs_comm[cmd]} rev-parse --quiet --verify HEAD)
diff --git a/INSTALL b/INSTALL
index cf70893a7..f347a4480 100644
--- a/INSTALL
+++ b/INSTALL
@@ -251,6 +251,16 @@ source code in the directory that "configure" is in.  For example,
 Note that this is mutually exclusive with using the source directories
 as make can become confused by build files created in the source directories.
 
+Writing third-party autoloadable functions
+------------------------------------------
+
+Third-party autoloadable functions, including but not limited to completion
+functions, should be installed into the share/zsh/site-functions/ directory
+under the respective installation prefix.  That would typically be written as
+$(DESTDIR)$(PREFIX)/share/zsh/site-functions/ in a makefile.  If the
+third-party tool's $(PREFIX) is not the same as zsh's prefix, then that
+directory should be added to $fpath in zsh's initialization files.
+
 
 ================================
 AUTOMATIC NEW USER CONFIGURATION
diff --git a/NEWS b/NEWS
index af59cb4e6..964e1633f 100644
--- a/NEWS
+++ b/NEWS
@@ -4,8 +4,22 @@ CHANGES FROM PREVIOUS VERSIONS OF ZSH
 
 Note also the list of incompatibilities in the README file.
 
-Changes since 5.7.1
--------------------
+Changes since 5.7.1-test-3
+--------------------------
+
+CVE-2019-20044: When unsetting the PRIVILEGED option, the shell sets its
+effective user and group IDs to match their respective real IDs. On some
+platforms (including Linux and macOS, but not FreeBSD), when the RUID and
+EUID were both non-zero, it was possible to regain the shell's former
+privileges by e.g. assigning to the EUID or EGID parameter. In the course
+of investigating this issue, it was also found that the setopt built-in
+did not correctly report errors when unsetting the option, which
+prevented users from handling them as the documentation recommended.
+setopt now returns non-zero if it is unable to safely drop privileges.
+[ Reported by Sam Foxman <samfoxman320@gmail.com>. ]
+
+Changes from 5.7.1 to 5.7.1-test-3
+----------------------------------
 
 The zsh/zutil module's zparseopts builtin learnt an -F option to abort
 parsing when an unrecognised option-like parameter is encountered.
diff --git a/README b/README
index bcec3def1..589c10da5 100644
--- a/README
+++ b/README
@@ -5,8 +5,9 @@ THE Z SHELL (ZSH)
 Version
 -------
 
-This is version 5.8 of the shell.  This is a stable release.  There are
-a few visible improvements since 5.7 as well as many bugfixes.
+This is version 5.8 of the shell.  This is a security and feature release.
+There are a few visible improvements since 5.7, as well as many bugfixes.
+All zsh installations are encouraged to upgrade as soon as possible.
 
 Note in particular the changes highlighted under "Incompatibilities since
 5.7.1" below.  See NEWS for more information.
@@ -60,6 +61,12 @@ This only affects you if you override that function in your dotfiles.
 The cd and chdir builtins no longer interpret operands like -1 and +2 as
 stack entries when POSIX_CD is enabled.
 
+Dropping privileges with `unsetopt privileged` may fail (with an error
+message) on some older and uncommon platforms due to library dependency
+changes made in the course of fixing CVE-2019-20044.  Please report this
+to the zsh-workers mailing list if your system is affected.  See NEWS for
+more.
+
 Incompatibilities between 5.6.2 and 5.7.1
 -----------------------------------------
 
diff --git a/Src/Builtins/rlimits.awk b/Src/Builtins/rlimits.awk
deleted file mode 100644
index e9c576c66..000000000
--- a/Src/Builtins/rlimits.awk
+++ /dev/null
@@ -1,116 +0,0 @@
-#
-# rlimits.awk: {g,n}awk script to generate rlimits.h
-#
-# NB: On SunOS 4.1.3 - user-functions don't work properly, also \" problems
-# Without 0 + hacks some nawks compare numbers as strings
-#
-BEGIN {limidx = 0}
-
-/^[\t ]*(#[\t ]*define[\t _]*RLIMIT_[A-Z_]*[\t ]*[0-9][0-9]*|RLIMIT_[A-Z_]*,[\t ]*|_*RLIMIT_[A-Z_]*[\t ]*=[\t ]*[0-9][0-9]*,[\t ]*)/ {
-    limindex = index($0, "RLIMIT_")
-    limtail = substr($0, limindex, 80)
-    split(limtail, tmp)
-    limnam = substr(tmp[1], 8, 20)
-    limnum = tmp[2]
-    # in this case I assume GNU libc resourcebits.h
-    if (limnum == "") {
-	limnum = limidx++
-	limindex = index($0, ",")
-	limnam = substr(limnam, 1, limindex-1)
-    }
-    if (limnum == "=") {
-	if (tmp[3] ~ /^[0-9]/) {
-	    limnum = tmp[3] + 0
-	} else {
-	    limnum = limidx++
-	}
-	limindex = index($0, ",")
-	limnam = substr(limnam, 1, limindex-1)
-    }
-    limrev[limnam] = limnum
-    if (lim[limnum] == "") {
-	lim[limnum] = limnam
-	if (limnum ~ /^[0-9]*$/) {
-	    if (limnam == "AIO_MEM") { msg[limnum] = "Maiomemorylocked" }
-	    if (limnam == "AIO_OPS") { msg[limnum] = "Naiooperations" }
-	    if (limnam == "AS")      { msg[limnum] = "Maddressspace" }
-	    if (limnam == "CORE")    { msg[limnum] = "Mcoredumpsize" }
-	    if (limnam == "CPU")     { msg[limnum] = "Tcputime" }
-	    if (limnam == "DATA")    { msg[limnum] = "Mdatasize" }
-	    if (limnam == "FSIZE")   { msg[limnum] = "Mfilesize" }
-	    if (limnam == "LOCKS")   { msg[limnum] = "Nmaxfilelocks" }
-	    if (limnam == "MEMLOCK") { msg[limnum] = "Mmemorylocked" }
-	    if (limnam == "NOFILE")  { msg[limnum] = "Ndescriptors" }
-	    if (limnam == "NPROC")   { msg[limnum] = "Nmaxproc" }
-	    if (limnam == "NTHR")    { msg[limnum] = "Nmaxpthreads" }
-	    if (limnam == "OFILE")   { msg[limnum] = "Ndescriptors" }
-	    if (limnam == "PTHREAD") { msg[limnum] = "Nmaxpthreads" }
-	    if (limnam == "RSS")     { msg[limnum] = "Mresident" }
-	    if (limnam == "SBSIZE")  { msg[limnum] = "Msockbufsize" }
-	    if (limnam == "STACK")   { msg[limnum] = "Mstacksize" }
-	    if (limnam == "TCACHE")  { msg[limnum] = "Ncachedthreads" }
-	    if (limnam == "VMEM")    { msg[limnum] = "Mvmemorysize" }
-	    if (limnam == "SIGPENDING") { msg[limnum] = "Nsigpending" }
-	    if (limnam == "MSGQUEUE") { msg[limnum] = "Nmsgqueue" }
-	    if (limnam == "NICE") { msg[limnum] = "Nnice" }
-	    if (limnam == "RTPRIO") { msg[limnum] = "Nrt_priority" }
-	    if (limnam == "RTTIME") { msg[limnum] = "Urt_time" }
-	    if (limnam == "POSIXLOCKS") { msg[limnum] = "Nposixlocks" }
-	    if (limnam == "NPTS")    { msg[limnum] = "Npseudoterminals" }
-	    if (limnam == "SWAP")    { msg[limnum] = "Mswapsize" }
-	    if (limnam == "KQUEUES") { msg[limnum] = "Nkqueues" }
-	    if (limnam == "UMTXP")   { msg[limnum] = "Numtxp" }
-        }
-    }
-}
-/^[\t ]*#[\t ]*define[\t _]*RLIM_NLIMITS[\t ]*[0-9][0-9]*/ {
-    limindex = index($0, "RLIM_")
-    limtail = substr($0, limindex, 80)
-    split(limtail, tmp)
-    nlimits = tmp[2]
-}
-# in case of GNU libc
-/^[\t ]*RLIM_NLIMITS[\t ]*=[\t ]*RLIMIT_NLIMITS/ {
-    if(!nlimits) { nlimits = limidx }
-}
-/^[\t _]*RLIM(IT)?_NLIMITS[\t ]*=[\t ]*[0-9][0-9]*/ {
-    limindex = index($0, "=")
-    limtail = substr($0, limindex, 80)
-    split(limtail, tmp)
-    nlimits = tmp[2]
-}
-
-END {
-    if (limrev["MEMLOCK"] != "") {
-        irss = limrev["RSS"]
-        msg[irss] = "Mmemoryuse"
-    }
-    ps = "%s"
-
-    printf("%s\n%s\n\n", "/** rlimits.h                              **/", "/** architecture-customized limits for zsh **/")
-    printf("#define ZSH_NLIMITS %d\n\nstatic char const *recs[ZSH_NLIMITS] = {\n", 0 + nlimits)
-
-    for (i = 0; i < 0 + nlimits; i++)
-	if (msg[i] == "")
-            printf("\t%c%s%c,\n", 34, lim[i], 34)
-	else
-	    printf("\t%c%s%c,\n", 34, substr(msg[i], 2, 30), 34)
-    print "};"
-    print ""
-    print "static int limtype[ZSH_NLIMITS] = {"
-    for (i = 0; i < 0 + nlimits; i++) {
-	if (msg[i] == "")
-	    limtype = "UNKNOWN"
-	else {
-	    limtype = substr(msg[i], 1, 1)
-	    if(limtype == "M") { limtype = "MEMORY" }
-	    if(limtype == "N") { limtype = "NUMBER" }
-	    if(limtype == "T") { limtype = "TIME" }
-	    if(limtype == "U") { limtype = "MICROSECONDS" }
-	}
-	printf("\tZLIMTYPE_%s,\n", limtype)
-    }
-    print "};"
-
-    exit(0)
-}
diff --git a/Src/Builtins/rlimits.c b/Src/Builtins/rlimits.c
index 6b552f3a9..b9128433f 100644
--- a/Src/Builtins/rlimits.c
+++ b/Src/Builtins/rlimits.c
@@ -32,20 +32,7 @@
 
 #if defined(HAVE_GETRLIMIT) && defined(RLIM_INFINITY)
 
-#if defined(HAVE_RLIMIT_POSIXLOCKS) && !defined(HAVE_RLIMIT_LOCKS)
-#  define RLIMIT_LOCKS		RLIMIT_POSIXLOCKS
-#  define HAVE_RLIMIT_LOCKS     1
-#endif
-
-#if defined(HAVE_RLIMIT_NTHR) && !defined(HAVE_RLIMIT_PTHREAD)
-#  define RLIMIT_PTHREAD	RLIMIT_NTHR
-#  define HAVE_RLIMIT_PTHREAD   1
-#  define THREAD_FMT            "-T: threads                         "
-#else
-#  define THREAD_FMT            "-T: threads per process             "
-#endif
-
-enum {
+enum zlimtype {
     ZLIMTYPE_MEMORY,
     ZLIMTYPE_NUMBER,
     ZLIMTYPE_TIME,
@@ -53,11 +40,214 @@ enum {
     ZLIMTYPE_UNKNOWN
 };
 
-/* Generated rec array containing limits required for the limit builtin.     *
- * They must appear in this array in numerical order of the RLIMIT_* macros. */
+typedef struct resinfo_T {
+    int	res;		/* RLIMIT_XXX */
+    char* name;		/* used by limit builtin */
+    enum zlimtype type;
+    int unit;		/* 1, 512, or 1024 */
+    char opt;		/* option character */
+    char* descr;	/* used by ulimit builtin */
+} resinfo_T;
+
+/* table of known resources */
+static const resinfo_T known_resources[] = {
+    {RLIMIT_CPU, "cputime", ZLIMTYPE_TIME, 1,
+		't', "cpu time (seconds)"},
+    {RLIMIT_FSIZE, "filesize", ZLIMTYPE_MEMORY, 512,
+		'f', "file size (blocks)"},
+    {RLIMIT_DATA, "datasize", ZLIMTYPE_MEMORY, 1024,
+		'd', "data seg size (kbytes)"},
+    {RLIMIT_STACK, "stacksize", ZLIMTYPE_MEMORY, 1024,
+		's', "stack size (kbytes)"},
+    {RLIMIT_CORE, "coredumpsize", ZLIMTYPE_MEMORY, 512,
+		'c', "core file size (blocks)"},
+# ifdef HAVE_RLIMIT_NOFILE
+    {RLIMIT_NOFILE, "descriptors", ZLIMTYPE_NUMBER, 1,
+		'n', "file descriptors"},
+# endif
+# if defined(HAVE_RLIMIT_AS) && !defined(RLIMIT_VMEM_IS_AS)
+    {RLIMIT_AS, "addressspace", ZLIMTYPE_MEMORY, 1024,
+		'v', "address space (kbytes)"},
+# endif
+# if defined(HAVE_RLIMIT_RSS) && !defined(RLIMIT_VMEM_IS_RSS) && !defined(RLIMIT_RSS_IS_AS)
+    {RLIMIT_RSS, "resident", ZLIMTYPE_MEMORY, 1024,
+		'm', "resident set size (kbytes)"},
+# endif
+# if defined(HAVE_RLIMIT_VMEM)
+    {RLIMIT_VMEM,
+#  if defined(RLIMIT_VMEM_IS_RSS)
+		 "resident", ZLIMTYPE_MEMORY, 1024,
+		 'm', "memory size (kbytes)"},
+#  else
+		 "vmemorysize", ZLIMTYPE_MEMORY, 1024,
+		 'v', "virtual memory size (kbytes)"},
+#  endif
+# endif
+# ifdef HAVE_RLIMIT_NPROC
+    {RLIMIT_NPROC, "maxproc", ZLIMTYPE_NUMBER, 1,
+		'u', "processes"},
+# endif
+# ifdef HAVE_RLIMIT_MEMLOCK
+    {RLIMIT_MEMLOCK, "memorylocked", ZLIMTYPE_MEMORY, 1024,
+		'l', "locked-in-memory size (kbytes)"},
+# endif
+    /* Linux */
+# ifdef HAVE_RLIMIT_LOCKS
+    {RLIMIT_LOCKS, "maxfilelocks", ZLIMTYPE_NUMBER, 1,
+		'x', "file locks"},
+# endif
+# ifdef HAVE_RLIMIT_SIGPENDING
+    {RLIMIT_SIGPENDING, "sigpending", ZLIMTYPE_NUMBER, 1,
+		'i', "pending signals"},
+# endif
+# ifdef HAVE_RLIMIT_MSGQUEUE
+    {RLIMIT_MSGQUEUE, "msgqueue", ZLIMTYPE_NUMBER, 1,
+		'q', "bytes in POSIX msg queues"},
+# endif
+# ifdef HAVE_RLIMIT_NICE
+    {RLIMIT_NICE, "nice", ZLIMTYPE_NUMBER, 1,
+		'e', "max nice"},
+# endif
+# ifdef HAVE_RLIMIT_RTPRIO
+    {RLIMIT_RTPRIO, "rt_priority", ZLIMTYPE_NUMBER, 1,
+		'r', "max rt priority"},
+# endif
+# ifdef HAVE_RLIMIT_RTTIME
+    {RLIMIT_RTTIME, "rt_time", ZLIMTYPE_MICROSECONDS, 1,
+		'N', "rt cpu time (microseconds)"},
+# endif
+    /* BSD */
+# ifdef HAVE_RLIMIT_SBSIZE
+    {RLIMIT_SBSIZE, "sockbufsize", ZLIMTYPE_MEMORY, 1,
+		'b', "socket buffer size (bytes)"},
+# endif
+# ifdef HAVE_RLIMIT_KQUEUES /* FreeBSD */
+    {RLIMIT_KQUEUES, "kqueues", ZLIMTYPE_NUMBER, 1,
+		'k', "kqueues"},
+# endif
+# ifdef HAVE_RLIMIT_NPTS    /* FreeBSD */
+    {RLIMIT_NPTS, "pseudoterminals", ZLIMTYPE_NUMBER, 1,
+		'p', "pseudo-terminals"},
+# endif
+# ifdef HAVE_RLIMIT_SWAP    /* FreeBSD */
+    {RLIMIT_SWAP, "swapsize", ZLIMTYPE_MEMORY, 1024,
+		'w', "swap size (kbytes)"},
+# endif
+# ifdef HAVE_RLIMIT_UMTXP   /* FreeBSD */
+    {RLIMIT_UMTXP, "umtxp", ZLIMTYPE_NUMBER, 1,
+		'o', "umtx shared locks"},
+# endif
+
+# ifdef HAVE_RLIMIT_POSIXLOCKS	/* DragonFly */
+    {RLIMIT_POSIXLOCKS, "posixlocks", ZLIMTYPE_NUMBER, 1,
+		'x', "number of POSIX locks"},
+# endif
+# if defined(HAVE_RLIMIT_NTHR) && !defined(HAVE_RLIMIT_RTPRIO) /* Net/OpenBSD */
+    {RLIMIT_NTHR, "maxpthreads", ZLIMTYPE_NUMBER, 1,
+		'r', "threads"},
+# endif
+    /* others */
+# if defined(HAVE_RLIMIT_PTHREAD) && !defined(HAVE_RLIMIT_NTHR)	/* IRIX ? */
+    {RLIMIT_PTHREAD, "maxpthreads", ZLIMTYPE_NUMBER, 1,
+		'T', "threads per process"},
+# endif
+# ifdef HAVE_RLIMIT_AIO_MEM /* HP-UX ? */
+    {RLIMIT_AIO_MEM, "aiomemorylocked", ZLIMTYPE_MEMORY, 1024,
+		'N', "AIO locked-in-memory (kbytes)"},
+# endif
+# ifdef HAVE_RLIMIT_AIO_OPS /* HP-UX ? */
+    {RLIMIT_AIO_OPS, "aiooperations", ZLIMTYPE_NUMBER, 1,
+		'N', "AIO operations"},
+# endif
+# ifdef HAVE_RLIMIT_TCACHE  /* HP-UX ? */
+    {RLIMIT_TCACHE, "cachedthreads", ZLIMTYPE_NUMBER, 1,
+		'N', "cached threads"},
+# endif
+};
 
-# include "rlimits.h"
+/* resinfo[RLIMIT_XXX] points to the corresponding entry
+ * in known_resources[] */
+static const resinfo_T **resinfo;
 
+/**/
+static void
+set_resinfo(void)
+{
+    int i;
+
+    resinfo = (const resinfo_T **)zshcalloc(RLIM_NLIMITS*sizeof(resinfo_T *));
+
+    for (i=0; i<sizeof(known_resources)/sizeof(resinfo_T); ++i) {
+	resinfo[known_resources[i].res] = &known_resources[i];
+    }
+    for (i=0; i<RLIM_NLIMITS; ++i) {
+	if (!resinfo[i]) {
+	    /* unknown resource */
+	    resinfo_T *info = (resinfo_T *)zshcalloc(sizeof(resinfo_T));
+	    char *buf = (char *)zalloc(12);
+	    snprintf(buf, 12, "UNKNOWN-%d", i);
+	    info->res = - 1;	/* negative value indicates "unknown" */
+	    info->name = buf;
+	    info->type = ZLIMTYPE_UNKNOWN;
+	    info->unit = 1;
+	    info->opt = 'N';
+	    info->descr = buf;
+	    resinfo[i] = info;
+	}
+    }
+}
+
+/**/
+static void
+free_resinfo(void)
+{
+    int i;
+    for (i=0; i<RLIM_NLIMITS; ++i) {
+	if (resinfo[i]->res < 0) {  /* unknown resource */
+	    free(resinfo[i]->name);
+	    free((void*)resinfo[i]);
+	}
+    }
+    free(resinfo);
+    resinfo = NULL;
+}
+
+/* Find resource by its option character */
+
+/**/
+static int
+find_resource(char c)
+{
+    int i;
+    for (i=0; i<RLIM_NLIMITS; ++i) {
+	if (resinfo[i]->opt == c)
+	    return i;
+    }
+    return -1;
+}
+
+/* Print a value of type rlim_t */
+
+/**/
+static void
+printrlim(rlim_t val, const char *unit)
+{
+# ifdef RLIM_T_IS_QUAD_T
+	printf("%qd%s", val, unit);
+# else
+#  ifdef RLIM_T_IS_LONG_LONG
+	printf("%lld%s", val, unit);
+#  else
+#   ifdef RLIM_T_IS_UNSIGNED
+	printf("%lu%s", (unsigned long)val, unit);
+#   else
+	printf("%ld%s", (long)val, unit);
+#   endif /* RLIM_T_IS_UNSIGNED */
+#  endif /* RLIM_T_IS_LONG_LONG */
+# endif /* RLIM_T_IS_QUAD_T */
+}
+
+/**/
 static rlim_t
 zstrtorlimt(const char *s, char **t, int base)
 {
@@ -97,8 +287,8 @@ static void
 showlimitvalue(int lim, rlim_t val)
 {
     /* display limit for resource number lim */
-    if (lim < ZSH_NLIMITS)
-	printf("%-16s", recs[lim]);
+    if (lim < RLIM_NLIMITS)
+	printf("%-16s", resinfo[lim]->name);
     else
     {
 	/* Unknown limit, hence unknown units. */
@@ -106,81 +296,25 @@ showlimitvalue(int lim, rlim_t val)
     }
     if (val == RLIM_INFINITY)
 	printf("unlimited\n");
-    else if (lim >= ZSH_NLIMITS)
-    {
-# ifdef RLIM_T_IS_QUAD_T
-	printf("%qd\n", val);
-# else
-#  ifdef RLIM_T_IS_LONG_LONG
-	printf("%lld\n", val);
-#  else
-#   ifdef RLIM_T_IS_UNSIGNED
-	printf("%lu\n", (unsigned long)val);
-#   else
-	printf("%ld\n", (long)val);
-#   endif /* RLIM_T_IS_UNSIGNED */
-#  endif /* RLIM_T_IS_LONG_LONG */
-# endif /* RLIM_T_IS_QUAD_T */
-    }
-    else if (limtype[lim] == ZLIMTYPE_TIME) {
+    else if (lim >= RLIM_NLIMITS)
+	printrlim(val, "\n");
+    else if (resinfo[lim]->type == ZLIMTYPE_TIME) {
 	/* time-type resource -- display as hours, minutes and
 	   seconds. */
 	printf("%d:%02d:%02d\n", (int)(val / 3600),
 	       (int)(val / 60) % 60, (int)(val % 60));
-    } else if (limtype[lim] == ZLIMTYPE_MICROSECONDS) {
-	/* microseconds */
-# ifdef RLIM_T_IS_QUAD_T
-	printf("%qdus\n", val);
-# else
-#  ifdef RLIM_T_IS_LONG_LONG
-	printf("%lldus\n", val);
-#  else
-#   ifdef RLIM_T_IS_UNSIGNED
-	printf("%luus\n", (unsigned long)val);
-#   else
-	printf("%ldus\n", (long)val);
-#   endif /* RLIM_T_IS_UNSIGNED */
-#  endif /* RLIM_T_IS_LONG_LONG */
-# endif /* RLIM_T_IS_QUAD_T */
-    } else if (limtype[lim] == ZLIMTYPE_NUMBER ||
-	       limtype[lim] == ZLIMTYPE_UNKNOWN) {
-	/* pure numeric resource */
-# ifdef RLIM_T_IS_QUAD_T
-	printf("%qd\n", val);
-# else
-#  ifdef RLIM_T_IS_LONG_LONG
-	printf("%lld\n", val);
-#  else
-#   ifdef RLIM_T_IS_UNSIGNED
-	printf("%lu\n", (unsigned long)val);
-#   else
-	printf("%ld\n", (long)val);
-#   endif /* RLIM_T_IS_UNSIGNED */
-#  endif /* RLIM_T_IS_LONG_LONG */
-# endif /* RLIM_T_IS_QUAD_T */
-    } else if (val >= 1024L * 1024L)
-	/* memory resource -- display with `K' or `M' modifier */
-# ifdef RLIM_T_IS_QUAD_T
-	printf("%qdMB\n", val / (1024L * 1024L));
-    else
-	printf("%qdkB\n", val / 1024L);
-# else
-#  ifdef RLIM_T_IS_LONG_LONG
-	printf("%lldMB\n", val / (1024L * 1024L));
-    else
-	printf("%lldkB\n", val / 1024L);
-#  else
-#   ifdef RLIM_T_IS_UNSIGNED
-    printf("%luMB\n", (unsigned long)(val / (1024L * 1024L)));
-    else
-	printf("%lukB\n", (unsigned long)(val / 1024L));
-#   else
-    printf("%ldMB\n", (long)val / (1024L * 1024L));
-    else
-	printf("%ldkB\n", (long)val / 1024L);
-#   endif /* RLIM_T_IS_UNSIGNED */
-#  endif /* RLIM_T_IS_LONG_LONG */
-# endif /* RLIM_T_IS_QUAD_T */
+    } else if (resinfo[lim]->type == ZLIMTYPE_MICROSECONDS)
+	printrlim(val, "us\n");	/* microseconds */
+    else if (resinfo[lim]->type == ZLIMTYPE_NUMBER ||
+	       resinfo[lim]->type == ZLIMTYPE_UNKNOWN)
+	printrlim(val, "\n");	/* pure numeric resource */
+    else {
+	/* memory resource -- display with `k' or `M' modifier */
+	if (val >= 1024L * 1024L)
+	    printrlim(val/(1024L * 1024L), "MB\n");
+	else
+	    printrlim(val/1024L, "kB\n");
+    }
 }
 
 /* Display resource limits.  hard indicates whether `hard' or `soft'  *
@@ -193,7 +327,7 @@ showlimits(char *nam, int hard, int lim)
 {
     int rt;
 
-    if (lim >= ZSH_NLIMITS)
+    if (lim >= RLIM_NLIMITS)
     {
 	/*
 	 * Not configured into the shell.  Ask the OS
@@ -215,7 +349,7 @@ showlimits(char *nam, int hard, int lim)
     else
     {
 	/* main loop over resource types */
-	for (rt = 0; rt != ZSH_NLIMITS; rt++)
+	for (rt = 0; rt != RLIM_NLIMITS; rt++)
 	    showlimitvalue(rt, (hard) ? limits[rt].rlim_max :
 			   limits[rt].rlim_cur);
     }
@@ -234,7 +368,7 @@ printulimit(char *nam, int lim, int hard, int head)
     rlim_t limit;
 
     /* get the limit in question */
-    if (lim >= ZSH_NLIMITS)
+    if (lim >= RLIM_NLIMITS)
     {
 	struct rlimit vals;
 
@@ -248,199 +382,25 @@ printulimit(char *nam, int lim, int hard, int head)
     else
 	limit = (hard) ? limits[lim].rlim_max : limits[lim].rlim_cur;
     /* display the appropriate heading */
-    switch (lim) {
-    case RLIMIT_CORE:
-	if (head)
-	    printf("-c: core file size (blocks)         ");
-	if (limit != RLIM_INFINITY)
-	    limit /= 512;
-	break;
-    case RLIMIT_DATA:
-	if (head)
-	    printf("-d: data seg size (kbytes)          ");
-	if (limit != RLIM_INFINITY)
-	    limit /= 1024;
-	break;
-    case RLIMIT_FSIZE:
-	if (head)
-	    printf("-f: file size (blocks)              ");
-	if (limit != RLIM_INFINITY)
-	    limit /= 512;
-	break;
-# ifdef HAVE_RLIMIT_SIGPENDING
-    case RLIMIT_SIGPENDING:
-	if (head)
-	    printf("-i: pending signals                 ");
-	break;
-# endif
-# ifdef HAVE_RLIMIT_MEMLOCK
-    case RLIMIT_MEMLOCK:
-	if (head)
-	    printf("-l: locked-in-memory size (kbytes)  ");
-	if (limit != RLIM_INFINITY)
-	    limit /= 1024;
-	break;
-# endif /* HAVE_RLIMIT_MEMLOCK */
-/* If RLIMIT_VMEM and RLIMIT_RSS are defined and equal, avoid *
- * duplicate case statement.  Observed on QNX Neutrino 6.1.0. */
-# if defined(HAVE_RLIMIT_RSS) && !defined(RLIMIT_VMEM_IS_RSS) && !defined(RLIMIT_RSS_IS_AS)
-    case RLIMIT_RSS:
-	if (head)
-	    printf("-m: resident set size (kbytes)      ");
-	if (limit != RLIM_INFINITY)
-	    limit /= 1024;
-	break;
-# endif /* HAVE_RLIMIT_RSS */
-# if defined(HAVE_RLIMIT_VMEM) && defined(HAVE_RLIMIT_RSS) && defined(RLIMIT_VMEM_IS_RSS)
-    case RLIMIT_VMEM:
-	if (head)
-	    printf("-m: memory size (kbytes)            ");
-	if (limit != RLIM_INFINITY)
-	    limit /= 1024;
-	break;
-# endif /* HAVE_RLIMIT_VMEM */
-# ifdef HAVE_RLIMIT_NOFILE
-    case RLIMIT_NOFILE:
-	if (head)
-	    printf("-n: file descriptors                ");
-	break;
-# endif /* HAVE_RLIMIT_NOFILE */
-# ifdef HAVE_RLIMIT_MSGQUEUE
-    case RLIMIT_MSGQUEUE:
-	if (head)
-	    printf("-q: bytes in POSIX msg queues       ");
-	break;
-# endif
-    case RLIMIT_STACK:
-	if (head)
-	    printf("-s: stack size (kbytes)             ");
-	if (limit != RLIM_INFINITY)
-	    limit /= 1024;
-	break;
-    case RLIMIT_CPU:
-	if (head)
-	    printf("-t: cpu time (seconds)              ");
-	break;
-# ifdef HAVE_RLIMIT_NPROC
-    case RLIMIT_NPROC:
-	if (head)
-	    printf("-u: processes                       ");
-	break;
-# endif /* HAVE_RLIMIT_NPROC */
-# if defined(HAVE_RLIMIT_VMEM) && (!defined(HAVE_RLIMIT_RSS) || !defined(RLIMIT_VMEM_IS_RSS))
-    case RLIMIT_VMEM:
-	if (head)
-	    printf("-v: virtual memory size (kbytes)    ");
-	if (limit != RLIM_INFINITY)
-	    limit /= 1024;
-	break;
-# endif /* HAVE_RLIMIT_VMEM */
-# if defined HAVE_RLIMIT_AS && !defined(RLIMIT_VMEM_IS_AS)
-    case RLIMIT_AS:
-	if (head)
-	    printf("-v: address space (kbytes)          ");
-	if (limit != RLIM_INFINITY)
-	    limit /= 1024;
-	break;
-# endif /* HAVE_RLIMIT_AS */
-# ifdef HAVE_RLIMIT_LOCKS
-    case RLIMIT_LOCKS:
-	if (head)
-	    printf("-x: file locks                      ");
-	break;
-# endif /* HAVE_RLIMIT_LOCKS */
-# ifdef HAVE_RLIMIT_AIO_MEM
-    case RLIMIT_AIO_MEM:
-	if (head)
-	    printf("-N %2d: AIO locked-in-memory (kbytes)", RLIMIT_AIO_MEM);
-	if (limit != RLIM_INFINITY)
-	    limit /= 1024;
-	break;
-# endif /* HAVE_RLIMIT_AIO_MEM */
-# ifdef HAVE_RLIMIT_AIO_OPS
-    case RLIMIT_AIO_OPS:
-	if (head)
-	    printf("-N %2d: AIO operations               ", RLIMIT_AIO_OPS);
-	break;
-# endif /* HAVE_RLIMIT_AIO_OPS */
-# ifdef HAVE_RLIMIT_TCACHE
-    case RLIMIT_TCACHE:
-	if (head)
-	    printf("-N %2d: cached threads               ", RLIMIT_TCACHE);
-	break;
-# endif /* HAVE_RLIMIT_TCACHE */
-# ifdef HAVE_RLIMIT_SBSIZE
-    case RLIMIT_SBSIZE:
-	if (head)
-	    printf("-b: socket buffer size (bytes)      ");
-	break;
-# endif /* HAVE_RLIMIT_SBSIZE */
-# ifdef HAVE_RLIMIT_PTHREAD
-    case RLIMIT_PTHREAD:
-	if (head)
-	    printf("%s", THREAD_FMT);
-	break;
-# endif /* HAVE_RLIMIT_PTHREAD */
-# ifdef HAVE_RLIMIT_NICE
-    case RLIMIT_NICE:
-	if (head)
-	    printf("-e: max nice                        ");
-	break;
-# endif /* HAVE_RLIMIT_NICE */
-# ifdef HAVE_RLIMIT_RTPRIO
-    case RLIMIT_RTPRIO:
-	if (head)
-	    printf("-r: max rt priority                 ");
-	break;
-# endif /* HAVE_RLIMIT_RTPRIO */
-# ifdef HAVE_RLIMIT_NPTS
-    case RLIMIT_NPTS:
-	if (head)
-	    printf("-p: pseudo-terminals                ");
-	break;
-# endif /* HAVE_RLIMIT_NPTS */
-# ifdef HAVE_RLIMIT_SWAP
-    case RLIMIT_SWAP:
-	if (head)
-	    printf("-w: swap size (kbytes)              ");
-	if (limit != RLIM_INFINITY)
-	    limit /= 1024;
-	break;
-# endif /* HAVE_RLIMIT_SWAP */
-# ifdef HAVE_RLIMIT_KQUEUES
-    case RLIMIT_KQUEUES:
-	if (head)
-	    printf("-k: kqueues                         ");
-	break;
-# endif /* HAVE_RLIMIT_KQUEUES */
-# ifdef HAVE_RLIMIT_UMTXP
-    case RLIMIT_UMTXP:
-	if (head)
-	    printf("-o: umtx shared locks               ");
-	break;
-# endif /* HAVE_RLIMIT_UMTXP */
-    default:
-	if (head)
-	    printf("-N %2d:                              ", lim);
-	break;
+    if (head) {
+	if (lim < RLIM_NLIMITS) {
+	    const resinfo_T *info = resinfo[lim];
+	    if (info->opt == 'N')
+		printf("-N %2d: %-29s", lim, info->descr);
+	    else
+		printf("-%c: %-32s", info->opt, info->descr);
+	}
+	else
+	    printf("-N %2d: %-29s", lim, "");
     }
     /* display the limit */
     if (limit == RLIM_INFINITY)
 	printf("unlimited\n");
     else {
-# ifdef RLIM_T_IS_QUAD_T
-	printf("%qd\n", limit);
-# else
-#  ifdef RLIM_T_IS_LONG_LONG
-	printf("%lld\n", limit);
-#  else
-#   ifdef RLIM_T_IS_UNSIGNED
-	printf("%lu\n", (unsigned long)limit);
-#   else
-	printf("%ld\n", (long)limit);
-#   endif /* RLIM_T_IS_UNSIGNED */
-#  endif /* RLIM_T_IS_LONG_LONG */
-# endif /* RLIM_T_IS_QUAD_T */
+	if (lim < RLIM_NLIMITS)
+	    printrlim(limit/resinfo[lim]->unit, "\n");
+	else
+	    printrlim(limit, "\n");
     }
 
     return 0;
@@ -450,7 +410,7 @@ printulimit(char *nam, int lim, int hard, int head)
 static int
 do_limit(char *nam, int lim, rlim_t val, int hard, int soft, int set)
 {
-    if (lim >= ZSH_NLIMITS) {
+    if (lim >= RLIM_NLIMITS) {
 	struct rlimit vals;
 	if (getrlimit(lim, &vals) < 0)
 	{
@@ -558,8 +518,8 @@ bin_limit(char *nam, char **argv, Options ops, UNUSED(int func))
 	    lim = (int)zstrtol(s, NULL, 10);
 	}
 	else
-	    for (lim = -1, limnum = 0; limnum < ZSH_NLIMITS; limnum++)
-		if (!strncmp(recs[limnum], s, strlen(s))) {
+	    for (lim = -1, limnum = 0; limnum < RLIM_NLIMITS; limnum++)
+		if (!strncmp(resinfo[limnum]->name, s, strlen(s))) {
 		    if (lim != -1)
 			lim = -2;
 		    else
@@ -576,7 +536,7 @@ bin_limit(char *nam, char **argv, Options ops, UNUSED(int func))
 	/* without value for limit, display the current limit */
 	if (!(s = *argv++))
 	    return showlimits(nam, hard, lim);
-	if (lim >= ZSH_NLIMITS)
+	if (lim >= RLIM_NLIMITS)
 	{
 	    val = zstrtorlimt(s, &s, 10);
 	    if (*s)
@@ -586,7 +546,7 @@ bin_limit(char *nam, char **argv, Options ops, UNUSED(int func))
 		return 1;
 	    }
 	}
-	else if (limtype[lim] == ZLIMTYPE_TIME) {
+	else if (resinfo[lim]->type == ZLIMTYPE_TIME) {
 	    /* time-type resource -- may be specified as seconds, or minutes or *
 	     * hours with the `m' and `h' modifiers, and `:' may be used to add *
 	     * together more than one of these.  It's easier to understand from *
@@ -604,9 +564,9 @@ bin_limit(char *nam, char **argv, Options ops, UNUSED(int func))
 		    return 1;
 		}
 	    }
-	} else if (limtype[lim] == ZLIMTYPE_NUMBER ||
-		   limtype[lim] == ZLIMTYPE_UNKNOWN ||
-		   limtype[lim] == ZLIMTYPE_MICROSECONDS) {
+	} else if (resinfo[lim]->type == ZLIMTYPE_NUMBER ||
+		   resinfo[lim]->type == ZLIMTYPE_UNKNOWN ||
+		   resinfo[lim]->type == ZLIMTYPE_MICROSECONDS) {
 	    /* pure numeric resource -- only a straight decimal number is
 	    permitted. */
 	    char *t = s;
@@ -642,7 +602,7 @@ static int
 do_unlimit(char *nam, int lim, int hard, int soft, int set, int euid)
 {
     /* remove specified limit */
-    if (lim >= ZSH_NLIMITS) {
+    if (lim >= RLIM_NLIMITS) {
 	struct rlimit vals;
 	if (getrlimit(lim, &vals) < 0)
 	{
@@ -718,8 +678,8 @@ bin_unlimit(char *nam, char **argv, Options ops, UNUSED(int func))
 	    if (idigit(**argv)) {
 		lim = (int)zstrtol(*argv, NULL, 10);
 	    } else {
-		for (lim = -1, limnum = 0; limnum < ZSH_NLIMITS; limnum++)
-		    if (!strncmp(recs[limnum], *argv, strlen(*argv))) {
+		for (lim = -1, limnum = 0; limnum < RLIM_NLIMITS; limnum++)
+		    if (!strncmp(resinfo[limnum]->name, *argv, strlen(*argv))) {
 			if (lim != -1)
 			    lim = -2;
 			else
@@ -800,116 +760,14 @@ bin_ulimit(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
 		    resmask = (1 << RLIM_NLIMITS) - 1;
 		    nres = RLIM_NLIMITS;
 		    continue;
-		case 't':
-		    res = RLIMIT_CPU;
-		    break;
-		case 'f':
-		    res = RLIMIT_FSIZE;
-		    break;
-		case 'd':
-		    res = RLIMIT_DATA;
-		    break;
-		case 's':
-		    res = RLIMIT_STACK;
-		    break;
-		case 'c':
-		    res = RLIMIT_CORE;
-		    break;
-# ifdef HAVE_RLIMIT_SBSIZE
-		case 'b':
-		    res = RLIMIT_SBSIZE;
-		    break;
-# endif /* HAVE_RLIMIT_SBSIZE */
-# ifdef HAVE_RLIMIT_MEMLOCK
-		case 'l':
-		    res = RLIMIT_MEMLOCK;
-		    break;
-# endif /* HAVE_RLIMIT_MEMLOCK */
-# ifdef HAVE_RLIMIT_RSS
-		case 'm':
-		    res = RLIMIT_RSS;
-		    break;
-# endif /* HAVE_RLIMIT_RSS */
-# ifdef HAVE_RLIMIT_NOFILE
-		case 'n':
-		    res = RLIMIT_NOFILE;
-		    break;
-# endif /* HAVE_RLIMIT_NOFILE */
-# ifdef HAVE_RLIMIT_NPROC
-		case 'u':
-		    res = RLIMIT_NPROC;
-		    break;
-# endif /* HAVE_RLIMIT_NPROC */
-# if defined(HAVE_RLIMIT_VMEM) || defined(HAVE_RLIMIT_AS)
-		case 'v':
-#  ifdef HAVE_RLIMIT_VMEM
-		    res = RLIMIT_VMEM;
-#  else
-		    res = RLIMIT_AS;
-#  endif
-		    break;
-# endif /* HAVE_RLIMIT_VMEM */
-# ifdef HAVE_RLIMIT_LOCKS
-		case 'x':
-		    res = RLIMIT_LOCKS;
-		    break;
-# endif
-# ifdef HAVE_RLIMIT_SIGPENDING
-		case 'i':
-		    res = RLIMIT_SIGPENDING;
-		    break;
-# endif
-# ifdef HAVE_RLIMIT_MSGQUEUE
-		case 'q':
-		    res = RLIMIT_MSGQUEUE;
-		    break;
-# endif
-# ifdef HAVE_RLIMIT_NICE
-		case 'e':
-		    res = RLIMIT_NICE;
-		    break;
-# endif
-# ifdef HAVE_RLIMIT_RTPRIO
-		case 'r':
-		    res = RLIMIT_RTPRIO;
-		    break;
-# else
-#  ifdef HAVE_RLIMIT_NTHR
-		    /* For compatibility with sh on NetBSD */
-		case 'r':
-		    res = RLIMIT_NTHR;
-		    break;
-#  endif /* HAVE_RLIMIT_NTHR */
-# endif
-# ifdef HAVE_RLIMIT_NPTS
-		case 'p':
-		    res = RLIMIT_NPTS;
-		    break;
-# endif
-# ifdef HAVE_RLIMIT_SWAP
-		case 'w':
-		    res = RLIMIT_SWAP;
-		    break;
-# endif
-# ifdef HAVE_RLIMIT_KQUEUES
-		case 'k':
-		    res = RLIMIT_KQUEUES;
-		    break;
-# endif
-# ifdef HAVE_RLIMIT_PTHREAD
-		case 'T':
-		    res = RLIMIT_PTHREAD;
-		    break;
-# endif
-# ifdef HAVE_RLIMIT_UMTXP
-		case 'o':
-		    res = RLIMIT_UMTXP;
-		    break;
-# endif
 		default:
-		    /* unrecognised limit */
-		    zwarnnam(name, "bad option: -%c", *options);
-		    return 1;
+		    res = find_resource(*options);
+		    if (res < 0) {
+			/* unrecognised limit */
+			zwarnnam(name, "bad option: -%c", *options);
+			return 1;
+		    }
+		    break;
 		}
 		if (options[1]) {
 		    resmask |= 1 << res;
@@ -961,34 +819,8 @@ bin_ulimit(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
 		    return 1;
 		}
 		/* scale appropriately */
-		switch (res) {
-		case RLIMIT_FSIZE:
-		case RLIMIT_CORE:
-		    limit *= 512;
-		    break;
-		case RLIMIT_DATA:
-		case RLIMIT_STACK:
-# ifdef HAVE_RLIMIT_RSS
-		case RLIMIT_RSS:
-# endif /* HAVE_RLIMIT_RSS */
-# ifdef HAVE_RLIMIT_MEMLOCK
-		case RLIMIT_MEMLOCK:
-# endif /* HAVE_RLIMIT_MEMLOCK */
-/* If RLIMIT_VMEM and RLIMIT_RSS are defined and equal, avoid *
- * duplicate case statement.  Observed on QNX Neutrino 6.1.0. */
-# if defined(HAVE_RLIMIT_VMEM) && !defined(RLIMIT_VMEM_IS_RSS)
-		case RLIMIT_VMEM:
-# endif /* HAVE_RLIMIT_VMEM */
-/* ditto RLIMIT_VMEM and RLIMIT_AS */
-# if defined(HAVE_RLIMIT_AS) && !defined(RLIMIT_VMEM_IS_AS) && !defined(RLIMIT_RSS_IS_AS)
-		case RLIMIT_AS:
-# endif /* HAVE_RLIMIT_AS */
-# ifdef HAVE_RLIMIT_AIO_MEM
-		case RLIMIT_AIO_MEM:
-# endif /* HAVE_RLIMIT_AIO_MEM */
-		    limit *= 1024;
-		    break;
-		}
+		if (res < RLIM_NLIMITS)
+		    limit *= resinfo[res]->unit;
 	    }
 	    if (do_limit(name, res, limit, hard, soft, 1))
 		ret++;
@@ -1052,6 +884,7 @@ enables_(Module m, int **enables)
 int
 boot_(UNUSED(Module m))
 {
+    set_resinfo();
     return 0;
 }
 
@@ -1059,6 +892,7 @@ boot_(UNUSED(Module m))
 int
 cleanup_(Module m)
 {
+    free_resinfo();
     return setfeatureenables(m, &module_features, NULL);
 }
 
diff --git a/Src/Builtins/rlimits.mdd b/Src/Builtins/rlimits.mdd
index 9e6e9e598..06c9e9c7f 100644
--- a/Src/Builtins/rlimits.mdd
+++ b/Src/Builtins/rlimits.mdd
@@ -6,18 +6,3 @@ autofeatures="b:limit b:ulimit b:unlimit"
 autofeatures_emu="b:ulimit"
 
 objects="rlimits.o"
-
-:<<\Make
-rlimits.o rlimits..o: rlimits.h
-
-# this file will not be made if limits are unavailable
-rlimits.h: rlimits.awk @RLIMITS_INC_H@
-	$(AWK) -f $(sdir)/rlimits.awk @RLIMITS_INC_H@ /dev/null > rlimits.h
-	@if grep ZLIMTYPE_UNKNOWN rlimits.h >/dev/null; then \
-	    echo >&2 WARNING: unknown limits: mail Src/Builtins/rlimits.h to developers; \
-	else :; fi
-
-clean-here: clean.rlimits
-clean.rlimits:
-	rm -f rlimits.h
-Make
diff --git a/Src/Modules/system.c b/Src/Modules/system.c
index 50de59cf9..fb3d80773 100644
--- a/Src/Modules/system.c
+++ b/Src/Modules/system.c
@@ -174,7 +174,7 @@ bin_sysread(char *nam, char **args, Options ops, UNUSED(int func))
 	}
 
 	while ((ret = select(infd+1, (SELECT_ARG_2_T) &fds,
-			     NULL, NULL,&select_tv)) < 1) {
+			     NULL, NULL,&select_tv)) < 0) {
 	    if (errno != EINTR || errflag || retflag || breaks || contflag)
 		break;
 	}
diff --git a/Src/Zle/zle_keymap.c b/Src/Zle/zle_keymap.c
index d13aed594..2389ab754 100644
--- a/Src/Zle/zle_keymap.c
+++ b/Src/Zle/zle_keymap.c
@@ -404,7 +404,7 @@ scankeys(HashNode hn, UNUSED(int flags))
 /**************************/
 
 /**/
-Keymap
+mod_export Keymap
 openkeymap(char *name)
 {
     KeymapName n = (KeymapName) keymapnamtab->getnode(keymapnamtab, name);
diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c
index be68f4722..8c0534708 100644
--- a/Src/Zle/zle_main.c
+++ b/Src/Zle/zle_main.c
@@ -1056,7 +1056,7 @@ getrestchar(int inchar, char *outstr, int *outcount)
 #endif
 
 /**/
-void
+mod_export void
 redrawhook(void)
 {
     Thingy initthingy;
diff --git a/Src/Zle/zle_move.c b/Src/Zle/zle_move.c
index 155fda80d..3bafff3f1 100644
--- a/Src/Zle/zle_move.c
+++ b/Src/Zle/zle_move.c
@@ -166,7 +166,7 @@ decpos(int *pos)
  */
 
 /**/
-char *
+mod_export char *
 backwardmetafiedchar(char *start, char *endptr, convchar_t *retchr)
 {
 #ifdef MULTIBYTE_SUPPORT
diff --git a/Src/builtin.c b/Src/builtin.c
index aa5767cf1..407cad159 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -2597,7 +2597,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
  */
 
 /**/
-int
+mod_export int
 bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func)
 {
     Param pm;
diff --git a/Src/compat.c b/Src/compat.c
index 8ab335aa1..74e426fba 100644
--- a/Src/compat.c
+++ b/Src/compat.c
@@ -496,7 +496,7 @@ zgetdir(struct dirsav *d)
  */
 
 /**/
-char *
+mod_export char *
 zgetcwd(void)
 {
     char *ret = zgetdir(NULL);
diff --git a/Src/exec.c b/Src/exec.c
index 356e2974b..bca051d4f 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -1036,7 +1036,8 @@ entersubsh(int flags, struct entersubsh_ret *retp)
     } else if (thisjob != -1 && (flags & ESUB_PGRP)) {
 	if (jobtab[list_pipe_job].gleader && (list_pipe || list_pipe_child)) {
 	    if (setpgrp(0L, jobtab[list_pipe_job].gleader) == -1 ||
-		killpg(jobtab[list_pipe_job].gleader, 0) == -1) {
+		(killpg(jobtab[list_pipe_job].gleader, 0) == -1  &&
+		 errno == ESRCH)) {
 		jobtab[list_pipe_job].gleader =
 		    jobtab[thisjob].gleader = (list_pipe_child ? mypgrp : getpid());
 		setpgrp(0L, jobtab[list_pipe_job].gleader);
@@ -5101,7 +5102,6 @@ execarith(Estate state, UNUSED(int do_exec))
     mnumber val = zero_mnumber;
     int htok = 0;
 
-    queue_signals();
     if (isset(XTRACE)) {
 	printprompt4();
 	fprintf(xtrerr, "((");
@@ -5121,8 +5121,6 @@ execarith(Estate state, UNUSED(int do_exec))
 	fprintf(xtrerr, " ))\n");
 	fflush(xtrerr);
     }
-    unqueue_signals();
-
     if (errflag) {
 	errflag &= ~ERRFLAG_ERROR;
 	return 2;
diff --git a/Src/init.c b/Src/init.c
index 2e2ef881c..04a5856ff 100644
--- a/Src/init.c
+++ b/Src/init.c
@@ -882,7 +882,7 @@ setupvals(char *cmd, char *runscript, char *zsh_name)
     char *ptr;
     int i, j;
 #if defined(SITEFPATH_DIR) || defined(FPATH_DIR) || defined (ADDITIONAL_FPATH) || defined(FIXED_FPATH_DIR)
-#define FPATH_NEEDS_INIT 1
+# define FPATH_NEEDS_INIT 1
     char **fpathptr;
 # if defined(FPATH_DIR) && defined(FPATH_SUBDIRS)
     char *fpath_subdirs[] = FPATH_SUBDIRS;
@@ -994,18 +994,29 @@ setupvals(char *cmd, char *runscript, char *zsh_name)
 # endif /* ADDITONAL_FPATH */
     fpath = fpathptr = (char **)zalloc((fpathlen+1)*sizeof(char *));
 # ifdef FIXED_FPATH_DIR
+    /* Zeroth: /usr/local/share/zsh/site-functions */
     *fpathptr++ = ztrdup(FIXED_FPATH_DIR);
     fpathlen--;
 # endif
 # ifdef SITEFPATH_DIR
+    /* First: the directory from --enable-site-fndir
+     *
+     * default: /usr/local/share/zsh/site-functions
+     * (but changeable by passing --prefix or --datadir to configure) */
     *fpathptr++ = ztrdup(SITEFPATH_DIR);
     fpathlen--;
 # endif /* SITEFPATH_DIR */
 # if defined(ADDITIONAL_FPATH)
+    /* Second: the directories from --enable-additional-fpath
+     * 
+     * default: empty list */
     for (j = 0; j < more_fndirs_len; j++)
 	*fpathptr++ = ztrdup(more_fndirs[j]);
 # endif
 # ifdef FPATH_DIR
+    /* Third: The directory from --enable-fndir
+     *
+     * default: /usr/local/share/zsh/${ZSH_VERSION}/functions */
 #  ifdef FPATH_SUBDIRS
 #   ifdef ADDITIONAL_FPATH
     for (j = more_fndirs_len; j < fpathlen; j++)
@@ -1013,7 +1024,7 @@ setupvals(char *cmd, char *runscript, char *zsh_name)
 #   else
     for (j = 0; j < fpathlen; j++)
 	*fpathptr++ = tricat(FPATH_DIR, "/", fpath_subdirs[j]);
-#endif
+#   endif
 #  else
     *fpathptr++ = ztrdup(FPATH_DIR);
 #  endif
diff --git a/Src/jobs.c b/Src/jobs.c
index e7438251e..8353f1152 100644
--- a/Src/jobs.c
+++ b/Src/jobs.c
@@ -283,7 +283,8 @@ handle_sub(int job, int fg)
 
 	    if ((cp = ((WIFEXITED(jn->procs->status) ||
 			WIFSIGNALED(jn->procs->status)) &&
-		       killpg(jn->gleader, 0) == -1))) {
+		       (killpg(jn->gleader, 0) == -1 &&
+			errno == ESRCH)))) {
 		Process p;
 		for (p = jn->procs; p->next; p = p->next);
 		jn->gleader = p->pid;
@@ -541,9 +542,13 @@ update_job(Job jn)
 
 	/* is this job in the foreground of an interactive shell? */
 	if (mypgrp != pgrp && inforeground &&
-	    (jn->gleader == pgrp || (pgrp > 1 && kill(-pgrp, 0) == -1))) {
+	    (jn->gleader == pgrp ||
+	     (pgrp > 1 &&
+	      (kill(-pgrp, 0) == -1 && errno == ESRCH)))) {
 	    if (list_pipe) {
-		if (somestopped || (pgrp > 1 && kill(-pgrp, 0) == -1)) {
+		if (somestopped || (pgrp > 1 &&
+				    kill(-pgrp, 0) == -1 &&
+				    errno == ESRCH)) {
 		    attachtty(mypgrp);
 		    /* check window size and adjust if necessary */
 		    adjustwinsize(0);
@@ -1854,13 +1859,14 @@ scanjobs(void)
 
 /* This simple function indicates whether or not s may represent      *
  * a number.  It returns true iff s consists purely of digits and     *
- * minuses.  Note that minus may appear more than once, and the empty *
- * string will produce a `true' response.                             */
+ * minuses.  Note that minus may appear more than once.               */
 
 /**/
 static int
 isanum(char *s)
 {
+    if (*s == '\0')
+	return 0;
     while (*s == '-' || idigit(*s))
 	s++;
     return *s == '\0';
@@ -2469,7 +2475,8 @@ bin_fg(char *name, char **argv, Options ops, int func)
 		    if ((jobtab[job].stat & STAT_SUPERJOB) &&
 			((!jobtab[job].procs->next ||
 			  (jobtab[job].stat & STAT_SUBLEADER) ||
-			  killpg(jobtab[job].gleader, 0) == -1)) &&
+			  (killpg(jobtab[job].gleader, 0) == -1  &&
+			  errno == ESRCH))) &&
 			jobtab[jobtab[job].other].gleader)
 			attachtty(jobtab[jobtab[job].other].gleader);
 		    else
diff --git a/Src/loop.c b/Src/loop.c
index 57858a150..95ef48b33 100644
--- a/Src/loop.c
+++ b/Src/loop.c
@@ -570,7 +570,7 @@ execif(Estate state, int do_exec)
 
     if (run) {
 	/* we need to ignore lastval until we reach execcmd() */
-	if (olderrexit)
+	if (olderrexit || run == 2)
 	    noerrexit = olderrexit;
 	else if (lastval)
 	    noerrexit |= NOERREXIT_EXIT | NOERREXIT_RETURN | NOERREXIT_UNTIL_EXEC;
diff --git a/Src/math.c b/Src/math.c
index a38770073..905b910ec 100644
--- a/Src/math.c
+++ b/Src/math.c
@@ -1133,8 +1133,7 @@ notzero(mnumber a)
 
 /* macro to pop three values off the value stack */
 
-/**/
-void
+static void
 op(int what)
 {
     mnumber a, b, c, *spval;
@@ -1569,14 +1568,19 @@ mathparse(int pc)
 
     if (errflag)
 	return;
+    queue_signals();
     mtok = zzlex();
     /* Handle empty input */
-    if (pc == TOPPREC && mtok == EOI)
+    if (pc == TOPPREC && mtok == EOI) {
+	unqueue_signals();
 	return;
+    }
     checkunary(mtok, optr);
     while (prec[mtok] <= pc) {
-	if (errflag)
+	if (errflag) {
+	    unqueue_signals();
 	    return;
+	}
 	switch (mtok) {
 	case NUM:
 	    push(yyval, NULL, 0);
@@ -1595,6 +1599,7 @@ mathparse(int pc)
 	    if (mtok != M_OUTPAR) {
 		if (!errflag)
 		    zerr("bad math expression: ')' expected");
+		unqueue_signals();
 		return;
 	    }
 	    break;
@@ -1613,6 +1618,7 @@ mathparse(int pc)
 	    if (mtok != COLON) {
 		if (!errflag)
 		    zerr("bad math expression: ':' expected");
+		unqueue_signals();
 		return;
 	    }
 	    if (q)
@@ -1636,4 +1642,5 @@ mathparse(int pc)
 	mtok = zzlex();
 	checkunary(mtok, optr);
     }
+    unqueue_signals();
 }
diff --git a/Src/openssh_bsd_setres_id.c b/Src/openssh_bsd_setres_id.c
new file mode 100644
index 000000000..65e91a40c
--- /dev/null
+++ b/Src/openssh_bsd_setres_id.c
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2012 Darren Tucker (dtucker at zip com au).
+ *
+ * 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.
+ */
+/*
+ * openssh_bsd_setres_id.c - setresuid() and setresgid() wrappers
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * It is based on the file openbsd-compat/bsd-setres_id.c in OpenSSH 7.9p1,
+ * which is subject to the copyright notice above.  The zsh modifications are
+ * licensed as follows:
+ *
+ * Copyright (c) 2019 Daniel Shahaf
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Daniel Shahaf or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Daniel Shahaf and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Daniel Shahaf and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Daniel Shahaf and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+
+#include <sys/types.h>
+
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "zsh.mdh"
+
+#if defined(ZSH_IMPLEMENT_SETRESGID) || defined(BROKEN_SETRESGID)
+int
+setresgid(gid_t rgid, gid_t egid, gid_t sgid)
+{
+	int ret = 0, saved_errno;
+
+	if (rgid != sgid) {
+		errno = ENOSYS;
+		return -1;
+	}
+#if defined(ZSH_HAVE_NATIVE_SETREGID) && !defined(BROKEN_SETREGID)
+	if (setregid(rgid, egid) < 0) {
+		saved_errno = errno;
+		zwarnnam("setregid", "to gid %L: %e", (long)rgid, errno);
+		errno = saved_errno;
+		ret = -1;
+	}
+#else
+	if (setegid(egid) < 0) {
+		saved_errno = errno;
+		zwarnnam("setegid", "to gid %L: %e", (long)(unsigned int)egid, errno);
+		errno = saved_errno;
+		ret = -1;
+	}
+	if (setgid(rgid) < 0) {
+		saved_errno = errno;
+		zwarnnam("setgid", "to gid %L: %e", (long)rgid, errno);
+		errno = saved_errno;
+		ret = -1;
+	}
+#endif
+	return ret;
+}
+#endif
+
+#if defined(ZSH_IMPLEMENT_SETRESUID) || defined(BROKEN_SETRESUID)
+int
+setresuid(uid_t ruid, uid_t euid, uid_t suid)
+{
+	int ret = 0, saved_errno;
+
+	if (ruid != suid) {
+		errno = ENOSYS;
+		return -1;
+	}
+#if defined(ZSH_HAVE_NATIVE_SETREUID) && !defined(BROKEN_SETREUID)
+	if (setreuid(ruid, euid) < 0) {
+		saved_errno = errno;
+		zwarnnam("setreuid", "to uid %L: %e", (long)ruid, errno);
+		errno = saved_errno;
+		ret = -1;
+	}
+#else
+
+# ifndef SETEUID_BREAKS_SETUID
+	if (seteuid(euid) < 0) {
+		saved_errno = errno;
+		zwarnnam("seteuid", "to uid %L: %e", (long)euid, errno);
+		errno = saved_errno;
+		ret = -1;
+	}
+# endif
+	if (setuid(ruid) < 0) {
+		saved_errno = errno;
+		zwarnnam("setuid", "to uid %L: %e", (long)ruid, errno);
+		errno = saved_errno;
+		ret = -1;
+	}
+#endif
+	return ret;
+}
+#endif
diff --git a/Src/options.c b/Src/options.c
index 48c14c179..08ba71917 100644
--- a/Src/options.c
+++ b/Src/options.c
@@ -577,6 +577,7 @@ int
 bin_setopt(char *nam, char **args, UNUSED(Options ops), int isun)
 {
     int action, optno, match = 0;
+    int retval = 0;
 
     /* With no arguments or options, display options. */
     if (!*args) {
@@ -604,18 +605,24 @@ bin_setopt(char *nam, char **args, UNUSED(Options ops), int isun)
 		    inittyptab();
 		    return 1;
 		}
-		if(!(optno = optlookup(*args)))
+		if(!(optno = optlookup(*args))) {
 		    zwarnnam(nam, "no such option: %s", *args);
-		else if(dosetopt(optno, action, 0, opts))
+		    retval |= 1;
+		} else if (dosetopt(optno, action, 0, opts)) {
 		    zwarnnam(nam, "can't change option: %s", *args);
+		    retval |= 1;
+		}
 		break;
 	    } else if(**args == 'm') {
 		match = 1;
 	    } else {
-	    	if (!(optno = optlookupc(**args)))
+		if (!(optno = optlookupc(**args))) {
 		    zwarnnam(nam, "bad option: -%c", **args);
-		else if(dosetopt(optno, action, 0, opts))
+		    retval |= 1;
+		} else if (dosetopt(optno, action, 0, opts)) {
 		    zwarnnam(nam, "can't change option: -%c", **args);
+		    retval |= 1;
+		}
 	    }
 	}
 	args++;
@@ -625,10 +632,13 @@ bin_setopt(char *nam, char **args, UNUSED(Options ops), int isun)
     if (!match) {
 	/* Not globbing the arguments -- arguments are simply option names. */
 	while (*args) {
-	    if(!(optno = optlookup(*args++)))
+	    if(!(optno = optlookup(*args++))) {
 		zwarnnam(nam, "no such option: %s", args[-1]);
-	    else if(dosetopt(optno, !isun, 0, opts))
+		retval |= 1;
+	    } else if (dosetopt(optno, !isun, 0, opts)) {
 		zwarnnam(nam, "can't change option: %s", args[-1]);
+		retval |= 1;
+	    }
 	}
     } else {
 	/* Globbing option (-m) set. */
@@ -651,7 +661,8 @@ bin_setopt(char *nam, char **args, UNUSED(Options ops), int isun)
 	    tokenize(s);
 	    if (!(pprog = patcompile(s, PAT_HEAPDUP, NULL))) {
 		zwarnnam(nam, "bad pattern: %s", *args);
-		continue;
+		retval |= 1;
+		break;
 	    }
 	    /* Loop over expansions. */
 	    scanmatchtable(optiontab, pprog, 0, 0, OPT_ALIAS,
@@ -660,7 +671,7 @@ bin_setopt(char *nam, char **args, UNUSED(Options ops), int isun)
 	}
     }
     inittyptab();
-    return 0;
+    return retval;
 }
 
 /* Identify an option name */
@@ -769,37 +780,99 @@ dosetopt(int optno, int value, int force, char *new_opts)
 	    return -1;
     } else if(optno == PRIVILEGED && !value) {
 	/* unsetting PRIVILEGED causes the shell to make itself unprivileged */
-#ifdef HAVE_SETUID
-	int ignore_err;
-	errno = 0;
+
+/* For simplicity's sake, require both setresgid() and setresuid() up-front. */
+#if !defined(HAVE_SETRESGID)
+	zwarnnam("unsetopt",
+	    "PRIVILEGED: can't drop privileges; setresgid() and friends not available");
+	return -1;
+#elif !defined(HAVE_SETRESUID)
+	zwarnnam("unsetopt",
+	    "PRIVILEGED: can't drop privileges; setresuid() and friends not available");
+	return -1;
+#else
+	/* If set, return -1 so lastval will be non-zero. */
+	int failed = 0;
+	const int orig_euid = geteuid();
+	const int orig_egid = getegid();
+
 	/*
 	 * Set the GID first as if we set the UID to non-privileged it
 	 * might be impossible to restore the GID.
-	 *
-	 * Some OSes (possibly no longer around) have been known to
-	 * fail silently the first time, so we attempt the change twice.
-	 * If it fails we are guaranteed to pick this up the second
-	 * time, so ignore the first time.
-	 *
-	 * Some versions of gcc make it hard to ignore the results the
-	 * first time, hence the following.  (These are probably not
-	 * systems that require the doubled calls.)
 	 */
-	ignore_err = setgid(getgid());
-	(void)ignore_err;
-	ignore_err = setuid(getuid());
-	(void)ignore_err;
-	if (setgid(getgid())) {
-            zwarn("failed to change group ID: %e", errno);
-            return -1;
-        } else if (setuid(getuid())) {
-            zwarn("failed to change user ID: %e", errno);
-            return -1;
+	if (setresgid(getgid(), getgid(), getgid())) {
+	    zwarnnam("unsetopt",
+		"PRIVILEGED: can't drop privileges; failed to change group ID: %e",
+		errno);
+	    return -1;
 	}
-#else
-        zwarn("setuid not available");
-        return -1;
-#endif /* not HAVE_SETUID */
+
+# ifdef HAVE_INITGROUPS
+	/* Set the supplementary groups list.
+	 *
+	 * Note that on macOS, FreeBSD, and possibly some other platforms,
+	 * initgroups() resets the EGID to its second argument (see setgroups(2) for
+	 * details). This has the potential to leave the EGID in an unexpected
+	 * state. However, it seems common in other projects that do this dance to
+	 * simply re-use the same GID that's going to become the EGID anyway, in
+	 * which case it doesn't matter. That's what we do here. It's therefore
+	 * possible, in some probably uncommon cases, that the shell ends up not
+	 * having the privileges of the RUID user's primary/passwd group. */
+	if (geteuid() == 0) {
+	    struct passwd *pw = getpwuid(getuid());
+	    if (pw == NULL) {
+		zwarnnam("unsetopt",
+		    "can't drop privileges; failed to get user information for uid %L: %e",
+		    (long)getuid(), errno);
+		failed = 1;
+	    /* This may behave strangely in the unlikely event that the same user
+	     * name appears with multiple UIDs in the passwd database */
+	    } else if (initgroups(pw->pw_name, getgid())) {
+		zwarnnam("unsetopt",
+		    "can't drop privileges; failed to set supplementary group list: %e",
+		    errno);
+		return -1;
+	    }
+	} else if (getuid() != 0 &&
+	    (geteuid() != getuid() || orig_egid != getegid())) {
+	    zwarnnam("unsetopt",
+		"PRIVILEGED: supplementary group list not changed due to lack of permissions: EUID=%L",
+		(long)geteuid());
+	    failed = 1;
+	}
+# else
+	/* initgroups() isn't in POSIX.  If it's not available on the system,
+	 * we silently skip it. */
+# endif
+
+	/* Set the UID second. */
+	if (setresuid(getuid(), getuid(), getuid())) {
+	    zwarnnam("unsetopt",
+		"PRIVILEGED: can't drop privileges; failed to change user ID: %e",
+		errno);
+	    return -1;
+	}
+
+	if (getuid() != 0 && orig_egid != getegid() &&
+		(setgid(orig_egid) != -1 || setegid(orig_egid) != -1)) {
+	    zwarnnam("unsetopt",
+		"PRIVILEGED: can't drop privileges; was able to restore the egid");
+	    return -1;
+	}
+
+	if (getuid() != 0 && orig_euid != geteuid() &&
+		(setuid(orig_euid) != -1 || seteuid(orig_euid) != -1)) {
+	    zwarnnam("unsetopt",
+		"PRIVILEGED: can't drop privileges; was able to restore the euid");
+	    return -1;
+	}
+
+	if (failed) {
+	    /* A warning message has been printed. */
+	    return -1;
+	}
+#endif /* HAVE_SETRESGID && HAVE_SETRESUID */
+
 #ifdef JOB_CONTROL
     } else if (!force && optno == MONITOR && value) {
 	if (new_opts[optno] == value)
diff --git a/Src/params.c b/Src/params.c
index 5eaafe34e..863b32600 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -478,7 +478,13 @@ static initparam argvparam_pm = IPDEF9("", &pparams, NULL, \
 
 static Param argvparam;
 
-/* hash table containing the parameters */
+/* "parameter table" - hash table containing the parameters
+ *
+ * realparamtab always points to the shell's global table.  paramtab is sometimes
+ * temporarily changed to point at another table, while dealing with the keys
+ * of an associative array (for example, see makecompparams() which initializes
+ * the associative array ${compstate}).
+ */
  
 /**/
 mod_export HashTable paramtab, realparamtab;
diff --git a/Src/signals.c b/Src/signals.c
index 96ff9e9b3..4adf03202 100644
--- a/Src/signals.c
+++ b/Src/signals.c
@@ -539,7 +539,8 @@ wait_for_processes(void)
 #endif
 		if (WIFEXITED(status) &&
 		    pn->pid == jn->gleader &&
-		    killpg(pn->pid, 0) == -1) {
+		    killpg(pn->pid, 0) == -1  &&
+		    errno == ESRCH) {
 		    if (last_attached_pgrp == jn->gleader &&
 			!(jn->stat & STAT_NOSTTY)) {
 			/*
diff --git a/Src/utils.c b/Src/utils.c
index f5667f389..f9c2d4a2b 100644
--- a/Src/utils.c
+++ b/Src/utils.c
@@ -1023,7 +1023,7 @@ xsymlinks(char *s, int full)
  */
 
 /**/
-char *
+mod_export char *
 xsymlink(char *s, int heap)
 {
     if (*s != '/')
diff --git a/Src/zsh.mdd b/Src/zsh.mdd
index 3e5788af5..9bcaccae5 100644
--- a/Src/zsh.mdd
+++ b/Src/zsh.mdd
@@ -13,7 +13,8 @@ objects="builtin.o compat.o cond.o context.o \
 exec.o glob.o hashtable.o hashnameddir.o \
 hist.o init.o input.o jobs.o lex.o linklist.o loop.o math.o \
 mem.o module.o options.o params.o parse.o pattern.o prompt.o signals.o \
-signames.o sort.o string.o subst.o text.o utils.o watch.o"
+signames.o sort.o string.o subst.o text.o utils.o watch.o \
+openssh_bsd_setres_id.o"
 
 headers="../config.h zsh_system.h zsh.h sigcount.h signals.h \
 prototypes.h hashtable.h ztype.h"
diff --git a/Src/zsh_system.h b/Src/zsh_system.h
index 85e198f2e..161b073b4 100644
--- a/Src/zsh_system.h
+++ b/Src/zsh_system.h
@@ -468,30 +468,90 @@ struct timespec {
 # define setpgrp setpgid
 #endif
 
-/* can we set the user/group id of a process */
+/* compatibility wrappers */
 
-#ifndef HAVE_SETUID
+/* Our strategy is as follows:
+ *
+ * - Ensure that either setre[ug]id() or set{e,}[ug]id() is available.
+ * - If setres[ug]id() are missing, provide them in terms of either
+ *   setre[ug]id() or set{e,}[ug]id(), whichever is available.
+ * - Provide replacement setre[ug]id() or set{e,}[ug]id() if they are not
+ *   available natively.
+ *
+ * There isn't a circular dependency because, right off the bat, we check that
+ * there's an end condition, and #error out otherwise.
+ */
+#if !defined(HAVE_SETREUID) && !(defined(HAVE_SETEUID) && defined(HAVE_SETUID))
+  /*
+   * If you run into this error, you have two options:
+   * - Teach zsh how to do the equivalent of setreuid() on your system
+   * - Remove support for PRIVILEGED option, and then remove the #error.
+   */
+# error "Don't know how to change UID"
+#endif
+#if !defined(HAVE_SETREGID) && !(defined(HAVE_SETEGID) && defined(HAVE_SETGID))
+  /* See above comment. */
+# error "Don't know how to change GID"
+#endif
+
+/* Provide setresuid(). */
+#ifndef HAVE_SETRESUID
+int	setresuid(uid_t, uid_t, uid_t);
+# define HAVE_SETRESUID
+# define ZSH_IMPLEMENT_SETRESUID
 # ifdef HAVE_SETREUID
-#  define setuid(X) setreuid(X,X)
-#  define setgid(X) setregid(X,X)
-#  define HAVE_SETUID
+#  define ZSH_HAVE_NATIVE_SETREUID
 # endif
 #endif
 
-/* can we set the effective user/group id of a process */
+/* Provide setresgid(). */
+#ifndef HAVE_SETRESGID
+int	setresgid(gid_t, gid_t, gid_t);
+# define HAVE_SETRESGID
+# define ZSH_IMPLEMENT_SETRESGID
+# ifdef HAVE_SETREGID
+#  define ZSH_HAVE_NATIVE_SETREGID
+# endif
+#endif
 
+/* Provide setreuid(). */
+#ifndef HAVE_SETREUID
+# define setreuid(X, Y) setresuid((X), (Y), -1)
+# define HAVE_SETREUID
+#endif
+
+/* Provide setregid(). */
+#ifndef HAVE_SETREGID
+# define setregid(X, Y) setresgid((X), (Y), -1)
+# define HAVE_SETREGID
+#endif
+
+/* Provide setuid(). */
+/* ### TODO: Either remove this (this function has been standard since 1985),
+ * ###       or rewrite this without multiply-evaluating the argument */
+#ifndef HAVE_SETUID
+# define setuid(X) setreuid((X), (X))
+# define HAVE_SETUID
+#endif
+
+/* Provide setgid(). */
+#ifndef HAVE_SETGID
+/* ### TODO: Either remove this (this function has been standard since 1985),
+ * ###       or rewrite this without multiply-evaluating the argument */
+#  define setgid(X) setregid((X), (X))
+#  define HAVE_SETGID
+#endif
+
+/* Provide seteuid(). */
 #ifndef HAVE_SETEUID
-# ifdef HAVE_SETREUID
-#  define seteuid(X) setreuid(-1,X)
-#  define setegid(X) setregid(-1,X)
-#  define HAVE_SETEUID
-# else
-#  ifdef HAVE_SETRESUID
-#   define seteuid(X) setresuid(-1,X,-1)
-#   define setegid(X) setresgid(-1,X,-1)
-#   define HAVE_SETEUID
-#  endif
-# endif
+# define seteuid(X) setreuid(-1, (X))
+# define HAVE_SETEUID
+#endif
+
+/* Provide setegid(). */
+#ifndef HAVE_SETEGID
+# define setegid(X) setregid(-1, (X))
+# define HAVE_SETEGID
 #endif
 
 #ifdef HAVE_SYS_RESOURCE_H
diff --git a/Test/B01cd.ztst b/Test/B01cd.ztst
index 3312f8707..21e751dcb 100644
--- a/Test/B01cd.ztst
+++ b/Test/B01cd.ztst
@@ -70,7 +70,7 @@
 # the expected status returned by the code when run, or - if it is
 # irrelevant.  An optional set of single-letter flags follows the status
 # or -.  The following are understood:
-#  . d  Don't diff stdout against the expected stdout.
+#   d   Don't diff stdout against the expected stdout.
 #   D   Don't diff stderr against the expected stderr.
 #   q   All redirection lines given in the test script (not the lines
 #       actually produced by the test) are subject to ordinary quoted shell
@@ -96,8 +96,8 @@
 # itself.  (The example below isn't particularly useful as errors with
 # `cd' are unusual.)
 #
-# A couple of features aren't used in this file, but are usefuil in cases
-# where features may not be available so should not be tested.  They boh
+# A couple of features aren't used in this file, but are useful in cases
+# where features may not be available so should not be tested.  They both
 # take the form of variables.  Note that to keep the test framework simple
 # there is no magic in setting the variables: the chunk of code being
 # executed needs to avoid executing any test code by appropriate structure
@@ -145,6 +145,10 @@ F:something is broken.  But you already knew that.
 1:Implicit cd with unset HOME.
 ?zsh:cd:1: HOME not set
 
+ $ZTST_testdir/../Src/zsh -fc 'cd -P ////dev && pwd'
+-f:(workers/45367) cd -P squashes multiple leading slashes
+>/dev  
+
 %clean
 # This optional section cleans up after the test, if necessary,
 # e.g. killing processes etc.  This is in addition to the removal of *.tmp
diff --git a/Test/B11kill.ztst b/Test/B11kill.ztst
new file mode 100644
index 000000000..dc6bf9b89
--- /dev/null
+++ b/Test/B11kill.ztst
@@ -0,0 +1,86 @@
+# Tests for the kill builtin.
+#
+# The exit codes 11 and 19 in this file don't mean anything special; they're
+# just exit codes which are specific enough that the failure of `kill` itself
+# can be differentiated from exiting due to executing a trap.
+
+%test
+
+# Correct invocation
+
+  if zmodload zsh/system &>/dev/null; then
+    (
+      trap 'exit 19' TERM
+      kill $sysparams[pid]
+    )
+  else
+    ZTST_skip='Cannot zmodload zsh/system, skipping kill with no sigspec'
+  fi
+19:kill with no sigspec
+
+
+  if zmodload zsh/system &>/dev/null; then
+    (
+      trap 'exit 11' USR1
+      kill -USR1 $sysparams[pid]
+    )
+  else
+    ZTST_skip='Cannot zmodload zsh/system, skipping kill with sigspec'
+  fi
+11:kill with sigspec
+
+# Incorrect invocation
+
+  (
+    kill a b c
+  )
+3:kill with multiple wrong inputs should increment status
+?(eval):kill:2: illegal pid: a
+?(eval):kill:2: illegal pid: b
+?(eval):kill:2: illegal pid: c
+
+  (
+    kill -INT a b c
+  )
+3:kill with sigspec and wrong inputs should increment status
+?(eval):kill:2: illegal pid: a
+?(eval):kill:2: illegal pid: b
+?(eval):kill:2: illegal pid: c
+
+  (
+    kill
+  )
+1:kill with no arguments
+?(eval):kill:2: not enough arguments
+
+  (
+    kill -INT
+  )
+1:kill with sigspec only
+?(eval):kill:2: not enough arguments
+
+# Regression tests: `kill ''` should not result in `kill 0`.
+#
+# We use SIGURG where an explicit sigspec can be provided as:
+#
+# 1. By default it's non-terminal, so even if we regress, we won't kill the
+#    test runner and other processes in the process group since we'll stop
+#    running this test before we get to the plain kill (and thus SIGTERM)
+#    cases;
+# 2. It's also unlikely to be sent for any other reason during the process
+#    lifetime, so the test shouldn't be flaky.
+
+  (
+    trap 'exit 11' URG
+    kill -URG ''
+  )
+1:kill with empty pid and sigspec should not send signal to current process group
+?(eval):kill:3: illegal pid: 
+
+  (
+    trap 'exit 19' TERM
+    kill ''
+  )
+1:Plain kill with empty pid should not send signal to current process group
+?(eval):kill:3: illegal pid: 
+
diff --git a/Test/B12limit.ztst b/Test/B12limit.ztst
new file mode 100644
index 000000000..5dd7afdbe
--- /dev/null
+++ b/Test/B12limit.ztst
@@ -0,0 +1,10 @@
+# check if there is unknown resouce(s)
+
+%test
+
+ limit | grep UNKNOWN || print OK
+0:Check if there is unknown resouce(s) in the system
+>OK
+F:A failure here does not indicate any error in zsh. It just means there
+F:is a resource in your system that is unknown to zsh developers. Please
+F:report this to zsh-workers mailing list.
diff --git a/Test/C03traps.ztst b/Test/C03traps.ztst
index e661aabd5..6f84e5db2 100644
--- a/Test/C03traps.ztst
+++ b/Test/C03traps.ztst
@@ -500,6 +500,15 @@
 >Succeed 2
 >Succeed 3
 
+  (set -e
+   if false; then
+   else
+       a=$(false)
+       print This should not appear
+   fi
+  )
+1:ERREXIT is triggered in an else block after a cmd subst returning false
+
   fn() {
     emulate -L zsh
     setopt errreturn
diff --git a/Test/D02glob.ztst b/Test/D02glob.ztst
index 7fd22d795..4e6dc2a7a 100644
--- a/Test/D02glob.ztst
+++ b/Test/D02glob.ztst
@@ -753,6 +753,10 @@
 0:non-directories not globbed as directories
 >glob.tmp/not-a-directory ,
 
+ () { echo $1:P } ////dev
+-f:(workers/45367) modifier ':P' squashes multiple slashes
+>/dev  
+
 %clean
 
  # Fix unreadable-directory permissions so ztst can clean up properly
diff --git a/Test/E01options.ztst b/Test/E01options.ztst
index c4b101bdb..cfe2c75cc 100644
--- a/Test/E01options.ztst
+++ b/Test/E01options.ztst
@@ -74,7 +74,6 @@
 #    HASH_LIST_ALL )
 #    PRINT_EXIT_STATUS   haven't worked out what this does yet, although
 #                        Bart suggested a fix.
-#    PRIVILEGED (similar to GLOBAL_RCS)
 #    RCS        (  "      "    "    " )
 #    SH_OPTION_LETTERS   even I found this too dull to set up a test for
 #    SINGLE_COMMAND      kills shell
@@ -95,6 +94,15 @@
 
 %test
 
+  # setopt should move on to the next operation in the face of an error, but
+  # preserve the >0 return code
+  unsetopt aliases
+  setopt not_a_real_option aliases && return 2
+  print -r - $options[aliases]
+0:setopt error handling
+?(eval):setopt:4: no such option: not_a_real_option
+>on
+
   alias echo='print foo'
   unsetopt aliases
   # use eval else aliases are all parsed at start
@@ -1391,3 +1399,18 @@ F:Regression test for workers/41811
 ?(anon):4: `break' active at end of function scope
 ?(anon):4: `break' active at end of function scope
 ?(anon):4: `break' active at end of function scope
+
+# There are further tests for PRIVILEGED in P01privileged.ztst.
+ if [[ -o privileged ]]; then
+   unsetopt privileged
+ fi
+ unsetopt privileged
+0:PRIVILEGED sanity check: unsetting is idempotent
+F:If this test fails at the first unsetopt, refer to P01privileged.ztst.
+
+  if [[ -o privileged ]]; then
+    (( UID != EUID ))
+  else
+    (( UID == EUID ))
+  fi
+0:PRIVILEGED sanity check: default value is correct
diff --git a/Test/P01privileged.ztst b/Test/P01privileged.ztst
new file mode 100644
index 000000000..c54112bb6
--- /dev/null
+++ b/Test/P01privileged.ztst
@@ -0,0 +1,197 @@
+# This file contains tests related to the PRIVILEGED option. In order to run,
+# it requires that the test process itself have super-user privileges (or that
+# one of the environment variables described below be set). This can be achieved
+# via, e.g., `sudo make check TESTNUM=P`.
+#
+# Optionally, the environment variables ZSH_TEST_UNPRIVILEGED_UID and/or
+# ZSH_TEST_UNPRIVILEGED_GID may be set to UID:EUID or GID:EGID pairs, where the
+# two IDs in each pair are different, non-0 IDs valid on the system being used
+# to run the tests. (The UIDs must both be non-0 to effectively test downgrading
+# of privileges, and they must be non-matching to test auto-enabling of
+# PRIVILEGED and to ensure that disabling PRIVILEGED correctly resets the saved
+# UID. Technically GID 0 is not special, but for simplicity's sake we apply the
+# same requirements here.)
+#
+# If either of the aforementioned environment variables is not set, the test
+# script will try to pick the first two >0 IDs from the passwd/group databases
+# on the current system.
+#
+# If either variable is set, the tests will run, but they will likely fail
+# without super-user privileges.
+
+%prep
+
+  # Mind your empty lines here. The logic in this %prep section is somewhat
+  # complex compared to most others; to avoid lots of nested/duplicated
+  # conditions we need to make sure that this all gets executed as a single
+  # function from which we can return early
+  [[ $EUID == 0 || -n $ZSH_TEST_UNPRIVILEGED_UID$ZSH_TEST_UNPRIVILEGED_GID ]] || {
+    ZTST_unimplemented='PRIVILEGED tests require super-user privileges (or env var)'
+    return 1
+  }
+  (( $+commands[perl] )) || { # @todo Eliminate this dependency with a C wrapper?
+    ZTST_unimplemented='PRIVILEGED tests require Perl'
+    return 1
+  }
+  grep -qE '#define HAVE_SETRES?UID' $ZTST_testdir/../config.h || {
+    ZTST_unimplemented='PRIVILEGED tests require setreuid()/setresuid()'
+    return 1
+  }
+  #
+  ruid= euid= rgid= egid=
+  #
+  if [[ -n $ZSH_TEST_UNPRIVILEGED_UID ]]; then
+    ruid=${ZSH_TEST_UNPRIVILEGED_UID%%:*}
+    euid=${ZSH_TEST_UNPRIVILEGED_UID##*:}
+  else
+    print -ru$ZTST_fd 'Selecting unprivileged UID:EUID pair automatically'
+    local tmp=$( getent passwd 2> /dev/null || < /etc/passwd )
+    # Note: Some awks require -v and its argument to be separate
+    ruid=$( awk -F:            '$3 > 0 { print $3; exit; }' <<< $tmp )
+    euid=$( awk -F: -v u=$ruid '$3 > u { print $3; exit; }' <<< $tmp )
+  fi
+  #
+  if [[ -n $ZSH_TEST_UNPRIVILEGED_GID ]]; then
+    rgid=${ZSH_TEST_UNPRIVILEGED_GID%%:*}
+    egid=${ZSH_TEST_UNPRIVILEGED_GID##*:}
+  else
+    print -ru$ZTST_fd 'Selecting unprivileged GID:EGID pair automatically'
+    local tmp=$( getent group 2> /dev/null || < /etc/group )
+    # Note: Some awks require -v and its argument to be separate
+    rgid=$( awk -F:            '$3 > 0 { print $3; exit; }' <<< $tmp )
+    egid=$( awk -F: -v g=$rgid '$3 > g { print $3; exit; }' <<< $tmp )
+  fi
+  #
+  [[ $ruid/$euid == <1->/<1-> && $ruid != $euid ]] || ruid= euid=
+  [[ $rgid/$egid == <1->/<1-> && $rgid != $egid ]] || rgid= egid=
+  #
+  [[ -n $ruid && -n $euid ]] || {
+    ZTST_unimplemented='PRIVILEGED tests require unprivileged UID:EUID'
+    return 1
+  }
+  [[ -n $rgid || -n $egid ]] || {
+    ZTST_unimplemented='PRIVILEGED tests require unprivileged GID:EGID'
+    return 1
+  }
+  #
+  print -ru$ZTST_fd \
+    "Using unprivileged UID $ruid, EUID $euid, GID $rgid, EGID $egid"
+  #
+  # Execute process with specified UID and EUID
+  # $1     => Real UID
+  # $2     => Effective UID
+  # $3     => Real GID
+  # $4     => Effective GID
+  # $5 ... => Command + args to execute (must NOT be a shell command string)
+  re_exec() {
+    perl -e '
+      die("re_exec: not enough arguments") unless (@ARGV >= 5);
+      my ($ruid, $euid, $rgid, $egid, @cmd) = @ARGV;
+      foreach my $id ($ruid, $euid, $rgid, $egid) {
+        die("re_exec: invalid ID: $id") unless ($id =~ /^(-1|\d+)$/a);
+      }
+      $< = 0 + $ruid if ($ruid >= 0);
+      $> = 0 + $euid if ($euid >= 0);
+      $( = 0 + $rgid if ($rgid >= 0);
+      $) = 0 + $egid if ($egid >= 0);
+      exec(@cmd);
+      die("re_exec: exec failed: $!");
+    ' -- "$@"
+  }
+  #
+  # Convenience wrapper for re_exec to call `zsh -c`
+  # -* ... => (optional) Command-line options to zsh
+  # $1     => Real UID
+  # $2     => Effective UID
+  # $3     => Real GID
+  # $4     => Effective GID
+  # $5 ... => zsh command string; multiple strings are joined by \n
+  re_zsh() {
+    local -a opts
+    while [[ $1 == -[A-Za-z-]* ]]; do
+      opts+=( $1 )
+      shift
+    done
+    re_exec "$1" "$2" "$3" "$4" $ZTST_exe $opts -fc \
+      "MODULE_PATH=${(q)MODULE_PATH}; ${(F)@[5,-1]}"
+  }
+  #
+  # Return one or more random unused UIDs
+  # $1 ... => Names of parameters to store UIDs in
+  get_unused_uid() {
+    while (( $# )); do
+      local i_=0 uid_=
+      until [[ -n $uid_ ]]; do
+        (( ++i_ > 99 )) && return 1
+        uid_=$RANDOM
+        id $uid_ &> /dev/null || break
+        uid_=
+      done
+      : ${(P)1::=$uid_}
+      shift
+    done
+  }
+
+%test
+
+  re_zsh $ruid $ruid -1 -1 'echo $UID/$EUID $options[privileged]'
+  re_zsh $euid $euid -1 -1 'echo $UID/$EUID $options[privileged]'
+  re_zsh $ruid $euid -1 -1 'echo $UID/$EUID $options[privileged]'
+0q:PRIVILEGED automatically enabled when RUID != EUID
+>$ruid/$ruid off
+>$euid/$euid off
+>$ruid/$euid on
+
+  re_zsh -1 -1 $rgid $rgid 'echo $GID/$EGID $options[privileged]'
+  re_zsh -1 -1 $egid $egid 'echo $GID/$EGID $options[privileged]'
+  re_zsh -1 -1 $rgid $egid 'echo $GID/$EGID $options[privileged]'
+0q:PRIVILEGED automatically enabled when RGID != EGID
+>$rgid/$rgid off
+>$egid/$egid off
+>$rgid/$egid on
+
+  re_zsh $ruid $euid -1 -1 'unsetopt privileged; echo $UID/$EUID'
+0q:EUID set to RUID after disabling PRIVILEGED
+*?zsh:unsetopt:1: PRIVILEGED: supplementary group list not changed *
+*?zsh:unsetopt:1: can't change option: privileged
+>$ruid/$ruid
+
+  re_zsh 0 $euid -1 -1 'unsetopt privileged && echo $UID/$EUID'
+0:RUID/EUID set to 0/0 when privileged after disabling PRIVILEGED
+>0/0
+
+  re_zsh $ruid $euid -1 -1 "unsetopt privileged; UID=$euid" ||
+  re_zsh $ruid $euid -1 -1 "unsetopt privileged; EUID=$euid"
+1:not possible to regain EUID when unprivileged after disabling PRIVILEGED
+*?zsh:unsetopt:1: PRIVILEGED: supplementary group list not changed *
+*?zsh:unsetopt:1: can't change option: privileged
+*?zsh:1: failed to change user ID: *
+*?zsh:unsetopt:1: PRIVILEGED: supplementary group list not changed *
+*?zsh:unsetopt:1: can't change option: privileged
+*?zsh:1: failed to change effective user ID: *
+
+  re_zsh -1 -1 $rgid $egid 'unsetopt privileged && echo $GID/$EGID'
+0q:EGID set to RGID after disabling PRIVILEGED
+>$rgid/$rgid
+
+# This test also confirms that we can't revert to the original EUID's primary
+# GID, which initgroups() may reset the EGID to on some systems
+  re_zsh $ruid 0 $rgid 0 'unsetopt privileged; GID=0' ||
+  re_zsh $ruid 0 $rgid 0 'unsetopt privileged; EGID=0'
+1:not possible to regain EGID when unprivileged after disabling PRIVILEGED
+*?zsh:1: failed to change group ID: *
+*?zsh:1: failed to change effective group ID: *
+
+  local rruid
+  grep -qF '#define HAVE_INITGROUPS' $ZTST_testdir/../config.h || {
+    ZTST_skip='initgroups() not available'
+    return 1
+  }
+  get_unused_uid rruid || {
+    ZTST_skip="Can't get unused UID"
+    return 1
+  }
+  re_zsh $rruid 0 -1 -1 'unsetopt privileged'
+1:getpwuid() fails with non-existent RUID and 0 EUID
+*?zsh:unsetopt:1: can't drop privileges; failed to get user information *
+*?zsh:unsetopt:1: can't change option: privileged
diff --git a/Test/README b/Test/README
index d012277ce..726d68e72 100644
--- a/Test/README
+++ b/Test/README
@@ -6,6 +6,7 @@ scripts names:
  C: shell commands with special syntax
  D: substititution
  E: options
+ P: privileged (needs super-user privileges)
  V: modules
  W: builtin interactive commands and constructs
  X: line editing
diff --git a/Test/V01zmodload.ztst b/Test/V01zmodload.ztst
index 339df7436..c3c64a79d 100644
--- a/Test/V01zmodload.ztst
+++ b/Test/V01zmodload.ztst
@@ -389,6 +389,22 @@
 0:unloading a module doesn't implicitly unset autoloadable parameters
 *>(on|off) *
 
+ $ZTST_testdir/../Src/zsh -fc "
+  MODULE_PATH=${(q)MODULE_PATH}
+  #
+  zmodload zsh/zutil
+  zmodload -Fal zsh/zutil | grep parse
+  zmodload -u zsh/zutil
+  #
+  zmodload -Fa zsh/zutil -b:zregexparse
+  zmodload zsh/zutil
+  zmodload -Fal zsh/zutil | grep parse >&2
+ "
+0:zmodload -Fa can disable features from being loaded
+>b:zparseopts
+>b:zregexparse
+?b:zparseopts
+
 %clean
 
  eval "$deps"
diff --git a/Util/ztst-syntax.vim b/Util/ztst-syntax.vim
index 01e4dae31..a39fe3fbb 100644
--- a/Util/ztst-syntax.vim
+++ b/Util/ztst-syntax.vim
@@ -25,7 +25,13 @@ syn clear
 
 syn include @zsh                   syntax/zsh.vim
 
-syn match  ztstPayload             /^\s\+\zs.*/ contains=@zsh
+" Note that we don't do /^\s\zs.*/ here.  If we did that, lines that start
+" with " #" (a space and a hash sign) would not be highlighted as comments,
+" because zshComment's patterns won't match unless the '#' is preceded by
+" a space or start-of-line.  See:
+"
+" https://github.com/chrisbra/vim-zsh/issues/21#issuecomment-577738791
+syn match  ztstPayload             /^\s.*/ contains=@zsh
 
 syn match  ztstExitCode            /^\d\+\|^-/                nextgroup=ztstFlags
 syn match  ztstFlags               /[.dDqf]*:/      contained nextgroup=ztstTestName contains=ztstColon
diff --git a/configure.ac b/configure.ac
index e517d880a..35e779c26 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1312,6 +1312,7 @@ AC_CHECK_FUNCS(strftime strptime mktime timelocal \
 	       getlogin getpwent getpwnam getpwuid getgrgid getgrnam \
 	       initgroups nis_list \
 	       setuid seteuid setreuid setresuid setsid \
+	       setgid setegid setregid setresgid \
 	       memcpy memmove strstr strerror strtoul \
 	       getrlimit getrusage \
 	       setlocale \
@@ -1928,6 +1929,7 @@ zsh_LIMIT_PRESENT(RLIMIT_SIGPENDING)
 zsh_LIMIT_PRESENT(RLIMIT_MSGQUEUE)
 zsh_LIMIT_PRESENT(RLIMIT_NICE)
 zsh_LIMIT_PRESENT(RLIMIT_RTPRIO)
+zsh_LIMIT_PRESENT(RLIMIT_RTTIME)
 zsh_LIMIT_PRESENT(RLIMIT_POSIXLOCKS)
 zsh_LIMIT_PRESENT(RLIMIT_NPTS)
 zsh_LIMIT_PRESENT(RLIMIT_SWAP)