Index | Thread | Search

From:
Jeremy Mates <jmates@thrig.me>
Subject:
Re: vi crash whilst global search running (and nvi too?)
To:
tech@openbsd.org
Date:
Thu, 5 Feb 2026 23:55:50 +0000

Download raw body.

Thread
On 2026-01-28 00:52:06 +0000, Jeremy Mates wrote:
> 	$ printf 'foo\nfoo\nfoo\n' > foo
> 	$ /usr/bin/ex foo
> 	foo: unmodified: line 3
> 	:g/foo/vi

So a problem is that ex_cmd() has been entered into due to the "global
/^/ visual" command (for each matching line, enter visual mode), and
there is some notice in the code, probably not very important,

        /*
         * We always start running the command on the top of the stack.
         * This means that *everything* must be resolved when we leave
         * this function for any reason.
         */

and then in visual mode if you type in ":quit" or any other ex command
ex_cmd is entered for a second time (uh oh?), whereupon it sees "vi" on
the top of the gp->ecq command stack, and the "quit" or whatever ex
command you typed is ignored. Usually the "Global/v command running when
the file/screen changed" message appears as the global started in ex
mode is noticed.

A subsequent ":quit" actually works and will probably cause vi to exit,
sometimes with a crash, probably on account of ex_cmd having been
entered more than once and the cleanup code maybe trying to do things
more than once.

As a horrible kluge one can use a patch such as the following, which
appears to at least let you enter ":quit" the first time and have vi
exit, probably with some error messages and maybe crashes from the
cleanup code. A less bad patch would need to figure out how to make
ex_cmd reentrant, or to ensure that the cleanup code does not close
ep->db twice or free already freed memory, and to confirm that making
ex_cmd reentrant does not cause problems anywhere else, which the robust
test suite for vi will doubtless help with.

--- usr.bin/vi/ex/ex.c
+++ usr.bin/vi/ex/ex.c
@@ -206,6 +206,7 @@ ex_cmd(SCR *sp)
 	int ch, cnt, delim, isaddr, namelen;
 	int newscreen, notempty, tmp, vi_address;
 	char *arg1, *p, *s, *t;
+	static int level = 0;
 
 	gp = sp->gp;
 	exp = EXP(sp);
@@ -214,8 +215,14 @@ ex_cmd(SCR *sp)
 	 * We always start running the command on the top of the stack.
 	 * This means that *everything* must be resolved when we leave
 	 * this function for any reason.
+	 *
+	 * !!! Not everything is resolved if from ex mode "g/^/vi" is
+	 * run, and then an ex command is entered in visual mode.
 	 */
+	++level;
 loop:	ecp = LIST_FIRST(&gp->ecq);
+	if (level > 1)
+		ecp = LIST_NEXT(ecp, q);
 
 	/* If we're reading a command from a file, set up error information. */
 	if (ecp->if_name != NULL) {
@@ -1603,6 +1610,8 @@ rsuccess:	tmp = 0;
 	/* Turn off the global bit. */
 	F_CLR(sp, SC_EX_GLOBAL);
 
+	--level;
+
 	return (tmp);
 }