💾 Archived View for thrig.me › blog › 2023 › 06 › 27 › errno.gmi captured on 2024-07-09 at 01:06:25. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-11-14)
-=-=-=-=-=-=-
errno is a global variable; anything can change it within a process. errno may also remain unchanged across who knows how much code, until it emerges with some non-zero value. This can be surprising, especially as the abstractions pile up and errno emerges from who knows where.
https://www.youtube.com/watch?v=n05Dmwy7JX8
First up, errno is copied through fork, so might be non-zero due to being set in a parent process:
$ perl -E '$! = 42; fork // die "argh?"; say 0 + $!' 42 42 $ cfu 'errno = 42; fork(); printf("%d\n", errno)' 42 42
It is typical to zero errno before an important call, e.g. before strtol(3) so that ERANGE or whatever is not carried over from who knows where.
char *ep; long n; errno = 0; n = strtol(buf, &ep, 10); ...
In higher level languages errno can leak through unexpectedly; one problem here is to incorrectly use errno without there being an actual failure:
$ cat failrrno #!/usr/bin/perl open my $fh, '/etc/passwd'; if ($!) { print "open failed: $!\n"; } print scalar readline $fh; $ perl failrrno open failed: Inappropriate ioctl for device root:*:0:0:Charlie &:/root:/bin/ksh $ errno 25 ENOTTY 25 Inappropriate ioctl for device
errno was set to 25 due to some call; the open did not actually fail. Less bad code would run along the lines of the following, which only uses $! (errno) when open actually fails:
my $file = '/etc/passwd'; open my $fh, $file or die "open failed '$file': $!\n";
Where did the 25 come from? ktrace(1) may show this,
$ ktrace perl failrrno ... $ kdump | grep -3 'errno 25' 89865 perl CALL kbind(0x7c4ce01716f8,24,0xbc5a98c260e13bfc) 89865 perl RET kbind 0 89865 perl CALL fcntl(3,F_ISATTY) 89865 perl RET fcntl -1 errno 25 Inappropriate ioctl for device 89865 perl CALL lseek(3,0,SEEK_CUR) 89865 perl RET lseek 0 89865 perl CALL kbind(0x7c4ce01718a8,24,0xbc5a98c260e13bfc) -- 89865 perl NAMI "/etc/passwd" 89865 perl RET open 3 89865 perl CALL fcntl(3,F_ISATTY) 89865 perl RET fcntl -1 errno 25 Inappropriate ioctl for device 89865 perl CALL lseek(3,0,SEEK_CUR) 89865 perl RET lseek 0 89865 perl CALL fstat(3,0x7c4ce0171800)
so perl is automatically doing a fcntl call on the file descriptor that fails and sets errno ($!) as part of normal operation. Higher level languages tend to have features like this. Here's some other late 1980s dynamic scripting languages:
$ cat failrrno.pie x = open('/etc/passwd', 'r') $ ktrace python3 failrrno.pie $ kdump | grep errno\ 25 99601 python3.10 RET fcntl -1 errno 25 Inappropriate ioctl for device 99601 python3.10 RET fcntl -1 errno 25 Inappropriate ioctl for device 99601 python3.10 RET fcntl -1 errno 25 Inappropriate ioctl for device 99601 python3.10 RET fcntl -1 errno 25 Inappropriate ioctl for device 99601 python3.10 RET fcntl -1 errno 25 Inappropriate ioctl for device 99601 python3.10 RET fcntl -1 errno 25 Inappropriate ioctl for device 99601 python3.10 RET fcntl -1 errno 25 Inappropriate ioctl for device 99601 python3.10 RET fcntl -1 errno 25 Inappropriate ioctl for device 99601 python3.10 RET fcntl -1 errno 25 Inappropriate ioctl for device 99601 python3.10 RET fcntl -1 errno 25 Inappropriate ioctl for device 99601 python3.10 RET fcntl -1 errno 25 Inappropriate ioctl for device 99601 python3.10 RET fcntl -1 errno 25 Inappropriate ioctl for device 99601 python3.10 RET fcntl -1 errno 25 Inappropriate ioctl for device 99601 python3.10 RET fcntl -1 errno 25 Inappropriate ioctl for device 99601 python3.10 RET fcntl -1 errno 25 Inappropriate ioctl for device 99601 python3.10 RET fcntl -1 errno 25 Inappropriate ioctl for device 99601 python3.10 RET fcntl -1 errno 25 Inappropriate ioctl for device $ cat failrrno.tcl #!/usr/local/bin/tclsh8.6 set fh [open /etc/passwd] $ ktrace tclsh8.6 failrrno.tcl $ kdump | grep errno\ 25 48902 tclsh8.6 RET fcntl -1 errno 25 Inappropriate ioctl for device 48902 tclsh8.6 RET fcntl -1 errno 25 Inappropriate ioctl for device 48902 tclsh8.6 RET fcntl -1 errno 25 Inappropriate ioctl for device
LISP on unix will set errno variously. sbcl at least does not have the fcntl thing like the above languages do.
$ cat open.lisp (with-open-file (fh "/etc/passwd") (format t "~a~&" (read-line fh nil))) $ ktrace /usr/local/bin/sbcl --script open.lisp root:*:0:0:Charlie &:/root:/bin/ksh $ kdump | grep errno\ 25 $ kdump | grep "errno " 84999 sbcl RET sigaltstack -1 errno 22 Invalid argument 84999 sbcl RET stat -1 errno 2 No such file or directory 84999 sbcl RET lstat -1 errno 2 No such file or directory 84999 sbcl RET sigaltstack -1 errno 22 Invalid argument
I said that "ktrace may show" errno being set. errno could however be set by anything anywhere, which ktrace may not see. In this case you'll probably need to use a debugger and to scour the code to see where errno is being set wrongly. Process tracing is however a good first step as most often it's some traceable function call that sets errno.
errno can be masked when a function makes multiple calls that may each set errno. In this case usually only the last error may be available, and despite there being errors the function may return a success. Most any function that does a PATH or LD_LIBRARY_PATH search will have this issue.
$ ktrace sysclean /usr/local/sbin/sysclean: error: need root privileges $ kdump | egrep 'RET *execve' 32620 ktrace RET execve -1 errno 2 No such file or directory 32620 ktrace RET execve -1 errno 2 No such file or directory 32620 ktrace RET execve -1 errno 2 No such file or directory 32620 ktrace RET execve -1 errno 2 No such file or directory 32620 ktrace RET execve -1 errno 2 No such file or directory 32620 ktrace RET execve -1 errno 2 No such file or directory 32620 ktrace RET execve -1 errno 2 No such file or directory 32620 ktrace RET execve -1 errno 2 No such file or directory 32620 ktrace RET execve -1 errno 2 No such file or directory 32620 ktrace RET execve -1 errno 2 No such file or directory 32620 perl RET execve JUSTRETURN
The multiple failures here are the PATH search looking for "sysclean" in each directory in PATH; "sysclean" just so happens to be in the last of the PATH directories, so there were a bunch of failures before it was found. All the errors are ignored. One or more of these errors might be important if a library cannot be loaded due to a permissions problem, but then the code finds the wrong version of that library in a subsequent directory: the correct library will be ignored, and the wrong one loaded with success. Process tracing will again help to show what is actually going on.
tags #unix