'terminal-window-size' throws ENOTTY ("Inappropriate ioctl for device")

  • Open
  • quality assurance status badge
Details
4 participants
  • Josselin Poiret
  • Ludovic Courtès
  • Maxime Devos
  • Csepp
Owner
unassigned
Submitted by
Csepp
Severity
important
C
another "inappropriate ioctl" bug :)
(address . bug-guix@gnu.org)
874jylf1v8.fsf@riseup.net
```
./pre-inst-env guix import pypi -r linode-cli | tee -a gnu/packages/python-xyz.scm

...
In guix/build/syscalls.scm:
2284:35 1 (_)
2273:8 0 (terminal-window-size _)

guix/build/syscalls.scm:2273:8: In procedure terminal-window-size:
In procedure terminal-window-size: Inappropriate ioctl for device
```

I assume the progress bar code does not gracefully handle the very
common case when stdout is not a terminal.

Good thing I'm in a local checkout so I can just make it return some
constant.
L
L
Ludovic Courtès wrote on 10 Aug 2022 15:38
control message for bug #57095
(address . control@debbugs.gnu.org)
874jyk1ann.fsf@gnu.org
retitle 57095 'terminal-window-size' throws ENOTTY ("Inappropriate ioctl for device")
quit
J
J
Josselin Poiret wrote on 10 Aug 2022 15:43
Re: bug#57095: another "inappropriate ioctl" bug :)
87bkssjjt5.fsf@jpoiret.xyz
Hi!

Csepp <raingloom@riseup.net> writes:

Toggle quote (15 lines)
> ```
> ./pre-inst-env guix import pypi -r linode-cli | tee -a gnu/packages/python-xyz.scm
>
> ...
> In guix/build/syscalls.scm:
> 2284:35 1 (_)
> 2273:8 0 (terminal-window-size _)
>
> guix/build/syscalls.scm:2273:8: In procedure terminal-window-size:
> In procedure terminal-window-size: Inappropriate ioctl for device
> ```
>
> I assume the progress bar code does not gracefully handle the very
> common case when stdout is not a terminal.

There is some code in place to handle this issue, and it works most of
the time, but not here. The issue is that we handle any error coming
from ioctl by first throwing a `'system-error`, and handling it with a
`catch` that checks whether the errno is ENOTTY (and deprecated
equivalents) and in that case falls back to a good default. In the case
where the error is not of that type, it is rethrown. This works well in
most cases, but here comes the Guile shenanigans:

We also use a big wrapping `with-error-handling` to display errors
properly in the case when they are not caught. The difference is that
`with-error-handling` adds a non-unwinding handler, while catch is
unwinding. My first thought was that non-unwinding handlers, even outer
ones would get priority over unwinding ones, since once the stack's
unwound you can't really go back (I have no idea if that last part is
actually true). In practice this is actually false, I tested with a
very simple example, but that lead me to test by setting `#:unwind? #t`
for the `guard*` definition in guix/ui.scm and the issue went away, so I'm
clueless as to why this happens. What seems weird is that the error is
not caught at all!

Does anyone have a clue?

Best,
--
Josselin Poiret
L
L
Ludovic Courtès wrote on 10 Aug 2022 16:53
(name . Josselin Poiret)(address . dev@jpoiret.xyz)
87lerwywt2.fsf@gnu.org
Hi!

Josselin Poiret <dev@jpoiret.xyz> skribis:

Toggle quote (12 lines)
> We also use a big wrapping `with-error-handling` to display errors
> properly in the case when they are not caught. The difference is that
> `with-error-handling` adds a non-unwinding handler, while catch is
> unwinding. My first thought was that non-unwinding handlers, even outer
> ones would get priority over unwinding ones, since once the stack's
> unwound you can't really go back (I have no idea if that last part is
> actually true). In practice this is actually false, I tested with a
> very simple example, but that lead me to test by setting `#:unwind? #t`
> for the `guard*` definition in guix/ui.scm and the issue went away, so I'm
> clueless as to why this happens. What seems weird is that the error is
> not caught at all!

The inner handler, even if it’s unwinding and the outer one is not,
appears to have higher precedence:

Toggle snippet (24 lines)
scheme@(guix read-print)> (with-exception-handler (lambda args (pk 'outer args))
(lambda ()
(with-exception-handler
(lambda args (pk 'inner args))
(lambda()
(rmdir "/"))
#:unwind? #t))
#:unwind? #f)

;;; (inner (#<&compound-exception components: (#<&external-error> #<&origin origin: "rmdir"> #<&message message: "~A"> #<&irritants irritants: ("Device or resource busy")> #<&exception-with-kind-and-args kind: system-error args: ("rmdir" "~A" ("Device or resource busy") (16))>)>))
$17 = (#<&compound-exception components: (#<&external-error> #<&origin origin: "rmdir"> #<&message message: "~A"> #<&irritants irritants: ("Device or resource busy")> #<&exception-with-kind-and-args kind: system-error args: ("rmdir" "~A" ("Device or resource busy") (16))>)>)
scheme@(guix read-print)> (with-exception-handler (lambda args (pk 'outer args))
(lambda ()
(catch 'system-error
(lambda ()
(rmdir "/"))
(lambda args
(pk 'inner args))))
#:unwind? #f)

;;; (inner (system-error "rmdir" "~A" ("Device or resource busy") (16)))
$18 = (system-error "rmdir" "~A" ("Device or resource busy") (16))

This appears to work:

Toggle snippet (12 lines)
scheme@(guile-user)> ,use(guix build syscalls)
scheme@(guile-user)> (terminal-columns)
$19 = 119
scheme@(guile-user)> ,use(guix ui)
scheme@(guile-user)> (with-error-handling (terminal-columns))
$20 = 119
scheme@(guile-user)> (terminal-columns (open-input-string ""))
$21 = 143
scheme@(guile-user)> (with-error-handling (terminal-columns (open-input-string "")))
$22 = 143

Needs more investigation…

Ludo’.
L
L
Ludovic Courtès wrote on 29 Aug 2022 15:01
control message for bug #57095
(address . control@debbugs.gnu.org)
87fshf6w5t.fsf@gnu.org
severity 57095 important
quit
L
L
Ludovic Courtès wrote on 1 Sep 2022 23:04
Re: bug#57095: 'terminal-window-size' throws ENOTTY ("Inappropriate ioctl for device")
(name . Josselin Poiret)(address . dev@jpoiret.xyz)
87bkryztzw.fsf_-_@gnu.org
Hi,

Josselin Poiret <dev@jpoiret.xyz> skribis:

Toggle quote (12 lines)
> We also use a big wrapping `with-error-handling` to display errors
> properly in the case when they are not caught. The difference is that
> `with-error-handling` adds a non-unwinding handler, while catch is
> unwinding. My first thought was that non-unwinding handlers, even outer
> ones would get priority over unwinding ones, since once the stack's
> unwound you can't really go back (I have no idea if that last part is
> actually true). In practice this is actually false, I tested with a
> very simple example, but that lead me to test by setting `#:unwind? #t`
> for the `guard*` definition in guix/ui.scm and the issue went away, so I'm
> clueless as to why this happens. What seems weird is that the error is
> not caught at all!

Here’s a reproducer:

Toggle snippet (8 lines)
scheme@(guile-user)> (with-exception-handler (lambda _ (catch 'x (lambda () (throw 'x)) (const 'good) ))
(lambda ()
(rmdir "/"))
#:unwind? #f)
ice-9/boot-9.scm:1685:16: In procedure raise-exception:
Throw to key `x' with args `()'.

Or, simplified:

Toggle snippet (11 lines)
scheme@(guile-user)> (with-exception-handler
(lambda _ (with-exception-handler (const 'good)
(lambda () (throw 'x))
#:unwind? #t))
(lambda ()
(rmdir "/"))
#:unwind? #f)
ice-9/boot-9.scm:1685:16: In procedure raise-exception:
Throw to key `x' with args `()'.

We expect 'x to be caught, leading the handler to return 'good.
Instead, 'x is not caught at all.

In both cases, setting #:unwind? #t in the outer
‘with-exception-handler’ gives the expected result.

I spent some time staring at the relevant code in ‘boot-9.scm’ but
nothing came out of it.

Ludo’.
M
M
Maxime Devos wrote on 1 Sep 2022 23:21
6b6cd940-261c-0cd3-1efd-99aff96d77db@telenet.be
On 01-09-2022 23:04, Ludovic Courtès wrote:
Toggle quote (13 lines)
> [...]
> We expect 'x to be caught, leading the handler to return 'good.
> Instead, 'x is not caught at all.
>
> In both cases, setting #:unwind? #t in the outer
> ‘with-exception-handler’ gives the expected result.
>
> I spent some time staring at the relevant code in ‘boot-9.scm’ but
> nothing came out of it.
>
> Ludo’.
>
>
Maybe my answer on IRC is relevant here:
Greetings,
Maxime.
Attachment: OpenPGP_signature
L
L
Ludovic Courtès wrote on 2 Sep 2022 11:17
(name . Maxime Devos)(address . maximedevos@telenet.be)
87mtbixhh8.fsf@gnu.org
Hi,

Maxime Devos <maximedevos@telenet.be> skribis:

Toggle quote (4 lines)
> Maybe my answer on IRC is relevant here:
>
> https://logs.guix.gnu.org/guile/2022-09-01.log#231503

Interesting.

It’s a Guile bug, and a tricky one. Here’s what Andy wrote on IRC:

<sneek> civodul, wingo says: i believe it is a bug. in a pre-unwind handler,
you want to make it so that if it throws again, it is handled by the
handler that was current when the with-exception-handler was invoked
(the dynamically outer handler)
<sneek> civodul, wingo says: to implement this, when a throw first starts, we
capture the current handler stack, and arrange for subsequent throws
to resolve via that captured handler stack
<sneek> civodul, wingo says: but if there is an additional
with-exception-handler within the exception handler, that doesn't
augment the captured exception handler stack to which inner exceptions
will dispatch
<sneek> civodul, wingo says: the fix will be small but also gnarly :P will
take some thinking. there are funny interactions with delimited
continuations

I’ll defer to Andy to see what the best way to fix it is…

What’s frustrating is that I can’t think of a way to work around it,
except by changing ‘terminal-columns’ & co. to not use exceptions (or
introducing a variant that does that).

Thoughts?

Ludo’.
M
M
Maxime Devos wrote on 2 Sep 2022 20:14
(name . Ludovic Courtès)(address . ludo@gnu.org)
11388f64-eb5f-6f87-78b4-494a0529f8bb@telenet.be
On 02-09-2022 11:17, Ludovic Courtès wrote:
Toggle quote (7 lines)
> [...]
>
> What’s frustrating is that I can’t think of a way to work around it,
> except by changing ‘terminal-columns’ & co. to not use exceptions (or
> introducing a variant that does that).
>
> Thoughts?
Exception handlers do not cross process boundaries. Maybe run the inner
(catch ...) or (with-exception-handle ...) or whatever is used here in a
separate thread + wait for the thread to return?
Greetings,
Maxime.
Attachment: OpenPGP_signature
?