Re: dash 0.5.11.2, busybox sh 1.32.0, FreeBSD 12.2 sh: spring TTOU but should not i think
On 23/12/2020 20:18, Harald van Dijk wrote: On 21/12/2020 16:24, Jilles Tjoelker wrote: Tradition is for job control shells to be a process group leader, but I don't really know why. Changing this will not fix the issue entirely anyway since the shell must perform tcsetpgrp() from the background when a foreground job has terminated. [...] This should, in my opinion, *only* happen for interactive shells, not for job-control-enabled non-interactive shells. [...] Consider also an extra difficulty arising from this process group switching: : | dash -m When a job-control-enabled shell terminates, or when job control is disabled via set +m, it attempts to re-join its initial process group and set that as the foreground process group. This can fail if the process group has ceased to exist after the shell left it, as it does here, resulting in: $ : | dash -m dash: 0: Cannot set tty process group (No such process) In theory, because of PID reuse, this may even result in some random unrelated process group temporarily becoming the foreground process group. I am leaning towards saying that once the shell has committed to becoming a process group leader, it should remain one. By basing this on the shell being interactive rather than on job control being enabled, this is easier to handle, as as far as POSIX is concerned "interactive" is a property that cannot be changed once the shell has started: set -i and set +i are extensions not required by POSIX, and if they are nonetheless supported it is easy to defend them not being fully equivalent to specifying or leaving out -i on the shell invocation. Cheers, Harald van Dijk
Re: dash 0.5.11.2, busybox sh 1.32.0, FreeBSD 12.2 sh: spring TTOU but should not i think
Jilles Tjoelker wrote: > > This seems a good approach, but certain writes to the terminal may need > the same treatment, for example the error message when fork fails for > the second and further elements of a pipeline. This also makes me wonder > why SIGTTOU is ignored at all by default. I think the approach of blocking all signals should be able to resolve this too if anyone cares enough about this case. Thanks, -- Email: Herbert Xu Home Page: http://gondor.apana.org.au/~herbert/ PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
Re: dash 0.5.11.2, busybox sh 1.32.0, FreeBSD 12.2 sh: spring TTOU but should not i think
On Wed, Dec 23, 2020 at 08:18:24PM +, Harald van Dijk wrote: > On 21/12/2020 16:24, Jilles Tjoelker wrote: > > Also, simply entering the command > > trap "echo TTOU" TTOU > > in an interactive shell makes it behave strangely. Created jobs > > immediately stop, "fg" works once but after that the shell tends to get > > stuck quickly. > Good catch, there is some work to be done there too. > > This seems a good approach, but certain writes to the terminal may need > > the same treatment, for example the error message when fork fails for > > the second and further elements of a pipeline. This also makes me wonder > > why SIGTTOU is ignored at all by default. > True. This is hard to create a reliable test case for, but there is only > limited code that can get executed while a job-control-enabled shell may not > be in the foreground process group. An rlimit (ulimit -u) will cause fork to fail after a number of processes, but will not work reliably if other code is executing concurrently with the same UID. > If fork fails halfway through a pipeline, it may also be a problem that some > of the commands in the pipeline may still run. This can be handled much like the case where an element in the pipeline fails immediately such as because a utility cannot be found. I am not sure how well this currently works. > > In mksh, the issue is resolved slightly differently: setting a trap on > > TTOU pretends to work but the signal disposition remains set to ignored. > zsh also rejects traps on TTOU, but does so explicitly: > zsh: can't trap SIGTTOU in interactive shells > Amusingly, it prints this in any shell where job control is enabled, > regardless of whether the shell is interactive. The rejection makes sense for any shell instance that has job control and uses tcsetpgrp(), whether interactive or not. I am not entirely happy with the rejection idea, though, since the check can be bypassed by temporarily disabling job control: set +m; trap 'echo TTOU' TTOU; set -m and running external utilities then fails with: zsh: can't set tty pgrp: interrupt > > Tradition is for job control shells to be a process group leader, but I > > don't really know why. Changing this will not fix the issue entirely > > anyway since the shell must perform tcsetpgrp() from the background when > > a foreground job has terminated. > I've been thinking more about this, and I suspect it's a another conflation > between interactive mode and job control. In an interactive shell that's not > executing any external program, you want any Ctrl-C to only send SIGINT to > that shell, not to any other process. In order for that to work, it needs to > be its own process group. > This should, in my opinion, *only* happen for interactive shells, not for > job-control-enabled non-interactive shells. Consider > sh -c 'sh -mc "while :; do :; done"; echo bye' > The behaviour I would want is that Ctrl-C kills the parent shell, so that > this does not print "bye". Different shells behave differently. I think the main effect of the -m option is that it places jobs in separate process groups, which may also be useful in scripts. After something like: set -m foo & foopid=$! set +m it is possible to kill -- "-$foopid" . Although non-portable kernel features such as cgroups (Linux) or reaper (FreeBSD) might be more useful for tracking processes like this, the work to define how to use them in a shell has not been done. In FreeBSD sh, I extended the possibilities here by allowing job control without an accessible tty in non-interactive mode. This applies both if there is no controlling terminal at all and if the shell is in the background. In the example sh -c 'sh -mc "while :; do :; done"; echo bye' the -m flag seems to have no effect since that shell only runs builtins. Changing it to sh -mc 'sh -c "while :; do :; done"; echo bye' the desired Ctrl+C behaviour can still work because the outer shell exits when it notices the inner shell has terminated because of SIGINT. -- Jilles Tjoelker
Re: dash 0.5.11.2, busybox sh 1.32.0, FreeBSD 12.2 sh: spring TTOU but should not i think
On 21/12/2020 16:24, Jilles Tjoelker wrote: Also, simply entering the command trap "echo TTOU" TTOU in an interactive shell makes it behave strangely. Created jobs immediately stop, "fg" works once but after that the shell tends to get stuck quickly. Good catch, there is some work to be done there too. This seems a good approach, but certain writes to the terminal may need the same treatment, for example the error message when fork fails for the second and further elements of a pipeline. This also makes me wonder why SIGTTOU is ignored at all by default. True. This is hard to create a reliable test case for, but there is only limited code that can get executed while a job-control-enabled shell may not be in the foreground process group. If fork fails halfway through a pipeline, it may also be a problem that some of the commands in the pipeline may still run. In mksh, the issue is resolved slightly differently: setting a trap on TTOU pretends to work but the signal disposition remains set to ignored. zsh also rejects traps on TTOU, but does so explicitly: zsh: can't trap SIGTTOU in interactive shells Amusingly, it prints this in any shell where job control is enabled, regardless of whether the shell is interactive. Tradition is for job control shells to be a process group leader, but I don't really know why. Changing this will not fix the issue entirely anyway since the shell must perform tcsetpgrp() from the background when a foreground job has terminated. I've been thinking more about this, and I suspect it's a another conflation between interactive mode and job control. In an interactive shell that's not executing any external program, you want any Ctrl-C to only send SIGINT to that shell, not to any other process. In order for that to work, it needs to be its own process group. This should, in my opinion, *only* happen for interactive shells, not for job-control-enabled non-interactive shells. Consider sh -c 'sh -mc "while :; do :; done"; echo bye' The behaviour I would want is that Ctrl-C kills the parent shell, so that this does not print "bye". Different shells behave differently. What is definitely required, though, is that the shell not read input or alter terminal settings if it is started in the background (possibly unless the script explicitly ignored SIGTTOU). This is what the loop with tcgetpgrp() does. Definitely. Cheers, Harald van Dijk
Re: dash 0.5.11.2, busybox sh 1.32.0, FreeBSD 12.2 sh: spring TTOU but should not i think
Jilles Tjoelker wrote in <20201221162442.ga26...@stack.nl>: |On Sat, Dec 19, 2020 at 11:52:31PM +, Harald van Dijk wrote: |> On 19/12/2020 22:21, Steffen Nurpmeso wrote: |>> Steffen Nurpmeso wrote in |>> <20201219172838.1b-wb%stef...@sdaoden.eu>: |>>|Long story short, after falsely accusing BSD make of not working | |>> After dinner i shortened it a bit more, and attach it again, ok? |>> It is terrible, but now less redundant than before. |>> Sorry for being so terse, that problem crosses my head for about |>> a week, and i was totally mislead and if you bang your head |>> against the wall so many hours bugs or misbehaviours in a handful |>> of other programs is not the expected outcome. | |> I think a minimal test case is simply | |> all: |> $(SHELL) -c 'trap "echo TTOU" TTOU; set -m; echo all good' | |> unless I accidentally oversimplified. | |Yes, and it can be simplified a bit more to remove make from the |picture: |(SHELL -c 'trap "echo TTOU" TTOU; set -m; echo all good'; :) Cool, .. but it all seems to depend on the outer shell #$ bash -c "( /bin/dash -c 'trap echo\ TTOU TTOU; set -m; echo all good' )" /bin/dash: 1: set: Cannot set tty process group (Interrupted system call) #$ mksh -c "( /bin/dash -c 'trap echo\ TTOU TTOU; set -m; echo all good' )" /bin/dash: 1: set: Cannot set tty process group (Interrupted system call) #$ dash -c "( /bin/dash -c 'trap echo\ TTOU TTOU; set -m; echo all good' )" all good which results in the funny experience #?0|kent:tmp$ smake -f t.mk SHELL=/bin/bash .../bin/bash -c 'trap "echo TTOU" TTOU; set -m; echo all good' all good #?0|kent:tmp$ smake -f t.mk SHELL=/bin/mksh .../bin/mksh -c 'trap "echo TTOU" TTOU; set -m; echo all good' all good #?0|kent:tmp$ smake -f t.mk SHELL=/bin/dash .../bin/dash -c 'trap "echo TTOU" TTOU; set -m; echo all good' /bin/dash: 1: set: Cannot set tty process group (Interrupted system call) smake: *** Code 2 (No such file or directory) from command line for target 'all'. smake: Couldn't make 'all'. Ah, it is a Monday .. --steffen | |Der Kragenbaer,The moon bear, |der holt sich munter he cheerfully and one by one |einen nach dem anderen runter wa.ks himself off |(By Robert Gernhardt)
Re: dash 0.5.11.2, busybox sh 1.32.0, FreeBSD 12.2 sh: spring TTOU but should not i think
On Sat, Dec 19, 2020 at 11:52:31PM +, Harald van Dijk wrote: > On 19/12/2020 22:21, Steffen Nurpmeso wrote: > > Steffen Nurpmeso wrote in > > <20201219172838.1b-wb%stef...@sdaoden.eu>: > > |Long story short, after falsely accusing BSD make of not working > > After dinner i shortened it a bit more, and attach it again, ok? > > It is terrible, but now less redundant than before. > > Sorry for being so terse, that problem crosses my head for about > > a week, and i was totally mislead and if you bang your head > > against the wall so many hours bugs or misbehaviours in a handful > > of other programs is not the expected outcome. > I think a minimal test case is simply > all: > $(SHELL) -c 'trap "echo TTOU" TTOU; set -m; echo all good' > unless I accidentally oversimplified. Yes, and it can be simplified a bit more to remove make from the picture: (SHELL -c 'trap "echo TTOU" TTOU; set -m; echo all good'; :) The idea is to start the shell without being a process group leader, set a trap for SIGTTOU and enable job control. Also, simply entering the command trap "echo TTOU" TTOU in an interactive shell makes it behave strangely. Created jobs immediately stop, "fg" works once but after that the shell tends to get stuck quickly. > The SIGTTOU is caused by setjobctl's xtcsetpgrp(fd, pgrp) call to make its > newly started process group the foreground process group when job control is > enabled, where xtcsetpgrp is a wrapper for tcsetpgrp. (That's in dash, the > other variants may have some small differences.) tcsetpgrp has this little > bit in its specification: >Attempts to use tcsetpgrp() from a process which is a member of >a background process group on a fildes associated with its con‐ >trolling terminal shall cause the process group to be sent a >SIGTTOU signal. If the calling thread is blocking SIGTTOU sig‐ >nals or the process is ignoring SIGTTOU signals, the process >shall be allowed to perform the operation, and no signal is >sent. > Ordinarily, when job control is enabled, SIGTTOU is ignored. However, when a > trap action is specified for SIGTTOU, the signal is not ignored, and there > is no blocking in place either, so the tcsetpgrp() call is not allowed. Right. > The lowest impact change to make here, the one that otherwise preserves the > existing shell behaviour, is to block signals before calling tcsetpgrp and > unblocking them afterwards. This ensures SIGTTOU does not get raised here, > but also ensures that if SIGTTOU is sent to the shell for another reason, > there is no window where it gets silently ignored. This seems a good approach, but certain writes to the terminal may need the same treatment, for example the error message when fork fails for the second and further elements of a pipeline. This also makes me wonder why SIGTTOU is ignored at all by default. In mksh, the issue is resolved slightly differently: setting a trap on TTOU pretends to work but the signal disposition remains set to ignored. > Another way to fix this is by not trying to make the shell start a new > process group, or at least not make it the foreground process group. Most > other shells appear to not try to do this. Tradition is for job control shells to be a process group leader, but I don't really know why. Changing this will not fix the issue entirely anyway since the shell must perform tcsetpgrp() from the background when a foreground job has terminated. What is definitely required, though, is that the shell not read input or alter terminal settings if it is started in the background (possibly unless the script explicitly ignored SIGTTOU). This is what the loop with tcgetpgrp() does. -- Jilles Tjoelker
Re: dash 0.5.11.2, busybox sh 1.32.0, FreeBSD 12.2 sh: spring TTOU but should not i think
On 19/12/2020 22:21, Steffen Nurpmeso wrote: Steffen Nurpmeso wrote in <20201219172838.1b-wb%stef...@sdaoden.eu>: |Long story short, after falsely accusing BSD make of not working After dinner i shortened it a bit more, and attach it again, ok? It is terrible, but now less redundant than before. Sorry for being so terse, that problem crosses my head for about a week, and i was totally mislead and if you bang your head against the wall so many hours bugs or misbehaviours in a handful of other programs is not the expected outcome. I think a minimal test case is simply all: $(SHELL) -c 'trap "echo TTOU" TTOU; set -m; echo all good' unless I accidentally oversimplified. The SIGTTOU is caused by setjobctl's xtcsetpgrp(fd, pgrp) call to make its newly started process group the foreground process group when job control is enabled, where xtcsetpgrp is a wrapper for tcsetpgrp. (That's in dash, the other variants may have some small differences.) tcsetpgrp has this little bit in its specification: Attempts to use tcsetpgrp() from a process which is a member of a background process group on a fildes associated with its con‐ trolling terminal shall cause the process group to be sent a SIGTTOU signal. If the calling thread is blocking SIGTTOU sig‐ nals or the process is ignoring SIGTTOU signals, the process shall be allowed to perform the operation, and no signal is sent. Ordinarily, when job control is enabled, SIGTTOU is ignored. However, when a trap action is specified for SIGTTOU, the signal is not ignored, and there is no blocking in place either, so the tcsetpgrp() call is not allowed. The lowest impact change to make here, the one that otherwise preserves the existing shell behaviour, is to block signals before calling tcsetpgrp and unblocking them afterwards. This ensures SIGTTOU does not get raised here, but also ensures that if SIGTTOU is sent to the shell for another reason, there is no window where it gets silently ignored. Another way to fix this is by not trying to make the shell start a new process group, or at least not make it the foreground process group. Most other shells appear to not try to do this. Cheers, Harald van Dijk
Re: dash 0.5.11.2, busybox sh 1.32.0, FreeBSD 12.2 sh: spring TTOU but should not i think
Steffen Nurpmeso wrote in <20201219172838.1b-wb%stef...@sdaoden.eu>: |Long story short, after falsely accusing BSD make of not working After dinner i shortened it a bit more, and attach it again, ok? It is terrible, but now less redundant than before. Sorry for being so terse, that problem crosses my head for about a week, and i was totally mislead and if you bang your head against the wall so many hours bugs or misbehaviours in a handful of other programs is not the expected outcome. #?0|kent:tmp$ SHELL=/bin/bash make -f jobo.mk ./mx-test.sh Starting job reaper (timeout of 2 seconds) .. waiting for job reaper to come up PRE T1 CHEK PREWAIT CHECK 10 ls: cannot access '/NONEXISTENT': No such file or directory CHECK /bin/bash 50 allexport off braceexpand on emacs off errexit off errtraceoff functrace off hashall on histexpand off history off ignoreeof off interactive-commentson keyword off monitor on noclobber off noexec off noglob off nolog off notify off nounset off onecmd off physicaloff pipefailoff posix off privileged off verbose off vi off xtrace off CHECK 23 CHECK 100 AFTER T1 CHEK Stopping job reaper make: *** [jobo.mk:2: test] Error 1 That is how it "should look" ;), monitor is on in there. #?2|kent:tmp$ SHELL=/bin/dash make -f jobo.mk ./mx-test.sh Starting job reaper (timeout of 2 seconds) .. waiting for job reaper to come up TTOU Cleaning up running jobs Stopping job reaper make: *** [jobo.mk:2: test] Error 2 Somehow refuses to work when supervised by (any) make (i tried). Works as desired when ridden directly. #?2|kent:tmp$ SHELL=/bin/mksh make -f jobo.mk ./mx-test.sh Starting job reaper (timeout of 2 seconds) .. waiting for job reaper to come up PRE T1 CHEK CHECK 10 PREWAIT ls: cannot access '/NONEXISTENT': No such file or directory PREWAIT PREWAIT PREWAIT PREWAIT PREWAIT PREWAIT PREWAIT TIMEOUT IN PARENT !! Timeout: reaped job 1 [X_errexit] Stopping job reaper And that condition is very strange, with or without supervision. Ciao! --steffen | |Der Kragenbaer,The moon bear, |der holt sich munter he cheerfully and one by one |einen nach dem anderen runter wa.ks himself off |(By Robert Gernhardt) mx-test.sh Description: Bourne shell script test: ./mx-test.sh