diff --git a/bin/ed/Makefile b/bin/ed/Makefile index 5a6a4ea4aee8..a99763cc9202 100644 --- a/bin/ed/Makefile +++ b/bin/ed/Makefile @@ -1,8 +1,7 @@ PROG= ed -CFLAGS+=-DVI_BANG -SRCS= ed.c re.c buf.c cbc.c -LINKS= ${BINDIR}/ed ${BINDIR}/red -MLINKS= ed.1 red.1 +SRCS= buf.c cbc.c glob.c io.c main.c re.c sub.c undo.c +LINKS= ${BINDIR}/ed ${BINDIR}/red +MLINKS= ed.1 red.1 .if exists(/usr/lib/libcrypt.a) CFLAGS+=-DDES @@ -10,7 +9,4 @@ LDADD+= -lcrypt DPADD+= ${LIBCRYPT} .endif -LDADD+= -lgnuregex -DPADD+= /usr/lib/libgnuregex.a - .include diff --git a/bin/ed/POSIX b/bin/ed/POSIX index 47a80b9e72a0..e724c12a2e3b 100644 --- a/bin/ed/POSIX +++ b/bin/ed/POSIX @@ -1,62 +1,101 @@ -This version of ed is not strictly POSIX compliant, as described in the -POSIX 1003.2 Draft 11.2 document. BSD commands have been implemented -wherever they do not conflict with the POSIX standard. 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. +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. -The BSD commands included are: - 1) `s' (i.e., s[rgp]*) to repeat a previous substitution, - 2) `W' for appending text to an existing file, - 3) `wq' for exiting after a write, and - 4) `z' for scrolling through the buffer. -BSD line addressing syntax (i.e., `^' and `%'). is also recognized. +OMISSIONS +--------- +1) Locale(3) is not supported yet. -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 (\). +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. -If crypt is available, files can be read and written using DES encryption. -The `x' command prompts the user to enter a key used for encrypting/ -decrypting subsequent reads and writes. If only a newline is entered as -the key, then encryption is disabled. Otherwise, a key is read in the -same manner as a password entry. The key remains in effect until -encryption is disabled. For more information on the encryption algorithm, -see the bdes(1) man page. Encryption/decryption should be fully compatible -with SunOS DES. +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 expect + space or newline can used as a delimiter. -An extension to the POSIX file commands `E', `e', `r', `W' and `w' is that - arguments are processed for backslash escapes, i.e., any character -preceded by a backslash is interpreted literally. If the first unescaped -character of a argument is a bang (!), then the rest of the line -is interpreted as a shell command, and no escape processing is performed -by ed. +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. -The vi editor's bang command syntax is supported, i.e., -(addr1,addr2) ! replaces the addressed lines with the output of - the command . -[rwe] !! reads/writes/edits the previous !. +2) If crypt(3) is available, files can be read and written using DES + encryption. The `x' command prompts the user to enter a key used for + encrypting/ decrypting subsequent reads and writes. If only a newline + is entered as the key, then encryption is disabled. Otherwise, a key + is read in the same manner as a password entry. The key remains in + effect until encryption is disabled. For more information on the + encryption algorithm, see the bdes(1) man page. Encryption/decryption + should be fully compatible with SunOS des(1). -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. +3) 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 (\). -ed runs in restricted mode if invoked as red. This limits editing of -files in the local directory only and prohibits ! commands. +4) An extension to the POSIX file commands `E', `e', `r', `W' and `w' is + that arguments are processed for backslash escapes, i.e., any + character preceded by a backslash is interpreted literally. If the + first unescaped character of a argument is a bang (!), then the + rest of the line is interpreted as a shell command, and no escape + processing is performed by ed. -Though ed is not a binary editor, it can be used (if painfully) 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. +5) For SunOS ed(1) compatibility, ed runs in restricted mode if invoked + as red. This limits editing of files in the local directory only and + prohibits shell commands. -Since the behavior of `u' (undo) within a `g' (global) command list is -not specified by POSIX D11/2, it follows the behavior of the SunOS ed -(this is the best way, I think, in that the alternatives are either too -complicated to implement or too confusing to use): undo forces a global -command list to be executed only once, rather than for each line matching -a global pattern. In addtion, each instance of `u' within a global command -undoes all previous commands (including undo's) in the command list. +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. -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. + 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 addtion, 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 occurences of either `censor1' or `censor2' might be + written as: + ed - file < -#include -#include #include -#include #include "ed.h" -extern char errmsg[]; FILE *sfp; /* scratch file pointer */ -char *sfbuf = NULL; /* scratch file input buffer */ -int sfbufsz = 0; /* scratch file input buffer size */ off_t sfseek; /* scratch file position */ int seek_write; /* seek before writing */ -line_t line0; /* initial node of line queue */ +line_t buffer_head; /* incore buffer */ -/* gettxt: get a line of text from the scratch file; return pointer +/* get_sbuf_line: get a line of text from the scratch file; return pointer to the text */ char * -gettxt(lp) +get_sbuf_line(lp) line_t *lp; { + static char *sfbuf = NULL; /* buffer */ + static int sfbufsz = 0; /* buffer size */ + int len, ct; - if (lp == &line0) + if (lp == &buffer_head) return NULL; seek_write = 1; /* force seek on write */ /* out of position */ @@ -77,8 +62,8 @@ gettxt(lp) return NULL; } } - len = lp->len & ~ACTV; - CKBUF(sfbuf, sfbufsz, len + 1, NULL); + len = lp->len; + REALLOC(sfbuf, sfbufsz, len + 1, NULL); if ((ct = fread(sfbuf, sizeof(char), len, sfp)) < 0 || ct != len) { fprintf(stderr, "%s\n", strerror(errno)); sprintf(errmsg, "cannot read temp file"); @@ -90,13 +75,10 @@ gettxt(lp) } -extern long curln; -extern long lastln; - -/* puttxt: write a line of text to the scratch file and add a line node +/* 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 * -puttxt(cs) +put_sbuf_line(cs) char *cs; { line_t *lp; @@ -115,7 +97,7 @@ puttxt(cs) sprintf(errmsg, "line too long"); return NULL; } - len = (s - cs) & ~ACTV; + len = s - cs; /* out of position */ if (seek_write) { if (fseek(sfp, 0L, SEEK_END) < 0) { @@ -126,7 +108,7 @@ puttxt(cs) sfseek = ftell(sfp); seek_write = 0; } - /* assert: spl1() */ + /* assert: SPL1() */ if ((ct = fwrite(cs, sizeof(char), len, sfp)) < 0 || ct != len) { sfseek = -1; fprintf(stderr, "%s\n", strerror(errno)); @@ -135,37 +117,37 @@ puttxt(cs) } lp->len = len; lp->seek = sfseek; - lpqueue(lp); + add_line_node(lp); sfseek += len; /* update file position */ return ++s; } -/* lpqueue: add a line node in the editor buffer after the current line */ +/* add_line_node: add a line node in the editor buffer after the current line */ void -lpqueue(lp) +add_line_node(lp) line_t *lp; { line_t *cp; - cp = getlp(curln); /* this getlp last! */ - insqueue(lp, cp); - lastln++; - curln++; + cp = get_addressed_line_node(current_addr); /* this get_addressed_line_node last! */ + insque(lp, cp); + addr_last++; + current_addr++; } -/* getaddr: return line number of pointer */ +/* get_line_node_addr: return line number of pointer */ long -getaddr(lp) +get_line_node_addr(lp) line_t *lp; { - line_t *cp = &line0; + line_t *cp = &buffer_head; long n = 0; - while (cp != lp && (cp = cp->next) != &line0) + while (cp != lp && (cp = cp->q_forw) != &buffer_head) n++; - if (n && cp == &line0) { + if (n && cp == &buffer_head) { sprintf(errmsg, "invalid address"); return ERR; } @@ -173,43 +155,47 @@ getaddr(lp) } -/* getlp: return pointer to a line node in the editor buffer */ +/* get_addressed_line_node: return pointer to a line node in the editor buffer */ line_t * -getlp(n) +get_addressed_line_node(n) long n; { - static line_t *lp = &line0; + static line_t *lp = &buffer_head; static long on = 0; - spl1(); + SPL1(); if (n > on) - if (n <= (on + lastln) >> 1) + if (n <= (on + addr_last) >> 1) for (; on < n; on++) - lp = lp->next; + lp = lp->q_forw; else { - lp = line0.prev; - for (on = lastln; on > n; on--) - lp = lp->prev; + 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->prev; + lp = lp->q_back; else { - lp = &line0; + lp = &buffer_head; for (on = 0; on < n; on++) - lp = lp->next; + lp = lp->q_forw; } - spl0(); + SPL0(); return lp; } +extern int newline_added; + char sfn[15] = ""; /* scratch file name */ -/* sbopen: open scratch file */ -sbopen() +/* open_sbuf: open scratch file */ +int +open_sbuf() { + isbinary = newline_added = 0; strcpy(sfn, "/tmp/ed.XXXXXX"); if (mktemp(sfn) == NULL || (sfp = fopen(sfn, "w+")) == NULL) { fprintf(stderr, "%s: %s\n", sfn, strerror(errno)); @@ -220,8 +206,9 @@ sbopen() } -/* sbclose: close scratch file */ -sbclose() +/* close_sbuf: close scratch file */ +int +close_sbuf() { if (sfp) { if (fclose(sfp) < 0) { @@ -237,7 +224,7 @@ sbclose() } -/* quit: remove scratch file and exit */ +/* quit: remove_lines scratch file and exit */ void quit(n) int n; @@ -252,23 +239,30 @@ quit(n) unsigned char ctab[256]; /* character translation table */ -/* init_buf: open scratch buffer; initialize line queue */ +/* init_buffers: open scratch buffer; initialize line queue */ void -init_buf() +init_buffers() { int i = 0; - if (sbopen() < 0) + /* Read stdin one character at a time to avoid i/o contention + with shell escapes invoked by nonterminal input, e.g., + ed - < +#include #include #include -#include -#include -#include -#include -#include -#include #include "ed.h" + /* * Define a divisor for rand() that yields a uniform distribution in the * range 0-255. @@ -76,7 +57,7 @@ static char sccsid[] = "@(#)cbc.c 5.5 (Berkeley) 6/27/91"; /* * BSD and System V systems offer special library calls that do - * block moves and fills, so if possible we take advantage of them + * block move_liness and fills, so if possible we take advantage of them */ #define MEMCPY(dest,src,len) memcpy((dest),(src),(len)) #define MEMZERO(dest,len) memset((dest), 0, (len)) @@ -84,10 +65,10 @@ static char sccsid[] = "@(#)cbc.c 5.5 (Berkeley) 6/27/91"; /* Hide the calls to the primitive encryption routines. */ #define DES_KEY(buf) \ if (des_setkey(buf)) \ - err("des_setkey"); + des_error("des_setkey"); #define DES_XFORM(buf) \ if (des_cipher(buf, buf, 0L, (inverse ? -1 : 1))) \ - err("des_cipher"); + des_error("des_cipher"); /* * read/write - no error checking @@ -119,14 +100,14 @@ char bits[] = { /* used to extract bits from a char */ }; int pflag; /* 1 to preserve parity bits */ -char des_buf[8]; /* shared buffer for desgetc/desputc */ -int des_ct = 0; /* count for desgetc/desputc */ -int des_n = 0; /* index for desputc/desgetc */ +unsigned char des_buf[8]; /* shared buffer for get_des_char/put_des_char */ +int des_ct = 0; /* count for get_des_char/put_des_char */ +int des_n = 0; /* index for put_des_char/get_des_char */ -/* desinit: initialize DES */ +/* init_des_cipher: initialize DES */ void -desinit() +init_des_cipher() { #ifdef DES int i; @@ -144,28 +125,30 @@ desinit() } -/* desgetc: return next char in an encrypted file */ -desgetc(fp) +/* get_des_char: return next char in an encrypted file */ +int +get_des_char(fp) FILE *fp; { #ifdef DES if (des_n >= des_ct) { des_n = 0; - des_ct = cbcdec(des_buf, fp); + des_ct = cbc_decode(des_buf, fp); } return (des_ct > 0) ? des_buf[des_n++] : EOF; #endif } -/* desputc: write a char to an encrypted file; return char written */ -desputc(c, fp) +/* put_des_char: write a char to an encrypted file; return char written */ +int +put_des_char(c, fp) int c; FILE *fp; { #ifdef DES if (des_n == sizeof des_buf) { - des_ct = cbcenc(des_buf, des_n, fp); + des_ct = cbc_encode(des_buf, des_n, fp); des_n = 0; } return (des_ct >= 0) ? (des_buf[des_n++] = c) : EOF; @@ -173,16 +156,17 @@ desputc(c, fp) } -/* desflush: flush an encrypted file's output; return status */ -desflush(fp) +/* flush_des_file: flush an encrypted file's output; return status */ +int +flush_des_file(fp) FILE *fp; { #ifdef DES if (des_n == sizeof des_buf) { - des_ct = cbcenc(des_buf, des_n, fp); + des_ct = cbc_encode(des_buf, des_n, fp); des_n = 0; } - return (des_ct >= 0 && cbcenc(des_buf, des_n, fp) >= 0) ? 0 : EOF; + return (des_ct >= 0 && cbc_encode(des_buf, des_n, fp) >= 0) ? 0 : EOF; #endif } @@ -190,7 +174,8 @@ desflush(fp) /* * get keyword from tty or stdin */ -getkey() +int +get_keyword() { register char *p; /* used to obtain the key */ Desbuf msgbuf; /* I/O buffer */ @@ -203,9 +188,9 @@ getkey() /* * copy it, nul-padded, into the key area */ - cvtkey(BUFFER(msgbuf), p); + expand_des_key(BUFFER(msgbuf), p); MEMZERO(p, _PASSWORD_LEN); - makekey(msgbuf); + set_des_key(msgbuf); MEMZERO(msgbuf, sizeof msgbuf); return 1; } @@ -213,13 +198,11 @@ getkey() } -extern char errmsg[]; - /* * print a warning message and, possibly, terminate */ void -err(s) +des_error(s) char *s; /* the message */ { (void)sprintf(errmsg, "%s", s ? s : strerror(errno)); @@ -228,7 +211,8 @@ err(s) /* * map a hex character to an integer */ -tobinhex(c, radix) +int +hex_to_binary(c, radix) int c; /* char to be converted */ int radix; /* base (2 to 16) */ { @@ -260,7 +244,7 @@ tobinhex(c, radix) * convert the key to a bit pattern */ void -cvtkey(obuf, ibuf) +expand_des_key(obuf, ibuf) char *obuf; /* bit pattern */ char *ibuf; /* the key itself */ { @@ -276,8 +260,8 @@ cvtkey(obuf, ibuf) * now translate it, bombing on any illegal hex digit */ for (i = 0; ibuf[i] && i < 16; i++) - if ((nbuf[i] = tobinhex((int) ibuf[i], 16)) == -1) - err("bad hex digit in key"); + if ((nbuf[i] = hex_to_binary((int) ibuf[i], 16)) == -1) + des_error("bad hex digit in key"); while (i < 16) nbuf[i++] = 0; for (i = 0; i < 8; i++) @@ -296,8 +280,8 @@ cvtkey(obuf, ibuf) * now translate it, bombing on any illegal binary digit */ for (i = 0; ibuf[i] && i < 16; i++) - if ((nbuf[i] = tobinhex((int) ibuf[i], 2)) == -1) - err("bad binary digit in key"); + if ((nbuf[i] = hex_to_binary((int) ibuf[i], 2)) == -1) + des_error("bad binary digit in key"); while (i < 64) nbuf[i++] = 0; for (i = 0; i < 8; i++) @@ -328,7 +312,7 @@ cvtkey(obuf, ibuf) * DES ignores the low order bit of each character. */ void -makekey(buf) +set_des_key(buf) Desbuf buf; /* key block */ { register int i, j; /* counter in a for loop */ @@ -357,7 +341,8 @@ makekey(buf) /* * This encrypts using the Cipher Block Chaining mode of DES */ -cbcenc(msgbuf, n, fp) +int +cbc_encode(msgbuf, n, fp) char *msgbuf; int n; FILE *fp; @@ -395,7 +380,8 @@ cbcenc(msgbuf, n, fp) /* * This decrypts using the Cipher Block Chaining mode of DES */ -cbcdec(msgbuf, fp) +int +cbc_decode(msgbuf, fp) char *msgbuf; /* I/O buffer */ FILE *fp; /* input file descriptor */ { @@ -419,7 +405,7 @@ cbcdec(msgbuf, fp) if ((c = fgetc(fp)) == EOF) { n = CHAR(msgbuf, 7); if (n < 0 || n > 7) { - err("decryption failed (block corrupted)"); + des_error("decryption failed (block corrupted)"); return EOF; } } else @@ -427,9 +413,9 @@ cbcdec(msgbuf, fp) return n; } if (n > 0) - err("decryption failed (incomplete block)"); + des_error("decryption failed (incomplete block)"); else if (n < 0) - err("cannot read file"); + des_error("cannot read file"); return EOF; } #endif /* DES */ diff --git a/bin/ed/ed.1 b/bin/ed/ed.1 index e9a318080b39..3bc77b0979c9 100644 --- a/bin/ed/ed.1 +++ b/bin/ed/ed.1 @@ -134,7 +134,7 @@ The default filename is set to only if it is not prefixed with a bang. .SS LINE ADDRESSING -An address represents the number of line in the buffer. +An address represents the number of a line in the buffer. .B ed maintains a .I current address @@ -165,25 +165,26 @@ and is legal wherever it makes sense. 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 the second. If an +value of the the second. If only one address is given in a range, then +the second address is set to the given address. If an .IR n- tuple of addresses is given where .I n > 2, -then the corresponding range is determined by the last two addresses -in the +then the corresponding range is determined by the last two addresses in +the .IR n- tuple. -If only one address is expected, then the last -address is used. +If only one address is expected, then the last address is used. 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. + The following address symbols are recognized. .TP 8 -\fR.\fR +\&. The current line (address) in the buffer. .TP 8 @@ -511,7 +512,9 @@ The current address is set to the last line read. .RI e \ !command Edits the standard output of .IR `!command' , -executed as described below. +(see +.RI ! command +below). The default filename is unchanged. Any lines in the buffer are deleted before the output of .I command @@ -629,6 +632,12 @@ deleted or otherwise modified. .TP 8 (.,.)l Prints the addressed lines unambiguously. +If a single line fills for than one screen (as might be the case +when viewing a binary file, for instance), a `--More--' +prompt is printed on the last line. +.B ed +waits until the RETURN key is pressed +before displaying the next screen. The current address is set to the last line printed. @@ -689,7 +698,9 @@ Reads to after the addressed line the standard output of .IR `!command' , -executed as described below. +(see the +.RI ! command +below). The default filename is unchanged. The current address is set to the last line read. @@ -707,9 +718,9 @@ matching a regular expression with .IR replacement . By default, only the first match in each line is replaced. -The +If the .I `g' -(global) suffix causes every match to be replaced. +(global) suffix is given, then every match to be replaced. The .I `n' suffix, where @@ -724,7 +735,10 @@ The current address is set the last line affected. .I re and .I replacement -may be delimited by any character other than space and newline. +may be delimited by any character other than space and newline +(see the +.I `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 .I `p' @@ -755,12 +769,18 @@ if they are escaped with a backslash (\\). Repeats the last substitution. This form of the .I `s' -command may be suffixed with -any combination of the characters +command accepts a count suffix +.IR `n' , +or any combination of the characters .IR `r' , .IR `g' , and .IR `p' . +If a count suffix +.I `n' +is given, then only the +.IR n th +match is replaced. The .I `r' suffix causes @@ -840,7 +860,9 @@ command. .RI (1,$)w \ !command Writes the addressed lines to the standard input of .IR `!command' , -executed as described below. +(see the +.RI ! command +below). The default filename and current address are unchanged. .TP 8 @@ -890,13 +912,6 @@ When the shell returns from execution, a `!' is printed to the standard output. The current line is unchanged. -.TP 8 -.RI (.,.)! command -Replaces the addressed lines with the output of -.I `!command' -as described above. -The current address is set to the last line read. - .TP 8 ($)= Prints the line number of the addressed line. diff --git a/bin/ed/ed.h b/bin/ed/ed.h index b3905370f949..ddf86fff320e 100644 --- a/bin/ed/ed.h +++ b/bin/ed/ed.h @@ -1,11 +1,8 @@ /* ed.h: type and constant definitions for the ed editor. */ /* - * Copyright (c) 1993 The Regents of the University of California. + * Copyright (c) 1993 Andrew Moore * All rights reserved. * - * This code is derived from software contributed to Berkeley by - * Andrew Moore, Talke Studio. - * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: @@ -14,13 +11,6 @@ * 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. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE @@ -34,22 +24,22 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * @(#)ed.h 5.5 (Berkeley) 3/28/93 + * @(#)$Id: ed.h,v 1.4 1993/12/15 15:22:02 alm Exp alm $ */ -#include -#include #if defined(BSD) && BSD >= 199103 || defined(__386BSD__) # include /* for MAXPATHLEN */ #endif +#include +#ifdef sun +# include +#endif #include #include - -#define BITSPERBYTE 8 -#define BITS(type) (BITSPERBYTE * (int)sizeof(type)) -#define CHARBITS BITS(char) -#define INTBITS BITS(int) -#define INTHIBIT (unsigned) (1 << (INTBITS - 1)) +#include +#include +#include +#include #define ERR (-2) #define EMOD (-3) @@ -59,21 +49,28 @@ # define MAXPATHLEN 255 /* _POSIX_PATH_MAX */ #endif -#define MAXFNAME MAXPATHLEN /* max file name size */ #define MINBUFSZ 512 /* minimum buffer size - must be > 0 */ -#define LINECHARS (INTHIBIT - 1) /* max chars per line */ #define SE_MAX 30 /* max subexpressions in a regular expression */ +#ifdef INT_MAX +# define LINECHARS INT_MAX /* max chars per line */ +#else +# define LINECHARS MAXINT /* max chars per line */ +#endif + +/* 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 */ typedef regex_t pattern_t; /* Line node */ typedef struct line { - struct line *next; - struct line *prev; + struct line *q_forw; + struct line *q_back; off_t seek; /* address of line in scratch buffer */ - -#define ACTV INTHIBIT /* active bit: high bit of len */ - int len; /* length of line */ } line_t; @@ -98,87 +95,94 @@ typedef struct undo { # define min(a,b) ((a) < (b) ? (a) : (b)) #endif -/* nextln: return line after l mod k */ -#define nextln(l,k) ((l)+1 > (k) ? 0 : (l)+1) +#define INC_MOD(l, k) ((l) + 1 > (k) ? 0 : (l) + 1) +#define DEC_MOD(l, k) ((l) - 1 < 0 ? (k) : (l) - 1) -/* nextln: return line before l mod k */ -#define prevln(l,k) ((l)-1 < 0 ? (k) : (l)-1) +/* SPL1: disable some interrupts (requires reliable signals) */ +#define SPL1() mutex++ -#define skipblanks() while (isspace(*ibufp) && *ibufp != '\n') ibufp++ - -/* spl1: disable some interrupts (requires reliable signals) */ -#define spl1() mutex++ - -/* spl0: enable all interrupts; check sigflags (requires reliable signals) */ -#define spl0() \ +/* SPL0: enable all interrupts; check sigflags (requires reliable signals) */ +#define SPL0() \ if (--mutex == 0) { \ - if (sigflags & (1 << SIGHUP)) dohup(SIGHUP); \ - if (sigflags & (1 << SIGINT)) dointr(SIGINT); \ + if (sigflags & (1 << (SIGHUP - 1))) handle_hup(SIGHUP); \ + if (sigflags & (1 << (SIGINT - 1))) handle_int(SIGINT); \ +} + +/* STRTOL: convert a string to long */ +#define STRTOL(i, p) { \ + if (((i = strtol(p, &p, 10)) == LONG_MIN || i == LONG_MAX) && \ + errno == ERANGE) { \ + sprintf(errmsg, "number out of range"); \ + i = 0; \ + return ERR; \ + } \ } #if defined(sun) || defined(NO_REALLOC_NULL) -/* CKBUF: assure at least a minimum size for buffer b */ -#define CKBUF(b,n,i,err) \ +/* 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(); \ + SPL1(); \ if ((b) != NULL) { \ if ((ts = (char *) realloc((b), ti += max((i), MINBUFSZ))) == NULL) { \ fprintf(stderr, "%s\n", strerror(errno)); \ sprintf(errmsg, "out of memory"); \ - spl0(); \ + SPL0(); \ return err; \ } \ } else { \ if ((ts = (char *) malloc(ti += max((i), MINBUFSZ))) == NULL) { \ fprintf(stderr, "%s\n", strerror(errno)); \ sprintf(errmsg, "out of memory"); \ - spl0(); \ + SPL0(); \ return err; \ } \ } \ (n) = ti; \ (b) = ts; \ - spl0(); \ + SPL0(); \ } #else /* NO_REALLOC_NULL */ -/* CKBUF: assure at least a minimum size for buffer b */ -#define CKBUF(b,n,i,err) \ +/* 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(); \ + SPL1(); \ if ((ts = (char *) realloc((b), ti += max((i), MINBUFSZ))) == NULL) { \ fprintf(stderr, "%s\n", strerror(errno)); \ sprintf(errmsg, "out of memory"); \ - spl0(); \ + SPL0(); \ return err; \ } \ (n) = ti; \ (b) = ts; \ - spl0(); \ + SPL0(); \ } #endif /* NO_REALLOC_NULL */ -/* requeue: link pred before succ */ -#define requeue(pred, succ) (pred)->next = (succ), (succ)->prev = (pred) +/* REQUE: link pred before succ */ +#define REQUE(pred, succ) (pred)->q_forw = (succ), (succ)->q_back = (pred) -/* insqueue: insert elem in circular queue after pred */ -#define insqueue(elem, pred) \ +#ifdef NEED_INSQUE +/* insque: insert elem in circular queue after pred */ +#define insque(elem, pred) \ { \ - requeue((elem), (pred)->next); \ - requeue((pred), elem); \ + REQUE((elem), (pred)->q_forw); \ + REQUE((pred), elem); \ } -/* remqueue: remove elem from circular queue */ -#define remqueue(elem) requeue((elem)->prev, (elem)->next); +/* remque: remove_lines elem from circular queue */ +#define remque(elem) REQUE((elem)->q_back, (elem)->q_forw); +#endif /* NEED_INSQUE */ -/* nultonl: overwrite ASCII NULs with newlines */ -#define nultonl(s, l) translit(s, l, '\0', '\n') +/* NUL_TO_NEWLINE: overwrite ASCII NULs with newlines */ +#define NUL_TO_NEWLINE(s, l) translit_text(s, l, '\0', '\n') -/* nltonul: overwrite newlines with ASCII NULs */ -#define nltonul(s, l) translit(s, l, '\n', '\0') +/* NEWLINE_TO_NUL: overwrite newlines with ASCII NULs */ +#define NEWLINE_TO_NUL(s, l) translit_text(s, l, '\n', '\0') #ifndef strerror # define strerror(n) sys_errlist[n] @@ -192,75 +196,101 @@ if ((i) > (n)) { \ # endif #endif -/* local function declarations */ -int append __P((long, int)); -int cbcdec __P((char *, FILE *)); -int cbcenc __P((char *, int, FILE *)); -char *ckfn __P((char *)); -int ckglob __P((void)); -int ckrange __P((long, long)); -int desflush __P((FILE *)); -int desgetc __P((FILE *)); -void desinit __P((void)); -int desputc __P((int, FILE *)); -int docmd __P((int)); -void err __P((char *)); -char *ccl __P((char *)); -void clrmark __P((line_t *)); -void cvtkey __P((char *, char *)); -long doglob __P((int)); -void dohup __P((int)); -void dointr __P((int)); -void dowinch __P((int)); -int doprint __P((long, long, int)); -long doread __P((long, char *)); -long dowrite __P((long, long, char *, char *)); -char *esctos __P((char *)); -long patscan __P((pattern_t *, int)); -long getaddr __P((line_t *)); -char *getcmdv __P((int *, int)); -char *getfn __P((void)); -int getkey __P((void)); -char *getlhs __P((int)); -int getline __P((void)); -int getlist __P((void)); -long getmark __P((int)); -long getnum __P((int)); -long getone __P((void)); -line_t *getlp __P((long)); -int getrhs __P((int)); -int getshcmd __P((void)); -char *gettxt __P((line_t *)); -void init_buf __P((void)); -int join __P((long, long)); -int lndelete __P((long, long)); -line_t *lpdup __P((line_t *)); -void lpqueue __P((line_t *)); -void makekey __P((char *)); -char *makesub __P((int)); -int move __P((long, int)); -int oddesc __P((char *, char *)); -void onhup __P((int)); -void onintr __P((int)); -pattern_t *optpat __P((void)); -int putmark __P((int, line_t *)); -void putstr __P((char *, int, long, int)); -char *puttxt __P((char *)); +/* Local Function Declarations */ +void add_line_node __P((line_t *)); +int append_lines __P((long)); +int apply_subst_template __P((char *, regmatch_t *, int, int)); +int build_active_list __P((int)); +int cbc_decode __P((char *, FILE *)); +int cbc_encode __P((char *, int, FILE *)); +int check_addr_range __P((long, long)); +void clear_active_list __P((void)); +void clear_undo_stack __P((void)); +int close_sbuf __P((void)); +int copy_lines __P((long)); +int delete_lines __P((long, long)); +void des_error __P((char *)); +int display_lines __P((long, long, int)); +line_t *dup_line_node __P((line_t *)); +int exec_command __P((void)); +long exec_global __P((int, int)); +void expand_des_key __P((char *, char *)); +int extract_addr_range __P((void)); +char *extract_pattern __P((int)); +int extract_subst_tail __P((int *, int *)); +char *extract_subst_template __P((void)); +int filter_lines __P((long, long, char *)); +int flush_des_file __P((FILE *)); +line_t *get_addressed_line_node __P((long)); +pattern_t *get_compiled_pattern __P((void)); +int get_des_char __P((FILE *)); +char *get_extended_line __P((int *, int)); +char *get_filename __P((void)); +int get_keyword __P((void)); +long get_line_node_addr __P((line_t *)); +long get_matching_node_addr __P((pattern_t *, int)); +long get_marked_node_addr __P((int)); +char *get_sbuf_line __P((line_t *)); +int get_shell_command __P((void)); +int get_stream_line __P((FILE *)); +int get_tty_line __P((void)); +void handle_hup __P((int)); +void handle_int __P((int)); +void handle_winch __P((int)); +int has_trailing_escape __P((char *, char *)); +int hex_to_binary __P((int, int)); +void init_buffers __P((void)); +void init_des_cipher __P((void)); +int is_legal_filename __P((char *)); +int join_lines __P((long, long)); +int mark_line_node __P((line_t *, int)); +int move_lines __P((long)); +line_t *next_active_node __P(()); +long next_addr __P((void)); +int open_sbuf __P((void)); +char *parse_char_class __P((char *)); +int pop_undo_stack __P((void)); +undo_t *push_undo_stack __P((int, long, long)); +int put_des_char __P((int, FILE *)); +char *put_sbuf_line __P((char *)); +int put_stream_line __P((FILE *, char *, int)); +int put_tty_line __P((char *, int, long, int)); void quit __P((int)); -int regsub __P((pattern_t *, line_t *, int)); -int sbclose __P((void)); -int sbopen __P((void)); -int sgetline __P((FILE *)); -int catsub __P((char *, regmatch_t *, int)); -int subst __P((pattern_t *, int)); -int tobinhex __P((int, int)); -int transfer __P((long)); -char *translit __P((char *, int, int, int)); -int undo __P((int)); -undo_t *upush __P((int, long, long)); -void ureset __P((void)); +long read_file __P((char *, long)); +long read_stream __P((FILE *, long)); +int search_and_replace __P((pattern_t *, int, int)); +int set_active_node __P((line_t *)); +void set_des_key __P((char *)); +void signal_hup __P((int)); +void signal_int __P((int)); +char *strip_escapes __P((char *)); +int substitute_matching_text __P((pattern_t *, line_t *, int, int)); +char *translit_text __P((char *, int, int, int)); +void unmark_line_node __P((line_t *)); +void unset_active_nodes __P((line_t *, line_t *)); +long write_file __P((char *, char *, long, long)); +long write_stream __P((FILE *, long, long)); +/* global buffers */ +extern char stdinbuf[]; +extern char *ibuf; +extern char *ibufp; +extern int ibufsz; -extern char *sys_errlist[]; +/* global flags */ +extern int isbinary; +extern int isglobal; +extern int modified; extern int mutex; extern int sigflags; + +/* global vars */ +extern long addr_last; +extern long current_addr; +extern char errmsg[]; +extern long first_addr; +extern int lineno; +extern long second_addr; +#ifdef sun +extern char *sys_errlist[]; +#endif diff --git a/bin/ed/io.c b/bin/ed/io.c new file mode 100644 index 000000000000..d0d56c1ef007 --- /dev/null +++ b/bin/ed/io.c @@ -0,0 +1,375 @@ +/* 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. + */ + +#ifndef lint +static char *rcsid = "@(#)$Id: io.c,v 1.3 1993/12/14 16:19:56 alm Exp $"; +#endif /* not lint */ + +#include "ed.h" + + +extern int scripted; + +/* read_file: read a named file/pipe into the buffer; return line count */ +long +read_file(fn, n) + char *fn; + long n; +{ + FILE *fp; + long size; + + + fp = (*fn == '!') ? popen(fn + 1, "r") : fopen(strip_escapes(fn), "r"); + if (fp == NULL) { + fprintf(stderr, "%s: %s\n", fn, strerror(errno)); + sprintf(errmsg, "cannot open input file"); + return ERR; + } else if ((size = read_stream(fp, n)) < 0) + return ERR; + else if (((*fn == '!') ? pclose(fp) : fclose(fp)) < 0) { + fprintf(stderr, "%s: %s\n", fn, strerror(errno)); + sprintf(errmsg, "cannot close input file"); + return ERR; + } + fprintf(stderr, !scripted ? "%lu\n" : "", size); + return current_addr - n; +} + + +extern int des; + +char *sbuf; /* file i/o buffer */ +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 */ +long +read_stream(fp, n) + FILE *fp; + long n; +{ + line_t *lp = get_addressed_line_node(n); + undo_t *up = NULL; + unsigned long size = 0; + int o_newline_added = newline_added; + int o_isbinary = isbinary; + int appended = (n == addr_last); + int len; + + isbinary = newline_added = 0; + if (des) + init_des_cipher(); + 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; + if (des) + size += 8 - size % 8; /* adjust DES size */ + return size; +} + + +/* get_stream_line: read a line of text from a stream; return line length */ +int +get_stream_line(fp) + FILE *fp; +{ + register int c; + register int i = 0; + + while (((c = des ? get_des_char(fp) : 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)) { + fprintf(stderr, "%s\n", strerror(errno)); + sprintf(errmsg, "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 */ +long +write_file(fn, mode, n, m) + char *fn; + char *mode; + long n; + long m; +{ + FILE *fp; + long size; + + fp = (*fn == '!') ? popen(fn+1, "w") : fopen(strip_escapes(fn), mode); + if (fp == NULL) { + fprintf(stderr, "%s: %s\n", fn, strerror(errno)); + sprintf(errmsg, "cannot open output file"); + return ERR; + } else if ((size = write_stream(fp, n, m)) < 0) + return ERR; + else if (((*fn == '!') ? pclose(fp) : fclose(fp)) < 0) { + fprintf(stderr, "%s: %s\n", fn, strerror(errno)); + sprintf(errmsg, "cannot close output file"); + return ERR; + } + fprintf(stderr, !scripted ? "%lu\n" : "", size); + return n ? m - n + 1 : 0; +} + + +/* write_stream: write a range of lines to a stream; return status */ +long +write_stream(fp, n, m) + FILE *fp; + long n; + long m; +{ + line_t *lp = get_addressed_line_node(n); + unsigned long size = 0; + char *s; + int len; + + if (des) + init_des_cipher(); + 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; + } + if (des) { + flush_des_file(fp); /* flush buffer */ + size += 8 - size % 8; /* adjust DES size */ + } + return size; +} + + +/* put_stream_line: write a line of text to a stream; return status */ +int +put_stream_line(fp, s, len) + FILE *fp; + char *s; + int len; +{ + while (len--) + if ((des ? put_des_char(*s++, fp) : fputc(*s++, fp)) < 0) { + fprintf(stderr, "%s\n", strerror(errno)); + sprintf(errmsg, "cannot write file"); + return ERR; + } + return 0; +} + +/* get_extended_line: get a an extended line from stdin */ +char * +get_extended_line(sizep, nonl) + 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') { + sprintf(errmsg, "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() +{ + register int oi = 0; + register 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)) { + fprintf(stderr, "stdin: %s\n", strerror(errno)); + sprintf(errmsg, "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(s, l, n, gflag) + char *s; + int l; + long n; + int gflag; +{ + int col = 0; + int lc = 0; + char *cp; + + if (gflag & GNP) { + printf("%ld\t", n); + col = 8; + } + for (; l--; s++) { + if ((gflag & GLS) && ++col > cols) { + fputs("\\\n", stdout); + col = 1; +#ifndef BACKWARDS + if (!scripted && !isglobal && ++lc > rows) { + lc = 0; + fputs("Press to continue... ", stdout); + fflush(stdout); + if (get_tty_line() < 0) + return ERR; + } +#endif + } + if (gflag & GLS) { + if (31 < *s && *s < 127 && *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); + } +#ifndef BACKWARDS + if (gflag & GLS) + putchar('$'); +#endif + putchar('\n'); + return 0; +} diff --git a/bin/ed/main.c b/bin/ed/main.c new file mode 100644 index 000000000000..fe970019eb53 --- /dev/null +++ b/bin/ed/main.c @@ -0,0 +1,1432 @@ +/* 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. + */ + +#ifndef lint +char *copyright = +"@(#) Copyright (c) 1993 Andrew Moore, Talke Studio. \n\ + All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +static char *rcsid = "@(#)$Id: main.c,v 1.5 1993/12/15 15:22:02 alm Exp alm $"; +#endif /* not lint */ + +/* + * 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. + * + * The cbc.c encryption code is adapted from + * the bdes program by Matt Bishop of Dartmouth College, + * Hanover, NH. + * + */ + +#include +#include +#include +#include +#include + +#include "ed.h" + + +#ifdef _POSIX_SOURCE +sigjmp_buf env; +#else +jmp_buf env; +#endif + +/* static buffers */ +char stdinbuf[1]; /* stdin buffer */ +char *shcmd; /* shell command buffer */ +int shcmdsz; /* shell command buffer size */ +int shcmdi; /* shell command buffer index */ +char *ibuf; /* ed command-line buffer */ +int ibufsz; /* ed command-line buffer size */ +char *ibufp; /* pointer to ed command-line buffer */ + +/* global flags */ +int des = 0; /* if set, use crypt(3) for i/o */ +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 mutex = 0; /* if set, signals set "sigflags" */ +int red = 0; /* if set, restrict shell/directory access */ +int scripted = 0; /* if set, suppress diagnostics */ +int sigflags = 0; /* if set, signals received while mutex set */ +int sigactive = 0; /* if set, signal handlers are enabled */ + +char old_filename[MAXPATHLEN + 1] = ""; /* default filename */ +long current_addr; /* current address in editor buffer */ +long addr_last; /* last address in editor buffer */ +int lineno; /* script line number */ +char *prompt; /* command-line prompt */ +char *dps = "*"; /* default command-line prompt */ + +char *usage = "usage: %s [-] [-sx] [-p string] [name]\n"; + +extern char errmsg[]; +extern int optind; +extern char *optarg; + +/* ed: line editor */ +int +main(argc, argv) + int argc; + char **argv; +{ + int c, n; + long status = 0; + + red = (n = strlen(argv[0])) > 2 && argv[0][n - 3] == 'r'; +top: + while ((c = getopt(argc, argv, "p:sx")) != EOF) + switch(c) { + case 'p': /* set prompt */ + prompt = optarg; + break; + case 's': /* run script */ + scripted = 1; + break; + case 'x': /* use crypt */ +#ifdef DES + des = get_keyword(); +#else + fprintf(stderr, "crypt unavailable\n?\n"); +#endif + 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--; + } + /* assert: reliable signals! */ +#ifdef SIGWINCH + handle_winch(SIGWINCH); + if (isatty(0)) signal(SIGWINCH, handle_winch); +#endif + signal(SIGHUP, signal_hup); + signal(SIGQUIT, SIG_IGN); + signal(SIGINT, signal_int); +#ifdef _POSIX_SOURCE + if (status = sigsetjmp(env, 1)) +#else + if (status = setjmp(env)) +#endif + { + fputs("\n?\n", stderr); + sprintf(errmsg, "interrupt"); + } else { + init_buffers(); + sigactive = 1; /* enable signal handlers */ + if (argc && **argv && is_legal_filename(*argv)) { + if (read_file(*argv, 0) < 0 && !isatty(0)) + quit(2); + else if (**argv != '!') + strcpy(old_filename, *argv); + } else if (argc) { + fputs("?\n", stderr); + if (**argv == '\0') + sprintf(errmsg, "invalid filename"); + if (!isatty(0)) + quit(2); + } + } + for (;;) { + if (status < 0 && garrulous) + fprintf(stderr, "%s\n", errmsg); + if (prompt) { + printf("%s", prompt); + fflush(stdout); + } + if ((n = get_tty_line()) < 0) { + status = ERR; + continue; + } else if (n == 0) { + if (modified && !scripted) { + fputs("?\n", stderr); + sprintf(errmsg, "warning: file modified"); + if (!isatty(0)) { + fprintf(stderr, garrulous ? + "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 */ + sprintf(errmsg, "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); + case EMOD: + modified = 0; + fputs("?\n", stderr); /* give warning */ + sprintf(errmsg, "warning: file modified"); + if (!isatty(0)) { + fprintf(stderr, garrulous ? + "script, line %d: %s\n" : + "", lineno, errmsg); + quit(2); + } + break; + case FATAL: + if (!isatty(0)) + fprintf(stderr, garrulous ? + "script, line %d: %s\n" : "", + lineno, errmsg); + else + fprintf(stderr, garrulous ? "%s\n" : "", + errmsg); + quit(3); + default: + fputs("?\n", stderr); + if (!isatty(0)) { + fprintf(stderr, garrulous ? + "script, line %d: %s\n" : "", + lineno, errmsg); + quit(2); + } + break; + } + } + /*NOTREACHED*/ +} + +long 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() +{ + long 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() while (isspace(*ibufp) && *ibufp != '\n') ibufp++ + +#define MUST_BE_FIRST() \ + if (!first) { sprintf(errmsg, "invalid address"); return ERR; } + +/* next_addr: return the next line address in the command buffer */ +long +next_addr() +{ + char *hd; + long addr = current_addr; + long n; + int first = 1; + int c; + + SKIP_BLANKS(); + for (hd = ibufp;; first = 0) + switch (c = *ibufp) { + case '+': + case '\t': + case ' ': + case '-': + case '^': + ibufp++; + SKIP_BLANKS(); + if (isdigit(*ibufp)) { + STRTOL(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(); + STRTOL(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(*ibufp++)) < 0) + return ERR; + break; + case '%': + case ',': + case ';': + if (first) { + ibufp++; + addr_cnt++; + second_addr = (c == ';') ? current_addr : 1; + addr = addr_last; + break; + } + /* FALL THROUGH */ + default: + if (ibufp == hd) + return EOF; + else if (addr < 0 || addr_last < addr) { + sprintf(errmsg, "invalid address"); + return ERR; + } else + return addr; + } + /* NOTREACHED */ +} + + +#ifdef BACKWARDS +/* GET_THIRD_ADDR: get a legal address from the command buffer */ +#define GET_THIRD_ADDR(addr) \ +{ \ + long ol1, ol2; \ +\ + ol1 = first_addr, ol2 = second_addr; \ + if (extract_addr_range() < 0) \ + return ERR; \ + else if (addr_cnt == 0) { \ + sprintf(errmsg, "destination expected"); \ + return ERR; \ + } else if (second_addr < 0 || addr_last < second_addr) { \ + sprintf(errmsg, "invalid address"); \ + return ERR; \ + } \ + addr = second_addr; \ + first_addr = ol1, second_addr = ol2; \ +} +#else /* BACKWARDS */ +/* GET_THIRD_ADDR: get a legal address from the command buffer */ +#define GET_THIRD_ADDR(addr) \ +{ \ + long ol1, ol2; \ +\ + ol1 = first_addr, ol2 = second_addr; \ + if (extract_addr_range() < 0) \ + return ERR; \ + if (second_addr < 0 || addr_last < second_addr) { \ + sprintf(errmsg, "invalid address"); \ + return ERR; \ + } \ + addr = second_addr; \ + first_addr = ol1, second_addr = ol2; \ +} +#endif + + +/* GET_COMMAND_SUFFIX: verify the command suffix in the command buffer */ +#define GET_COMMAND_SUFFIX() { \ + 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') { \ + sprintf(errmsg, "invalid command suffix"); \ + return ERR; \ + } \ +} + + +/* 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() */ + +long rows = 22; /* scroll length: ws_row - 2 */ + +/* exec_command: execute the next command in command buffer; return print + request, if any */ +int +exec_command() +{ + extern long u_current_addr; + extern long u_addr_last; + + static pattern_t *pat = NULL; + static int sgflag = 0; + static int sgnum = 0; + + pattern_t *tpat; + char *fnp; + int gflag = 0; + int sflags = 0; + long addr = 0; + int n = 0; + int c; + + SKIP_BLANKS(); + switch(c = *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; + /* fall through */ + case 'E': + if (addr_cnt > 0) { + sprintf(errmsg, "unexpected address"); + return ERR; + } else if (!isspace(*ibufp)) { + sprintf(errmsg, "unexpected command suffix"); + return ERR; + } else if ((fnp = get_filename()) == 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 (*fnp && *fnp != '!') strcpy(old_filename, fnp); +#ifdef BACKWARDS + if (*fnp == '\0' && *old_filename == '\0') { + sprintf(errmsg, "no current filename"); + return ERR; + } +#endif + if (read_file(*fnp ? fnp : old_filename, 0) < 0) + return ERR; + clear_undo_stack(); + modified = 0; + u_current_addr = u_addr_last = -1; + break; + case 'f': + if (addr_cnt > 0) { + sprintf(errmsg, "unexpected address"); + return ERR; + } else if (!isspace(*ibufp)) { + sprintf(errmsg, "unexpected command suffix"); + return ERR; + } else if ((fnp = get_filename()) == NULL) + return ERR; + else if (*fnp == '!') { + sprintf(errmsg, "invalid redirection"); + return ERR; + } + GET_COMMAND_SUFFIX(); + if (*fnp) strcpy(old_filename, fnp); + printf("%s\n", strip_escapes(old_filename)); + break; + case 'g': + case 'v': + case 'G': + case 'V': + if (isglobal) { + sprintf(errmsg, "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) { + sprintf(errmsg, "unexpected address"); + return ERR; + } + GET_COMMAND_SUFFIX(); + if (*errmsg) fprintf(stderr, "%s\n", errmsg); + break; + case 'H': + if (addr_cnt > 0) { + sprintf(errmsg, "unexpected address"); + return ERR; + } + GET_COMMAND_SUFFIX(); + if ((garrulous = 1 - garrulous) && *errmsg) + fprintf(stderr, "%s\n", errmsg); + break; + case 'i': + if (second_addr == 0) { + sprintf(errmsg, "invalid address"); + return ERR; + } + 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 = *ibufp++; + if (second_addr == 0) { + sprintf(errmsg, "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) { + sprintf(errmsg, "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) { + sprintf(errmsg, "unexpected address"); + return ERR; + } + GET_COMMAND_SUFFIX(); + prompt = prompt ? NULL : optarg ? optarg : dps; + break; + case 'q': + case 'Q': + if (addr_cnt > 0) { + sprintf(errmsg, "unexpected address"); + return ERR; + } + GET_COMMAND_SUFFIX(); + gflag = (modified && !scripted && c == 'q') ? EMOD : EOF; + break; + case 'r': + if (!isspace(*ibufp)) { + sprintf(errmsg, "unexpected command suffix"); + return ERR; + } else if (addr_cnt == 0) + second_addr = addr_last; + if ((fnp = get_filename()) == NULL) + return ERR; + GET_COMMAND_SUFFIX(); + if (!isglobal) clear_undo_stack(); + if (*old_filename == '\0' && *fnp != '!') + strcpy(old_filename, fnp); +#ifdef BACKWARDS + if (*fnp == '\0' && *old_filename == '\0') { + sprintf(errmsg, "no current filename"); + return ERR; + } +#endif + if ((addr = read_file(*fnp ? fnp : old_filename, second_addr)) < 0) + return ERR; + else if (addr && addr != addr_last) + 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': + STRTOL(sgnum, ibufp); + sflags |= SGF; + sgflag &= ~GSG; /* override GSG */ + break; + default: + if (sflags) { + sprintf(errmsg, "invalid command suffix"); + return ERR; + } + } + } while (sflags && *ibufp != '\n'); + if (sflags && !pat) { + sprintf(errmsg, "no previous substitution"); + return ERR; + } else if (sflags & SGG) + sgnum = 0; /* override numeric arg */ + if (*ibufp != '\n' && *(ibufp + 1) == '\n') { + sprintf(errmsg, "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) { + sprintf(errmsg, "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(*ibufp)) { + sprintf(errmsg, "unexpected command suffix"); + return ERR; + } else if ((fnp = get_filename()) == 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 (*old_filename == '\0' && *fnp != '!') + strcpy(old_filename, fnp); +#ifdef BACKWARDS + if (*fnp == '\0' && *old_filename == '\0') { + sprintf(errmsg, "no current filename"); + return ERR; + } +#endif + if ((addr = write_file(*fnp ? fnp : old_filename, + (c == 'W') ? "a" : "w", first_addr, second_addr)) < 0) + return ERR; + else if (addr == addr_last) + modified = 0; + else if (modified && !scripted && n == 'q') + gflag = EMOD; + break; + case 'x': + if (addr_cnt > 0) { + sprintf(errmsg, "unexpected address"); + return ERR; + } + GET_COMMAND_SUFFIX(); +#ifdef DES + des = get_keyword(); +#else + sprintf(errmsg, "crypt unavailable"); + return ERR; +#endif + break; + case 'z': +#ifdef BACKWARDS + if (check_addr_range(first_addr = 1, current_addr + 1) < 0) +#else + if (check_addr_range(first_addr = 1, current_addr + !isglobal) < 0) +#endif + return ERR; + else if ('0' < *ibufp && *ibufp <= '9') + STRTOL(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) { + sprintf(errmsg, "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': +#ifdef BACKWARDS + if (check_addr_range(first_addr = 1, current_addr + 1) < 0 +#else + if (check_addr_range(first_addr = 1, current_addr + !isglobal) < 0 +#endif + || display_lines(second_addr, second_addr, 0) < 0) + return ERR; + break; + default: + sprintf(errmsg, "unknown command"); + return ERR; + } + return gflag; +} + + +/* check_addr_range: return status of address range check */ +int +check_addr_range(n, m) + long n, m; +{ + if (addr_cnt == 0) { + first_addr = n; + second_addr = m; + } + if (first_addr > second_addr || 1 > first_addr || + second_addr > addr_last) { + sprintf(errmsg, "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 */ +long +get_matching_node_addr(pat, dir) + pattern_t *pat; + int dir; +{ + char *s; + long 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); + sprintf(errmsg, "no match"); + return ERR; +} + + +/* get_filename: return pointer to copy of filename in the command buffer */ +char * +get_filename() +{ + static char *file = NULL; + static int filesz = 0; + + int n; + + if (*ibufp != '\n') { + SKIP_BLANKS(); + if (*ibufp == '\n') { + sprintf(errmsg, "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 - 1 > MAXPATHLEN) { + sprintf(errmsg, "filename too long"); + return NULL; + } + } +#ifndef BACKWARDS + else if (*old_filename == '\0') { + sprintf(errmsg, "no current filename"); + return NULL; + } +#endif + REALLOC(file, filesz, MAXPATHLEN + 1, NULL); + for (n = 0; *ibufp != '\n';) + file[n++] = *ibufp++; + file[n] = '\0'; + return is_legal_filename(file) ? file : NULL; +} + + +/* get_shell_command: read a shell command from stdin; return substitution + status */ +int +get_shell_command() +{ + static char *buf = NULL; + static int n = 0; + + char *s; /* substitution char pointer */ + int i = 0; + int j = 0; + + if (red) { + sprintf(errmsg, "shell access restricted"); + return ERR; + } else 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++; + } +#ifdef BACKWARDS + else if (shcmd == NULL || *(shcmd + 1) == '\0') +#else + else if (shcmd == NULL) +#endif + { + sprintf(errmsg, "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') { + sprintf(errmsg, "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; + } + 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 */ +int +append_lines(n) + long 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 */ +int +join_lines(from, to) + long from; + long 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 */ +int +move_lines(addr) + long addr; +{ + line_t *b1, *a1, *b2, *a2; + long n = INC_MOD(second_addr, addr_last); + long 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 */ +int +copy_lines(addr) + long addr; +{ + line_t *lp, *np = get_addressed_line_node(first_addr); + undo_t *up = NULL; + long n = second_addr - first_addr + 1; + long 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(from, to) + long from, 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(from, to, gflag) + long from; + long to; + int gflag; +{ + line_t *bp; + line_t *ep; + char *s; + + if (!from) { + sprintf(errmsg, "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 */ + +line_t *mark[MAXMARK]; /* line markers */ +int markno; /* line marker count */ + +/* mark_line_node: set a line node mark */ +int +mark_line_node(lp, n) + line_t *lp; + int n; +{ + if (!islower(n)) { + sprintf(errmsg, "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 */ +long +get_marked_node_addr(n) + int n; +{ + if (!islower(n)) { + sprintf(errmsg, "invalid mark character"); + return ERR; + } + return get_line_node_addr(mark[n - 'a']); +} + + +/* unmark_line_node: clear line node mark */ +void +unmark_line_node(lp) + 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 */ +line_t * +dup_line_node(lp) + line_t *lp; +{ + line_t *np; + + if ((np = (line_t *) malloc(sizeof(line_t))) == NULL) { + fprintf(stderr, "%s\n", strerror(errno)); + sprintf(errmsg, "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(s, t) + 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 MAXPATHLEN */ +char * +strip_escapes(s) + char *s; +{ + static char *file = NULL; + static int filesz = 0; + + int i = 0; + + REALLOC(file, filesz, MAXPATHLEN + 1, NULL); + /* assert: no trailing escape */ + while (file[i++] = (*s == '\\') ? *++s : *s) + s++; + return file; +} + + +void +signal_hup(signo) + int signo; +{ + if (mutex) + sigflags |= (1 << (signo - 1)); + else handle_hup(signo); +} + + +void +signal_int(signo) + int signo; +{ + if (mutex) + sigflags |= (1 << (signo - 1)); + else handle_int(signo); +} + + +void +handle_hup(signo) + int signo; +{ + char *hup = NULL; /* hup filename */ + char *s; + int n; + + if (!sigactive) + quit(1); + sigflags &= ~(1 << (signo - 1)); + if (addr_last && write_file("ed.hup", "w", 1, addr_last) < 0 && + (s = getenv("HOME")) != NULL && + (n = strlen(s)) + 8 <= MAXPATHLEN && /* "ed.hup" + '/' */ + (hup = (char *) malloc(n + 10)) != NULL) { + strcpy(hup, s); + if (hup[n - 1] != '/') + hup[n] = '/', hup[n+1] = '\0'; + strcat(hup, "ed.hup"); + write_file(hup, "w", 1, addr_last); + } + quit(2); +} + + +void +handle_int(signo) + int signo; +{ + if (!sigactive) + quit(1); + sigflags &= ~(1 << (signo - 1)); +#ifdef _POSIX_SOURCE + siglongjmp(env, -1); +#else + longjmp(env, -1); +#endif +} + + +int cols = 72; /* wrap column */ + +void +handle_winch(signo) + int signo; +{ + struct winsize ws; /* window size structure */ + + sigflags &= ~(1 << (signo - 1)); + if (ioctl(0, TIOCGWINSZ, (char *) &ws) >= 0) { + if (ws.ws_row > 2) rows = ws.ws_row - 2; + if (ws.ws_col > 8) cols = ws.ws_col - 8; + } +} + + +/* is_legal_filename: return a legal filename */ +int +is_legal_filename(s) + char *s; +{ + if (red && (*s == '!' || !strcmp(s, "..") || strchr(s, '/'))) { + sprintf(errmsg, "shell access restricted"); + return 0; + } + return 1; +} diff --git a/bin/ed/re.c b/bin/ed/re.c index 7553d5f2e332..d0d4e6c0e20b 100644 --- a/bin/ed/re.c +++ b/bin/ed/re.c @@ -1,12 +1,9 @@ /* re.c: This file contains the regular expression interface routines for the ed line editor. */ /*- - * Copyright (c) 1993 The Regents of the University of California. + * Copyright (c) 1993 Andrew Moore, Talke Studio. * All rights reserved. * - * This code is derived from software contributed to Berkeley - * by Andrew Moore, Talke Studio. - * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: @@ -15,18 +12,11 @@ * 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. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * 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 REGENTS OR CONTRIBUTORS BE LIABLE + * 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) @@ -37,40 +27,34 @@ */ #ifndef lint -static char sccsid[] = "@(#)re.c 5.5 (Berkeley) 3/28/93"; +static char *rcsid = "@(#)$Id: re.c,v 1.2 1993/12/14 16:19:56 alm Exp $"; #endif /* not lint */ -#include -#include -#include - #include "ed.h" -extern char *lhbuf; -extern int lhbufsz; -extern char *ibufp; -extern int ibufsz; + extern int patlock; -char errmsg[MAXFNAME + 40] = ""; +char errmsg[MAXPATHLEN + 40] = ""; -/* optpat: return pointer to compiled pattern from command buffer */ +/* get_compiled_pattern: return pointer to compiled pattern from command + buffer */ pattern_t * -optpat() +get_compiled_pattern() { static pattern_t *exp = NULL; char *exps; - char delim; + char delimiter; int n; - if ((delim = *ibufp) == ' ') { + if ((delimiter = *ibufp) == ' ') { sprintf(errmsg, "invalid pattern delimiter"); return NULL; - } else if (delim == '\n' || *++ibufp == '\n' || *ibufp == delim) { + } else if (delimiter == '\n' || *++ibufp == '\n' || *ibufp == delimiter) { if (!exp) sprintf(errmsg, "no previous pattern"); return exp; - } else if ((exps = getlhs(delim)) == NULL) + } else if ((exps = extract_pattern(delimiter)) == NULL) return NULL; /* buffer alloc'd && not reserved */ if (exp && !patlock) @@ -90,23 +74,24 @@ optpat() } -extern int isbinary; - -/* getlhs: copy a pattern string from the command buffer; return pointer - to the copy */ +/* extract_pattern: copy a pattern string from the command buffer; return + pointer to the copy */ char * -getlhs(delim) - int delim; +extract_pattern(delimiter) + int delimiter; { + static char *lhbuf = NULL; /* buffer */ + static int lhbufsz = 0; /* buffer size */ + char *nd; int len; - for (nd = ibufp; *nd != delim && *nd != '\n'; nd++) + for (nd = ibufp; *nd != delimiter && *nd != '\n'; nd++) switch (*nd) { default: break; case '[': - if ((nd = ccl(++nd)) == NULL) { + if ((nd = parse_char_class(++nd)) == NULL) { sprintf(errmsg, "unbalanced brackets ([])"); return NULL; } @@ -119,17 +104,17 @@ getlhs(delim) break; } len = nd - ibufp; - CKBUF(lhbuf, lhbufsz, len + 1, NULL); + REALLOC(lhbuf, lhbufsz, len + 1, NULL); memcpy(lhbuf, ibufp, len); lhbuf[len] = '\0'; ibufp = nd; - return (isbinary) ? nultonl(lhbuf, len) : lhbuf; + return (isbinary) ? NUL_TO_NEWLINE(lhbuf, len) : lhbuf; } -/* ccl: expand a POSIX character class */ +/* parse_char_class: expand a POSIX character class */ char * -ccl(s) +parse_char_class(s) char *s; { int c, d; diff --git a/bin/ed/sub.c b/bin/ed/sub.c new file mode 100644 index 000000000000..6d01650f7da7 --- /dev/null +++ b/bin/ed/sub.c @@ -0,0 +1,263 @@ +/* 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. + */ + +#ifndef lint +static char *rcsid = "@(#)$Id: sub.c,v 1.3 1993/12/15 15:22:02 alm Exp alm $"; +#endif /* not lint */ + +#include "ed.h" + + +char *rhbuf; /* rhs substitution buffer */ +int rhbufsz; /* rhs substitution buffer size */ +int rhbufi; /* rhs substitution buffer index */ + +/* extract_subst_tail: extract substitution tail from the command buffer */ +int +extract_subst_tail(flagp, np) + 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') { + STRTOL(*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 */ +char * +extract_subst_template() +{ + int n = 0; + int i = 0; + char c; + char delimiter = *ibufp++; + + if (*ibufp == '%' && *(ibufp + 1) == delimiter) { + ibufp++; + if (!rhbuf) sprintf(errmsg, "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; +} + + +char *rbuf; /* substitute_matching_text buffer */ +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(pat, gflag, kth) + pattern_t *pat; + int gflag; + int kth; +{ + undo_t *up; + char *txt; + char *eot; + long lc; + 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++; + } + } + if (nsubs == 0 && !(gflag & GLB)) { + sprintf(errmsg, "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 pointer to the modified text */ +int +substitute_matching_text(pat, lp, gflag, kth) + pattern_t *pat; + line_t *lp; + int gflag; + int kth; +{ + int off = 0; + int changed = 0; + int matchno = 0; + int i = 0; + regmatch_t rm[SE_MAX]; + char *txt; + char *eot; + + if ((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 { + if (!kth || kth == ++matchno) { + changed++; + i = rm[0].rm_so; + REALLOC(rbuf, rbufsz, off + i, ERR); + if (isbinary) + NEWLINE_TO_NUL(txt, rm[0].rm_eo); + memcpy(rbuf + off, txt, i); + off += i; + if ((off = apply_subst_template(txt, rm, off, + pat->re_nsub)) < 0) + return ERR; + } else { + i = rm[0].rm_eo; + REALLOC(rbuf, rbufsz, off + i, ERR); + if (isbinary) + NEWLINE_TO_NUL(txt, i); + memcpy(rbuf + off, txt, i); + off += i; + } + txt += rm[0].rm_eo; + } while (*txt && (!changed || (gflag & GSG) && rm[0].rm_eo) && + !regexec(pat, txt, SE_MAX, rm, REG_NOTBOL)); + i = eot - txt; + REALLOC(rbuf, rbufsz, off + i + 2, ERR); + if (i > 0 && !rm[0].rm_eo && (gflag & GSG)) { + sprintf(errmsg, "infinite substitution loop"); + return ERR; + } + if (isbinary) + NEWLINE_TO_NUL(txt, i); + memcpy(rbuf + off, txt, 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 */ +int +apply_subst_template(boln, rm, off, re_nsub) + 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/test/Makefile b/bin/ed/test/Makefile index ca45a5183ebe..84166c141679 100644 --- a/bin/ed/test/Makefile +++ b/bin/ed/test/Makefile @@ -1,17 +1,23 @@ +SHELL= /bin/sh ED= ../obj/ed -all: build test - @echo done +all: check + @: + +check: build test + @if grep -h '\*\*\*' errs.o scripts.o; then :; else \ + echo "tests completed successfully."; \ + fi build: mkscripts.sh - @echo building test scripts... - @chmod +x mkscripts.sh - @./mkscripts.sh ${ED} + @if [ -f errs.o ]; then :; else \ + echo "building test scripts for $(ED) ..."; \ + $(SHELL) mkscripts.sh $(ED); \ + fi test: build ckscripts.sh - @echo running test scripts... - @chmod +x ckscripts.sh - @./ckscripts.sh ${ED} + @echo testing $(ED) ... + @$(SHELL) ckscripts.sh $(ED) clean: - rm -f *.ed *.[oz] *~ + rm -f *.ed *.red *.[oz] *~ diff --git a/bin/ed/test/README b/bin/ed/test/README index 8917f36acec8..ab66b9228cd5 100644 --- a/bin/ed/test/README +++ b/bin/ed/test/README @@ -19,14 +19,6 @@ which look like: or: *** Output u.o of script u.ed is incorrect *** -It is assumed that the ed being tested processes escapes (\) in file names. -This is so that a name starting with bang (!) can be read, via: - r \!file -Without the escape, a POSIX ed would attempt to read the output of -the shell command `file'. If the ed being tested does not support escape -processing on file names, then the script `mkscripts.sh' should be modified -accordingly. - The POSIX requirement that an address range not be used where at most a single address is expected has been relaxed in this version of ed. Therefore, the following scripts which test for compliance with this @@ -36,6 +28,3 @@ a1-err.ed i1-err.ed k1-err.ed r1-err.ed - -In addition, one of bang1-err.ed or bang2.ed will fail, depending on whether or -not ed was compiled with the VI_BANG directive. diff --git a/bin/ed/test/addr.d b/bin/ed/test/addr.d new file mode 100644 index 000000000000..8f7ba1b5d359 --- /dev/null +++ b/bin/ed/test/addr.d @@ -0,0 +1,9 @@ +line 1 +line 2 +line 3 +line 4 +line5 +1ine6 +line7 +line8 +line9 diff --git a/bin/ed/test/addr.r b/bin/ed/test/addr.r new file mode 100644 index 000000000000..04caf17f4228 --- /dev/null +++ b/bin/ed/test/addr.r @@ -0,0 +1,2 @@ +line 2 +line9 diff --git a/bin/ed/test/addr.t b/bin/ed/test/addr.t new file mode 100644 index 000000000000..750b224ed888 --- /dev/null +++ b/bin/ed/test/addr.t @@ -0,0 +1,5 @@ +1 d +1 1 d +1,2,d +1;+ + ,d +1,2;., + 2d diff --git a/bin/ed/test/ckscripts.sh b/bin/ed/test/ckscripts.sh index 87a7e9beb560..edc935d28701 100644 --- a/bin/ed/test/ckscripts.sh +++ b/bin/ed/test/ckscripts.sh @@ -5,26 +5,24 @@ PATH="/bin:/usr/bin:/usr/local/bin/:." ED=$1 -[ X"$ED" = X -o ! -x $ED ] && ED="../ed" [ ! -x $ED ] && { echo "$ED: cannot execute"; exit 1; } -# Run the *-err.ed scripts first, since these don't generate output; -# rename then to *-err.ed~; they exit with non-zero status -for i in *-err.ed; do - echo $i~ +# Run the *.red scripts first, since these don't generate output; +# they exit with non-zero status +for i in *.red; do + echo $i if $i; then - echo "*** The script $i~ exited abnormally ***" + echo "*** The script $i exited abnormally ***" fi - mv $i $i~ done >errs.o 2>&1 # Run the remainding scripts; they exit with zero status for i in *.ed; do - base=`expr $i : '\([^.]*\)'` +# base=`expr $i : '\([^.]*\)'` # base=`echo $i | sed 's/\..*//'` -# base=`$ED - \!"echo \\\\$i" <<-EOF -# s/\..* -# EOF` + base=`$ED - \!"echo $i" <<-EOF + s/\..* + EOF` if $base.ed; then if cmp -s $base.o $base.r; then :; else echo "*** Output $base.o of script $i is incorrect ***" diff --git a/bin/ed/test/g5.d b/bin/ed/test/g5.d new file mode 100644 index 000000000000..a92d664bc20a --- /dev/null +++ b/bin/ed/test/g5.d @@ -0,0 +1,3 @@ +line 1 +line 2 +line 3 diff --git a/bin/ed/test/g5.r b/bin/ed/test/g5.r new file mode 100644 index 000000000000..15a26758bac2 --- /dev/null +++ b/bin/ed/test/g5.r @@ -0,0 +1,9 @@ +line 1 +line 2 +line 3 +line 2 +line 3 +line 1 +line 3 +line 1 +line 2 diff --git a/bin/ed/test/g5.t b/bin/ed/test/g5.t new file mode 100644 index 000000000000..e213481d54ce --- /dev/null +++ b/bin/ed/test/g5.t @@ -0,0 +1,2 @@ +g/./1,3t$\ +1d diff --git a/bin/ed/test/mkscripts.sh b/bin/ed/test/mkscripts.sh index 724db0cc05a9..fa3147f56328 100644 --- a/bin/ed/test/mkscripts.sh +++ b/bin/ed/test/mkscripts.sh @@ -3,69 +3,71 @@ PATH="/bin:/usr/bin:/usr/local/bin/:." ED=$1 -[ X"$ED" = X -o ! -x $ED ] && ED="../ed" [ ! -x $ED ] && { echo "$ED: cannot execute"; exit 1; } for i in *.t; do # base=${i%.*} # base=`echo $i | sed 's/\..*//'` - base=`expr $i : '\([^.]*\)'` - ( - echo "#!/bin/sh -" - echo "$ED - <<\EOT" - echo "r \\$base.d" - cat $i - echo "w \\$base.o" - echo EOT - ) >$base.ed - chmod +x $base.ed -# The following is pretty ugly and not appropriate use of ed -# but the point is that it can be done... -# base=`$ED - \!"echo \\\\$i" <<-EOF -# s/\..* -# EOF` -# $ED - <<-EOF -# a -# #!/bin/sh - -# $ED - <<\EOT -# r \\$base.d -# w \\$base.o -# EOT -# . -# -2r \\$i -# w \\$base.ed -# !chmod +x \\$base.ed -# EOF +# base=`expr $i : '\([^.]*\)'` +# ( +# echo "#!/bin/sh -" +# echo "$ED - <<\EOT" +# echo "r $base.d" +# cat $i +# echo "w $base.o" +# echo EOT +# ) >$base.ed +# chmod +x $base.ed +# The following is pretty ugly way of doing the above, and not appropriate +# use of ed but the point is that it can be done... + base=`$ED - \!"echo $i" <<-EOF + s/\..* + EOF` + $ED - <<-EOF + a + #!/bin/sh - + $ED - <<\EOT + H + r $base.d + w $base.o + EOT + . + -2r $i + w $base.ed + !chmod +x $base.ed + EOF done for i in *.err; do # base=${i%.*} # base=`echo $i | sed 's/\..*//'` - base=`expr $i : '\([^.]*\)'` - ( - echo "#!/bin/sh -" - echo "$ED - <<\EOT" - echo H - echo "r \\$base.err" - cat $i - echo "w \\$base.o" - echo EOT - ) >$base-err.ed - chmod +x $base-err.ed -# base=`$ED - \!"echo \\\\$i" <<-EOF -# s/\..* -# EOF` -# $ED - <<-EOF -# a -# #!/bin/sh - -# $ED - <<\EOT -# H -# r \\$base.err -# w \\$base.o -# EOT -# . -# -2r \\$i -# w \\${base}-err.ed -# !chmod +x ${base}-err.ed -# EOF +# base=`expr $i : '\([^.]*\)'` +# ( +# echo "#!/bin/sh -" +# echo "$ED - <<\EOT" +# echo H +# echo "r $base.err" +# cat $i +# echo "w $base.o" +# echo EOT +# ) >$base-err.ed +# chmod +x $base-err.ed +# The following is pretty ugly way of doing the above, and not appropriate +# use of ed but the point is that it can be done... + base=`$ED - \!"echo $i" <<-EOF + s/\..* + EOF` + $ED - <<-EOF + a + #!/bin/sh - + $ED - <<\EOT + H + r $base.err + w $base.o + EOT + . + -2r $i + w ${base}.red + !chmod +x ${base}.red + EOF done diff --git a/bin/ed/undo.c b/bin/ed/undo.c new file mode 100644 index 000000000000..9dea62163d03 --- /dev/null +++ b/bin/ed/undo.c @@ -0,0 +1,154 @@ +/* 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. + */ + +#ifndef lint +static char *rcsid = "@(#)$Id: undo.c,v 1.2 1993/12/14 16:19:56 alm Exp $"; +#endif /* not lint */ + +#include "ed.h" + + +#define USIZE 100 /* undo stack size */ +undo_t *ustack = NULL; /* undo stack */ +long usize = 0; /* stack size variable */ +long u_p = 0; /* undo stack pointer */ + +/* push_undo_stack: return pointer to intialized undo node */ +undo_t * +push_undo_stack(type, from, to) + int type; + long from; + long to; +{ + undo_t *t; + +#if defined(sun) || defined(NO_REALLOC_NULL) + if (ustack == NULL && + (ustack = (undo_t *) malloc((usize = USIZE) * sizeof(undo_t))) == NULL) { + fprintf(stderr, "%s\n", strerror(errno)); + sprintf(errmsg, "out of memory"); + return NULL; + } +#endif + t = ustack; + if (u_p < usize || + (t = (undo_t *) realloc(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 */ + fprintf(stderr, "%s\n", strerror(errno)); + sprintf(errmsg, "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; \ +} + + +long u_current_addr = -1; /* if >= 0, undo enabled */ +long u_addr_last = -1; /* if >= 0, undo enabled */ + +/* pop_undo_stack: undo last change to the editor buffer */ +int +pop_undo_stack() +{ + long n; + long o_current_addr = current_addr; + long o_addr_last = addr_last; + + if (u_current_addr == -1 || u_addr_last == -1) { + sprintf(errmsg, "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() +{ + 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; +}