Sysexits – preferable exit codes for programs
freebsd.org
freebsd.org
It would say it's certainly better to say nothing than to imply a scheme is some sort of standard when it's really a largely unusable not-at-all a standard.
Every time I try to use them, sysexits feels like a strictly less usable and relevant version of HTTP categories 400 and 500 smashed together with no rhyme or reason.
sysexit(3) defines a contract that calling programs would find useful to rely upon — to do different things when programs exit with different particular values. But because of these human factors, calling programs cannot rely on it. There's no signal in the noise. The contract is not enforced.
This goes from just useless to actively harmful, because the process of 1. finding the sysexit code standard, 2. thinking you can rely on it; 3. writing a program that does rely on it; 4. finding out that in practice its contract is rarely obeyed; and 5. ripping that code back out of your program — all takes time and human labor. The existence of this "standard" thus creates net-negative utility. It consumes people's time to no useful end.
If you don't document how to use your program, that's another problem and the nobody will be able to use it anyway.
2xx - Fine, you get to decide what to do
4xx - Problem on your end, mate
5xx - Problem on our end. Sorry, mate
3xx - It's complicated...
GraphQL responding 200 to erroneous queries is... surprising (assuming only HTTP semantics), but what kind of errors were these?Of course, we're assuming servers implemented by at least semi-reasonable people.
EDIT: That said, I'd actually be happy with a protocol-level 2, 4, 5, 3 and leaving the rest up the application layer. I know that isn't very HATEOAS, but in practice all people truly want is RPC. (Sadly.)
To me, both GraphQL (and also JSON-RPC, another weird one for HTTP errors) are really application-layer protocols of their own that are just "spoken over" HTTP, and as such implement their own error signalling standards. In cases like that, an HTTP error doesn't pertain to anything being discussed "inside" the overlay protocol; but instead refers to the carrier protocol.
For example, seeing an HTTP 404 in response to hitting a GraphQL or JSON-RPC endpoint, wouldn't mean that the resource you asked for with your overlay-protocol request doesn't exist, but rather that the endpoint you wanted to speak the overlay protocol to doesn't exist.
Consider, by analogy: a websocket, which actually does speak a formal HTTP sub-protocol. If you make the right request, you get your HTTP connection upgraded into a WebSocket connection. If you don't? Carrier protocol error.
* JSON-RPC has no dependency on HTTP, you can use it over regular sockets or websocket (that's exactly what the Chrome DevTools Protocol does). That is why JSON-RPC has a concept of "request ids", similar to protocols like IMAP: it was designed for compatibility with full-duplex communications and asynchronous results (rather than strict request/response).
* GraphQL's semantics are not strictly fitting within HTTP, what with being able to send multiple queries in the same request (which is a different proposition than sending concurrent requests, even when pipelining, when it comes to server resources).
And in further fairness, I believe there can be multiple 'responses' within, some of which may be errors and others not.
But still ugh, personally I'd rather have resource-oriented semantic HTTP (read: 'REST' without triggering empassioned debate) just for that. I don't think adds enough over HTTP to warrant this sort of weirdness (sibling comment describes it as the next layer) - if it were just an HTTP alternative sitting on TCP fine, and I'm sure it would happily not have a top-level status code (or a more meaningful one) itself anyway.
Fun thing to learn about: WebDAV introduced the HTTP 207 "Multi-Status" response status, for precisely the case where the response is actually a multi-response envelope. https://httpstatuses.com/207
I often feel that the extra methods and features introduced by WebDAV would be usefully generalized to other analogous use-cases, like GraphQL. But then I recall that most of the WebDAV-specific stuff specifies XML-based request/response message formats, and quickly give up on the idea of any modern programmer doing it as a cool hack. Can't have "cool hack" and "XML" in the same sentence, after all ;)
For actually digging into the "anything else" and fixing it (if you even have the privileges on the offending system) — well, that's what error messages are for.
The lowest 3 bits of the status value is used to denote severity (0:warning, 1:success, 2:error, 3:info, 4:severe/fatal).
In addition to that, each "facility" (either system or user service/application) defines its set of status values and corresp. messages, which can be incorporated for use with system message facilty. So that on a program exit a user can query the resulting status/message to get an explanation and possible remedies, if applicable (HELP /MESSSAGE $STATUS).
C requires exit(0) to indicate a successful status. The OpenVMS C library translates that to a status of 1.
A lot of C code uses exit(1) to indicate failure, but it indicates success in OpenVMS. (Use EXIT_FAILURE, a macro defined in <stdlib.h>, instead.)
...EXIT_SUCCESS too. This at least would provide a basic portability.
Only using 0 and 1, period, and not differentiating between the situations 1 and 2 is a bit too sloppy though.
If I was to advise someone I'd tell o interpret and write programs using the following rule of thumb and not to worry too much:
0 = success / true
1 = failure (read stderr) / false
>1 = failure (read stderr & docs)
- 2 is "misuse of shell built-ins" for bash [1] - Python's argparse uses 2 [2]
I feel like we looked at more but can't remember which all they were
[0] https://github.com/clap-rs/clap/pull/1637#issuecomment-58103...
[1] https://tldp.org/LDP/abs/html/exitcodes.html
[2] https://github.com/python/cpython/blob/main/Lib/argparse.py#...
$ ruby -e 'raise StandardError.new "oops"'
Traceback (most recent call last):
-e:1:in `<main>': oops (StandardError)
$ echo $?
1
And so will this one: $ ruby -e 'invalid method call'
Traceback (most recent call last):
-e:1:in `<main>': undefined local variable or method `call' for main:Object (NameError)
Did you mean? caller
So, it's nice to distinguish between a possibly unexpected exceptional exit (1), and an "error case detected by the programmer", which is what I use exit (2) for.https://refspecs.linuxbase.org/LSB_5.0.0/LSB-Core-generic/LS...
That way automated CI pipelines (or whatever) can switch on the return code if needed rather than try to parse stderr
0, OK
1, Error
2, Bad arguments
However I'm not surprised that requirements evolved and somebody needs more structured and informative exit codes.
> The author of this document proposes restricting user-defined exit codes to the range 64 - 113 (in addition to 0, for success), to conform with the C/C++ standard. This would allot 50 valid codes, and make troubleshooting scripts more straightforward.[2]
> ...
> [2] An update of /usr/include/sysexits.h allocates previously unused exit codes from 64 - 78. It may be anticipated that the range of unallotted exit codes will be further restricted in the future. The author of this document will not do fixups on the scripting examples to conform to the changing standard. This should not cause any problems, since there is no overlap or conflict in usage of exit codes between compiled C/C++ binaries and shell scripts.
The C and C++ standards don't say anything that suggests reserving the range 64-113.
Here's what the C standard says:
> If the value of status is zero or EXIT_SUCCESS, an implementation-defined form of the status successful termination is returned. If the value of status is EXIT_FAILURE, an implementation-defined form of the status unsuccessful termination is returned. Otherwise the status returned is implementation-defined.
C++ inherits C's specification.
EXIT_SUCCESS is (almost?) always defined as 0. EXIT_FAILURE is usually defined as 1 (but not on OpenVMS, where the OS defines 1 as a form of success).
The restriction to 8-bit values is POSIX, not C or C++.
Knowing that these exist has, in my experience, conferred nearly "magical" powers of diagnosis w/ Windows system-related errors.
C:\> perl -MWin32 -E "say Win32::FormatMessage(1303)"
No encryption key is available. A well-known encryption key was returned.
You can put that in a batch file: C:\> type %USERPROFILE%\bin\wm.bat
@perl -MWin32 -E "$e = shift; say defined($e) ? Win32::FormatMessage($e) : 'Need exit code'" %*
so it's always available: C:\> wm 2450
The user accounts database is not configured correctly.
I am not sure what the right way to do this in PowerShell[1] is, but this batch script has served me well for almost two decades now.[1]: https://geekeefy.wordpress.com/2016/06/14/powershell-import-...
$ err 2450
# for decimal 2450 / hex 0x992
NERR_BadUasConfig lmerr.h
# /* The user accounts database is not configured correctly.
# */
# No results found for hex 0x2450 / decimal 9296
# 1 matches found for "2450"
[0] https://www.microsoft.com/en-us/download/details.aspx?id=100...My little one liner has the advantage of having worked for me since Windows 98 days (OK, Perl did not have `say` then), but this tool provides more clues:
C:\> Err_6.4.5.exe 343
# for decimal 343 / hex 0x157
KERNEL_THREAD_PRIORITY_FLOOR_VIOLATION bugcodes.h
ERROR_EXTERNAL_BACKING_PROVIDER_UNKNOWN winerror.h
# The external backing provider is not recognized.
# for hex 0x343 / decimal 835
ERROR_INVALID_SERVER_CERT raserror.h
# as an HRESULT: Severity: SUCCESS (0), FACILITY_NULL (0x0), Code 0x157
# for decimal 343 / hex 0x157
ERROR_EXTERNAL_BACKING_PROVIDER_UNKNOWN winerror.h
# The external backing provider is not recognized.
# 4 matches found for "343"
versus C:\> wm 343
The external backing provider is not recognized.I'd prefer some mechanism for making a cross-program shortened stack trace indicating the specific error (ie. 'no space on device') and the sequence of things in progress that have now all failed (ie. 'cannot create output file', 'cannot decompress files', 'cannot install XYZ package', 'cannot update system').
Then it's very clear to the user - The system can't be updated because XYZ package couldn't be installed, because files couldn't be decompressed, because the output file couldn't be created, because there is no space left.
Obviously a single integer is not going to be a rich error-reporting mechanism, but particularly in the context of something like a process manager or job runner, it could be helpful to know what kinds of errors might go away on a retry, for example.
Instead just use paging, pushing out cache, or memory compression to have a soft limit to ensure you never need to handle out of memory.
In today's world of delayed page allocation, you'll probably just get killed with SIGBUS anyway.
So, exit(256) means exit code 0 on Linux and Mac, but 256 on Windows.
[1] https://doc.rust-lang.org/std/process/fn.exit.html#platform-...
I got a laugh out of how this assumes that checking the command's documentation isn't an option. If you want to know about program's behavior, you're going to be looking at the source code.
What fraction of apps actually use sysexits.h? 0.1%? 0.01%? If an application fails and exits with some >1 error code, it would be insane to assume it's following this convention.
Hopefully the command's documentation describes the error codes (either referencing sysexits or explicitly listing the non-standard exit codes), and if it doesn't, you'll be stuck looking at the source code anyway.
Most (recent) open source projects don't even come close. So you can redirect your laughs to one of the other "open source" projects.
-- https://pkg.go.dev/git.lukeshu.com/go/libsystemd@v0.5.3/sd_d...
1. Find myself writing another BASH script in the future
2. Remember this article, go find it
3. Observe that my particular error state does not naturally fall in any of the categories
Why do I expect this? Because that's what always happens, perhaps?
In Linux, there's "msgsend". In Windows there's PostMessage. In QNX there's MsgSend. It's not 1990 any more.
Interestingly enough, it claims that the file first appeared in 4.0BSD as opposed to “somewhere after 4.3BSD”, like the FreeBSD one does.
> The <sysexits.h> header appeared somewhere after 4.3BSD.
> The manual page for it appeared in NetBSD 4.0.
Also it does not seem to be in any of the 4.3 man page archives on the freebsd man page website.