Package: libncursesw6
Version: 6.4+20231121-1
Severity: normal

Dear Maintainer,

urlview 1c-1 whose xterm was killed but which didn't die consumes 100% CPU.
The event loop is for(;;) switch(getch()) ... and ignores -1
(https://git.sr.ht/~nabijaczleweli/urlview-ng/tree/1c/item/urlview.c#L576).

strace -p:
  read(0, "", 1)                          = 0
  read(0, "", 1)                          = 0
  read(0, "", 1)                          = 0
  read(0, "", 1)                          = 0
  read(0, "", 1)                          = 0
  read(0, "", 1)                          = 0
&c.

ltrace -p:
  wgetch(0x55e356e14860, 0, 0, 1)                   = 0xffffffff
  wgetch(0x55e356e14860, 0xffffffff, 0, 0xffffffff) = 0xffffffff
  wgetch(0x55e356e14860, 0, 0, 1)                   = 0xffffffff
  wgetch(0x55e356e14860, 0xffffffff, 0, 0xffffffff) = 0xffffffff
  wgetch(0x55e356e14860, 0, 0, 1)                   = 0xffffffff
  wgetch(0x55e356e14860, 0xffffffff, 0, 0xffffffff) = 0xffffffff
  wgetch(0x55e356e14860, 0, 0, 1)                   = 0xffffffff
  wgetch(0x55e356e14860, 0xffffffff, 0, 0xffffffff) = 0xffffffff
&c.

gdb -p, b ./urlview.c:576:
  (gdb) cont
  Continuing.
  
  Breakpoint 1, main (argc=<optimized out>, argv=<optimized out>) at 
./urlview.c:576
  576                     int c = getch();
  (gdb) n
  578                     switch(c) {
  (gdb) p c
  $1 = -1
  (gdb) p *__errno_location()
  $3 = 5
this is EIO. By resetting *__errno_location() = 0 and letting it rip for
a few more loops, I see it continue to be 0,
so it just returns 0 but doesn't change errno
(I'm assuming changing errno is a side-effect of read(),
 which completes successfully but emptily here),
presumably the EIO is from an earlier refresh to a dead pty.

getch(3ncurses) on bookworm and sid says
  RETURN VALUE
    All routines return the integer ERR upon failure and an integer
    value other than ERR (OK in the case of ungetch) upon successful
    completion.

    wgetch returns ERR if the window pointer is null, or if its
    timeout expires without having any data, or if the execution
    was interrupted by a signal (errno will be set to EINTR).
so how precisely you're meant to handle this is unclear to me.

The first sentence implies "case ERR: exit(1)",
but the second implies "case ERR: if(errno != EINTR) exit(1); break"
to me.

I set no timeouts so I expect that bit to not apply,
but I read this as the EINTR parenthetical applying to both the
interruption and timeout cases.

Either way, if errno isn't (re)set, then returning ERR isn't really
reliable or meaningful i think (lest you "errno=0, getch()" always?
which kinda sucks).

I see SUSv2 XCURSES say:
  Upon successful completion getch(), mvgetch(), mvwgetch() and
  wgetch() return the single-byte character, KEY_ value, or ERR.
  When in the nodelay mode and no data is available, ERR is returned.
which makes it silent on the issue.

Reopening its pty fails with EIO, as expected, but consulting stty
against urlview in a live xterm I see
  speed 38400 baud; rows 54; cols 172; line = 0;
  -brkint -icrnl -imaxbel iutf8
  -onlcr
  -icanon -echo
so -icanon min=1 time=0, which is by no means a "nodelay" mode,
and it doesn't have a "timeout" set.

Naturally, setting min=0 time=1 I reproduce the empty-returning read()s,
and the unchanging errno.

So to this point, I think this just needs disambiguation to the tune of
"having any data (with errno unchanged)".

Empty read()s from a hung-up teletype aren't covered by the
documentation. Naturally this is an adversarial reading,
but it may not be obvious to all readers that these are homologous
conditions:
"or if the read from returns empty \(em be it due to a timeout
 expiring with no data or due to a hangup (with errno unchanged),"

In a similar vein, I see a lot of references to a "cbreak mode"
(sometimes in bold) and a "nocbreak mode" (never in bold).
I suppose this is a left-over from old berkeley manuals(?),
but that's not a feature of the Linux teletype driver,
and according to my notes[1] the CBREAK mode features in [V7, 4.4BSD)
exclusively, which isn't really useful to the modern reader,
with the 4.4BSD URM being released close to 30 years ago now.
The only remnant of this remains in stty(1), but that consistently calls
it cbreak/-cbreak mapping it to -icanon/icanon. This makes it even more
jarring to a modern reader.

Anyway, is this behaviour expected, and is the loop best-served as
  for(;;) switch(errno=0, getch()) case ERR: if(errno != EINTR) { err = true; 
break 2; }
? Or is there a better way to drive this?

Best,
наб

[1]: 
https://lfs.nabijaczleweli.xyz/0012-groff-mdoc-*q-spacing/2022-10-23-stty.1-preprint/a4.pdf
     pp. 20, 67-68

-- System Information:
Debian Release: 12.2
  APT prefers stable-updates
  APT policy: (500, 'stable-updates'), (500, 'stable-security'), (500, 
'stable-debug'), (500, 'stable')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 6.1.0-9-amd64 (SMP w/24 CPU threads; PREEMPT)
Kernel taint flags: TAINT_PROPRIETARY_MODULE, TAINT_FIRMWARE_WORKAROUND, 
TAINT_OOT_MODULE, TAINT_UNSIGNED_MODULE
Locale: LANG=en_GB.UTF-8, LC_CTYPE=en_GB.UTF-8 (charmap=UTF-8), 
LANGUAGE=en_GB:en
Shell: /bin/sh linked to /usr/bin/dash
Init: systemd (via /run/systemd/system)
LSM: AppArmor: enabled

Versions of packages libncursesw6 depends on:
ii  libc6      2.36-9+deb12u3
ii  libtinfo6  6.4+20231121-1

Versions of packages libncursesw6 recommends:
ii  libgpm2  1.20.7-10+b1

libncursesw6 suggests no packages.

-- no debconf information

Attachment: signature.asc
Description: PGP signature

Reply via email to