On Mon, 2021-08-09 at 14:15 +0200, Martijn van Duren wrote:
> On Mon, 2021-08-09 at 13:19 +0200, Ingo Schwarze wrote:
> > Hi,
> > 
> > as mentioned earlier, deraadt@ reported that sftp(1) ignores Ctrl-C.
> > Fixing that without longjmp(3) requires making editline(3) better
> > behaved.
> > 
> > Currently, when read(2) from the terminal gets interrupted by a
> > signal, editline(3) ignores the (first) signal and unconditionally
> > calls read(2) a second time.  That seems wrong: if the user hits
> > Ctrl-C, it is sane to assume that they meant it, not that they
> > want it ignored.
> > 
> > The following patch causes el_gets(3) and el_wgets(3) to return
> > failure when read(2)ing from the terminal is interrupted by a
> > signal other than SIGCONT or SIGWINCH.  That allows the calling
> > program to decide what to do, usually either exit the program or
> > provide a fresh prompt to the user.
> > 
> > If i know how to grep(1), the following programs in the base system
> > use -ledit:
> > 
> >  * bc(1)
> >    It behaves well with the patch: Ctrl-C discards the current
> >    input line and starts a new input line.
> >    The reason why this already works even without the patch
> >    is that bc(1) does very scary stuff inside the signal handler:
> >    pass a file-global EditLine pointer on the heap to el_line(3)
> >    and access fields inside the returned struct.  Needless to
> >    say that no signal handler should do such things...
> > 
> >  * cdio(1)
> >    Behaviour is acceptable and unchanged with the patch:
> >    Ctrl-C exits cdio(1).
> > 
> >  * ftp(1)
> >    It behaves well with the patch: Ctrl-C discards the current
> >    input line and provides a fresh prompt.
> >    The reason why it already works without the patch is that ftp(1)
> >    uses setjmp(3)/longjmp(3) to forcefully grab back control
> >    from el_gets(3) without waiting for it to return.
> > 
> >  * lldb(1)
> >    It misbehaves with or without the patch and ignores Ctrl-C.
> >    I freely admit that i don't feel too enthusiastic about
> >    debugging that beast.
> > 
> >  * sftp(1)
> >    Behaviour is improved with the patch: Ctrl-C now exits sftp(1).
> >    If desired, i can supply a very simple follow-up patch to sftp.c
> >    to instead behave like ftp(1) and bc(1), i.e. discard the
> >    current input line and provide a fresh prompt.
> >    Neither doing undue work in the signal handler nor longjmp(3)
> >    will be required for that (if this patch gets committed).
> > 
> >  * bgplgsh(8), fsdb(8)
> >    I have no idea how to test those.  Does anyone think that testing
> >    either of them would be required?
> > 
> > Regarding the patch below, note that differentiating EINTR behaviour
> > by signal number is not needed because the calling code in read_char()
> > already handles SIGCONT and SIGWINCH, so those two never arrive in
> > read__fixio() in the first place.
> > 
> > Also note that deraadt@ pointed out in private mail to me that the
> > fact that read__fixio() clears FIONBIO is probably a symptom of
> > botched editline(3) API design.  That might be worth fixing, too,
> > as far as that is feasible, but it is unrelated to the sftp(1)
> > Ctrl-C issue; let's address one topic at a time.
> > 
> > Any feedback is welcome.
> > 
> > Yours,
> >   Ingo
> > 
> > P.S.
> > I decided to Cc: tech@ again because this patch might affect
> > even people who are not specifically interested in editline(3),
> > and i have no intention to cause unpleasant surprises.
> > 
> If we're stripping down fixio to a single function, why not inline the
> whole thing?
> 
> Also, as far as my brain explains things to me, it could theoretically
> be possible that the signal handler kicks in right after we return a -1
> from read(2), making it possible to get something like an EIO and
> entering the sig switch statement. Assuming that a signal handler
> doesn't clobber our errno (which libedit doesn't do, but who knows what
> bad code is out there) we should probably only check the signal type
> when we know we have an EINTR.
> 
> Finally, I don't understand why we only have a single retry on EAGAIN?
> If the application keeps resetting the FIONBIO in such a furious way
> that it happens more then once in a single call (no idea how that could
> happen) it might be an uphill battle, but we just burn away cpu cycles.
> It is not and should probably not be treated like something fatal.
> 
> Diff below does this. (minus 27 LoC)
> 
> The following ports use libedit (there might be a better way of finding
> them, but this works):
> $ find . -name Makefile | xargs grep -F '.include <bsd.port.mk>' | \
> > cut -d: -f1 | while read file; do \
> > (cd $(dirname $file); make show=WANTLIB | \
> > grep -q '\<edit\>' && echo $(dirname $file)); \>
> > done 2> /dev/null 
> ./devel/clang-tools-extra
> ./devel/llvm
> ./mail/dcc
> ./mail/nmh
> ./emulators/gsplus
> ./emulators/nono
> ./lang/brainfuck
> ./lang/eltclsh
> ./lang/lua/5.1
> ./lang/lua/5.2
> ./lang/lua/5.3
> ./lang/swi-prolog
> ./math/gbc
> ./net/dnsdist
> ./net/honeyd
> ./net/icinga/core2
> ./net/knot
> ./net/ntp
> ./security/kc
> ./security/pivy
> ./shells/dash
> ./shells/nsh
> ./sysutils/ipmitool
> 
> So if we decide which of our interpretations should take precedence it
> might be a good idea to put it into snaps for a while.
> 
> martijn@
> 
We can make it even smaller by making use of this little known construct
named while.

Index: read.c
===================================================================
RCS file: /cvs/src/lib/libedit/read.c,v
retrieving revision 1.45
diff -u -p -r1.45 read.c
--- read.c      9 Aug 2021 09:11:26 -0000       1.45
+++ read.c      9 Aug 2021 12:21:55 -0000
@@ -66,7 +66,6 @@ struct el_read_t {
        int              read_errno;
 };
 
-static int     read__fixio(int, int);
 static int     read_char(EditLine *, wchar_t *);
 static int     read_getcmd(EditLine *, el_action_t *, wchar_t *);
 static void    read_clearmacros(struct macros *);
@@ -132,29 +131,6 @@ el_read_getfn(struct el_read_t *el_read)
 }
 
 
-/* read__fixio():
- *     Try to recover from a read error
- */
-static int
-read__fixio(int fd, int e)
-{
-       int zero = 0;
-
-       switch (e) {
-       case EAGAIN:
-               if (ioctl(fd, FIONBIO, &zero) == -1)
-                       return -1;
-               return 0;
-
-       case EINTR:
-               return 0;
-
-       default:
-               return -1;
-       }
-}
-
-
 /* el_push():
  *     Push a macro
  */
@@ -234,33 +210,29 @@ static int
 read_char(EditLine *el, wchar_t *cp)
 {
        ssize_t num_read;
-       int tried = 0;
        char cbuf[MB_LEN_MAX];
        int cbp = 0;
-       int save_errno = errno;
+       int zero = 0;
 
- again:
        el->el_signal->sig_no = 0;
        while ((num_read = read(el->el_infd, cbuf + cbp, 1)) == -1) {
-               int e = errno;
-               switch (el->el_signal->sig_no) {
-               case SIGCONT:
-                       el_set(el, EL_REFRESH);
-                       /*FALLTHROUGH*/
-               case SIGWINCH:
-                       sig_set(el);
-                       goto again;
-               default:
-                       break;
-               }
-               if (!tried && read__fixio(el->el_infd, e) == 0) {
-                       errno = save_errno;
-                       tried = 1;
-               } else {
-                       errno = e;
-                       *cp = L'\0';
-                       return -1;
+               if (errno == EINTR) {
+                       switch (el->el_signal->sig_no) {
+                       case SIGCONT:
+                               el_set(el, EL_REFRESH);
+                               /*FALLTHROUGH*/
+                       case SIGWINCH:
+                               sig_set(el);
+                               continue;
+                       default:
+                               break;
+                       }
+               } else if (errno == EAGAIN) {
+                       if (ioctl(el->el_infd, FIONBIO, &zero) != -1)
+                               continue;
                }
+               *cp = L'\0';
+               return -1;
        }
 
        /* Test for EOF */


Reply via email to