The bug is that an #if condition may be mis-evaluated if it immediately follows a use of a function-like macro. I also modified a couple other places that could possibly trigger the bug (including a possibly more reliable fix for the instance of this problem I worked around earlier in include/xatonum.h).
We do this by maintaining a mask (for each pid) giving the fds to be closed on exec. We wrap functions that close fds so that their close-on-exec bits can be cleared at that point.
This implementation may close the fds even if the execve operation ultimately fails.
Also, fix issue where callers of getopt32() weren't properly detecting errors on GNO due to a size mismatch.
This avoids strange behavior when commands using getopt32 (like export) are invoked multiple times, sometimes with invalid arguments.
This avoids problems stemming from the fact that GNO's environment implementation may deallocate those strings, in some cases before we're done with them. It also allows us to uppercase the variable names, although since we made them case-insensitive this only matters for display purposes (in "set" output).
This avoids a problem where one of the SIGALRM signals could be delivered after the original SIGALRM handler was restored, which would normally cause the process to terminate.
For now, we just rely on polling in a loop to determine when the child is done. This isn't optimal, but should be OK. If procsend() worked, we could use that in the child to signal the parent. However, procsend() is broken in GNO 2.0.6 -- it seems that it actually tries to invoke the send() call for sockets, but with the wrong arguments, leading to a crash.
This should allow most typical cases of arguments with spaces to work correctly, although it will still break down in some cases. We can't do this perfectly, because we're ultimately dependent on the argument parsing code in the target application, and at least the code generated by ORCA/C doesn't give us a way to make an arbitrary string be treated as a single argument.
*Most significantly, we avoid using setpgid(), because it doesn't work and in fact corrupts the kernel's process group table.
*Also, work around tctpgrp() returning garbage instead of 0 on success.
This adds an implementation of tcsetpgrp that works by reading the process tables to find a process in the appropriate group. This isn't used for the main job control operations, though, since it might be relatively slow.
At this point, basic job control seems to work.
* Push/pop environment to make sure it is isolated from our parents and children.
* Make all environment vars (and shell vars) case-insensitive, consistent with GNO's internal handling of environment vars.
* Wrap putenv and unsetenv to make sure they are called with lower-case variable names, which is necessary to maintain consistency between the environ array and the kernel's internal representation of variables.
The version in libc both matches things it shouldn't and doesn't match things it should, in (at least) cases where a pattern includes additional stuff beyond a *.
This should avoid strange behavior due to races when the parent has resumed but the child is still running the exec* code in libc, which mainly manifests itself when running at low speed.
We also change to signaling the child's completion with SIGALRM, and setting an extra alarm in the parent in case the child doesn't actually do it.
GNO's _exit (contrary to its man page) does clean-up for stuff like the memory allocator, which is inappropriate in a forked child process and leads to hangs and crashes.
*Use .null instead of /dev/null
*Account for GNO's dup2(), which non-standardly returns 0 on success
*Always call open with appropriate number of arguments
*Use STDIN_FILENO instead of (implicitly) 0
In particular, it will buffer information about children other than the one being waited for, so it isn't lost. It can also emulate a non-blocking wait by arranging to interrupt the wait with a signal.
This involved breaking things up into more segments in debug mode, since the code is larger. I also had to remove some unused extern definitions, which were causing link errors when debug code was enabled.
To enable debug code, pass "DEBUG=1" to make or build.gs.
At this point, hush can successfully build and run, although it doesn't work correctly.
The main Makefile is now dedicated to the GNO build. There's now a separate makefile for building on modern systems with GCC/Clang.
This involved using signal() rather than sigaction(), which GNO doesn't have. signal() doesn't have the signal-blocking feature of sigaction(), but I've tried to emulate it.
This is a BSD-licensed poll() implementation by Brian M. Clapper. Its performance characteristics aren't as good as a "native" poll() would be, but this shouldn't be a problem in practice.
Dmake gets confused if you use over-15-character file names, so they needed to be shortened to get it to work. While I was at it, I also switched _ to . in the filenames so they're fully ProDOS compatible.
The shell/hush_test directory (not needed to build the code) still uses ProDOS-incompatible file names.