usr.bin/mail: cmd3.c:bangexp(): "borked"?, and BSD fails POSIX compat

From: Steffen Nurpmeso <steffen_at_sdaoden.eu>
Date: Tue, 02 May 2023 21:40:14 UTC
Hallo, and sorry for the cross-post, but so all in one (maybe) go.

This is about a niche "feature" of mail, the shell command "bang"
(! / ~! in compose mode):

  ? !echo no!bang
  no!bang
  !
  ? set bang
  ? ! echo no!bang
  !echo nobang
  nobang
  !
  ? ! !
  !echo nobang
  nobang
  !
  ? ! echo no!bang
  !echo noecho nobangbang
  noecho nobangbang
  !
  ?

In short: they all do it differently, and they all do it "wrong".

- Apple mail simply does not act unless "bang" is set: bangexp()
  simply returns.

- Free-, Net- and OpenBSD do not look for a "bang" variable at
  all, they simply do bang-style expansion whether that is set or
  not.

- V10 mailx (and the code i maintain) do some mix (expand the
  "last bang" only if "bang" is set, but know about \! escapes and
  such.

POSIX now says / will say

  If the bang variable is set, each unescaped occurrence of '!' in
  command shall be replaced with the command executed by the
  previous ! command or ˜! command escape.

thus

  - All commands entered for ! and ~! shall be stored.

  - If "bang" is set, an unquoted ! shall be replaced by that
    storage.

The code everywhere is more or less what Kurt A. Shoens wrote as

  commit ae3dba0da2068b0de91ef163ea95fe774196a501
  Author:     Kurt A. Schoens <kas@ucbvax.Berkeley.EDU>
  AuthorDate: 1980-10-09 02:48:47 -0800
  Commit:     Kurt A. Schoens <kas@ucbvax.Berkeley.EDU>
  CommitDate: 1980-10-09 02:48:47 -0800

      Made shell escapes expand !'s to previous command

      SCCS-vsn: usr.bin/mail/cmd3.c 1.2

It will fail to properly "bang" a second time if some escaped
question mark was present in the first round,

    while(*cp){
      ...
      if (*cp == '!') {
        BANGEXP
        ...
        continue;
      }
      ...
      if (*cp == '\\' && cp[1] == '!') {
      ...
          *cp2++ = '!';
          cp += 2;
          changed++;
[fallthru]
      }
      ...
      *cp2++ = *cp++;

and it stores and pushes through the second escape for "\!\!".

I am thinking of dropping this entirely, as it can properly work
only once if some escape took place, and i do not know how to fix
this the right way.  (And the line editor has history.)

--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)
|~~
|..and in spring, hear David Leonard sing..
|
|The black bear,          The black bear,
|blithely holds his own   holds himself at leisure
|beating it, up and down  tossing over his ups and downs with pleasure
|~~
|Farewell, dear collar bear