Hello,
The POSIX specification of ed(1) includes a table in the rationale
section which (among others) mandates the following address handling
rules [1]:
Address Addr1 Addr2
,, $ $
;; $ $
Unfortunately, OpenBSD does not correctly handle these two example
addresses. As an example, consider the following `,,p` print command:
printf "a\nfoo\nbar\nbaz\n.\n,,p\nQ\n" | ed
This should only print the last line (as `,,p` should expand to `$,$p`)
but instead prints all lines with OpenBSD ed. This seems to be a
regression introduced by Jerome Frgagic in 2017 [2]. Prior to this
change, `,,p` as well as `;;p` correctly printed the last line. The
aforementioned change added a recursive invocation of next_addr() which
does not update the local first variable (causing the second address
separator to be mistaken for the first). The patch below fixes this by
tracking recursive invocations of next_addr() via an additional function
parameter.
See also: https://austingroupbugs.net/view.php?id=1582
[1]:
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ed.html#tag_20_38_18
[2]:
https://github.com/openbsd/src/commit/b4c905bbf3932a50011ce3436b1e22acc742cfeb
diff --git bin/ed/main.c bin/ed/main.c
index 231d021ef19..ca1aeb102b3 100644
--- bin/ed/main.c
+++ bin/ed/main.c
@@ -64,7 +64,7 @@ void signal_hup(int);
void signal_int(int);
void handle_winch(int);
-static int next_addr(void);
+static int next_addr(int);
static int check_addr_range(int, int);
static int get_matching_node_addr(regex_t *, int);
static char *get_filename(int);
@@ -296,7 +296,7 @@ extract_addr_range(void)
addr_cnt = 0;
first_addr = second_addr = current_addr;
- while ((addr = next_addr()) >= 0) {
+ while ((addr = next_addr(0)) >= 0) {
addr_cnt++;
first_addr = second_addr;
second_addr = addr;
@@ -328,7 +328,7 @@ extract_addr_range(void)
/* next_addr: return the next line address in the command buffer */
static int
-next_addr(void)
+next_addr(int recur)
{
char *hd;
int addr = current_addr;
@@ -382,11 +382,15 @@ next_addr(void)
case '%':
case ',':
case ';':
- if (first) {
+ if (first && !recur) {
ibufp++;
addr_cnt++;
second_addr = (c == ';') ? current_addr : 1;
- if ((addr = next_addr()) < 0)
+
+ // If the next address is omitted (e.g. "," or
";") or
+ // if it is also a separator (e.g. ",," or
";;") then
+ // return the last address (as per the omission
rules).
+ if ((addr = next_addr(1)) < 0)
addr = addr_last;
break;
}