Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

[PATCHv1] [long] improvements to limit/ulimit API and doc



Hello,

I found a small issue in the "limit" builtin in that for instance
"limit msgqueue 1M" sets that limit to 1 byte instead of 1MiB
(that M suffix is silently ignored) and that "limit msgqueue 10"
was not following documentation and tried to have a go at
addressing it, but ended up finding a few more small issues,
went down the rabbit hole, also trying to address the problem
(not specific to zsh) that you never know what unit those
resource limits are meant to be expressed as.

Please see the details in commit log below.

Though I think it could be applied as is (I don't think I've
broken backward compatibility), there are a few things I'm not
completely happy or sure about so I'd appreciate some input.

The few remaining issues I've not really addressed here:

- ulimit output rounds down the values (some of them anyway) so
  we lose information. Is it worth addressing? (like with a
  "raw" option)?

- Some might disapprove the switch to kibibyte/mebibyte KiK/MiB
  (and the MB meaning 1,000,000).

- Is it worth accepting floating point values like:
  limit filesize 1.2G

- I've only tested it on Ubuntu, FreeBSD and Cygwin. I suspect
  my test cases will fail on 32bit systems. They're skipped on
  Cygwin which doesn't let you set any limit AFAICT. I don't
  have much coverage in those two tests, but with limits being
  very system specific, I'm not sure how to tackle it.

- ulimit still outputs the limits set for children processes. I
  think that's probably best. So it outputs the limits set by
  limit, ulimit or ulimit -s, even if strictly speaking, it
  doesn't give you what's returned by getrlimit() like in other
  shells (that only ever becomes visible if you use "limit"
  anyway which is not in those other shells.

- there are some corner case issues that could surprise users
  and may be worth documenting like:
  (limit filesize 1k; (print {1..2000} > file)) still creates a
  8K file because the fork for the (print...) was optimised out
  so the limits are not applied.

- I've made it so "limit -s filesize" reports the shell's own
  limit (-s is for "set" initially, but it could also be "shell"
  or "self"). But while "limit" outputs all children limits,
  "limit -s" still copies those children limits to the shell's
  and there's no way to print *all* self limits. That doesn't
  make for a very intuitive API.

BTW, POSIX is currently looking at extending the "ulimit"
builtin specification
(https://www.austingroupbugs.net/view.php?id=1418) which is what
got me looking in the first place. I think zsh is mostly
compliant with their currently proposed resolution.


From 5c1971d40b238edb1a745b7298b0169cff2b8053 Mon Sep 17 00:00:00 2001
From: Stephane Chazelas <stephane@xxxxxxxxxxxx>
Date: Mon, 23 Nov 2020 17:31:02 +0000
Subject: [PATCH] improve limit/ulimit API and documentation

A few issues addressed:

* msgqueue limit specifies a number of bytes but was classified as
  ZLIMTYPE_NUMBER which meant k/m suffixes were not allowed and the
  default unit for `limit` was 1 (byte) instead of the 1024 (KiB)
  specified by documentation.

  -> turns out tcsh's `limit` handled that limit (called `maxmessage`
     there) the same way and `bash`, `bosh` and `mksh`'s `ulimit` (not
     ksh93 which takes kibibytes there) also expect a number of bytes
     there.

     So, the type was changed to ZLIMTYPE_MEMORY, but the unit remains
     bytes for both `limit` and `ulimit` as a (now documented) special
     quirk for backward compatibility.

* unit suffixes, where not supported (like for ZLIMTYPE_NUMBER) are
  silently ignored.

  -> now return an error on unexpected or unrecognised suffixes.

* limit output using ambigous kB, MB units. These days, those tend to
  imply decimal scaling factors (1000B, 1000000B).

  -> changed output to use KiB, MiB, the ISO 80000 unambiguous ones
     which are now more or less mainstream.
  -> those, extended to KMGTPE..iB are also accepted on input while
     KB, MB... are interpreted as decimal. (K, M, G remain binary).
  -> documentation updated to avoid kilobyte, megabyte and use
     unambiguous kibibyte, mebibyte... instead.

-> rt_time limit is now `ulimit -R` like in bash or bosh.

* "nice" limit description ("max nice") was misleading as it's more
  linked to the *minimum* niceness the process can get.

  -> Changed to "max nice priority" matching the Linux kernel's own
     description.

* documentation for both `limit` and `ulimit` missing a few limits ->
  added

* time limits are output as h:mm:ss but that's not accepted on input.
  1:00:00 silently truncated to 1:00 (one minute instead of one hour).

  -> accept [[hh:]mm:]ss[.usec]

* limit and ulimit output truncate precision (1000B limit represented as
  0kB and 1 respectively)

  -> addressed in `limit` but not `ulimit` (where
     compliance/compatibility with ksh matters). By only using scaling
     factors when the limit can be represented in an integer number of
     them (using B, as in 1000B above when none qualify).

* some limits can't be set to arbitrary values (like that 1000 above for
  filesize) and it's not always obvious what the default unit should be.

  -> recognise the B suffix on input for "memory" type limits and
     "s"/"ms"/"ns" for time/microsecond type units so the user can input
     values unambiguously.
  -> those suffixes are now recognised by both limit and ulimit. Parsing
     code factorised into `zstrtorlimt`.

* `limit` and `unlimit` are disabled when zsh is started in csh
  emulation even though those builtins come from there.

  -> changed the features_emu in rlimits.mdd (only used there) and
    mkbltnmlst.sh to features_posix for those to only be disabled
    in sh/ksh emulation.

* `limit` changes the limits for children, `ulimit` for the shell,
  but both report limits for children, and it's not possible to
  retrieve the shell's own limits.

  -> ulimit not changed as it's probably better that way.
  -> `limit -s somelimit` changed so it reports the somelimit value
     for the shell process

-> documentation improved.
---
 Doc/Zsh/builtins.yo      | 129 +++++++++----
 Doc/Zsh/expn.yo          |  18 +-
 Doc/Zsh/mod_zpty.yo      |   4 +-
 Doc/Zsh/params.yo        |  10 +-
 NEWS                     |   7 +
 Src/Builtins/rlimits.c   | 390 +++++++++++++++++++++++++++------------
 Src/Builtins/rlimits.mdd |   8 +-
 Src/mkbltnmlst.sh        |  10 +-
 Test/B12limit.ztst       | 129 +++++++++++++
 9 files changed, 530 insertions(+), 175 deletions(-)

diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo
index ebb29f632..f794abf1a 100644
--- a/Doc/Zsh/builtins.yo
+++ b/Doc/Zsh/builtins.yo
@@ -1116,15 +1116,18 @@ cindex(resource limits)
 cindex(limits, resource)
 item(tt(limit) [ tt(-hs) ] [ var(resource) [ var(limit) ] ] ...)(
 Set or display resource limits.  Unless the tt(-s) flag is given,
-the limit applies only the children of the shell.  If tt(-s) is
-given without other arguments, the resource limits of the current
-shell is set to the previously set resource limits of the children.
+the limit applies only to child processes and executed commands, not the
+current shell process itself.
 
-If var(limit) is not specified, print the current limit placed
-on var(resource), otherwise
-set the limit to the specified value.  If the tt(-h) flag
-is given, use hard limits instead of soft limits.
-If no var(resource) is given, print all limits.
+If tt(-s) is given without other arguments, the resource limits of the
+current shell is set to the previously set resource limits of the
+children.
+
+If var(limit) is not specified, print the current limit placed on
+var(resource) (of the shell with tt(-s), of children without), otherwise
+set the limit to the specified value.  If the tt(-h) flag is given, use
+hard limits instead of soft limits. If no var(resource) is given, print
+all limits.
 
 When looping over multiple resources, the shell will abort immediately if
 it detects a badly formed argument.  However, if it fails to set a limit
@@ -1143,18 +1146,23 @@ sitem(tt(datasize))(Maximum data size (including stack) for each process.)
 sitem(tt(descriptors))(Maximum value for a file descriptor.)
 sitem(tt(filesize))(Largest single file allowed.)
 sitem(tt(kqueues))(Maximum number of kqueues allocated.)
+sitem(tt(maxfilelocks))(Maximum number of file locks.)
 sitem(tt(maxproc))(Maximum number of processes.)
-sitem(tt(maxpthreads))(Maximum number of threads per process.)
+sitem(tt(maxpthreads))(Maximum number of threads per uid (NetBSD/OpenBSD) or per process.)
 sitem(tt(memorylocked))(Maximum amount of memory locked in RAM.)
 sitem(tt(memoryuse))(Maximum resident set size.)
 sitem(tt(msgqueue))(Maximum number of bytes in POSIX message queues.)
+sitem(tt(nice))(Maximum nice priority.)
 sitem(tt(posixlocks))(Maximum number of POSIX locks per user.)
 sitem(tt(pseudoterminals))(Maximum number of pseudo-terminals.)
 sitem(tt(resident))(Maximum resident set size.)
+sitem(tt(rt_priority))(Maximum realtime priority.)
+sitem(tt(rt_time))(Maximum realtime CPU time slice.)
 sitem(tt(sigpending))(Maximum number of pending signals.)
 sitem(tt(sockbufsize))(Maximum size of all socket buffers.)
 sitem(tt(stacksize))(Maximum stack size for each process.)
 sitem(tt(swapsize))(Maximum amount of swap used.)
+sitem(tt(umtxp))(Maximum number of umtx shared locks.)
 sitem(tt(vmemorysize))(Maximum amount of virtual memory.)
 endsitem()
 
@@ -1169,18 +1177,46 @@ the limit anyway, and will report an error if this fails.  As the shell
 does not store such resources internally, an attempt to set the limit will
 fail unless the tt(-s) option is present.
 
-var(limit) is a number, with an optional scaling factor, as follows:
+If var(limit) is a decimal integer number without suffix, then for
+historical reason and compatibility with csh where that command comes from,
+the unit will depend on the type of limit: microsecond for tt(rt_time),
+second for all other time limits, kibibytes (1024 bytes) for all limits that
+express a number of bytes (except tt(msgqueue)).
+
+Instead, to avoid confusion, a suffix (case insensitive) may be appended to
+the (integer only) decimal number to specify the unit:
 
 startsitem()
-sitem(var(n)tt(h))(hours)
-sitem(var(n)tt(k))(kilobytes (default))
-sitem(var(n)tt(m))(megabytes or minutes)
-sitem(var(n)tt(g))(gigabytes)
-sitem([var(mm)tt(:)]var(ss))(minutes and seconds)
+sitem(time limits)(
+startsitem()
+sitem(tt(h))(hours)
+sitem(tt(m))(minutes)
+sitem(tt(s))(seconds)
+sitem(tt(ms))(milliseconds)
+sitem(tt(us))(microseconds)
+sitem(tt([[var(h):]var(m):]var(s)[.var(usec)]))(all combined)
+endsitem()
+
+For instance a 2 millisecond limit of tt(rt_time), can be expressed as
+tt(2ms), tt(2000us), tt(0.002), tt(0:0:0.002) or tt(2000) without unit.)
+sitem(memory limits)(
+startsitem()
+sitem(tt(k, kib))(kibibytes (1024 bytes))
+sitem(tt(m, mib))(mibibytes)
+sitem(...)(and so on with tt(g), tt(t), tt(p), tt(e) for gibibytes,
+tebibytes, pebibytes and exbibytes respectively)
+sitem(tt(kb))(kilobytes (1,000 bytes))
+sitem(tt(mb))(megabytes (1,000,000 bytes))
+sitem(...)(and so on with tt(g), tt(t), tt(p), tt(e) for gigabytes,
+terabytes, petabytes and exabytes respectively.)
+sitem(tt(b))(em(byte) to set the limit with arbitrary precisions)
+endsitem())
+sitem(other limits)(other limits are assumed to be numerical and ony
+a decimal integer number is accepted.)
 endsitem()
 
 The tt(limit) command is not made available by default when the
-shell starts in a mode emulating another shell.  It can be made available
+shell starts in sh or ksh emulation mode.  It can be made available
 with the command `tt(zmodload -F zsh/rlimits b:limit)'.
 )
 findex(local)
@@ -2250,8 +2286,8 @@ together with the tt(-H) flag set both hard and soft limits.
 If no options are used, the file size limit (tt(-f)) is assumed.
 
 If var(limit) is omitted the current value of the specified resources are
-printed.  When more than one resource value is printed, the limit name and
-unit is printed before each value.
+printed (rounded down to the corresponding unit).  When more than one resource
+value is printed, the limit name and unit is printed before each value.
 
 When looping over multiple resources, the shell will abort immediately if
 it detects a badly formed argument.  However, if it fails to set a limit
@@ -2260,30 +2296,45 @@ for some other reason it will continue trying to set the remaining limits.
 Not all the following resources are supported on all systems.  Running
 tt(ulimit -a) will show which are supported.
 
+Whilst tt(limit) is the BSD/csh-style command, tt(ulimit) is the SysV/Korn
+shell equivalent (added later to zsh for compatibility with ksh). While
+tt(limit) in zsh sets the limits for child processes by default, tt(ulimit)
+sets them for both the shell and child processes, and reports the ones
+set for child processes.
+
+The same suffixes are recognised as for tt(limit), but bear in mind that
+default units when no suffix is specified varry between the two.
+
+Below, the corresponding tt(limit) keyword for each tt(ulimit) option is
+shown in parenthesis for reference:
+
 startsitem()
 sitem(tt(-a))(Lists all of the current resource limits.)
-sitem(tt(-b))(Socket buffer size in bytes LPAR()N.B. not kilobytes+RPAR())
-sitem(tt(-c))(512-byte blocks on the size of core dumps.)
-sitem(tt(-d))(Kilobytes on the size of the data segment.)
-sitem(tt(-f))(512-byte blocks on the size of files written.)
-sitem(tt(-i))(The number of pending signals.)
-sitem(tt(-k))(The number of kqueues allocated.)
-sitem(tt(-l))(Kilobytes on the size of locked-in memory.)
-sitem(tt(-m))(Kilobytes on the size of physical memory.)
-sitem(tt(-n))(open file descriptors.)
-sitem(tt(-p))(The number of pseudo-terminals.)
-sitem(tt(-q))(Bytes in POSIX message queues.)
+sitem(tt(-b))(Socket buffer size in bytes LPAR()N.B. not kibibytes+RPAR() (tt(sockbufsize)).)
+sitem(tt(-c))(512-byte blocks on the size of core dumps (tt(coredumpsize)).)
+sitem(tt(-d))(Kibibytes on the size of the data segment (tt(datasize)).)
+sitem(tt(-e))(nice priority (tt(nice)).)
+sitem(tt(-f))(512-byte blocks on the size of files written (tt(filesize)).)
+sitem(tt(-i))(The number of pending signals (tt(sigpending)).)
+sitem(tt(-k))(The number of kqueues allocated (tt(kqueues)).)
+sitem(tt(-l))(Kibibytes on the size of locked-in memory (tt(memorylocked)).)
+sitem(tt(-m))(Kibibytes on the size of physical memory (tt(resident)).)
+sitem(tt(-n))(open file descriptors (tt(descriptors)).)
+sitem(tt(-o))(umtx shared locks (tt(umtxp)).)
+sitem(tt(-p))(The number of pseudo-terminals (tt(pseudoterminals)).)
+sitem(tt(-q))(Bytes in POSIX message queues (tt(msgqueue)).)
 sitem(tt(-r))(Maximum real time priority.  On some systems where this
 is not available, such as NetBSD, this has the same effect as tt(-T)
-for compatibility with tt(sh).)
-sitem(tt(-s))(Kilobytes on the size of the stack.)
-sitem(tt(-T))(The number of simultaneous threads available to the user.)
-sitem(tt(-t))(CPU seconds to be used.)
-sitem(tt(-u))(The number of processes available to the user.)
-sitem(tt(-v))(Kilobytes on the size of virtual memory.  On some systems this
-refers to the limit called `address space'.)
-sitem(tt(-w))(Kilobytes on the size of swapped out memory.)
-sitem(tt(-x))(The number of locks on files.)
+for compatibility with tt(sh) (tt(rt_priority) / tt(maxpthreads)).)
+sitem(tt(-R))(realtime CPU time slice (tt(rt_time)).)
+sitem(tt(-s))(Kibibytes on the size of the stack (tt(stacksize)).)
+sitem(tt(-T))(The number of simultaneous threads available to the user (tt(maxpthreads)).)
+sitem(tt(-t))(CPU seconds to be used (tt(cputime)).)
+sitem(tt(-u))(The number of processes available to the user (tt(maxproc)).)
+sitem(tt(-v))(Kibibytes on the size of virtual memory.  On some systems this
+refers to the limit called em(address space) (tt(vmemorysize) / tt(addressspace)).)
+sitem(tt(-w))(Kibibytes on the size of swapped out memory (tt(swapsize)).)
+sitem(tt(-x))(The number of locks on files (tt(maxfilelocks)).)
 endsitem()
 
 A resource may also be specified by integer in the form `tt(-N)
@@ -2343,7 +2394,7 @@ The resources of the shell process are only changed if the tt(-s)
 flag is given.
 
 The tt(unlimit) command is not made available by default when the
-shell starts in a mode emulating another shell.  It can be made available
+shell starts in sh or ksh emulation mode.  It can be made available
 with the command `tt(zmodload -F zsh/rlimits b:unlimit)'.
 )
 findex(unset)
diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo
index b3396721f..588426af0 100644
--- a/Doc/Zsh/expn.yo
+++ b/Doc/Zsh/expn.yo
@@ -2837,15 +2837,15 @@ exactly var(n) bytes in length.
 
 If this flag is directly followed by a em(size specifier) `tt(k)' (`tt(K)'),
 `tt(m)' (`tt(M)'), or `tt(p)' (`tt(P)') (e.g. `tt(Lk-50)') the check is
-performed with kilobytes, megabytes, or blocks (of 512 bytes) instead.
-(On some systems additional specifiers are available for gigabytes,
-`tt(g)' or `tt(G)', and terabytes, `tt(t)' or `tt(T)'.) If a size specifier
-is used a file is regarded as "exactly" the size if the file size rounded up
-to the next unit is equal to the test size.  Hence `tt(*LPAR()Lm1+RPAR())'
-matches files from 1 byte up to 1 Megabyte inclusive.  Note also that
-the set of files "less than" the test size only includes files that would
-not match the equality test; hence `tt(*LPAR()Lm-1+RPAR())' only matches
-files of zero size.
+performed with kibibytes, mebibytes, or blocks (of 512 bytes; catch: not
+em(pebibytes)) instead. (On some systems additional specifiers are available
+for gibibytes, `tt(g)' or `tt(G)', and tebibytes, `tt(t)' or `tt(T)'.) If a
+size specifier is used, a file is regarded as "exactly" the size if the file
+size rounded up to the next unit is equal to the test size.  Hence
+`tt(*LPAR()Lm1+RPAR())' matches files from 1 byte up to 1 Mebibyte
+inclusive.  Note also that the set of files "less than" the test size only
+includes files that would not match the equality test; hence
+`tt(*LPAR()Lm-1+RPAR())' only matches files of zero size.
 )
 item(tt(^))(
 negates all qualifiers following it
diff --git a/Doc/Zsh/mod_zpty.yo b/Doc/Zsh/mod_zpty.yo
index 3ca031c01..ba59e1783 100644
--- a/Doc/Zsh/mod_zpty.yo
+++ b/Doc/Zsh/mod_zpty.yo
@@ -66,8 +66,8 @@ read matches the var(pattern), even in the non-blocking case.  The return
 status is zero if the string read matches the pattern, or if the command
 has exited but at least one character could still be read.  If the option
 tt(-m) is present, the return status is zero only if the pattern matches.
-As of this writing, a maximum of one megabyte of output can be consumed
-this way; if a full megabyte is read without matching the pattern, the
+As of this writing, a maximum of one mebibyte of output can be consumed
+this way; if a full mebibyte is read without matching the pattern, the
 return status is non-zero.
 
 In all cases, the return status is non-zero if nothing could be read, and
diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo
index 36c1ae4c2..6c305da34 100644
--- a/Doc/Zsh/params.yo
+++ b/Doc/Zsh/params.yo
@@ -1473,7 +1473,7 @@ is specified with no command.  Defaults to tt(more).
 vindex(REPORTMEMORY)
 item(tt(REPORTMEMORY))(
 If nonnegative, commands whose maximum resident set size (roughly
-speaking, main memory usage) in kilobytes is greater than this
+speaking, main memory usage) in kibibytes is greater than this
 value have timing statistics reported.  The format used to output
 statistics is the value of the tt(TIMEFMT) parameter, which is the same
 as for the tt(REPORTTIME) variable and the tt(time) builtin; note that
@@ -1603,12 +1603,12 @@ sitem(tt(%E))(Elapsed time in seconds.)
 sitem(tt(%P))(The CPU percentage, computed as
 100*(tt(%U)PLUS()tt(%S))/tt(%E).)
 sitem(tt(%W))(Number of times the process was swapped.)
-sitem(tt(%X))(The average amount in (shared) text space used in kilobytes.)
+sitem(tt(%X))(The average amount in (shared) text space used in kibibytes.)
 sitem(tt(%D))(The average amount in (unshared) data/stack space used in
-kilobytes.)
-sitem(tt(%K))(The total space used (tt(%X)PLUS()tt(%D)) in kilobytes.)
+kibibytes.)
+sitem(tt(%K))(The total space used (tt(%X)PLUS()tt(%D)) in kibibytes.)
 sitem(tt(%M))(The  maximum memory the process had in use at any time in
-kilobytes.)
+kibibytes.)
 sitem(tt(%F))(The number of major page faults (page needed to be brought
 from disk).)
 sitem(tt(%R))(The number of minor page faults.)
diff --git a/NEWS b/NEWS
index a8e7df80e..f5534eba0 100644
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,13 @@ The zsh/system module's `zsystem flock` command learnt an -i option to
 set the wait interval used with -t. Additionally, -t now supports
 fractional seconds.
 
+The `limit` builtin accepts more units for resource limits, now shared
+with the `ulimit` builtin allowing to specify arbitrary limit values.
+`limit -s somelimit` now reports the shell process' own value of the limit.
+The `limit` and `unlimit` builtins are now available again in csh emulation.
+More generally, the resouce limit handling interfaces and documentation have
+been improved.
+
 Changes from 5.7.1-test-3 to 5.8
 --------------------------------
 
diff --git a/Src/Builtins/rlimits.c b/Src/Builtins/rlimits.c
index 5f9c84b0f..1b2474583 100644
--- a/Src/Builtins/rlimits.c
+++ b/Src/Builtins/rlimits.c
@@ -55,7 +55,8 @@ typedef struct resinfo_T {
  * 1. Add zsh_LIMIT_PRESENT(RLIMIT_XXX) in configure.ac.
  * 2. Add an entry for RLIMIT_XXX to known_resources[].
  *    Make sure the option letter (resinto_T.opt) is unique.
- * 3. Build zsh and run the test B12rlimit.ztst.
+ * 3. Add entry in documentation for the limit and ulimit builtins
+ * 4. Build zsh and run the test B12rlimit.ztst.
  */
 static const resinfo_T known_resources[] = {
     {RLIMIT_CPU, "cputime", ZLIMTYPE_TIME, 1,
@@ -109,12 +110,23 @@ static const resinfo_T known_resources[] = {
 		'i', "pending signals"},
 # endif
 # ifdef HAVE_RLIMIT_MSGQUEUE
-    {RLIMIT_MSGQUEUE, "msgqueue", ZLIMTYPE_NUMBER, 1,
+    {RLIMIT_MSGQUEUE, "msgqueue", ZLIMTYPE_MEMORY, 1,
 		'q', "bytes in POSIX msg queues"},
 # endif
 # ifdef HAVE_RLIMIT_NICE
+    /*
+     * Not max niceness. On Linux ranges [1..40] for that RLIM translates
+     * to [-19..20] meaning that for instance setting it to 20 means
+     * processes can lower their niceness as far down as -1 and with the
+     * default of 0, unprivileged processes can't lower their niceness.
+     *
+     * Increasing niceness is always possible.
+     *
+     * "max nice priority" is the wording used in /proc/self/limits on
+     * Linux.
+     */
     {RLIMIT_NICE, "nice", ZLIMTYPE_NUMBER, 1,
-		'e', "max nice"},
+		'e', "max nice priority"},
 # endif
 # ifdef HAVE_RLIMIT_RTPRIO
     {RLIMIT_RTPRIO, "rt_priority", ZLIMTYPE_NUMBER, 1,
@@ -122,7 +134,7 @@ static const resinfo_T known_resources[] = {
 # endif
 # ifdef HAVE_RLIMIT_RTTIME
     {RLIMIT_RTTIME, "rt_time", ZLIMTYPE_MICROSECONDS, 1,
-		'N', "rt cpu time (microseconds)"},
+		'R', "rt cpu time slice (microseconds)"},
 # endif
     /* BSD */
 # ifdef HAVE_RLIMIT_SBSIZE
@@ -192,9 +204,9 @@ set_resinfo(void)
 	if (!resinfo[i]) {
 	    /* unknown resource */
 	    resinfo_T *info = (resinfo_T *)zshcalloc(sizeof(resinfo_T));
-	    char *buf = (char *)zalloc(12);
+	    char *buf = (char *)zalloc(8 /* UNKNOWN- */ + 3 /* digits */ + 1 /* '\0' */);
 	    snprintf(buf, 12, "UNKNOWN-%d", i);
-	    info->res = - 1;	/* negative value indicates "unknown" */
+	    info->res = -1;	/* negative value indicates "unknown" */
 	    info->name = buf;
 	    info->type = ZLIMTYPE_UNKNOWN;
 	    info->unit = 1;
@@ -255,38 +267,204 @@ printrlim(rlim_t val, const char *unit)
 # endif /* RLIM_T_IS_QUAD_T */
 }
 
+/*
+ * Parse a string into the corresponding value based on the limit type
+ *
+ *   ZLIMTYPE_UNKNOWN / ZLIMTYPE_NUMBER:
+ *     decimal integer without sign only: raw value
+ *
+ *   ZLIMTYPE_TIME / ZLIMTYPE_MICROSECONDS:
+ *     <decimal> only gives seconds or microseconds depending on type
+ *     or:
+ *     [[hour:]min:]sec[.usec]
+ *     or:
+ *     <decimal> with h (hour), m (min), s (sec), ms, us suffix.
+ *
+ *   ZLIMTYPE_MEMORY:
+ *     <decimal> without suffix interpreted as KiB by "limit" (except for
+ *     RLIMIT_MSGQUEUE, see below) and based on resinfo.unit by "ulimit".
+ *
+ *     K/M/G/T/P/E suffix and same with iB suffix use 1024 factor
+ *     KB/MB/GB... use 1000 factor.
+ *
+ *     B for bytes (avoids that mess about default units).
+ *
+ * All suffixes are case insensitive.
+ *
+ */
+
 /**/
 static rlim_t
-zstrtorlimt(const char *s, char **t, int base)
+zstrtorlimt(const char *s, int lim, int ulimit, char **err)
 {
     rlim_t ret = 0;
+    const char *orig = s;
+    enum zlimtype type = resinfo[lim]->type;
+    *err = NULL;
 
-    if (strcmp(s, "unlimited") == 0) {
-	if (t)
-	    *t = (char *) s + 9;
+    if (strcmp(s, "unlimited") == 0)
 	return RLIM_INFINITY;
+
+    for (; *s >= '0' && *s <= '9'; s++)
+	ret = ret * 10 + *s - '0';
+
+    if (s == orig) {
+	*err = "decimal integer expected";
+	return 0;
+    }
+
+    if (lim >= RLIM_NLIMITS ||
+	type == ZLIMTYPE_NUMBER ||
+	type == ZLIMTYPE_UNKNOWN) {
+	/*
+	 * pure numeric resource -- only a straight decimal number is
+	 * permitted.
+	 */
+	if (*s) {
+	    *err = "limit must be a decimal integer";
+	    return 0;
+	}
+    }
+    else if (type == ZLIMTYPE_TIME ||
+	     type == ZLIMTYPE_MICROSECONDS) {
+	if (*s) {
+	    int divisor = 1;
+	    int factor = 1;
+	    switch (*s++) {
+
+	    case 'm': /* m for minute or ms for milisecond */
+	    case 'M':
+		if (*s == 's' || *s == 'S') {
+		    s++;
+		    divisor = 1000;
+		}
+		else {
+		    factor = 60;
+		}
+		break;
+
+	    case 'h':
+	    case 'H':
+		factor = 3600;
+		break;
+
+	    case 's':
+	    case 'S':
+		break;
+
+	    case 'u':
+	    case 'U':
+		divisor = 1000000;
+		if (*s == 's' || *s == 'S')
+		    s++;
+		break;
+
+	    case ':':
+		do {
+		    int more = 0;
+		    while (*s >= '0' && *s <= '9') {
+			more = more * 10 + (*s - '0');
+			s++;
+		    }
+		    ret = ret * 60 + more;
+		} while (*s == ':' && ++s);
+		if (*s == '.')
+		    s++;
+		    /* fallthrough */
+		else
+		    break;
+	    case '.':
+		if (type == ZLIMTYPE_MICROSECONDS) {
+		    int frac;
+		    for (frac = 0; *s >= '0' && *s <= '9'; s++) {
+			if (divisor < 1000000) {
+			    /* ignore digits past the 6th */
+			    divisor *= 10;
+			    frac = frac * 10 + (*s - '0');
+			}
+		    }
+		    ret = ret * divisor + frac;
+		}
+		else {
+		    /* fractional part ignored */
+		    while (*s >= '0' && *s <= '9')
+			s++;
+		}
+		break;
+	    default:
+		*err = "invalid time specification";
+		return 0;
+	    }
+
+	    if (*s) {
+		*err = "invalid time specification";
+		return 0;
+	    }
+
+	    ret *= factor;
+	    if (type == ZLIMTYPE_MICROSECONDS)
+		ret *= 1000000 / divisor;
+	    else
+		ret /= divisor;
+	}
+    }
+    else {
+	/*
+	 * memory-type resource
+	 */
+	if (*s) {
+	    if (*s == 'b' || *s == 'B')
+		s++;
+	    else {
+		const char *suffix = "kKmMgGtTpPeE";
+		char *offset;
+
+		if ((offset = strchr(suffix, *s))) {
+		    s++;
+		    if (*s == 'b' || *s == 'B') {
+			/* KB == 1000 */
+			const char *p;
+			for (p = suffix; p <= offset; p += 2)
+			    ret *= 1000;
+			s++;
+		    }
+		    else {
+			/* K/KiB == 1024 */
+			if ((s[0] == 'i' || s[0] == 'I') &&
+			    (s[1] == 'b' || s[1] == 'B'))
+			    s += 2;
+			ret <<= ((offset - suffix) / 2 + 1) * 10;
+		    }
+		}
+	    }
+	    if (*s) {
+		*err = "invalid unit";
+		return 0;
+	    }
+	}
+	else {
+	    if (ulimit)
+		ret *= resinfo[lim]->unit;
+	    else
+#ifdef HAVE_RLIMIT_MSGQUEUE
+		if (lim != RLIMIT_MSGQUEUE)
+		    /*
+		     * Historical quirk. In tcsh's limit (and bash's and mksh's
+		     * ulimit, but not ksh93), that limit expects bytes instead
+		     * of kibibytes and earlier versions of zsh were treating
+		     * it as a ZLIMTYPE_NUMBER.
+		     *
+		     * We still want to treat it as ZLIMTYPE_MEMORY and accept
+		     * KMG... suffixes as it is a number of bytes.
+		     *
+		     * But we carry on taking the value as a number of *bytes*
+		     * in the "limit" builtin for backward compatibility and
+		     * compatibility with tcsh.
+		     */
+#endif
+		    ret *= 1024;
+	}
     }
-# if defined(RLIM_T_IS_QUAD_T) || defined(RLIM_T_IS_LONG_LONG) || defined(RLIM_T_IS_UNSIGNED)
-    if (!base) {
-	if (*s != '0')
-	    base = 10;
-	else if (*++s == 'x' || *s == 'X')
-	    base = 16, s++;
-	else
-	    base = 8;
-    } 
-    if (base <= 10)
-	for (; *s >= '0' && *s < ('0' + base); s++)
-	    ret = ret * base + *s - '0';
-    else
-	for (; idigit(*s) || (*s >= 'a' && *s < ('a' + base - 10))
-	     || (*s >= 'A' && *s < ('A' + base - 10)); s++)
-	    ret = ret * base + (idigit(*s) ? (*s - '0') : (*s & 0x1f) + 9);
-    if (t)
-	*t = (char *)s;
-# else /* !RLIM_T_IS_QUAD_T && !RLIM_T_IS_LONG_LONG && !RLIM_T_IS_UNSIGNED */
-    ret = zstrtol(s, t, base);
-# endif /* !RLIM_T_IS_QUAD_T && !RLIM_T_IS_LONG_LONG && !RLIM_T_IS_UNSIGNED */
     return ret;
 }
 
@@ -317,25 +495,42 @@ showlimitvalue(int lim, rlim_t val)
 	       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");
+	/*
+	 * memory resource -- display with KiB/MiB... for exact
+	 * multiples of those units
+	 */
+	const char *units = "KMGTPE";
+	rlim_t v = val;
+	int n = 0;
+	while (units[n] && (v & 1023) == 0 && v >> 10) {
+	    n++;
+	    v >>= 10;
+	}
+	if (n) {
+	    char suffix[] = "XiB\n";
+	    *suffix = units[n-1];
+	    printrlim(v, suffix);
+	}
 	else
-	    printrlim(val/1024L, "kB\n");
+	    printrlim(val, "B\n");
     }
 }
 
-/* Display resource limits.  hard indicates whether `hard' or `soft'  *
- * limits should be displayed.  lim specifies the limit, or may be -1 *
- * to show all.                                                       */
+/*
+ * Display resource limits.  hard indicates whether `hard' or `soft'
+ * limits should be displayed.  lim specifies the limit, or may be -1
+ * to show all.  If `shell' is non-zero, the limits in place for the
+ * shell process retrieved with getrlimits() are shown.
+ */
 
 /**/
 static int
-showlimits(char *nam, int hard, int lim)
+showlimits(char *nam, int hard, int lim, int shell)
 {
     int rt;
+    int ret = 0;
 
-    if (lim >= RLIM_NLIMITS)
+    if (shell || lim >= RLIM_NLIMITS)
     {
 	/*
 	 * Not configured into the shell.  Ask the OS
@@ -357,17 +552,33 @@ showlimits(char *nam, int hard, int lim)
     else
     {
 	/* main loop over resource types */
-	for (rt = 0; rt != RLIM_NLIMITS; rt++)
-	    showlimitvalue(rt, (hard) ? limits[rt].rlim_max :
-			   limits[rt].rlim_cur);
+	for (rt = 0; rt != RLIM_NLIMITS; rt++) {
+	    struct rlimit vals, *plim;
+	    if (shell) {
+		plim = &vals;
+		if (getrlimit(rt, plim) < 0)
+		{
+		    zwarnnam(nam, "can't read \"%s\" limit: %e", resinfo[rt]->name, errno);
+		    ret = 1;
+		    continue;
+		} 
+	    }
+	    else
+		plim = &(limits[rt]);
+
+	    showlimitvalue(rt, (hard) ? plim->rlim_max :
+			   plim->rlim_cur);
+	}
     }
 
-    return 0;
+    return ret;
 }
 
-/* Display a resource limit, in ulimit style.  lim specifies which   *
- * limit should be displayed, and hard indicates whether the hard or *
- * soft limit should be displayed.                                   */
+/*
+ * Display a resource limit, in ulimit style.  lim specifies which
+ * limit should be displayed, and hard indicates whether the hard or
+ * soft limit should be displayed.
+ */
 
 /**/
 static int
@@ -394,12 +605,12 @@ printulimit(char *nam, int lim, int hard, int head)
 	if (lim < RLIM_NLIMITS) {
 	    const resinfo_T *info = resinfo[lim];
 	    if (info->opt == 'N')
-		printf("-N %2d: %-29s", lim, info->descr);
+		printf("-N %2d: %-32s ", lim, info->descr);
 	    else
-		printf("-%c: %-32s", info->opt, info->descr);
+		printf("-%c: %-35s ", info->opt, info->descr);
 	}
 	else
-	    printf("-N %2d: %-29s", lim, "");
+	    printf("-N %2d: %-32s ", lim, "");
     }
     /* display the limit */
     if (limit == RLIM_INFINITY)
@@ -511,16 +722,18 @@ bin_limit(char *nam, char **argv, Options ops, UNUSED(int func))
     rlim_t val;
     int ret = 0;
 
-    hard = OPT_ISSET(ops,'h');
-    if (OPT_ISSET(ops,'s') && !*argv)
+    hard = OPT_ISSET(ops, 'h');
+    if (OPT_ISSET(ops, 's') && !*argv)
 	return setlimits(NULL);
     /* without arguments, display limits */
     if (!*argv)
-	return showlimits(nam, hard, -1);
+	return showlimits(nam, hard, -1, OPT_ISSET(ops, 's'));
     while ((s = *argv++)) {
 	/* Search for the appropriate resource name.  When a name matches (i.e. *
 	 * starts with) the argument, the lim variable changes from -1 to the   *
 	 * number of the resource.  If another match is found, lim goes to -2.  */
+	char *err;
+
 	if (idigit(*s))
 	{
 	    lim = (int)zstrtol(s, NULL, 10);
@@ -543,61 +756,12 @@ 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 >= RLIM_NLIMITS)
-	{
-	    val = zstrtorlimt(s, &s, 10);
-	    if (*s)
-	    {
-		/* unknown limit, no idea how to scale */
-		zwarnnam(nam, "unknown scaling factor: %s", s);
-		return 1;
-	    }
-	}
-	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 *
-	     * the code:                                                        */
-	    val = zstrtorlimt(s, &s, 10);
-	    if (*s) {
-		if ((*s == 'h' || *s == 'H') && !s[1])
-		    val *= 3600L;
-		else if ((*s == 'm' || *s == 'M') && !s[1])
-		    val *= 60L;
-		else if (*s == ':')
-		    val = val * 60 + zstrtorlimt(s + 1, &s, 10);
-		else {
-		    zwarnnam(nam, "unknown scaling factor: %s", s);
-		    return 1;
-		}
-	    }
-	} 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;
-	    val = zstrtorlimt(t, &s, 10);
-	    if (s == t) {
-		zwarnnam(nam, "limit must be a number");
-		return 1;
-	    }
-	} else {
-	    /* memory-type resource -- `k', `M' and `G' modifiers are *
-	     * permitted, meaning (respectively) 2^10, 2^20 and 2^30. */
-	    val = zstrtorlimt(s, &s, 10);
-	    if (!*s || ((*s == 'k' || *s == 'K') && !s[1])) {
-		if (val != RLIM_INFINITY)
-		    val *= 1024L;
-	    } else if ((*s == 'M' || *s == 'm') && !s[1])
-		val *= 1024L * 1024;
-	    else if ((*s == 'G' || *s == 'g') && !s[1])
-		val *= 1024L * 1024 * 1024;
-	    else {
-		zwarnnam(nam, "unknown scaling factor: %s", s);
-		return 1;
-	    }
+	    return showlimits(nam, hard, lim, OPT_ISSET(ops, 's'));
+
+	val = zstrtorlimt(s, lim, 0, &err);
+	if (err) {
+	    zwarnnam(nam, err);
+	    return 1;
 	}
 	if (do_limit(nam, lim, val, hard, !hard, OPT_ISSET(ops, 's')))
 	    ret++;
@@ -821,14 +985,12 @@ bin_ulimit(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
 		    limit = vals.rlim_max;
 		}
 	    } else {
-		limit = zstrtorlimt(*argv, &eptr, 10);
-		if (*eptr) {
-		    zwarnnam(name, "invalid number: %s", *argv);
+		char *err;
+		limit = zstrtorlimt(*argv, res, 1, &err);
+		if (err) {
+		    zwarnnam(name, "%s: %s", *argv, err);
 		    return 1;
 		}
-		/* scale appropriately */
-		if (res < RLIM_NLIMITS)
-		    limit *= resinfo[res]->unit;
 	    }
 	    if (do_limit(name, res, limit, hard, soft, 1))
 		ret++;
diff --git a/Src/Builtins/rlimits.mdd b/Src/Builtins/rlimits.mdd
index 06c9e9c7f..248a03e61 100644
--- a/Src/Builtins/rlimits.mdd
+++ b/Src/Builtins/rlimits.mdd
@@ -3,6 +3,12 @@ link=either
 load=yes
 
 autofeatures="b:limit b:ulimit b:unlimit"
-autofeatures_emu="b:ulimit"
+
+# limit is the csh builtin, while ulimit is the ksh/posix one.
+# Autoloading ulimit in csh emulation should be relatively
+# harmless as "ulimit" contrary to "limit" is not otherwise
+# a common English word. So we're only accomodating sh/ksh
+# emulations.
+autofeatures_posix="b:ulimit"
 
 objects="rlimits.o"
diff --git a/Src/mkbltnmlst.sh b/Src/mkbltnmlst.sh
index c4611d8b3..7ebc2a751 100644
--- a/Src/mkbltnmlst.sh
+++ b/Src/mkbltnmlst.sh
@@ -37,10 +37,10 @@ for x_mod in $x_mods; do
         echo "/* non-linked-in known module \`$x_mod' */"
 	linked=no
     esac
-    unset moddeps autofeatures autofeatures_emu
+    unset moddeps autofeatures autofeatures_posix
     . $srcdir/../$modfile
     if test "x$autofeatures" != x; then
-        if test "x$autofeatures_emu" != x; then
+        if test "x$autofeatures_posix" != x; then
             echo "  {"
 	    echo "    char *zsh_features[] = { "
 	    for feature in $autofeatures; do
@@ -48,14 +48,14 @@ for x_mod in $x_mods; do
 	    done
 	    echo "      NULL"
 	    echo "    }; "
-	    echo "    char *emu_features[] = { "
-	    for feature in $autofeatures_emu; do
+	    echo "    char *posix_features[] = { "
+	    for feature in $autofeatures_posix; do
 		echo "      \"$feature\","
 	    done
 	    echo "      NULL"
 	    echo "    }; "
 	    echo "    autofeatures(\"zsh\", \"$x_mod\","
-	    echo "       EMULATION(EMULATE_ZSH) ? zsh_features : emu_features,"
+	    echo "       EMULATION(EMULATE_KSH|EMULATE_SH) ? posix_features : zsh_features,"
 	    echo "       0, 1);"
 	    echo "  }"
         else
diff --git a/Test/B12limit.ztst b/Test/B12limit.ztst
index 48d33e6e3..3665279fa 100644
--- a/Test/B12limit.ztst
+++ b/Test/B12limit.ztst
@@ -26,3 +26,132 @@ F:report this to zsh-workers mailing list.
   }
 0:check if limit option letters are unique
 
+  if sh -c 'ulimit 2048' > /dev/null 2>&1; then
+    (
+      set -o braceccl -o pipefail
+      list=(1b 1{kmgtpe}{,b,ib})
+      for cmd in "limit filesize" ulimit; do
+	for l in $list $list:u; do
+	  $=cmd $l &&
+	    limit filesize &&
+	    ulimit || exit
+	done
+      done | sed 'N;s/\n/ /;s/  */ /g'
+    )
+  else
+    ZTST_skip='Cannot set the filesize limit on this system'
+  fi
+0:filesize suffixes with limit and ulimit
+>filesize 1B 0
+>filesize 1EiB 2251799813685248
+>filesize 976562500000000KiB 1953125000000000
+>filesize 1EiB 2251799813685248
+>filesize 1GiB 2097152
+>filesize 1000000000B 1953125
+>filesize 1GiB 2097152
+>filesize 1KiB 2
+>filesize 1000B 1
+>filesize 1KiB 2
+>filesize 1MiB 2048
+>filesize 1000000B 1953
+>filesize 1MiB 2048
+>filesize 1PiB 2199023255552
+>filesize 976562500000KiB 1953125000000
+>filesize 1PiB 2199023255552
+>filesize 1TiB 2147483648
+>filesize 976562500KiB 1953125000
+>filesize 1TiB 2147483648
+>filesize 1B 0
+>filesize 1EiB 2251799813685248
+>filesize 976562500000000KiB 1953125000000000
+>filesize 1EiB 2251799813685248
+>filesize 1GiB 2097152
+>filesize 1000000000B 1953125
+>filesize 1GiB 2097152
+>filesize 1KiB 2
+>filesize 1000B 1
+>filesize 1KiB 2
+>filesize 1MiB 2048
+>filesize 1000000B 1953
+>filesize 1MiB 2048
+>filesize 1PiB 2199023255552
+>filesize 976562500000KiB 1953125000000
+>filesize 1PiB 2199023255552
+>filesize 1TiB 2147483648
+>filesize 976562500KiB 1953125000
+>filesize 1TiB 2147483648
+>filesize 1B 0
+>filesize 1EiB 2251799813685248
+>filesize 976562500000000KiB 1953125000000000
+>filesize 1EiB 2251799813685248
+>filesize 1GiB 2097152
+>filesize 1000000000B 1953125
+>filesize 1GiB 2097152
+>filesize 1KiB 2
+>filesize 1000B 1
+>filesize 1KiB 2
+>filesize 1MiB 2048
+>filesize 1000000B 1953
+>filesize 1MiB 2048
+>filesize 1PiB 2199023255552
+>filesize 976562500000KiB 1953125000000
+>filesize 1PiB 2199023255552
+>filesize 1TiB 2147483648
+>filesize 976562500KiB 1953125000
+>filesize 1TiB 2147483648
+>filesize 1B 0
+>filesize 1EiB 2251799813685248
+>filesize 976562500000000KiB 1953125000000000
+>filesize 1EiB 2251799813685248
+>filesize 1GiB 2097152
+>filesize 1000000000B 1953125
+>filesize 1GiB 2097152
+>filesize 1KiB 2
+>filesize 1000B 1
+>filesize 1KiB 2
+>filesize 1MiB 2048
+>filesize 1000000B 1953
+>filesize 1MiB 2048
+>filesize 1PiB 2199023255552
+>filesize 976562500000KiB 1953125000000
+>filesize 1PiB 2199023255552
+>filesize 1TiB 2147483648
+>filesize 976562500KiB 1953125000
+>filesize 1TiB 2147483648
+
+  if sh -c 'ulimit -t 3600' > /dev/null 2>&1; then
+  (
+    set -o pipefail
+    list=(1h 30m 20s 30 1:23:45.123456 2:23 56.4)
+    for cmd in "limit cputime" "ulimit -t"; do
+      for l in $list ${(MU)list:#*[a-z]*}; do
+        $=cmd $l &&
+	  limit cputime &&
+	  ulimit -t || exit
+      done
+    done | sed 'N;s/\n/ /;s/  */ /g'
+  )
+  else
+    ZTST_skip='Cannot set the cputime limit on this system'
+  fi
+0:time limit formats
+>cputime 1:00:00 3600
+>cputime 0:30:00 1800
+>cputime 0:00:20 20
+>cputime 0:00:30 30
+>cputime 1:23:45 5025
+>cputime 0:02:23 143
+>cputime 0:00:56 56
+>cputime 1:00:00 3600
+>cputime 0:30:00 1800
+>cputime 0:00:20 20
+>cputime 1:00:00 3600
+>cputime 0:30:00 1800
+>cputime 0:00:20 20
+>cputime 0:00:30 30
+>cputime 1:23:45 5025
+>cputime 0:02:23 143
+>cputime 0:00:56 56
+>cputime 1:00:00 3600
+>cputime 0:30:00 1800
+>cputime 0:00:20 20
-- 
2.25.1





Messages sorted by: Reverse Date, Date, Thread, Author