diff options
author | Cem Keylan <cem@ckyln.com> | 2021-07-14 17:07:06 +0300 |
---|---|---|
committer | Cem Keylan <cem@ckyln.com> | 2021-07-14 17:07:06 +0300 |
commit | d7f8dce37ecea2fa24883b58a8bbd5f25fb69221 (patch) | |
tree | 34f8326573d65b5cf5704bf4c9fb4c3e36f106ec /bin/ed | |
parent | 9270286d5a675a4924d94adaca4099441a3cae5a (diff) | |
download | otools-d7f8dce37ecea2fa24883b58a8bbd5f25fb69221.tar.gz |
ed: port to otools
Diffstat (limited to 'bin/ed')
-rw-r--r-- | bin/ed/CVS/Entries | 14 | ||||
-rw-r--r-- | bin/ed/CVS/Repository | 1 | ||||
-rw-r--r-- | bin/ed/CVS/Root | 1 | ||||
-rw-r--r-- | bin/ed/Makefile | 6 | ||||
-rw-r--r-- | bin/ed/POSIX | 91 | ||||
-rw-r--r-- | bin/ed/README | 19 | ||||
-rw-r--r-- | bin/ed/buf.c | 289 | ||||
-rw-r--r-- | bin/ed/ed.1 | 859 | ||||
-rw-r--r-- | bin/ed/ed.h | 201 | ||||
-rw-r--r-- | bin/ed/glbl.c | 207 | ||||
-rw-r--r-- | bin/ed/io.c | 343 | ||||
-rw-r--r-- | bin/ed/main.c | 1394 | ||||
-rw-r--r-- | bin/ed/re.c | 139 | ||||
-rw-r--r-- | bin/ed/sub.c | 272 | ||||
-rw-r--r-- | bin/ed/undo.c | 146 |
15 files changed, 3982 insertions, 0 deletions
diff --git a/bin/ed/CVS/Entries b/bin/ed/CVS/Entries new file mode 100644 index 0000000..fa6d8e5 --- /dev/null +++ b/bin/ed/CVS/Entries @@ -0,0 +1,14 @@ +D/USD.doc//// +D/test//// +/Makefile/1.12/Wed Jul 14 12:55:12 2021// +/POSIX/1.8/Wed Jul 14 12:55:12 2021// +/README/1.5/Wed Jul 14 12:55:12 2021// +/buf.c/1.24/Wed Jul 14 12:55:12 2021// +/ed.1/1.76/Wed Jul 14 12:55:24 2021// +/ed.h/1.22/Wed Jul 14 12:55:12 2021// +/glbl.c/1.20/Wed Jul 14 12:55:12 2021// +/io.c/1.24/Wed Jul 14 12:55:12 2021// +/main.c/1.66/Wed Jul 14 12:55:12 2021// +/re.c/1.19/Wed Jul 14 12:55:12 2021// +/sub.c/1.18/Wed Jul 14 12:55:12 2021// +/undo.c/1.14/Wed Jul 14 12:55:12 2021// diff --git a/bin/ed/CVS/Repository b/bin/ed/CVS/Repository new file mode 100644 index 0000000..6ee887f --- /dev/null +++ b/bin/ed/CVS/Repository @@ -0,0 +1 @@ +src/bin/ed diff --git a/bin/ed/CVS/Root b/bin/ed/CVS/Root new file mode 100644 index 0000000..3811072 --- /dev/null +++ b/bin/ed/CVS/Root @@ -0,0 +1 @@ +/cvs diff --git a/bin/ed/Makefile b/bin/ed/Makefile new file mode 100644 index 0000000..bf60cf2 --- /dev/null +++ b/bin/ed/Makefile @@ -0,0 +1,6 @@ +# $OpenBSD: Makefile,v 1.12 2018/06/15 08:46:24 martijn Exp $ + +PROG= ed +SRCS= buf.c glbl.c io.c main.c re.c sub.c undo.c + +.include <bsd.prog.mk> diff --git a/bin/ed/POSIX b/bin/ed/POSIX new file mode 100644 index 0000000..1cd2afc --- /dev/null +++ b/bin/ed/POSIX @@ -0,0 +1,91 @@ +$OpenBSD: POSIX,v 1.8 2014/05/24 01:35:55 daniel Exp $ +$NetBSD: POSIX,v 1.9 1995/03/21 09:04:32 cgd Exp $ + +This version of ed(1) is not strictly POSIX compliant, as described in +the POSIX 1003.2 document. The following is a summary of the omissions, +extensions and possible deviations from POSIX 1003.2. + +OMISSIONS +--------- +1) Locale(3) is not supported yet. + +2) For backwards compatibility, the POSIX rule that says a range of + addresses cannot be used where only a single address is expected has + been relaxed. + +3) To support the BSD `s' command (see extension [1] below), + substitution patterns cannot be delimited by numbers or the characters + `r', `g' and `p'. In contrast, POSIX specifies any character except + space or newline can be used as a delimiter. + +EXTENSIONS +---------- +1) BSD commands have been implemented wherever they do not conflict with + the POSIX standard. The BSD-ism's included are: + i) `s' (i.e., s[n][rgp]*) to repeat a previous substitution, + ii) `W' for appending text to an existing file, + iii) `wq' for exiting after a write, + iv) `z' for scrolling through the buffer, and + v) BSD line addressing syntax (i.e., `^' and `%') is recognized. + +2) The POSIX interactive global commands `G' and `V' are extended to + support multiple commands, including `a', `i' and `c'. The command + format is the same as for the global commands `g' and `v', i.e., one + command per line with each line, except for the last, ending in a + backslash (\). + +3) An extension to the POSIX file commands `E', `e', `r', `W' and `w' is + that <file> arguments are processed for backslash escapes, i.e., any + character preceded by a backslash is interpreted literally. If the + first unescaped character of a <file> argument is a bang (!), then the + rest of the line is interpreted as a shell command, and no escape + processing is performed by ed. + +DEVIATIONS +---------- +1) Though ed is not a stream editor, it can be used to edit binary files. + To assist in binary editing, when a file containing at least one ASCII + NUL character is written, a newline is not appended if it did not + already contain one upon reading. In particular, reading /dev/null + prior to writing prevents appending a newline to a binary file. + + For example, to create a file with ed containing a single NUL character: + $ ed file + a + ^@ + . + r /dev/null + wq + + Similarly, to remove a newline from the end of binary `file': + $ ed file + r /dev/null + wq + +2) Since the behavior of `u' (undo) within a `g' (global) command list is + not specified by POSIX, it follows the behavior of the SunOS ed: + undo forces a global command list to be executed only once, rather than + for each line matching a global pattern. In addition, each instance of + `u' within a global command undoes all previous commands (including + undo's) in the command list. This seems the best way, since the + alternatives are either too complicated to implement or too confusing + to use. + + The global/undo combination is useful for masking errors that + would otherwise cause a script to fail. For instance, an ed script + to remove any occurrences of either `censor1' or `censor2' might be + written as: + ed - file <<EOF + 1g/.*/u\ + ,s/censor1//g\ + ,s/censor2//g + ... + +3) The `m' (move) command within a `g' command list also follows the SunOS + ed implementation: any moved lines are removed from the global command's + `active' list. + +4) If ed is invoked with a name argument prefixed by a bang (!), then the + remainder of the argument is interpreted as a shell command. To invoke + ed on a file whose name starts with bang, prefix the name with a + backslash. diff --git a/bin/ed/README b/bin/ed/README new file mode 100644 index 0000000..4405e45 --- /dev/null +++ b/bin/ed/README @@ -0,0 +1,19 @@ +$OpenBSD: README,v 1.5 2018/06/15 08:46:24 martijn Exp $ +$NetBSD: README,v 1.9 1995/03/21 09:04:33 cgd Exp $ + +ed is an 8-bit-clean, POSIX-compliant line editor. It should work with +any regular expression package that conforms to the POSIX interface +standard, such as GNU regex(3). + +If reliable signals are supported (e.g., POSIX sigaction(2)), it should +compile with little trouble. Otherwise, the macros SPL1() and SPL0() +should be redefined to disable interrupts. + +The file `POSIX' describes extensions to and deviations from the POSIX +standard. + +The ./test directory contains regression tests for ed. The README +file in that directory explains how to run these. + +For a description of the ed algorithm, see Kernighan and Plauger's book +"Software Tools in Pascal," Addison-Wesley, 1981. diff --git a/bin/ed/buf.c b/bin/ed/buf.c new file mode 100644 index 0000000..06dc7eb --- /dev/null +++ b/bin/ed/buf.c @@ -0,0 +1,289 @@ +/* $OpenBSD: buf.c,v 1.24 2019/06/28 13:34:59 deraadt Exp $ */ +/* $NetBSD: buf.c,v 1.15 1995/04/23 10:07:28 cgd Exp $ */ + +/* buf.c: This file contains the scratch-file buffer routines for the + ed line editor. */ +/*- + * Copyright (c) 1993 Andrew Moore, Talke Studio. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <limits.h> +#include <regex.h> +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "ed.h" + + +static FILE *sfp; /* scratch file pointer */ +static off_t sfseek; /* scratch file position */ +static int seek_write; /* seek before writing */ +static line_t buffer_head; /* incore buffer */ + +/* get_sbuf_line: get a line of text from the scratch file; return pointer + to the text */ +char * +get_sbuf_line(line_t *lp) +{ + static char *sfbuf = NULL; /* buffer */ + static int sfbufsz = 0; /* buffer size */ + int len; + + if (lp == &buffer_head) + return NULL; + seek_write = 1; /* force seek on write */ + /* out of position */ + if (sfseek != lp->seek) { + sfseek = lp->seek; + if (fseeko(sfp, sfseek, SEEK_SET) == -1) { + perror(NULL); + seterrmsg("cannot seek temp file"); + return NULL; + } + } + len = lp->len; + REALLOC(sfbuf, sfbufsz, len + 1, NULL); + if (fread(sfbuf, sizeof(char), len, sfp) != len) { + perror(NULL); + seterrmsg("cannot read temp file"); + return NULL; + } + sfseek += len; /* update file position */ + sfbuf[len] = '\0'; + return sfbuf; +} + + +/* put_sbuf_line: write a line of text to the scratch file and add a line node + to the editor buffer; return a pointer to the end of the text */ +char * +put_sbuf_line(char *cs) +{ + line_t *lp; + int len; + char *s; + + if ((lp = malloc(sizeof(line_t))) == NULL) { + perror(NULL); + seterrmsg("out of memory"); + return NULL; + } + /* assert: cs is '\n' terminated */ + for (s = cs; *s != '\n'; s++) + ; + if (s - cs >= LINECHARS) { + seterrmsg("line too long"); + free(lp); + return NULL; + } + len = s - cs; + /* out of position */ + if (seek_write) { + if (fseek(sfp, 0L, SEEK_END) == -1) { + perror(NULL); + seterrmsg("cannot seek temp file"); + free(lp); + return NULL; + } + sfseek = ftello(sfp); + seek_write = 0; + } + /* assert: SPL1() */ + if (fwrite(cs, sizeof(char), len, sfp) != len) { + sfseek = -1; + perror(NULL); + seterrmsg("cannot write temp file"); + free(lp); + return NULL; + } + lp->len = len; + lp->seek = sfseek; + add_line_node(lp); + sfseek += len; /* update file position */ + return ++s; +} + + +/* add_line_node: add a line node in the editor buffer after the current line */ +void +add_line_node(line_t *lp) +{ + line_t *cp; + + /* this get_addressed_line_node last! */ + cp = get_addressed_line_node(current_addr); + INSQUE(lp, cp); + addr_last++; + current_addr++; +} + + +/* get_line_node_addr: return line number of pointer */ +int +get_line_node_addr(line_t *lp) +{ + line_t *cp = &buffer_head; + int n = 0; + + while (cp != lp && (cp = cp->q_forw) != &buffer_head) + n++; + if (n && cp == &buffer_head) { + seterrmsg("invalid address"); + return ERR; + } + return n; +} + + +/* get_addressed_line_node: return pointer to a line node in the editor buffer */ +line_t * +get_addressed_line_node(int n) +{ + static line_t *lp = &buffer_head; + static int on = 0; + + SPL1(); + if (n > on) { + if (n <= (on + addr_last) >> 1) + for (; on < n; on++) + lp = lp->q_forw; + else { + lp = buffer_head.q_back; + for (on = addr_last; on > n; on--) + lp = lp->q_back; + } + } else { + if (n >= on >> 1) + for (; on > n; on--) + lp = lp->q_back; + else { + lp = &buffer_head; + for (on = 0; on < n; on++) + lp = lp->q_forw; + } + } + SPL0(); + return lp; +} + + +extern int newline_added; + +#define SCRATCH_TEMPLATE "/tmp/ed.XXXXXXXXXX" +static char sfn[sizeof(SCRATCH_TEMPLATE)+1] = ""; /* scratch file name */ + +/* open_sbuf: open scratch file */ +int +open_sbuf(void) +{ + int fd = -1; + + isbinary = newline_added = 0; + strlcpy(sfn, SCRATCH_TEMPLATE, sizeof sfn); + if ((fd = mkstemp(sfn)) == -1 || + (sfp = fdopen(fd, "w+")) == NULL) { + if (fd != -1) + close(fd); + perror(sfn); + seterrmsg("cannot open temp file"); + return ERR; + } + return 0; +} + + +/* close_sbuf: close scratch file */ +int +close_sbuf(void) +{ + if (sfp) { + if (fclose(sfp) == EOF) { + perror(sfn); + seterrmsg("cannot close temp file"); + return ERR; + } + sfp = NULL; + unlink(sfn); + } + sfseek = seek_write = 0; + return 0; +} + + +/* quit: remove_lines scratch file and exit */ +void +quit(int n) +{ + if (sfp) { + fclose(sfp); + unlink(sfn); + } + exit(n); +} + + +static unsigned char ctab[256]; /* character translation table */ + +/* init_buffers: open scratch buffer; initialize line queue */ +void +init_buffers(void) +{ + int i = 0; + + /* Read stdin one character at a time to avoid i/o contention + with shell escapes invoked by nonterminal input, e.g., + ed - <<EOF + !cat + hello, world + EOF */ + setvbuf(stdin, NULL, _IONBF, 0); + if (open_sbuf() < 0) + quit(2); + REQUE(&buffer_head, &buffer_head); + for (i = 0; i < 256; i++) + ctab[i] = i; +} + + +/* translit_text: translate characters in a string */ +char * +translit_text(char *s, int len, int from, int to) +{ + static int i = 0; + + unsigned char *us; + + ctab[i] = i; /* restore table to initial state */ + ctab[i = from] = to; + for (us = (unsigned char *) s; len-- > 0; us++) + *us = ctab[*us]; + return s; +} diff --git a/bin/ed/ed.1 b/bin/ed/ed.1 new file mode 100644 index 0000000..f5fc5be --- /dev/null +++ b/bin/ed/ed.1 @@ -0,0 +1,859 @@ +.\" $OpenBSD: ed.1,v 1.76 2021/03/08 02:47:26 jsg Exp $ +.\" +.\" Copyright (c) 1993 Andrew Moore, Talke Studio. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd $Mdocdate: March 8 2021 $ +.Dt ED 1 +.Os +.Sh NAME +.Nm ed +.Nd text editor +.Sh SYNOPSIS +.Nm ed +.Op Fl +.Op Fl s +.Op Fl p Ar string +.Op Ar file +.Sh DESCRIPTION +.Nm +is a line-oriented text editor. +It is used to create, display, modify, and otherwise manipulate text files. +If invoked with a +.Ar file +argument, then a copy of +.Ar file +is read into the editor's buffer. +Changes are made to this copy and not directly to +.Ar file +itself. +Upon quitting +.Nm ed , +any changes not explicitly saved with a +.Ic w +command are lost. +.Pp +Editing is done in two distinct modes: +.Em command +and +.Em input . +When first invoked, +.Nm +is in command mode. +In this mode, commands are read from the standard input and +executed to manipulate the contents of the editor buffer. +.Pp +A typical command might look like: +.Pp +.Dl ,s/old/new/g +.Pp +which replaces all occurrences of the string +.Qq old +with +.Qq new . +.Pp +When an input command, such as +.Ic a +.Pq append , +.Ic i +.Pq insert , +or +.Ic c +.Pq change +is given, +.Nm +enters input mode. +This is the primary means of adding text to a file. +In this mode, no commands are available; +instead, the standard input is written directly to the editor buffer. +Lines consist of text up to and including a newline character. +Input mode is terminated by entering a single period +.Pq Ql \&. +on a line. +.Pp +All +.Nm +commands operate on whole lines or ranges of lines; e.g., +the +.Ic d +command deletes lines; the +.Ic m +command moves lines, and so on. +It is possible to modify only a portion of a line by means of replacement, +as in the example above. +However, even here, the +.Ic s +command is applied to whole lines at a time. +.Pp +In general, +.Nm +commands consist of zero or more line addresses, followed by a single +character command and possibly additional parameters; i.e., +commands have the structure: +.Pp +.Sm off +.D1 Oo Ar address Oo , Ar address Oc Oc Ar command Op Ar parameters +.Sm on +.Pp +The address(es) indicate the line or range of lines to be affected by the +command. +If fewer addresses are given than the command accepts, then +default addresses are supplied. +.Pp +Many +.Nm +commands and line addresses support basic regular expressions +.Pq BREs . +See +.Xr re_format 7 +for more information on regular expressions. +.Pp +The options are as follows: +.Bl -tag -width "-p string" +.It Fl +Same as the +.Fl s +option +.Pq deprecated . +.It Fl p Ar string +Specifies a command prompt. +This may be toggled on and off with the +.Ic P +command. +.It Fl s +Suppress diagnostics. +This should be used if +.Nm +standard input is from a script. +.It Ar file +Specifies the name of a file to read. +If +.Ar file +is prefixed with a +bang +.Pq Ql \&! , +then it is interpreted as a shell command. +In this case, what is read is the standard output of +.Ar file +executed via +.Xr sh 1 . +To read a file whose name begins with a bang, prefix the +name with a backslash +.Pq Ql \e . +The default filename is set to +.Ar file +only if it is not prefixed with a bang. +.El +.Ss LINE ADDRESSING +An address represents the number of a line in the buffer. +.Nm +maintains a +.Em current address +which is typically supplied to commands as the default address +when none is specified. +When a file is first read, the current address is set to the last line +of the file. +In general, the current address is set to the last line affected by a command. +.Pp +A line address is +constructed from one of the bases in the list below, optionally followed +by a numeric offset. +The offset may include any combination of digits, operators (e.g., +.Ql + , +.Ql - , +and +.Ql ^ ) , +and whitespace. +Addresses are read from left to right, and their values are computed +relative to the current address. +.Pp +One exception to the rule that addresses represent line numbers is the +address +.Ad 0 +.Pq zero . +This means +.Dq before the first line , +and is legal wherever it makes sense. +.Pp +An address range is two addresses separated either by a comma or semi-colon. +The value of the first address in a range cannot exceed the +value of the second. +If only one address is given in a range, +then the second address is set to the given address. +If an +.Ar n Ns -tuple +of addresses is given where +.Ar n +\*(Gt 2, +then the corresponding range is determined by the last two addresses in the +.Ar n Ns -tuple . +If only one address is expected, then the last address is used. +.Pp +Each address in a comma-delimited range is interpreted relative to the +current address. +In a semi-colon-delimited range, the first address is +used to set the current address, and the second address is interpreted +relative to the first. +.Pp +The following address symbols are recognized: +.Bl -tag -width Ds +.It \&. +The current line +.Pq address +in the buffer. +.It $ +The last line in the buffer. +.It Ar n +The +.Ar n Ns th +line in the buffer, where +.Ar n +is a number in the range +.Ad [0,$] . +.It - or ^ +The previous line. +This is equivalent to +.Ad \-1 +and may be repeated with cumulative effect. +.It Xo +.Pf - Ar n No or\ \& +.Pf ^ Ar n +.Xc +The +.Ar n Ns th +previous line, where +.Ar n +is a non-negative number. +.It + +The next line. +This is equivalent to +.Ad +1 +and may be repeated with cumulative effect. +.It + Ns Ar n +The +.Ar n Ns th +next line, where +.Ar n +is a non-negative number. +.It \&, or % +The first through last lines in the buffer. +This is equivalent to the address range +.Ad 1,$ . +.It \&; +The current through last lines in the buffer. +This is equivalent to the address range +.Ad .,$ . +.It / Ns Ar re Ns / +The next line containing the regular expression +.Ar re . +The search wraps to the beginning of the buffer and continues down to the +current line, if necessary. +The second slash can be omitted if it ends a line. +.Qq // +repeats the last search. +.It Pf ? Ar re ? +The previous line containing the regular expression +.Ar re . +The search wraps to the end of the buffer and continues up to the +current line, if necessary. +The second question mark can be omitted if it ends a line. +.Qq ?? +repeats the last search. +.It \&' Ns Ar lc +The line previously marked by a +.Ic k +.Pq mark +command, where +.Ar lc +is a lower case letter. +.El +.Ss COMMANDS +All +.Nm +commands are single characters, though some require additional parameters. +If a command's parameters extend over several lines, then +each line except for the last must be terminated with a backslash +.Pq Ql \e . +.Pp +In general, at most one command is allowed per line. +However, most commands accept a print suffix, which is any of +.Ic p +.Pq print , +.Ic l +.Pq list , +or +.Ic n +.Pq enumerate , +to print the last line affected by the command. +.Pp +.Nm +recognizes the following commands. +The commands are shown together with +the default address or address range supplied if none is specified +.Pq in parentheses , +and other possible arguments on the right. +.Bl -tag -width Dxxs +.It (.) Ns Ic a +Appends text to the buffer after the addressed line. +Text is entered in input mode. +The current address is set to last line entered. +.It (.,.) Ns Ic c +Changes lines in the buffer. +The addressed lines are deleted from the buffer, +and text is appended in their place. +Text is entered in input mode. +The current address is set to last line entered. +.It (.,.) Ns Ic d +Deletes the addressed lines from the buffer. +If there is a line after the deleted range, then the current address is set +to this line. +Otherwise the current address is set to the line before the deleted range. +.It Ic e Ar file +Edits +.Ar file , +and sets the default filename. +If +.Ar file +is not specified, then the default filename is used. +Any lines in the buffer are deleted before the new file is read. +The current address is set to the last line read. +.It Ic e No \&! Ns Ar command +Edits the standard output of +.No \&! Ns Ar command , +(see +.Ic \&! Ns Ar command +below). +The default filename is unchanged. +Any lines in the buffer are deleted before the output of +.Ar command +is read. +The current address is set to the last line read. +.It Ic E Ar file +Edits +.Ar file +unconditionally. +This is similar to the +.Ic e +command, except that unwritten changes are discarded without warning. +The current address is set to the last line read. +.It Ic f Ar file +Sets the default filename to +.Ar file . +If +.Ar file +is not specified, then the default unescaped filename is printed. +.Sm off +.It Xo +.Pf (1,$) Ic g No / +.Ar re No / Ar command-list +.Xc +.Sm on +Mark each addressed line matching the regular expression +.Ar re +for modification. +The current address is set to each marked line in turn, and then the +.Ar command-list +is executed each time. +The command-list can change the current line number, +and it is not changed back after the command-list ended. +When a marked line is changed, it is unmarked +and the command-list won't be executed for it any more. +If no lines were matched, +the current line number remains unchanged. +.Pp +Each command in +.Ar command-list +must be on a separate line, +and every line except for the last must be terminated by a backslash +.Pq Sq \e . +Any commands are allowed, except for +.Ic g , +.Ic G , +.Ic v , +and +.Ic V . +An empty +.Ar command-list +is equivalent to a +.Ic p +command \(em unlike for the +.Cm G +command, where an empty command-list does nothing, and unlike an empty +command, which is equivalent to the command +.Cm +p . +If the +.Ar command-list +is empty, the trailing slash can be omitted. +.Sm off +.It (1,$) Ic G No / Ar re No / +.Sm on +Interactively edits the addressed lines matching a regular expression +.Ar re . +The trailing slash after +.Ar re +can be omitted. +For each matching line, the line is printed, the current address is set, +and the user is prompted to enter a +.Ar command-list . +At the end of the +.Ic G +command, the current address is set to the last line affected by +.Pq the last +command-list. +If no lines were matched, +the current line number remains unchanged. +.Pp +The format of +.Ar command-list +is the same as that of the +.Ic g +command, but an empty command list does nothing. +A single +.Sq & +repeats the last non-empty command list. +.It Ic H +Toggles the printing of error explanations. +By default, explanations are not printed. +It is recommended that +.Nm +scripts begin with this command to aid in debugging. +.It Ic h +Prints an explanation of the last error. +.It (.) Ns Ic i +Inserts text in the buffer before the current line. +Text is entered in input mode. +The current address is set to the last line entered. +.It (.,+) Ns Ic j +Joins the addressed lines. +The addressed lines are deleted from the buffer and replaced by a single +line containing their joined text. +The current address is set to the resultant line. +.It (.) Ns Ic k Ns Ar lc +Marks a line with a lower case letter +.Ar lc . +The line can then be addressed as +.Ic ' Ns Ar lc +(i.e., a single quote followed by +.Ar lc ) +in subsequent commands. +The mark is not cleared until the line is deleted or otherwise modified. +.It (.,.) Ns Ic l +Prints the addressed lines unambiguously. +The current address is set to the last line printed. +.It (.,.) Ns Ic m Ns (.) +Moves lines in the buffer. +The addressed lines are moved to after the +right-hand destination address, which may be the address +.Ad 0 +.Pq zero . +The current address is set to the last line moved. +.It (.,.) Ns Ic n +Prints the addressed lines along with their line numbers. +The current address is set to the last line printed. +.It (.,.) Ns Ic p +Prints the addressed lines. +The current address is set to the last line printed. +.It Ic P +Toggles the command prompt on and off. +Unless a prompt was specified with the command-line option +.Fl p Ar string , +the command prompt is by default turned off. +.It Ic q +Quits +.Nm ed . +.It Ic Q +Quits +.Nm +unconditionally. +This is similar to the +.Ic q +command, except that unwritten changes are discarded without warning. +.It ($) Ns Ic r Ar file +Reads +.Ar file +to after the addressed line. +If +.Ar file +is not specified, then the default filename is used. +If there was no default filename prior to the command, +then the default filename is set to +.Ar file . +Otherwise, the default filename is unchanged. +The current address is set to the last line read. +.It ($) Ns Ic r No \&! Ns Ar command +Reads to after the addressed line the standard output of +.No \&! Ns Ar command , +(see +.Ic \&! Ns Ar command +below). +The default filename is unchanged. +The current address is set to the last line read. +.Sm off +.It Xo +.Pf (.,.) Ic s No / Ar re +.No / Ar replacement No /\ \& +.Pf (.,.) Ic s No / Ar re +.No / Ar replacement No / Ic g\ \& +.No (.,.) Ic s No / Ar re +.No / Ar replacement No / Ar n +.Xc +.Sm on +Replaces text in the addressed lines matching a regular expression +.Ar re +with +.Ar replacement . +By default, only the first match in each line is replaced. +If the +.Ic g +.Pq global +suffix is given, then every match is replaced. +The +.Ar n +suffix, where +.Ar n +is a positive number, causes only the +.Ar n Ns th +match to be replaced. +It is an error if no substitutions are performed on any of the addressed +lines. +The current address is set the last line affected. +.Pp +.Ar re +and +.Ar replacement +may be delimited by any character other than space and newline +(see the +.Ic s +command below). +If one or two of the last delimiters is omitted, then the last line +affected is printed as though the print suffix +.Ic p +were specified. +.Pp +An unescaped +.Ql & +in +.Ar replacement +is replaced by the currently matched text. +The character sequence +.Pf \e Ar m , +where +.Ar m +is a number in the range [1,9], is replaced by the +.Ar m Ns th +backreference expression of the matched text. +If +.Ar replacement +consists of a single +.Ql % , +then +.Ar replacement +from the last substitution is used. +Newlines may be embedded in +.Ar replacement +if they are escaped with a backslash +.Pq Ql \e . +.It (.,.) Ns Ic s +Repeats the last substitution. +This form of the +.Ic s +command accepts a count suffix +.Ar n , +or any combination of the characters +.Ic r , +.Ic g , +and +.Ic p . +If a count suffix +.Ar n +is given, then only the +.Ar n Ns th +match is replaced. +The +.Ic r +suffix causes the regular expression of the last search to be used +instead of that of the last substitution. +The +.Ic g +suffix toggles the global suffix of the last substitution. +The +.Ic p +suffix toggles the print suffix of the last substitution. +The current address is set to the last line affected. +.It (.,.) Ns Ic t Ns (.) +Copies +.Pq i.e., transfers +the addressed lines to after the right-hand destination address, +which may be the address +.Ad 0 +.Pq zero . +The current address is set to the last line copied. +.It Ic u +Undoes the last command and restores the current address +to what it was before the command. +The global commands +.Ic g , +.Ic G , +.Ic v , +and +.Ic V +are treated as a single command by undo. +.Ic u +is its own inverse. +.Sm off +.It Xo +.Pf (1,$) Ic v No / Ar re +.Pf / Ar command-list +.Xc +.Sm on +The same as the +.Ic g +command, except that it applies +.Ar command-list +to each of the addressed lines not matching the regular expression +.Ar re . +.Sm off +.It Xo +.Pf (1,$) Ic V No / +.Ar re No / +.Xc +.Sm on +The same as the +.Ic G +command, except that it interactively edits the addressed lines +not matching the regular expression +.Ar re . +.It (1,$) Ns Ic w Ar file +Writes the addressed lines to +.Ar file . +Any previous contents of +.Ar file +are lost without warning. +If there is no default filename, then the default filename is set to +.Ar file , +otherwise it is unchanged. +If no filename is specified, then the default filename is used. +The current address is unchanged. +.It (1,$) Ns Ic wq Ar file +Writes the addressed lines to +.Ar file , +and then executes a +.Ic q +command. +.It (1,$) Ns Ic w No \&! Ns Ar command +Writes the addressed lines to the standard input of +.No \&! Ns Ar command , +(see +.Ic \&! Ns Ar command +below). +The default filename and current address are unchanged. +.It (1,$) Ns Ic W Ar file +Appends the addressed lines to the end of +.Ar file . +This is similar to the +.Ic w +command, except that the previous contents of file are not clobbered. +The current address is unchanged. +.It (+) Ns Ic z Ns Ar n +Scrolls +.Ar n +lines at a time starting at addressed line. +If +.Ar n +is not specified, then the current window size is used. +The current address is set to the last line printed. +.It ($) Ns Ic = +Prints the line number of the addressed line. +.It (+) +An address without a command prints the addressed line +and sets the current address to that line. +If the address is also omitted, it defaults to the next line (+). +.It Ic \&! Ns Ar command +Executes +.Ar command +via +.Xr sh 1 . +If the first character of +.Ar command +is +.Sq !\& , +then it is replaced by text of the previous +.Ic \&! Ns Ar command . +.Nm +does not process +.Ar command +for +.Sq \e +.Pq backslash +escapes. +However, an unescaped +.Sq % +is replaced by the default filename. +When the shell returns from execution, a +.Sq \&! +is printed to the standard output. +The current line is unchanged. +.El +.Sh ASYNCHRONOUS EVENTS +.Bl -tag -width "SIGWINCH" +.It Dv SIGHUP +If the current buffer has changed since it was last written, +.Nm +attempts to write the buffer to the file +.Pa ed.hup . +Nothing is written to the currently remembered file, and +.Nm +exits. +.It Dv SIGINT +When an interrupt occurs, +.Nm +prints +.Sq ?\en +and returns to command mode. +If interrupted during text input, +the text already input is written to the current buffer, +as if text input had been normally terminated. +.It Dv SIGQUIT +This signal is ignored. +.It Dv SIGWINCH +The screen is resized. +.El +.Sh FILES +.Bl -tag -width /tmp/ed.* -compact +.It Pa /tmp/ed.* +buffer file +.It Pa ed.hup +where +.Nm +attempts to write the buffer if the terminal hangs up +.El +.Sh EXIT STATUS +.Ex -std ed +.Sh DIAGNOSTICS +When an error occurs, +.Nm +prints a +.Sq \&? +and either returns to command mode or exits if its input is from a script. +An explanation of the last error can be printed with the +.Ic h +.Pq help +command. +.Pp +Since the +.Ic g +.Pq global +command masks any errors from failed searches and substitutions, +it can be used to perform conditional operations in scripts; e.g., +.Pp +.Dl g/old/s//new/ +.Pp +replaces any occurrences of +.Qq old +with +.Qq new . +.Pp +If the +.Ic u +.Pq undo +command occurs in a global command list, +then the command list is executed only once. +.Pp +If diagnostics are not disabled, attempting to quit +.Nm +or edit another file before writing a modified buffer results in an error. +If the command is entered a second time, it succeeds, +but any changes to the buffer are lost. +.Sh SEE ALSO +.Xr sed 1 , +.Xr sh 1 , +.Xr vi 1 , +.Xr re_format 7 +.Rs +.\" 4.4BSD USD:9 +.%A B. W. Kernighan +.%T A Tutorial Introduction to the UNIX Text Editor +.Re +.Rs +.\" 4.4BSD USD:10 +.%A B. W. Kernighan +.%T Advanced Editing on UNIX +.Re +.Rs +.%A B. W. Kernighan +.%A P. J. Plauger +.%B Software Tools in Pascal +.%O Addison-Wesley +.%D 1981 +.Re +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. +.Pp +The commands +.Cm s +(to repeat the last substitution), +.Cm W , +.Cm wq , +and +.Cm z +as well as the address specifier +.Sq % +are extensions to that specification. +.Pp +The +.St -p1003.1-2008 +specification says the +.Sq ^ +address specifier is neither required nor prohibited; +additionally, it says behaviour for the +.Fl +option is +.Dq unspecified . +.Sh HISTORY +An +.Nm +command appeared in +.At v1 . +.Sh CAVEATS +.Nm +processes +.Ar file +arguments for backslash escapes, i.e., in a filename, +any characters preceded by a backslash +.Pq Ql \e +are interpreted literally. +.Pp +If a text +.Pq non-binary +file is not terminated by a newline character, +then +.Nm +appends one on reading/writing it. +In the case of a binary file, +.Nm +does not append a newline on reading/writing. diff --git a/bin/ed/ed.h b/bin/ed/ed.h new file mode 100644 index 0000000..12d7e3d --- /dev/null +++ b/bin/ed/ed.h @@ -0,0 +1,201 @@ +/* $OpenBSD: ed.h,v 1.22 2016/03/27 00:43:38 mmcc Exp $ */ +/* $NetBSD: ed.h,v 1.23 1995/03/21 09:04:40 cgd Exp $ */ + +/* ed.h: type and constant definitions for the ed editor. */ +/* + * Copyright (c) 1993 Andrew Moore + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ed.h,v 1.5 1994/02/01 00:34:39 alm Exp + */ + +#include <limits.h> +#include <regex.h> +#include <signal.h> + +#define ERR (-2) +#define EMOD (-3) +#define FATAL (-4) + +#define MINBUFSZ 512 /* minimum buffer size - must be > 0 */ +#define SE_MAX 30 /* max subexpressions in a regular expression */ +#define LINECHARS INT_MAX /* max chars per line */ + +/* gflags */ +#define GLB 001 /* global command */ +#define GPR 002 /* print after command */ +#define GLS 004 /* list after command */ +#define GNP 010 /* enumerate after command */ +#define GSG 020 /* global substitute */ + +/* Line node */ +typedef struct line { + struct line *q_forw; + struct line *q_back; + off_t seek; /* address of line in scratch buffer */ + int len; /* length of line */ +} line_t; + + +typedef struct undo { + +/* type of undo nodes */ +#define UADD 0 +#define UDEL 1 +#define UMOV 2 +#define VMOV 3 + + int type; /* command type */ + line_t *h; /* head of list */ + line_t *t; /* tail of list */ +} undo_t; + +#ifndef max +# define max(a,b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef min +# define min(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#define INC_MOD(l, k) ((l) + 1 > (k) ? 0 : (l) + 1) +#define DEC_MOD(l, k) ((l) - 1 < 0 ? (k) : (l) - 1) + +/* SPL1: disable some interrupts (requires reliable signals) */ +#define SPL1() mutex++ + +/* SPL0: enable all interrupts; check signal flags (requires reliable signals) */ +#define SPL0() \ + do { \ + if (--mutex == 0) { \ + if (sighup) \ + handle_hup(SIGHUP); \ + if (sigint) \ + handle_int(SIGINT); \ + } \ + } while (0) + +/* STRTOI: convert a string to int */ +#define STRTOI(i, p) { \ + long l = strtol(p, &p, 10); \ + if (l <= INT_MIN || l >= INT_MAX) { \ + seterrmsg("number out of range"); \ + i = 0; \ + return ERR; \ + } else \ + i = (int)l; \ +} + +/* REALLOC: assure at least a minimum size for buffer b */ +#define REALLOC(b,n,i,err) \ +if ((i) > (n)) { \ + int ti = (n); \ + char *ts; \ + SPL1(); \ + if ((ts = realloc((b), ti += max((i), MINBUFSZ))) == NULL) { \ + perror(NULL); \ + seterrmsg("out of memory"); \ + SPL0(); \ + return err; \ + } \ + (n) = ti; \ + (b) = ts; \ + SPL0(); \ +} + +/* REQUE: link pred before succ */ +#define REQUE(pred, succ) (pred)->q_forw = (succ), (succ)->q_back = (pred) + +/* INSQUE: insert elem in circular queue after pred */ +#define INSQUE(elem, pred) \ +{ \ + REQUE((elem), (pred)->q_forw); \ + REQUE((pred), elem); \ +} + +/* remque: remove_lines elem from circular queue */ +#define REMQUE(elem) REQUE((elem)->q_back, (elem)->q_forw); + +/* NUL_TO_NEWLINE: overwrite ASCII NULs with newlines */ +#define NUL_TO_NEWLINE(s, l) translit_text(s, l, '\0', '\n') + +/* NEWLINE_TO_NUL: overwrite newlines with ASCII NULs */ +#define NEWLINE_TO_NUL(s, l) translit_text(s, l, '\n', '\0') + +/* Local Function Declarations */ +void add_line_node(line_t *); +int build_active_list(int); +void clear_active_list(void); +void clear_undo_stack(void); +int close_sbuf(void); +int delete_lines(int, int); +int display_lines(int, int, int); +int exec_command(void); +int exec_global(int, int); +int extract_addr_range(void); +int extract_subst_tail(int *, int *); +line_t *get_addressed_line_node(int); +regex_t *get_compiled_pattern(void); +char *get_extended_line(int *, int); +int get_line_node_addr(line_t *); +char *get_sbuf_line(line_t *); +int get_tty_line(void); +void handle_hup(int); +void handle_int(int); +int has_trailing_escape(char *, char *); +void init_buffers(void); +int open_sbuf(void); +int pop_undo_stack(void); +undo_t *push_undo_stack(int, int, int); +char *put_sbuf_line(char *); +int put_tty_line(char *, int, int, int); +void quit(int); +int read_file(char *, int); +int search_and_replace(regex_t *, int, int); +void seterrmsg(char *); +char *strip_escapes(char *); +char *translit_text(char *, int, int, int); +void unmark_line_node(line_t *); +void unset_active_nodes(line_t *, line_t *); +int write_file(char *, char *, int, int); + +/* global buffers */ +extern char *ibuf; +extern char *ibufp; +extern int ibufsz; + +/* global flags */ +extern int isbinary; +extern int isglobal; +extern int modified; + +extern volatile sig_atomic_t mutex; +extern volatile sig_atomic_t sighup; +extern volatile sig_atomic_t sigint; + +/* global vars */ +extern int addr_last; +extern int current_addr; +extern int first_addr; +extern int lineno; +extern int second_addr; diff --git a/bin/ed/glbl.c b/bin/ed/glbl.c new file mode 100644 index 0000000..90e602c --- /dev/null +++ b/bin/ed/glbl.c @@ -0,0 +1,207 @@ +/* $OpenBSD: glbl.c,v 1.20 2018/06/04 13:26:21 martijn Exp $ */ +/* $NetBSD: glbl.c,v 1.2 1995/03/21 09:04:41 cgd Exp $ */ + +/* glob.c: This file contains the global command routines for the ed line + editor */ +/*- + * Copyright (c) 1993 Andrew Moore, Talke Studio. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/ioctl.h> +#include <sys/wait.h> + +#include <regex.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "ed.h" + +static int set_active_node(line_t *); +static line_t *next_active_node(void); + +/* build_active_list: add line matching a pattern to the global-active list */ +int +build_active_list(int isgcmd) +{ + regex_t *pat; + line_t *lp; + int n; + char *s; + char delimiter; + + if ((delimiter = *ibufp) == ' ' || delimiter == '\n') { + seterrmsg("invalid pattern delimiter"); + return ERR; + } else if ((pat = get_compiled_pattern()) == NULL) + return ERR; + else if (*ibufp == delimiter) + ibufp++; + clear_active_list(); + lp = get_addressed_line_node(first_addr); + for (n = first_addr; n <= second_addr; n++, lp = lp->q_forw) { + if ((s = get_sbuf_line(lp)) == NULL) + return ERR; + if (isbinary) + NUL_TO_NEWLINE(s, lp->len); + if ((!regexec(pat, s, 0, NULL, 0)) == isgcmd && + set_active_node(lp) < 0) + return ERR; + } + return 0; +} + + +/* exec_global: apply command list in the command buffer to the active + lines in a range; return command status */ +int +exec_global(int interact, int gflag) +{ + static char *ocmd = NULL; + static int ocmdsz = 0; + + line_t *lp = NULL; + int status; + int n; + char *cmd = NULL; + + if (!interact) { + if (!strcmp(ibufp, "\n")) + cmd = "p\n"; /* null cmd-list == `p' */ + else if ((cmd = get_extended_line(&n, 0)) == NULL) + return ERR; + } + clear_undo_stack(); + while ((lp = next_active_node()) != NULL) { + if ((current_addr = get_line_node_addr(lp)) < 0) + return ERR; + if (interact) { + /* print current_addr; get a command in global syntax */ + if (display_lines(current_addr, current_addr, gflag) < 0) + return ERR; + while ((n = get_tty_line()) > 0 && + ibuf[n - 1] != '\n') + clearerr(stdin); + if (n < 0) + return ERR; + else if (n == 0) { + seterrmsg("unexpected end-of-file"); + return ERR; + } else if (n == 1 && !strcmp(ibuf, "\n")) + continue; + else if (n == 2 && !strcmp(ibuf, "&\n")) { + if (cmd == NULL) { + seterrmsg("no previous command"); + return ERR; + } else cmd = ocmd; + } else if ((cmd = get_extended_line(&n, 0)) == NULL) + return ERR; + else { + REALLOC(ocmd, ocmdsz, n + 1, ERR); + memcpy(ocmd, cmd, n + 1); + cmd = ocmd; + } + + } + ibufp = cmd; + for (; *ibufp;) + if ((status = extract_addr_range()) < 0 || + (status = exec_command()) < 0 || + (status > 0 && (status = display_lines( + current_addr, current_addr, status)) < 0)) + return status; + } + return 0; +} + + +static line_t **active_list; /* list of lines active in a global command */ +static int active_last; /* index of last active line in active_list */ +static int active_size; /* size of active_list */ +static int active_ptr; /* active_list index (non-decreasing) */ +static int active_ndx; /* active_list index (modulo active_last) */ + +/* set_active_node: add a line node to the global-active list */ +static int +set_active_node(line_t *lp) +{ + if (active_last + 1 > active_size) { + int ti = active_size; + line_t **ts; + SPL1(); + if ((ts = reallocarray(active_list, + (ti += MINBUFSZ), sizeof(line_t **))) == NULL) { + perror(NULL); + seterrmsg("out of memory"); + SPL0(); + return ERR; + } + active_size = ti; + active_list = ts; + SPL0(); + } + active_list[active_last++] = lp; + return 0; +} + + +/* unset_active_nodes: remove a range of lines from the global-active list */ +void +unset_active_nodes(line_t *np, line_t *mp) +{ + line_t *lp; + int i; + + for (lp = np; lp != mp; lp = lp->q_forw) + for (i = 0; i < active_last; i++) + if (active_list[active_ndx] == lp) { + active_list[active_ndx] = NULL; + active_ndx = INC_MOD(active_ndx, active_last - 1); + break; + } else active_ndx = INC_MOD(active_ndx, active_last - 1); +} + + +/* next_active_node: return the next global-active line node */ +static line_t * +next_active_node(void) +{ + while (active_ptr < active_last && active_list[active_ptr] == NULL) + active_ptr++; + return (active_ptr < active_last) ? active_list[active_ptr++] : NULL; +} + + +/* clear_active_list: clear the global-active list */ +void +clear_active_list(void) +{ + SPL1(); + active_size = active_last = active_ptr = active_ndx = 0; + free(active_list); + active_list = NULL; + SPL0(); +} diff --git a/bin/ed/io.c b/bin/ed/io.c new file mode 100644 index 0000000..97306be --- /dev/null +++ b/bin/ed/io.c @@ -0,0 +1,343 @@ +/* $OpenBSD: io.c,v 1.24 2019/06/28 13:41:42 deraadt Exp $ */ +/* $NetBSD: io.c,v 1.2 1995/03/21 09:04:43 cgd Exp $ */ + +/* io.c: This file contains the i/o routines for the ed line editor */ +/*- + * Copyright (c) 1993 Andrew Moore, Talke Studio. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <regex.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "ed.h" + +static int read_stream(FILE *, int); +static int get_stream_line(FILE *); +static int write_stream(FILE *, int, int); +static int put_stream_line(FILE *, char *, int); + +extern int scripted; + +/* read_file: read a named file/pipe into the buffer; return line count */ +int +read_file(char *fn, int n) +{ + FILE *fp; + int size; + + + fp = (*fn == '!') ? popen(fn + 1, "r") : fopen(strip_escapes(fn), "r"); + if (fp == NULL) { + perror(fn); + seterrmsg("cannot open input file"); + return ERR; + } else if ((size = read_stream(fp, n)) < 0) + return ERR; + else if ((*fn == '!') ? pclose(fp) == -1 : fclose(fp) == EOF) { + perror(fn); + seterrmsg("cannot close input file"); + return ERR; + } + if (!scripted) + fprintf(stderr, "%d\n", size); + return current_addr - n; +} + + +static char *sbuf; /* file i/o buffer */ +static int sbufsz; /* file i/o buffer size */ +int newline_added; /* if set, newline appended to input file */ + +/* read_stream: read a stream into the editor buffer; return status */ +static int +read_stream(FILE *fp, int n) +{ + line_t *lp = get_addressed_line_node(n); + undo_t *up = NULL; + unsigned int size = 0; + int o_newline_added = newline_added; + int o_isbinary = isbinary; + int appended = (n == addr_last); + int len; + + isbinary = newline_added = 0; + for (current_addr = n; (len = get_stream_line(fp)) > 0; size += len) { + SPL1(); + if (put_sbuf_line(sbuf) == NULL) { + SPL0(); + return ERR; + } + lp = lp->q_forw; + if (up) + up->t = lp; + else if ((up = push_undo_stack(UADD, current_addr, + current_addr)) == NULL) { + SPL0(); + return ERR; + } + SPL0(); + } + if (len < 0) + return ERR; + if (appended && size && o_isbinary && o_newline_added) + fputs("newline inserted\n", stderr); + else if (newline_added && (!appended || (!isbinary && !o_isbinary))) + fputs("newline appended\n", stderr); + if (isbinary && newline_added && !appended) + size += 1; + if (!size) + newline_added = 1; + newline_added = appended ? newline_added : o_newline_added; + isbinary = isbinary | o_isbinary; + return size; +} + +/* get_stream_line: read a line of text from a stream; return line length */ +static int +get_stream_line(FILE *fp) +{ + int c; + int i = 0; + + while (((c = getc(fp)) != EOF || (!feof(fp) && + !ferror(fp))) && c != '\n') { + REALLOC(sbuf, sbufsz, i + 1, ERR); + if (!(sbuf[i++] = c)) + isbinary = 1; + } + REALLOC(sbuf, sbufsz, i + 2, ERR); + if (c == '\n') + sbuf[i++] = c; + else if (ferror(fp)) { + perror(NULL); + seterrmsg("cannot read input file"); + return ERR; + } else if (i) { + sbuf[i++] = '\n'; + newline_added = 1; + } + sbuf[i] = '\0'; + return (isbinary && newline_added && i) ? --i : i; +} + + +/* write_file: write a range of lines to a named file/pipe; return line count */ +int +write_file(char *fn, char *mode, int n, int m) +{ + FILE *fp; + int size; + + fp = (*fn == '!') ? popen(fn+1, "w") : fopen(strip_escapes(fn), mode); + if (fp == NULL) { + perror(fn); + seterrmsg("cannot open output file"); + return ERR; + } else if ((size = write_stream(fp, n, m)) < 0) + return ERR; + else if ((*fn == '!') ? pclose(fp) == -1 : fclose(fp) == EOF) { + perror(fn); + seterrmsg("cannot close output file"); + return ERR; + } + if (!scripted) + fprintf(stderr, "%d\n", size); + return n ? m - n + 1 : 0; +} + + +/* write_stream: write a range of lines to a stream; return status */ +static int +write_stream(FILE *fp, int n, int m) +{ + line_t *lp = get_addressed_line_node(n); + unsigned int size = 0; + char *s; + int len; + + for (; n && n <= m; n++, lp = lp->q_forw) { + if ((s = get_sbuf_line(lp)) == NULL) + return ERR; + len = lp->len; + if (n != addr_last || !isbinary || !newline_added) + s[len++] = '\n'; + if (put_stream_line(fp, s, len) < 0) + return ERR; + size += len; + } + return size; +} + + +/* put_stream_line: write a line of text to a stream; return status */ +static int +put_stream_line(FILE *fp, char *s, int len) +{ + while (len--) { + if (fputc(*s, fp) == EOF) { + perror(NULL); + seterrmsg("cannot write file"); + return ERR; + } + s++; + } + return 0; +} + +/* get_extended_line: get a an extended line from stdin */ +char * +get_extended_line(int *sizep, int nonl) +{ + static char *cvbuf = NULL; /* buffer */ + static int cvbufsz = 0; /* buffer size */ + + int l, n; + char *t = ibufp; + + while (*t++ != '\n') + ; + if ((l = t - ibufp) < 2 || !has_trailing_escape(ibufp, ibufp + l - 1)) { + *sizep = l; + return ibufp; + } + *sizep = -1; + REALLOC(cvbuf, cvbufsz, l, NULL); + memcpy(cvbuf, ibufp, l); + *(cvbuf + --l - 1) = '\n'; /* strip trailing esc */ + if (nonl) + l--; /* strip newline */ + for (;;) { + if ((n = get_tty_line()) < 0) + return NULL; + else if (n == 0 || ibuf[n - 1] != '\n') { + seterrmsg("unexpected end-of-file"); + return NULL; + } + REALLOC(cvbuf, cvbufsz, l + n, NULL); + memcpy(cvbuf + l, ibuf, n); + l += n; + if (n < 2 || !has_trailing_escape(cvbuf, cvbuf + l - 1)) + break; + *(cvbuf + --l - 1) = '\n'; /* strip trailing esc */ + if (nonl) l--; /* strip newline */ + } + REALLOC(cvbuf, cvbufsz, l + 1, NULL); + cvbuf[l] = '\0'; + *sizep = l; + return cvbuf; +} + + +/* get_tty_line: read a line of text from stdin; return line length */ +int +get_tty_line(void) +{ + int oi = 0; + int i = 0; + int c; + + for (;;) + switch (c = getchar()) { + default: + oi = 0; + REALLOC(ibuf, ibufsz, i + 2, ERR); + if (!(ibuf[i++] = c)) isbinary = 1; + if (c != '\n') + continue; + lineno++; + ibuf[i] = '\0'; + ibufp = ibuf; + return i; + case EOF: + if (ferror(stdin)) { + perror("stdin"); + seterrmsg("cannot read stdin"); + clearerr(stdin); + ibufp = NULL; + return ERR; + } else { + clearerr(stdin); + if (i != oi) { + oi = i; + continue; + } else if (i) + ibuf[i] = '\0'; + ibufp = ibuf; + return i; + } + } +} + + + +#define ESCAPES "\a\b\f\n\r\t\v\\$" +#define ESCCHARS "abfnrtv\\$" + +extern int rows; +extern int cols; + +/* put_tty_line: print text to stdout */ +int +put_tty_line(char *s, int l, int n, int gflag) +{ + int col = 0; + char *cp; + + if (gflag & GNP) { + printf("%d\t", n); + col = 8; + } + for (; l--; s++) { + if ((gflag & GLS) && ++col > cols) { + fputs("\\\n", stdout); + col = 1; + } + if (gflag & GLS) { + if (31 < *s && *s < 127 && *s != '\\' && *s != '$') + putchar(*s); + else { + putchar('\\'); + col++; + if (*s && (cp = strchr(ESCAPES, *s)) != NULL) + putchar(ESCCHARS[cp - ESCAPES]); + else { + putchar((((unsigned char) *s & 0300) >> 6) + '0'); + putchar((((unsigned char) *s & 070) >> 3) + '0'); + putchar(((unsigned char) *s & 07) + '0'); + col += 2; + } + } + + } else + putchar(*s); + } + if (gflag & GLS) + putchar('$'); + putchar('\n'); + return 0; +} diff --git a/bin/ed/main.c b/bin/ed/main.c new file mode 100644 index 0000000..dc73dd9 --- /dev/null +++ b/bin/ed/main.c @@ -0,0 +1,1394 @@ +/* $OpenBSD: main.c,v 1.66 2019/06/28 13:34:59 deraadt Exp $ */ +/* $NetBSD: main.c,v 1.3 1995/03/21 09:04:44 cgd Exp $ */ + +/* main.c: This file contains the main control and user-interface routines + for the ed line editor. */ +/*- + * Copyright (c) 1993 Andrew Moore, Talke Studio. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * CREDITS + * + * This program is based on the editor algorithm described in + * Brian W. Kernighan and P. J. Plauger's book "Software Tools + * in Pascal," Addison-Wesley, 1981. + * + * The buffering algorithm is attributed to Rodney Ruddock of + * the University of Guelph, Guelph, Ontario. + * + */ + +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <limits.h> +#include <pwd.h> +#include <regex.h> +#include <setjmp.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "ed.h" + +void signal_hup(int); +void signal_int(int); +void handle_winch(int); + +static int next_addr(void); +static int check_addr_range(int, int); +static int get_matching_node_addr(regex_t *, int); +static char *get_filename(int); +static int get_shell_command(void); +static int append_lines(int); +static int join_lines(int, int); +static int move_lines(int); +static int copy_lines(int); +static int mark_line_node(line_t *, int); +static int get_marked_node_addr(int); +static line_t *dup_line_node(line_t *); + +sigjmp_buf env; + +/* static buffers */ +static char errmsg[PATH_MAX + 40]; /* error message buffer */ +static char *shcmd; /* shell command buffer */ +static int shcmdsz; /* shell command buffer size */ +static int shcmdi; /* shell command buffer index */ +static char old_filename[PATH_MAX]; /* default filename */ + +/* global buffers */ +char *ibuf; /* ed command-line buffer */ +int ibufsz; /* ed command-line buffer size */ +char *ibufp; /* pointer to ed command-line buffer */ + +/* global flags */ +int garrulous = 0; /* if set, print all error messages */ +int isbinary; /* if set, buffer contains ASCII NULs */ +int isglobal; /* if set, doing a global command */ +int modified; /* if set, buffer modified since last write */ +int scripted = 0; /* if set, suppress diagnostics */ +int interactive = 0; /* if set, we are in interactive mode */ + +volatile sig_atomic_t mutex = 0; /* if set, signals set flags */ +volatile sig_atomic_t sighup = 0; /* if set, sighup received while mutex set */ +volatile sig_atomic_t sigint = 0; /* if set, sigint received while mutex set */ + +/* if set, signal handlers are enabled */ +volatile sig_atomic_t sigactive = 0; + +int current_addr; /* current address in editor buffer */ +int addr_last; /* last address in editor buffer */ +int lineno; /* script line number */ +static char *prompt; /* command-line prompt */ +static char *dps = "*"; /* default command-line prompt */ + +static const char usage[] = "usage: %s [-] [-s] [-p string] [file]\n"; + +static char *home; /* home directory */ + +void +seterrmsg(char *s) +{ + strlcpy(errmsg, s, sizeof(errmsg)); +} + +/* ed: line editor */ +int +main(volatile int argc, char ** volatile argv) +{ + int c, n; + int status = 0; + + if (pledge("stdio rpath wpath cpath proc exec tty", NULL) == -1) + err(1, "pledge"); + + home = getenv("HOME"); + +top: + while ((c = getopt(argc, argv, "p:sx")) != -1) + switch (c) { + case 'p': /* set prompt */ + dps = prompt = optarg; + break; + case 's': /* run script */ + scripted = 1; + break; + case 'x': /* use crypt */ + fprintf(stderr, "crypt unavailable\n?\n"); + break; + default: + fprintf(stderr, usage, argv[0]); + exit(1); + } + argv += optind; + argc -= optind; + if (argc && **argv == '-') { + scripted = 1; + if (argc > 1) { + optind = 1; + goto top; + } + argv++; + argc--; + } + + if (!(interactive = isatty(0))) { + struct stat sb; + + /* assert: pipes show up as fifo's when fstat'd */ + if (fstat(STDIN_FILENO, &sb) || !S_ISFIFO(sb.st_mode)) { + if (lseek(STDIN_FILENO, 0, SEEK_CUR)) { + interactive = 1; + setvbuf(stdout, NULL, _IOLBF, 0); + } + } + } + + /* assert: reliable signals! */ + if (isatty(STDIN_FILENO)) { + handle_winch(SIGWINCH); + signal(SIGWINCH, handle_winch); + } + signal(SIGHUP, signal_hup); + signal(SIGQUIT, SIG_IGN); + signal(SIGINT, signal_int); + if (sigsetjmp(env, 1)) { + status = -1; + fputs("\n?\n", stderr); + seterrmsg("interrupt"); + } else { + init_buffers(); + sigactive = 1; /* enable signal handlers */ + if (argc && **argv) { + if (read_file(*argv, 0) < 0 && !interactive) + quit(2); + else if (**argv != '!') + strlcpy(old_filename, *argv, + sizeof old_filename); + } else if (argc) { + fputs("?\n", stderr); + if (**argv == '\0') + seterrmsg("invalid filename"); + if (!interactive) + quit(2); + } + } + for (;;) { + if (status < 0 && garrulous) + fprintf(stderr, "%s\n", errmsg); + if (prompt) { + fputs(prompt, stdout); + fflush(stdout); + } + if ((n = get_tty_line()) < 0) { + status = ERR; + continue; + } else if (n == 0) { + if (modified && !scripted) { + fputs("?\n", stderr); + seterrmsg("warning: file modified"); + if (!interactive) { + if (garrulous) + fprintf(stderr, + "script, line %d: %s\n", + lineno, errmsg); + quit(2); + } + clearerr(stdin); + modified = 0; + status = EMOD; + continue; + } else + quit(0); + } else if (ibuf[n - 1] != '\n') { + /* discard line */ + seterrmsg("unexpected end-of-file"); + clearerr(stdin); + status = ERR; + continue; + } + isglobal = 0; + if ((status = extract_addr_range()) >= 0 && + (status = exec_command()) >= 0) + if (!status || (status && + (status = display_lines(current_addr, current_addr, + status)) >= 0)) + continue; + switch (status) { + case EOF: + quit(0); + break; + case EMOD: + modified = 0; + fputs("?\n", stderr); /* give warning */ + seterrmsg("warning: file modified"); + if (!interactive) { + if (garrulous) + fprintf(stderr, + "script, line %d: %s\n", + lineno, errmsg); + quit(2); + } + break; + case FATAL: + if (!interactive) { + if (garrulous) + fprintf(stderr, + "script, line %d: %s\n", + lineno, errmsg); + } else if (garrulous) + fprintf(stderr, "%s\n", errmsg); + quit(3); + break; + default: + fputs("?\n", stderr); + if (!interactive) { + if (garrulous) + fprintf(stderr, + "script, line %d: %s\n", + lineno, errmsg); + quit(2); + } + break; + } + } + /*NOTREACHED*/ +} + +int first_addr, second_addr, addr_cnt; + +/* extract_addr_range: get line addresses from the command buffer until an + illegal address is seen; return status */ +int +extract_addr_range(void) +{ + int addr; + + addr_cnt = 0; + first_addr = second_addr = current_addr; + while ((addr = next_addr()) >= 0) { + addr_cnt++; + first_addr = second_addr; + second_addr = addr; + if (*ibufp != ',' && *ibufp != ';') + break; + else if (*ibufp++ == ';') + current_addr = addr; + } + if ((addr_cnt = min(addr_cnt, 2)) == 1 || second_addr != addr) + first_addr = second_addr; + return (addr == ERR) ? ERR : 0; +} + + +#define SKIP_BLANKS() \ + do { \ + while (isspace((unsigned char)*ibufp) && *ibufp != '\n') \ + ibufp++; \ + } while (0) + +#define MUST_BE_FIRST() \ + do { \ + if (!first) { \ + seterrmsg("invalid address"); \ + return ERR; \ + } \ + } while (0) + + +/* next_addr: return the next line address in the command buffer */ +static int +next_addr(void) +{ + char *hd; + int addr = current_addr; + int n; + int first = 1; + int c; + + SKIP_BLANKS(); + for (hd = ibufp;; first = 0) + switch ((c = (unsigned char)*ibufp)) { + case '+': + case '\t': + case ' ': + case '-': + case '^': + ibufp++; + SKIP_BLANKS(); + if (isdigit((unsigned char)*ibufp)) { + STRTOI(n, ibufp); + addr += (c == '-' || c == '^') ? -n : n; + } else if (!isspace(c)) + addr += (c == '-' || c == '^') ? -1 : 1; + break; + case '0': case '1': case '2': + case '3': case '4': case '5': + case '6': case '7': case '8': case '9': + MUST_BE_FIRST(); + STRTOI(addr, ibufp); + break; + case '.': + case '$': + MUST_BE_FIRST(); + ibufp++; + addr = (c == '.') ? current_addr : addr_last; + break; + case '/': + case '?': + MUST_BE_FIRST(); + if ((addr = get_matching_node_addr( + get_compiled_pattern(), c == '/')) < 0) + return ERR; + else if (c == *ibufp) + ibufp++; + break; + case '\'': + MUST_BE_FIRST(); + ibufp++; + if ((addr = get_marked_node_addr((unsigned char)*ibufp++)) < 0) + return ERR; + break; + case '%': + case ',': + case ';': + if (first) { + ibufp++; + addr_cnt++; + second_addr = (c == ';') ? current_addr : 1; + if ((addr = next_addr()) < 0) + addr = addr_last; + break; + } + /* FALLTHROUGH */ + default: + if (ibufp == hd) + return EOF; + else if (addr < 0 || addr_last < addr) { + seterrmsg("invalid address"); + return ERR; + } else + return addr; + } + /* NOTREACHED */ +} + + +/* GET_THIRD_ADDR: get a legal address from the command buffer */ +#define GET_THIRD_ADDR(addr) \ + do { \ + int ol1, ol2; \ + \ + ol1 = first_addr; \ + ol2 = second_addr; \ + if (extract_addr_range() < 0) \ + return ERR; \ + else if (addr_cnt == 0) { \ + seterrmsg("destination expected"); \ + return ERR; \ + } else if (second_addr < 0 || addr_last < second_addr) { \ + seterrmsg("invalid address"); \ + return ERR; \ + } \ + addr = second_addr; \ + first_addr = ol1; \ + second_addr = ol2; \ + } while (0) + + +/* GET_COMMAND_SUFFIX: verify the command suffix in the command buffer */ +#define GET_COMMAND_SUFFIX() \ + do { \ + int done = 0; \ + do { \ + switch (*ibufp) { \ + case 'p': \ + gflag |= GPR; \ + ibufp++; \ + break; \ + case 'l': \ + gflag |= GLS; \ + ibufp++; \ + break; \ + case 'n': \ + gflag |= GNP; \ + ibufp++; \ + break; \ + default: \ + done++; \ + } \ + } while (!done); \ + if (*ibufp++ != '\n') { \ + seterrmsg("invalid command suffix"); \ + return ERR; \ + } \ + } while (0) + +/* sflags */ +#define SGG 001 /* complement previous global substitute suffix */ +#define SGP 002 /* complement previous print suffix */ +#define SGR 004 /* use last regex instead of last pat */ +#define SGF 010 /* repeat last substitution */ + +int patlock = 0; /* if set, pattern not freed by get_compiled_pattern() */ + +volatile sig_atomic_t rows = 22; /* scroll length: ws_row - 2 */ +volatile sig_atomic_t cols = 72; /* wrap column */ + +/* exec_command: execute the next command in command buffer; return print + request, if any */ +int +exec_command(void) +{ + extern int u_current_addr; + extern int u_addr_last; + + static regex_t *pat = NULL; + static int sgflag = 0; + static int sgnum = 0; + + regex_t *tpat; + char *fnp; + int gflag = 0; + int sflags = 0; + int addr = 0; + int n = 0; + int c; + + SKIP_BLANKS(); + switch ((c = (unsigned char)*ibufp++)) { + case 'a': + GET_COMMAND_SUFFIX(); + if (!isglobal) clear_undo_stack(); + if (append_lines(second_addr) < 0) + return ERR; + break; + case 'c': + if (check_addr_range(current_addr, current_addr) < 0) + return ERR; + GET_COMMAND_SUFFIX(); + if (!isglobal) clear_undo_stack(); + if (delete_lines(first_addr, second_addr) < 0 || + append_lines(current_addr) < 0) + return ERR; + break; + case 'd': + if (check_addr_range(current_addr, current_addr) < 0) + return ERR; + GET_COMMAND_SUFFIX(); + if (!isglobal) clear_undo_stack(); + if (delete_lines(first_addr, second_addr) < 0) + return ERR; + else if ((addr = INC_MOD(current_addr, addr_last)) != 0) + current_addr = addr; + break; + case 'e': + if (modified && !scripted) + return EMOD; + /* FALLTHROUGH */ + case 'E': + if (addr_cnt > 0) { + seterrmsg("unexpected address"); + return ERR; + } else if (!isspace((unsigned char)*ibufp)) { + seterrmsg("unexpected command suffix"); + return ERR; + } else if ((fnp = get_filename(1)) == NULL) + return ERR; + GET_COMMAND_SUFFIX(); + if (delete_lines(1, addr_last) < 0) + return ERR; + clear_undo_stack(); + if (close_sbuf() < 0) + return ERR; + else if (open_sbuf() < 0) + return FATAL; + if (read_file(fnp, 0) < 0) + return ERR; + clear_undo_stack(); + modified = 0; + u_current_addr = u_addr_last = -1; + break; + case 'f': + if (addr_cnt > 0) { + seterrmsg("unexpected address"); + return ERR; + } else if (!isspace((unsigned char)*ibufp)) { + seterrmsg("unexpected command suffix"); + return ERR; + } else if ((fnp = get_filename(1)) == NULL) + return ERR; + else if (*fnp == '!') { + seterrmsg("invalid redirection"); + return ERR; + } + GET_COMMAND_SUFFIX(); + puts(strip_escapes(fnp)); + break; + case 'g': + case 'v': + case 'G': + case 'V': + if (isglobal) { + seterrmsg("cannot nest global commands"); + return ERR; + } else if (check_addr_range(1, addr_last) < 0) + return ERR; + else if (build_active_list(c == 'g' || c == 'G') < 0) + return ERR; + else if ((n = (c == 'G' || c == 'V'))) + GET_COMMAND_SUFFIX(); + isglobal++; + if (exec_global(n, gflag) < 0) + return ERR; + break; + case 'h': + if (addr_cnt > 0) { + seterrmsg("unexpected address"); + return ERR; + } + GET_COMMAND_SUFFIX(); + if (*errmsg) fprintf(stderr, "%s\n", errmsg); + break; + case 'H': + if (addr_cnt > 0) { + seterrmsg("unexpected address"); + return ERR; + } + GET_COMMAND_SUFFIX(); + if ((garrulous = 1 - garrulous) && *errmsg) + fprintf(stderr, "%s\n", errmsg); + break; + case 'i': + if (second_addr == 0) { + second_addr = 1; + } + GET_COMMAND_SUFFIX(); + if (!isglobal) clear_undo_stack(); + if (append_lines(second_addr - 1) < 0) + return ERR; + break; + case 'j': + if (check_addr_range(current_addr, current_addr + 1) < 0) + return ERR; + GET_COMMAND_SUFFIX(); + if (!isglobal) clear_undo_stack(); + if (first_addr != second_addr && + join_lines(first_addr, second_addr) < 0) + return ERR; + break; + case 'k': + c = (unsigned char)*ibufp++; + if (second_addr == 0) { + seterrmsg("invalid address"); + return ERR; + } + GET_COMMAND_SUFFIX(); + if (mark_line_node(get_addressed_line_node(second_addr), c) < 0) + return ERR; + break; + case 'l': + if (check_addr_range(current_addr, current_addr) < 0) + return ERR; + GET_COMMAND_SUFFIX(); + if (display_lines(first_addr, second_addr, gflag | GLS) < 0) + return ERR; + gflag = 0; + break; + case 'm': + if (check_addr_range(current_addr, current_addr) < 0) + return ERR; + GET_THIRD_ADDR(addr); + if (first_addr <= addr && addr < second_addr) { + seterrmsg("invalid destination"); + return ERR; + } + GET_COMMAND_SUFFIX(); + if (!isglobal) clear_undo_stack(); + if (move_lines(addr) < 0) + return ERR; + break; + case 'n': + if (check_addr_range(current_addr, current_addr) < 0) + return ERR; + GET_COMMAND_SUFFIX(); + if (display_lines(first_addr, second_addr, gflag | GNP) < 0) + return ERR; + gflag = 0; + break; + case 'p': + if (check_addr_range(current_addr, current_addr) < 0) + return ERR; + GET_COMMAND_SUFFIX(); + if (display_lines(first_addr, second_addr, gflag | GPR) < 0) + return ERR; + gflag = 0; + break; + case 'P': + if (addr_cnt > 0) { + seterrmsg("unexpected address"); + return ERR; + } + GET_COMMAND_SUFFIX(); + prompt = prompt ? NULL : optarg ? optarg : dps; + break; + case 'q': + case 'Q': + if (addr_cnt > 0) { + seterrmsg("unexpected address"); + return ERR; + } + GET_COMMAND_SUFFIX(); + gflag = (modified && !scripted && c == 'q') ? EMOD : EOF; + break; + case 'r': + if (!isspace((unsigned char)*ibufp)) { + seterrmsg("unexpected command suffix"); + return ERR; + } else if (addr_cnt == 0) + second_addr = addr_last; + if ((fnp = get_filename(0)) == NULL) + return ERR; + GET_COMMAND_SUFFIX(); + if (!isglobal) clear_undo_stack(); + if ((addr = read_file(fnp, second_addr)) < 0) + return ERR; + else if (addr) + modified = 1; + break; + case 's': + do { + switch (*ibufp) { + case '\n': + sflags |=SGF; + break; + case 'g': + sflags |= SGG; + ibufp++; + break; + case 'p': + sflags |= SGP; + ibufp++; + break; + case 'r': + sflags |= SGR; + ibufp++; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + STRTOI(sgnum, ibufp); + sflags |= SGF; + sgflag &= ~GSG; /* override GSG */ + break; + default: + if (sflags) { + seterrmsg("invalid command suffix"); + return ERR; + } + } + } while (sflags && *ibufp != '\n'); + if (sflags && !pat) { + seterrmsg("no previous substitution"); + return ERR; + } else if (sflags & SGG) + sgnum = 0; /* override numeric arg */ + if (*ibufp != '\n' && *(ibufp + 1) == '\n') { + seterrmsg("invalid pattern delimiter"); + return ERR; + } + tpat = pat; + SPL1(); + if ((!sflags || (sflags & SGR)) && + (tpat = get_compiled_pattern()) == NULL) { + SPL0(); + return ERR; + } else if (tpat != pat) { + if (pat) { + regfree(pat); + free(pat); + } + pat = tpat; + patlock = 1; /* reserve pattern */ + } + SPL0(); + if (!sflags && extract_subst_tail(&sgflag, &sgnum) < 0) + return ERR; + else if (isglobal) + sgflag |= GLB; + else + sgflag &= ~GLB; + if (sflags & SGG) + sgflag ^= GSG; + if (sflags & SGP) { + sgflag ^= GPR; + sgflag &= ~(GLS | GNP); + } + do { + switch (*ibufp) { + case 'p': + sgflag |= GPR; + ibufp++; + break; + case 'l': + sgflag |= GLS; + ibufp++; + break; + case 'n': + sgflag |= GNP; + ibufp++; + break; + default: + n++; + } + } while (!n); + if (check_addr_range(current_addr, current_addr) < 0) + return ERR; + GET_COMMAND_SUFFIX(); + if (!isglobal) clear_undo_stack(); + if (search_and_replace(pat, sgflag, sgnum) < 0) + return ERR; + break; + case 't': + if (check_addr_range(current_addr, current_addr) < 0) + return ERR; + GET_THIRD_ADDR(addr); + GET_COMMAND_SUFFIX(); + if (!isglobal) clear_undo_stack(); + if (copy_lines(addr) < 0) + return ERR; + break; + case 'u': + if (addr_cnt > 0) { + seterrmsg("unexpected address"); + return ERR; + } + GET_COMMAND_SUFFIX(); + if (pop_undo_stack() < 0) + return ERR; + break; + case 'w': + case 'W': + if ((n = *ibufp) == 'q' || n == 'Q') { + gflag = EOF; + ibufp++; + } + if (!isspace((unsigned char)*ibufp)) { + seterrmsg("unexpected command suffix"); + return ERR; + } else if ((fnp = get_filename(0)) == NULL) + return ERR; + if (addr_cnt == 0 && !addr_last) + first_addr = second_addr = 0; + else if (check_addr_range(1, addr_last) < 0) + return ERR; + GET_COMMAND_SUFFIX(); + if ((addr = write_file(fnp, (c == 'W') ? "a" : "w", + first_addr, second_addr)) < 0) + return ERR; + else if (addr == addr_last && *fnp != '!') + modified = 0; + else if (modified && !scripted && n == 'q') + gflag = EMOD; + break; + case 'x': + if (addr_cnt > 0) { + seterrmsg("unexpected address"); + return ERR; + } + GET_COMMAND_SUFFIX(); + seterrmsg("crypt unavailable"); + return ERR; + case 'z': + first_addr = 1; + if (check_addr_range(first_addr, current_addr + 1) < 0) + return ERR; + else if ('0' < *ibufp && *ibufp <= '9') + STRTOI(rows, ibufp); + GET_COMMAND_SUFFIX(); + if (display_lines(second_addr, min(addr_last, + second_addr + rows), gflag) < 0) + return ERR; + gflag = 0; + break; + case '=': + GET_COMMAND_SUFFIX(); + printf("%d\n", addr_cnt ? second_addr : addr_last); + break; + case '!': + if (addr_cnt > 0) { + seterrmsg("unexpected address"); + return ERR; + } else if ((sflags = get_shell_command()) < 0) + return ERR; + GET_COMMAND_SUFFIX(); + if (sflags) printf("%s\n", shcmd + 1); + system(shcmd + 1); + if (!scripted) printf("!\n"); + break; + case '\n': + first_addr = 1; + if (check_addr_range(first_addr, current_addr + 1) < 0 + || display_lines(second_addr, second_addr, 0) < 0) + return ERR; + break; + default: + seterrmsg("unknown command"); + return ERR; + } + return gflag; +} + + +/* check_addr_range: return status of address range check */ +static int +check_addr_range(int n, int m) +{ + if (addr_cnt == 0) { + first_addr = n; + second_addr = m; + } + if (first_addr > second_addr || 1 > first_addr || + second_addr > addr_last) { + seterrmsg("invalid address"); + return ERR; + } + return 0; +} + + +/* get_matching_node_addr: return the address of the next line matching a + pattern in a given direction. wrap around begin/end of editor buffer if + necessary */ +static int +get_matching_node_addr(regex_t *pat, int dir) +{ + char *s; + int n = current_addr; + line_t *lp; + + if (!pat) return ERR; + do { + if ((n = dir ? INC_MOD(n, addr_last) : DEC_MOD(n, addr_last))) { + lp = get_addressed_line_node(n); + if ((s = get_sbuf_line(lp)) == NULL) + return ERR; + if (isbinary) + NUL_TO_NEWLINE(s, lp->len); + if (!regexec(pat, s, 0, NULL, 0)) + return n; + } + } while (n != current_addr); + seterrmsg("no match"); + return ERR; +} + + +/* get_filename: return pointer to copy of filename in the command buffer */ +static char * +get_filename(int save) +{ + static char filename[PATH_MAX]; + char *p; + int n; + + if (*ibufp != '\n') { + SKIP_BLANKS(); + if (*ibufp == '\n') { + seterrmsg("invalid filename"); + return NULL; + } else if ((ibufp = get_extended_line(&n, 1)) == NULL) + return NULL; + else if (*ibufp == '!') { + ibufp++; + if ((n = get_shell_command()) < 0) + return NULL; + if (n) printf("%s\n", shcmd + 1); + return shcmd; + } else if (n >= PATH_MAX - 1) { + seterrmsg("filename too long"); + return NULL; + } + } else { + if (*old_filename == '\0') { + seterrmsg("no current filename"); + return NULL; + } + return old_filename; + } + + p = save ? old_filename : *old_filename ? filename : old_filename; + for (n = 0; *ibufp != '\n';) + p[n++] = *ibufp++; + p[n] = '\0'; + return p; +} + + +/* get_shell_command: read a shell command from stdin; return substitution + status */ +static int +get_shell_command(void) +{ + static char *buf = NULL; + static int n = 0; + + char *s; /* substitution char pointer */ + int i = 0; + int j = 0; + + if ((s = ibufp = get_extended_line(&j, 1)) == NULL) + return ERR; + REALLOC(buf, n, j + 1, ERR); + buf[i++] = '!'; /* prefix command w/ bang */ + while (*ibufp != '\n') + switch (*ibufp) { + default: + REALLOC(buf, n, i + 2, ERR); + buf[i++] = *ibufp; + if (*ibufp++ == '\\') + buf[i++] = *ibufp++; + break; + case '!': + if (s != ibufp) { + REALLOC(buf, n, i + 1, ERR); + buf[i++] = *ibufp++; + } + else if (shcmd == NULL) + { + seterrmsg("no previous command"); + return ERR; + } else { + REALLOC(buf, n, i + shcmdi, ERR); + for (s = shcmd + 1; s < shcmd + shcmdi;) + buf[i++] = *s++; + s = ibufp++; + } + break; + case '%': + if (*old_filename == '\0') { + seterrmsg("no current filename"); + return ERR; + } + j = strlen(s = strip_escapes(old_filename)); + REALLOC(buf, n, i + j, ERR); + while (j--) + buf[i++] = *s++; + s = ibufp++; + break; + } + if (i == 1) { + seterrmsg("no command"); + return ERR; + } + REALLOC(shcmd, shcmdsz, i + 1, ERR); + memcpy(shcmd, buf, i); + shcmd[shcmdi = i] = '\0'; + return *s == '!' || *s == '%'; +} + + +/* append_lines: insert text from stdin to after line n; stop when either a + single period is read or EOF; return status */ +static int +append_lines(int n) +{ + int l; + char *lp = ibuf; + char *eot; + undo_t *up = NULL; + + for (current_addr = n;;) { + if (!isglobal) { + if ((l = get_tty_line()) < 0) + return ERR; + else if (l == 0 || ibuf[l - 1] != '\n') { + clearerr(stdin); + return l ? EOF : 0; + } + lp = ibuf; + } else if (*(lp = ibufp) == '\0') + return 0; + else { + while (*ibufp++ != '\n') + ; + l = ibufp - lp; + } + if (l == 2 && lp[0] == '.' && lp[1] == '\n') { + return 0; + } + eot = lp + l; + SPL1(); + do { + if ((lp = put_sbuf_line(lp)) == NULL) { + SPL0(); + return ERR; + } else if (up) + up->t = get_addressed_line_node(current_addr); + else if ((up = push_undo_stack(UADD, current_addr, + current_addr)) == NULL) { + SPL0(); + return ERR; + } + } while (lp != eot); + modified = 1; + SPL0(); + } + /* NOTREACHED */ +} + + +/* join_lines: replace a range of lines with the joined text of those lines */ +static int +join_lines(int from, int to) +{ + static char *buf = NULL; + static int n; + + char *s; + int size = 0; + line_t *bp, *ep; + + ep = get_addressed_line_node(INC_MOD(to, addr_last)); + bp = get_addressed_line_node(from); + for (; bp != ep; bp = bp->q_forw) { + if ((s = get_sbuf_line(bp)) == NULL) + return ERR; + REALLOC(buf, n, size + bp->len, ERR); + memcpy(buf + size, s, bp->len); + size += bp->len; + } + REALLOC(buf, n, size + 2, ERR); + memcpy(buf + size, "\n", 2); + if (delete_lines(from, to) < 0) + return ERR; + current_addr = from - 1; + SPL1(); + if (put_sbuf_line(buf) == NULL || + push_undo_stack(UADD, current_addr, current_addr) == NULL) { + SPL0(); + return ERR; + } + modified = 1; + SPL0(); + return 0; +} + + +/* move_lines: move a range of lines */ +static int +move_lines(int addr) +{ + line_t *b1, *a1, *b2, *a2; + int n = INC_MOD(second_addr, addr_last); + int p = first_addr - 1; + int done = (addr == first_addr - 1 || addr == second_addr); + + SPL1(); + if (done) { + a2 = get_addressed_line_node(n); + b2 = get_addressed_line_node(p); + current_addr = second_addr; + } else if (push_undo_stack(UMOV, p, n) == NULL || + push_undo_stack(UMOV, addr, INC_MOD(addr, addr_last)) == NULL) { + SPL0(); + return ERR; + } else { + a1 = get_addressed_line_node(n); + if (addr < first_addr) { + b1 = get_addressed_line_node(p); + b2 = get_addressed_line_node(addr); + /* this get_addressed_line_node last! */ + } else { + b2 = get_addressed_line_node(addr); + b1 = get_addressed_line_node(p); + /* this get_addressed_line_node last! */ + } + a2 = b2->q_forw; + REQUE(b2, b1->q_forw); + REQUE(a1->q_back, a2); + REQUE(b1, a1); + current_addr = addr + ((addr < first_addr) ? + second_addr - first_addr + 1 : 0); + } + if (isglobal) + unset_active_nodes(b2->q_forw, a2); + modified = 1; + SPL0(); + return 0; +} + + +/* copy_lines: copy a range of lines; return status */ +static int +copy_lines(int addr) +{ + line_t *lp, *np = get_addressed_line_node(first_addr); + undo_t *up = NULL; + int n = second_addr - first_addr + 1; + int m = 0; + + current_addr = addr; + if (first_addr <= addr && addr < second_addr) { + n = addr - first_addr + 1; + m = second_addr - addr; + } + for (; n > 0; n=m, m=0, np = get_addressed_line_node(current_addr + 1)) + for (; n-- > 0; np = np->q_forw) { + SPL1(); + if ((lp = dup_line_node(np)) == NULL) { + SPL0(); + return ERR; + } + add_line_node(lp); + if (up) + up->t = lp; + else if ((up = push_undo_stack(UADD, current_addr, + current_addr)) == NULL) { + SPL0(); + return ERR; + } + modified = 1; + SPL0(); + } + return 0; +} + + +/* delete_lines: delete a range of lines */ +int +delete_lines(int from, int to) +{ + line_t *n, *p; + + SPL1(); + if (push_undo_stack(UDEL, from, to) == NULL) { + SPL0(); + return ERR; + } + n = get_addressed_line_node(INC_MOD(to, addr_last)); + p = get_addressed_line_node(from - 1); + /* this get_addressed_line_node last! */ + if (isglobal) + unset_active_nodes(p->q_forw, n); + REQUE(p, n); + addr_last -= to - from + 1; + current_addr = from - 1; + modified = 1; + SPL0(); + return 0; +} + + +/* display_lines: print a range of lines to stdout */ +int +display_lines(int from, int to, int gflag) +{ + line_t *bp; + line_t *ep; + char *s; + + if (!from) { + seterrmsg("invalid address"); + return ERR; + } + ep = get_addressed_line_node(INC_MOD(to, addr_last)); + bp = get_addressed_line_node(from); + for (; bp != ep; bp = bp->q_forw) { + if ((s = get_sbuf_line(bp)) == NULL) + return ERR; + if (put_tty_line(s, bp->len, current_addr = from++, gflag) < 0) + return ERR; + } + return 0; +} + + +#define MAXMARK 26 /* max number of marks */ + +static line_t *mark[MAXMARK]; /* line markers */ +static int markno; /* line marker count */ + +/* mark_line_node: set a line node mark */ +static int +mark_line_node(line_t *lp, int n) +{ + if (!islower(n)) { + seterrmsg("invalid mark character"); + return ERR; + } else if (mark[n - 'a'] == NULL) + markno++; + mark[n - 'a'] = lp; + return 0; +} + + +/* get_marked_node_addr: return address of a marked line */ +static int +get_marked_node_addr(int n) +{ + if (!islower(n)) { + seterrmsg("invalid mark character"); + return ERR; + } + return get_line_node_addr(mark[n - 'a']); +} + + +/* unmark_line_node: clear line node mark */ +void +unmark_line_node(line_t *lp) +{ + int i; + + for (i = 0; markno && i < MAXMARK; i++) + if (mark[i] == lp) { + mark[i] = NULL; + markno--; + } +} + + +/* dup_line_node: return a pointer to a copy of a line node */ +static line_t * +dup_line_node(line_t *lp) +{ + line_t *np; + + if ((np = malloc(sizeof(line_t))) == NULL) { + perror(NULL); + seterrmsg("out of memory"); + return NULL; + } + np->seek = lp->seek; + np->len = lp->len; + return np; +} + + +/* has_trailing_escape: return the parity of escapes preceding a character + in a string */ +int +has_trailing_escape(char *s, char *t) +{ + return (s == t || *(t - 1) != '\\') ? 0 : !has_trailing_escape(s, t - 1); +} + + +/* strip_escapes: return copy of escaped string of at most length PATH_MAX */ +char * +strip_escapes(char *s) +{ + static char *file = NULL; + static int filesz = 0; + + int i = 0; + + REALLOC(file, filesz, PATH_MAX, NULL); + /* assert: no trailing escape */ + while ((file[i++] = (*s == '\\') ? *++s : *s) != '\0' && + i < PATH_MAX-1) + s++; + file[PATH_MAX-1] = '\0'; + return file; +} + + +void +signal_hup(int signo) +{ + int save_errno = errno; + + if (mutex) + sighup = 1; + else + handle_hup(signo); + errno = save_errno; +} + + +void +signal_int(int signo) +{ + int save_errno = errno; + + if (mutex) + sigint = 1; + else + handle_int(signo); + errno = save_errno; +} + + +void +handle_hup(int signo) +{ + char hup[PATH_MAX]; + + if (!sigactive) + quit(1); /* XXX signal race */ + sighup = 0; + /* XXX signal race */ + if (addr_last && write_file("ed.hup", "w", 1, addr_last) < 0 && + home != NULL && home[0] == '/') { + if (strlcpy(hup, home, sizeof(hup)) < sizeof(hup) && + strlcat(hup, "/ed.hup", sizeof(hup)) < sizeof(hup)) + write_file(hup, "w", 1, addr_last); + } + _exit(2); +} + + +void +handle_int(int signo) +{ + if (!sigactive) + _exit(1); + sigint = 0; + siglongjmp(env, -1); +} + + +void +handle_winch(int signo) +{ + int save_errno = errno; + struct winsize ws; /* window size structure */ + + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == 0) { + if (ws.ws_row > 2) + rows = ws.ws_row - 2; + if (ws.ws_col > 8) + cols = ws.ws_col - 8; + } + errno = save_errno; +} diff --git a/bin/ed/re.c b/bin/ed/re.c new file mode 100644 index 0000000..c81a3ee --- /dev/null +++ b/bin/ed/re.c @@ -0,0 +1,139 @@ +/* $OpenBSD: re.c,v 1.19 2018/06/19 12:36:18 martijn Exp $ */ +/* $NetBSD: re.c,v 1.14 1995/03/21 09:04:48 cgd Exp $ */ + +/* re.c: This file contains the regular expression interface routines for + the ed line editor. */ +/*- + * Copyright (c) 1993 Andrew Moore, Talke Studio. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <regex.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "ed.h" + +static char *extract_pattern(int); +static char *parse_char_class(char *); + +extern int patlock; + + +/* get_compiled_pattern: return pointer to compiled pattern from command + buffer */ +regex_t * +get_compiled_pattern(void) +{ + static regex_t *exp = NULL; + char errbuf[128] = ""; + + char *exps; + char delimiter; + int n; + + if ((delimiter = *ibufp) == ' ') { + seterrmsg("invalid pattern delimiter"); + return NULL; + } else if (delimiter == '\n' || *++ibufp == '\n' || *ibufp == delimiter) { + if (!exp) + seterrmsg("no previous pattern"); + return exp; + } else if ((exps = extract_pattern(delimiter)) == NULL) + return NULL; + /* buffer alloc'd && not reserved */ + if (exp && !patlock) + regfree(exp); + else if ((exp = malloc(sizeof(regex_t))) == NULL) { + perror(NULL); + seterrmsg("out of memory"); + return NULL; + } + patlock = 0; + if ((n = regcomp(exp, exps, 0)) != 0) { + regerror(n, exp, errbuf, sizeof errbuf); + seterrmsg(errbuf); + free(exp); + return exp = NULL; + } + return exp; +} + + +/* extract_pattern: copy a pattern string from the command buffer; return + pointer to the copy */ +static char * +extract_pattern(int delimiter) +{ + static char *lhbuf = NULL; /* buffer */ + static int lhbufsz = 0; /* buffer size */ + + char *nd; + int len; + + for (nd = ibufp; *nd != delimiter && *nd != '\n'; nd++) + switch (*nd) { + default: + break; + case '[': + if ((nd = parse_char_class(++nd)) == NULL) { + seterrmsg("unbalanced brackets ([])"); + return NULL; + } + break; + case '\\': + if (*++nd == '\n') { + seterrmsg("trailing backslash (\\)"); + return NULL; + } + break; + } + len = nd - ibufp; + REALLOC(lhbuf, lhbufsz, len + 1, NULL); + memcpy(lhbuf, ibufp, len); + lhbuf[len] = '\0'; + ibufp = nd; + return (isbinary) ? NUL_TO_NEWLINE(lhbuf, len) : lhbuf; +} + + +/* parse_char_class: expand a POSIX character class */ +static char * +parse_char_class(char *s) +{ + int c, d; + + if (*s == '^') + s++; + if (*s == ']') + s++; + for (; *s != ']' && *s != '\n'; s++) + if (*s == '[' && ((d = *(s+1)) == '.' || d == ':' || d == '=')) + for (s++, c = *++s; *s != ']' || c != d; s++) + if ((c = *s) == '\n') + return NULL; + return (*s == ']') ? s : NULL; +} diff --git a/bin/ed/sub.c b/bin/ed/sub.c new file mode 100644 index 0000000..f920d9d --- /dev/null +++ b/bin/ed/sub.c @@ -0,0 +1,272 @@ +/* $OpenBSD: sub.c,v 1.18 2016/10/11 06:54:05 martijn Exp $ */ +/* $NetBSD: sub.c,v 1.4 1995/03/21 09:04:50 cgd Exp $ */ + +/* sub.c: This file contains the substitution routines for the ed + line editor */ +/*- + * Copyright (c) 1993 Andrew Moore, Talke Studio. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <limits.h> +#include <regex.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifndef REG_STARTEND +#define REG_STARTEND 0004 +#endif + +#include "ed.h" + +static char *extract_subst_template(void); +static int substitute_matching_text(regex_t *, line_t *, int, int); +static int apply_subst_template(char *, regmatch_t *, int, int); + +static char *rhbuf; /* rhs substitution buffer */ +static int rhbufsz; /* rhs substitution buffer size */ +static int rhbufi; /* rhs substitution buffer index */ + +/* extract_subst_tail: extract substitution tail from the command buffer */ +int +extract_subst_tail(int *flagp, int *np) +{ + char delimiter; + + *flagp = *np = 0; + if ((delimiter = *ibufp) == '\n') { + rhbufi = 0; + *flagp = GPR; + return 0; + } else if (extract_subst_template() == NULL) + return ERR; + else if (*ibufp == '\n') { + *flagp = GPR; + return 0; + } else if (*ibufp == delimiter) + ibufp++; + if ('1' <= *ibufp && *ibufp <= '9') { + STRTOI(*np, ibufp); + return 0; + } else if (*ibufp == 'g') { + ibufp++; + *flagp = GSG; + return 0; + } + return 0; +} + + +/* extract_subst_template: return pointer to copy of substitution template + in the command buffer */ +static char * +extract_subst_template(void) +{ + int n = 0; + int i = 0; + char c; + char delimiter = *ibufp++; + + if (*ibufp == '%' && *(ibufp + 1) == delimiter) { + ibufp++; + if (!rhbuf) + seterrmsg("no previous substitution"); + return rhbuf; + } + while (*ibufp != delimiter) { + REALLOC(rhbuf, rhbufsz, i + 2, NULL); + if ((c = rhbuf[i++] = *ibufp++) == '\n' && *ibufp == '\0') { + i--, ibufp--; + break; + } else if (c != '\\') + ; + else if ((rhbuf[i++] = *ibufp++) != '\n') + ; + else if (!isglobal) { + while ((n = get_tty_line()) == 0 || + (n > 0 && ibuf[n - 1] != '\n')) + clearerr(stdin); + if (n < 0) + return NULL; + } + } + REALLOC(rhbuf, rhbufsz, i + 1, NULL); + rhbuf[rhbufi = i] = '\0'; + return rhbuf; +} + + +static char *rbuf; /* substitute_matching_text buffer */ +static int rbufsz; /* substitute_matching_text buffer size */ + +/* search_and_replace: for each line in a range, change text matching a pattern + according to a substitution template; return status */ +int +search_and_replace(regex_t *pat, int gflag, int kth) +{ + undo_t *up; + char *txt; + char *eot; + int lc; + int xa = current_addr; + int nsubs = 0; + line_t *lp; + int len; + + current_addr = first_addr - 1; + for (lc = 0; lc <= second_addr - first_addr; lc++) { + lp = get_addressed_line_node(++current_addr); + if ((len = substitute_matching_text(pat, lp, gflag, kth)) < 0) + return ERR; + else if (len) { + up = NULL; + if (delete_lines(current_addr, current_addr) < 0) + return ERR; + txt = rbuf; + eot = rbuf + len; + SPL1(); + do { + if ((txt = put_sbuf_line(txt)) == NULL) { + SPL0(); + return ERR; + } else if (up) + up->t = get_addressed_line_node(current_addr); + else if ((up = push_undo_stack(UADD, + current_addr, current_addr)) == NULL) { + SPL0(); + return ERR; + } + } while (txt != eot); + SPL0(); + nsubs++; + xa = current_addr; + } + } + current_addr = xa; + if (nsubs == 0 && !(gflag & GLB)) { + seterrmsg("no match"); + return ERR; + } else if ((gflag & (GPR | GLS | GNP)) && + display_lines(current_addr, current_addr, gflag) < 0) + return ERR; + return 0; +} + + +/* substitute_matching_text: replace text matched by a pattern according to + a substitution template; return length of rbuf if changed, 0 if unchanged, or + ERR on error */ +static int +substitute_matching_text(regex_t *pat, line_t *lp, int gflag, int kth) +{ + int off = 0; + int changed = 0; + int matchno = 0; + int i = 0; + int nempty = -1; + regmatch_t rm[SE_MAX]; + char *txt; + char *eot, *eom; + + if ((eom = txt = get_sbuf_line(lp)) == NULL) + return ERR; + if (isbinary) + NUL_TO_NEWLINE(txt, lp->len); + eot = txt + lp->len; + if (!regexec(pat, txt, SE_MAX, rm, 0)) { + do { +/* Don't do a 0-length match directly after a non-0-length */ + if (rm[0].rm_eo == nempty) { + rm[0].rm_so++; + rm[0].rm_eo = lp->len; + continue; + } + if (!kth || kth == ++matchno) { + changed = 1; + i = rm[0].rm_so - (eom - txt); + REALLOC(rbuf, rbufsz, off + i, ERR); + if (isbinary) + NEWLINE_TO_NUL(eom, + rm[0].rm_eo - (eom - txt)); + memcpy(rbuf + off, eom, i); + off += i; + if ((off = apply_subst_template(txt, rm, off, + pat->re_nsub)) < 0) + return ERR; + eom = txt + rm[0].rm_eo; + if (kth) + break; + } + if (rm[0].rm_so == rm[0].rm_eo) + rm[0].rm_so = rm[0].rm_eo + 1; + else + nempty = rm[0].rm_so = rm[0].rm_eo; + rm[0].rm_eo = lp->len; + } while (rm[0].rm_so < lp->len && (gflag & GSG || kth) && + !regexec(pat, txt, SE_MAX, rm, REG_STARTEND | REG_NOTBOL)); + i = eot - eom; + REALLOC(rbuf, rbufsz, off + i + 2, ERR); + if (isbinary) + NEWLINE_TO_NUL(eom, i); + memcpy(rbuf + off, eom, i); + memcpy(rbuf + off + i, "\n", 2); + } + return changed ? off + i + 1 : 0; +} + + +/* apply_subst_template: modify text according to a substitution template; + return offset to end of modified text */ +static int +apply_subst_template(char *boln, regmatch_t *rm, int off, int re_nsub) +{ + int j = 0; + int k = 0; + int n; + char *sub = rhbuf; + + for (; sub - rhbuf < rhbufi; sub++) + if (*sub == '&') { + j = rm[0].rm_so; + k = rm[0].rm_eo; + REALLOC(rbuf, rbufsz, off + k - j, ERR); + while (j < k) + rbuf[off++] = boln[j++]; + } else if (*sub == '\\' && '1' <= *++sub && *sub <= '9' && + (n = *sub - '0') <= re_nsub) { + j = rm[n].rm_so; + k = rm[n].rm_eo; + REALLOC(rbuf, rbufsz, off + k - j, ERR); + while (j < k) + rbuf[off++] = boln[j++]; + } else { + REALLOC(rbuf, rbufsz, off + 1, ERR); + rbuf[off++] = *sub; + } + REALLOC(rbuf, rbufsz, off + 1, ERR); + rbuf[off] = '\0'; + return off; +} diff --git a/bin/ed/undo.c b/bin/ed/undo.c new file mode 100644 index 0000000..4291be0 --- /dev/null +++ b/bin/ed/undo.c @@ -0,0 +1,146 @@ +/* $OpenBSD: undo.c,v 1.14 2016/03/22 17:58:28 mmcc Exp $ */ +/* $NetBSD: undo.c,v 1.2 1995/03/21 09:04:52 cgd Exp $ */ + +/* undo.c: This file contains the undo routines for the ed line editor */ +/*- + * Copyright (c) 1993 Andrew Moore, Talke Studio. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <regex.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> + +#include "ed.h" + +#define USIZE 100 /* undo stack size */ +static undo_t *ustack = NULL; /* undo stack */ +static int usize = 0; /* stack size variable */ +static int u_p = 0; /* undo stack pointer */ + +/* push_undo_stack: return pointer to initialized undo node */ +undo_t * +push_undo_stack(int type, int from, int to) +{ + undo_t *t; + + t = ustack; + if (u_p < usize || + (t = reallocarray(ustack, (usize += USIZE), sizeof(undo_t))) != NULL) { + ustack = t; + ustack[u_p].type = type; + ustack[u_p].t = get_addressed_line_node(to); + ustack[u_p].h = get_addressed_line_node(from); + return ustack + u_p++; + } + /* out of memory - release undo stack */ + perror(NULL); + seterrmsg("out of memory"); + clear_undo_stack(); + free(ustack); + ustack = NULL; + usize = 0; + return NULL; +} + + +/* USWAP: swap undo nodes */ +#define USWAP(x,y) { \ + undo_t utmp; \ + utmp = x, x = y, y = utmp; \ +} + + +int u_current_addr = -1; /* if >= 0, undo enabled */ +int u_addr_last = -1; /* if >= 0, undo enabled */ + +/* pop_undo_stack: undo last change to the editor buffer */ +int +pop_undo_stack(void) +{ + int n; + int o_current_addr = current_addr; + int o_addr_last = addr_last; + + if (u_current_addr == -1 || u_addr_last == -1) { + seterrmsg("nothing to undo"); + return ERR; + } else if (u_p) + modified = 1; + get_addressed_line_node(0); /* this get_addressed_line_node last! */ + SPL1(); + for (n = u_p; n-- > 0;) { + switch(ustack[n].type) { + case UADD: + REQUE(ustack[n].h->q_back, ustack[n].t->q_forw); + break; + case UDEL: + REQUE(ustack[n].h->q_back, ustack[n].h); + REQUE(ustack[n].t, ustack[n].t->q_forw); + break; + case UMOV: + case VMOV: + REQUE(ustack[n - 1].h, ustack[n].h->q_forw); + REQUE(ustack[n].t->q_back, ustack[n - 1].t); + REQUE(ustack[n].h, ustack[n].t); + n--; + break; + default: + /*NOTREACHED*/ + ; + } + ustack[n].type ^= 1; + } + /* reverse undo stack order */ + for (n = u_p; n-- > (u_p + 1)/ 2;) + USWAP(ustack[n], ustack[u_p - 1 - n]); + if (isglobal) + clear_active_list(); + current_addr = u_current_addr, u_current_addr = o_current_addr; + addr_last = u_addr_last, u_addr_last = o_addr_last; + SPL0(); + return 0; +} + + +/* clear_undo_stack: clear the undo stack */ +void +clear_undo_stack(void) +{ + line_t *lp, *ep, *tl; + + while (u_p--) + if (ustack[u_p].type == UDEL) { + ep = ustack[u_p].t->q_forw; + for (lp = ustack[u_p].h; lp != ep; lp = tl) { + unmark_line_node(lp); + tl = lp->q_forw; + free(lp); + } + } + u_p = 0; + u_current_addr = current_addr; + u_addr_last = addr_last; +} |