1 /* vi: set sw=4 ts=4: */
3 * Copyright (c) 2002 by David I. Bell
4 * Permission is granted to use, distribute, or modify this source,
5 * provided that this copyright notice remains intact.
7 * The "ed" built-in command (much simplified)
14 //config: The original 1970's Unix text editor, from the days of teletypes.
15 //config: Small, simple, evil. Part of SUSv3. If you're not already using
16 //config: this, you don't need it.
18 //kbuild:lib-$(CONFIG_ED) += ed.o
20 //applet:IF_ED(APPLET(ed, BB_DIR_BIN, BB_SUID_DROP))
22 //usage:#define ed_trivial_usage ""
23 //usage:#define ed_full_usage ""
26 #include "common_bufsiz.h"
36 #define searchString bb_common_bufsiz1
39 USERSIZE
= COMMON_BUFSIZE
> 1024 ? 1024
40 : COMMON_BUFSIZE
- 1, /* max line length typed in by user */
41 INITBUF_SIZE
= 1024, /* initial buffer size */
57 #define G (*ptr_to_globals)
58 #define curLine (G.curLine )
59 #define bufBase (G.bufBase )
60 #define bufPtr (G.bufPtr )
61 #define fileName (G.fileName )
62 #define curNum (G.curNum )
63 #define lastNum (G.lastNum )
64 #define bufUsed (G.bufUsed )
65 #define bufSize (G.bufSize )
66 #define dirty (G.dirty )
67 #define lines (G.lines )
68 #define marks (G.marks )
69 #define INIT_G() do { \
70 setup_common_bufsiz(); \
71 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
75 static void doCommands(void);
76 static void subCommand(const char *cmd
, int num1
, int num2
);
77 static int getNum(const char **retcp
, smallint
*retHaveNum
, int *retNum
);
78 static int setCurNum(int num
);
79 static void addLines(int num
);
80 static int insertLine(int num
, const char *data
, int len
);
81 static void deleteLines(int num1
, int num2
);
82 static int printLines(int num1
, int num2
, int expandFlag
);
83 static int writeLines(const char *file
, int num1
, int num2
);
84 static int readLines(const char *file
, int num
);
85 static int searchLines(const char *str
, int num1
, int num2
);
86 static LINE
*findLine(int num
);
87 static int findString(const LINE
*lp
, const char * str
, int len
, int offset
);
90 static int bad_nums(int num1
, int num2
, const char *for_what
)
92 if ((num1
< 1) || (num2
> lastNum
) || (num1
> num2
)) {
93 bb_error_msg("bad line range for %s", for_what
);
100 static char *skip_blank(const char *cp
)
108 int ed_main(int argc
, char **argv
) MAIN_EXTERNALLY_VISIBLE
;
109 int ed_main(int argc UNUSED_PARAM
, char **argv
)
113 bufSize
= INITBUF_SIZE
;
114 bufBase
= xmalloc(bufSize
);
120 fileName
= xstrdup(argv
[1]);
121 if (!readLines(fileName
, 1)) {
134 * Read commands until we are told to stop.
136 static void doCommands(void)
139 char *endbuf
, buf
[USERSIZE
];
141 smallint have1
, have2
;
145 * -1 on read errors or EOF, or on bare Ctrl-D.
147 * >0 length of input string, including terminating '\n'
149 len
= read_line_input(NULL
, ": ", buf
, sizeof(buf
), /*timeout*/ -1);
152 endbuf
= &buf
[len
- 1];
153 while ((endbuf
> buf
) && isblank(endbuf
[-1]))
157 cp
= skip_blank(buf
);
161 if ((curNum
== 0) && (lastNum
> 0)) {
163 curLine
= lines
.next
;
166 if (!getNum(&cp
, &have1
, &num1
))
173 if (!getNum(&cp
, &have2
, &num2
))
193 deleteLines(num1
, num2
);
198 deleteLines(num1
, num2
);
202 if (*cp
&& !isblank(*cp
)) {
203 bb_error_msg("bad file command");
209 printf("\"%s\"\n", fileName
);
211 puts("No file name");
215 fileName
= xstrdup(cp
);
224 if ((*cp
< 'a') || (*cp
> 'z') || cp
[1]) {
225 bb_error_msg("bad mark name");
228 marks
[*cp
- 'a'] = num2
;
232 printLines(num1
, num2
, TRUE
);
236 printLines(num1
, num2
, FALSE
);
242 bb_error_msg("bad quit command");
247 len
= read_line_input(NULL
, "Really quit? ", buf
, 16, /*timeout*/ -1);
248 /* read error/EOF - no way to continue */
251 cp
= skip_blank(buf
);
252 if ((*cp
| 0x20) == 'y') /* Y or y */
257 if (*cp
&& !isblank(*cp
)) {
258 bb_error_msg("bad read command");
263 bb_error_msg("no file name");
268 if (readLines(cp
, num1
+ 1))
270 if (fileName
== NULL
)
271 fileName
= xstrdup(cp
);
275 subCommand(cp
, num1
, num2
);
279 if (*cp
&& !isblank(*cp
)) {
280 bb_error_msg("bad write command");
291 bb_error_msg("no file name specified");
294 writeLines(cp
, num1
, num2
);
300 printLines(curNum
- 21, curNum
, FALSE
);
303 printLines(curNum
- 11, curNum
+ 10, FALSE
);
306 printLines(curNum
, curNum
+ 21, FALSE
);
313 bb_error_msg("no arguments allowed");
316 printLines(curNum
, curNum
, FALSE
);
320 if (setCurNum(curNum
- 1))
321 printLines(curNum
, curNum
, FALSE
);
325 printf("%d\n", num1
);
329 printLines(num2
, num2
, FALSE
);
332 if (setCurNum(curNum
+ 1))
333 printLines(curNum
, curNum
, FALSE
);
337 bb_error_msg("unimplemented command");
345 * Do the substitute command.
346 * The current line is set to the last substitution done.
348 static void subCommand(const char *cmd
, int num1
, int num2
)
350 char *cp
, *oldStr
, *newStr
, buf
[USERSIZE
];
351 int delim
, oldLen
, newLen
, deltaLen
, offset
;
353 int globalFlag
, printFlag
, didSub
, needPrint
;
355 if (bad_nums(num1
, num2
, "substitute"))
364 * Copy the command so we can modify it.
369 if (isblank(*cp
) || (*cp
== '\0')) {
370 bb_error_msg("bad delimiter for substitute");
377 cp
= strchr(cp
, delim
);
379 bb_error_msg("missing 2nd delimiter for substitute");
386 cp
= strchr(cp
, delim
);
393 while (*cp
) switch (*cp
++) {
401 bb_error_msg("unknown option for substitute");
405 if (*oldStr
== '\0') {
406 if (searchString
[0] == '\0') {
407 bb_error_msg("no previous search string");
410 oldStr
= searchString
;
413 if (oldStr
!= searchString
)
414 strcpy(searchString
, oldStr
);
420 oldLen
= strlen(oldStr
);
421 newLen
= strlen(newStr
);
422 deltaLen
= newLen
- oldLen
;
426 while (num1
<= num2
) {
427 offset
= findString(lp
, oldStr
, oldLen
, offset
);
431 printLines(num1
, num1
, FALSE
);
440 needPrint
= printFlag
;
445 * If the replacement string is the same size or shorter
446 * than the old string, then the substitution is easy.
449 memcpy(&lp
->data
[offset
], newStr
, newLen
);
451 memcpy(&lp
->data
[offset
+ newLen
],
452 &lp
->data
[offset
+ oldLen
],
453 lp
->len
- offset
- oldLen
);
461 printLines(num1
, num1
, FALSE
);
470 * The new string is larger, so allocate a new line
471 * structure and use that. Link it in place of
472 * the old line structure.
474 nlp
= xmalloc(sizeof(LINE
) + lp
->len
+ deltaLen
);
476 nlp
->len
= lp
->len
+ deltaLen
;
478 memcpy(nlp
->data
, lp
->data
, offset
);
479 memcpy(&nlp
->data
[offset
], newStr
, newLen
);
480 memcpy(&nlp
->data
[offset
+ newLen
],
481 &lp
->data
[offset
+ oldLen
],
482 lp
->len
- offset
- oldLen
);
484 nlp
->next
= lp
->next
;
485 nlp
->prev
= lp
->prev
;
486 nlp
->prev
->next
= nlp
;
487 nlp
->next
->prev
= nlp
;
501 printLines(num1
, num1
, FALSE
);
510 bb_error_msg("no substitutions found for \"%s\"", oldStr
);
515 * Search a line for the specified string starting at the specified
516 * offset in the line. Returns the offset of the found string, or -1.
518 static int findString(const LINE
*lp
, const char *str
, int len
, int offset
)
521 const char *cp
, *ncp
;
523 cp
= &lp
->data
[offset
];
524 left
= lp
->len
- offset
;
526 while (left
>= len
) {
527 ncp
= memchr(cp
, *str
, left
);
534 if (memcmp(cp
, str
, len
) == 0)
535 return (cp
- lp
->data
);
545 * Add lines which are typed in by the user.
546 * The lines are inserted just before the specified line number.
547 * The lines are terminated by a line containing a single dot (ugly!),
548 * or by an end of file.
550 static void addLines(int num
)
553 char buf
[USERSIZE
+ 1];
557 * -1 on read errors or EOF, or on bare Ctrl-D.
559 * >0 length of input string, including terminating '\n'
561 len
= read_line_input(NULL
, "", buf
, sizeof(buf
), /*timeout*/ -1);
563 /* Previously, ctrl-C was exiting to shell.
564 * Now we exit to ed prompt. Is in important? */
567 if ((buf
[0] == '.') && (buf
[1] == '\n') && (buf
[2] == '\0'))
569 if (!insertLine(num
++, buf
, len
))
576 * Parse a line number argument if it is present. This is a sum
577 * or difference of numbers, '.', '$', 'x, or a search string.
578 * Returns TRUE if successful (whether or not there was a number).
579 * Returns FALSE if there was a parsing error, with a message output.
580 * Whether there was a number is returned indirectly, as is the number.
581 * The character pointer which stopped the scan is also returned.
583 static int getNum(const char **retcp
, smallint
*retHaveNum
, int *retNum
)
586 char *endStr
, str
[USERSIZE
];
588 smallint haveNum
, minus
;
613 if ((*cp
< 'a') || (*cp
> 'z')) {
614 bb_error_msg("bad mark name");
618 num
= marks
[*cp
++ - 'a'];
623 endStr
= strchr(str
, '/');
626 cp
+= (endStr
- str
);
629 num
= searchLines(str
, curNum
, lastNum
);
638 *retHaveNum
= haveNum
;
644 num
= num
* 10 + *cp
++ - '0';
649 value
+= (minus
? -num
: num
);
666 *retHaveNum
= haveNum
;
675 * Read lines from a file at the specified line number.
676 * Returns TRUE if the file was successfully read.
678 static int readLines(const char *file
, int num
)
681 int len
, lineCount
, charCount
;
684 if ((num
< 1) || (num
> lastNum
+ 1)) {
685 bb_error_msg("bad line for read");
691 bb_simple_perror_msg(file
);
701 printf("\"%s\", ", file
);
705 cp
= memchr(bufPtr
, '\n', bufUsed
);
708 len
= (cp
- bufPtr
) + 1;
709 if (!insertLine(num
, bufPtr
, len
)) {
721 if (bufPtr
!= bufBase
) {
722 memcpy(bufBase
, bufPtr
, bufUsed
);
723 bufPtr
= bufBase
+ bufUsed
;
726 if (bufUsed
>= bufSize
) {
727 len
= (bufSize
* 3) / 2;
728 cp
= xrealloc(bufBase
, len
);
730 bufPtr
= bufBase
+ bufUsed
;
734 cc
= safe_read(fd
, bufPtr
, bufSize
- bufUsed
);
740 bb_simple_perror_msg(file
);
746 if (!insertLine(num
, bufPtr
, bufUsed
)) {
751 charCount
+= bufUsed
;
756 printf("%d lines%s, %d chars\n", lineCount
,
757 (bufUsed
? " (incomplete)" : ""), charCount
);
764 * Write the specified lines out to the specified file.
765 * Returns TRUE if successful, or FALSE on an error with a message output.
767 static int writeLines(const char *file
, int num1
, int num2
)
770 int fd
, lineCount
, charCount
;
772 if (bad_nums(num1
, num2
, "write"))
778 fd
= creat(file
, 0666);
780 bb_simple_perror_msg(file
);
784 printf("\"%s\", ", file
);
793 while (num1
++ <= num2
) {
794 if (full_write(fd
, lp
->data
, lp
->len
) != lp
->len
) {
795 bb_simple_perror_msg(file
);
799 charCount
+= lp
->len
;
805 bb_simple_perror_msg(file
);
809 printf("%d lines, %d chars\n", lineCount
, charCount
);
815 * Print lines in a specified range.
816 * The last line printed becomes the current line.
817 * If expandFlag is TRUE, then the line is printed specially to
818 * show magic characters.
820 static int printLines(int num1
, int num2
, int expandFlag
)
826 if (bad_nums(num1
, num2
, "print"))
833 while (num1
<= num2
) {
835 write(STDOUT_FILENO
, lp
->data
, lp
->len
);
842 * Show control characters and characters with the
843 * high bit set specially.
848 if ((count
> 0) && (cp
[count
- 1] == '\n'))
851 while (count
-- > 0) {
852 ch
= (unsigned char) *cp
++;
853 fputc_printable(ch
| PRINTABLE_META
, stdout
);
856 fputs("$\n", stdout
);
867 * Insert a new line with the specified text.
868 * The line is inserted so as to become the specified line,
869 * thus pushing any existing and further lines down one.
870 * The inserted line is also set to become the current line.
871 * Returns TRUE if successful.
873 static int insertLine(int num
, const char *data
, int len
)
877 if ((num
< 1) || (num
> lastNum
+ 1)) {
878 bb_error_msg("inserting at bad line number");
882 newLp
= xmalloc(sizeof(LINE
) + len
- 1);
884 memcpy(newLp
->data
, data
, len
);
892 free((char *) newLp
);
898 newLp
->prev
= lp
->prev
;
899 lp
->prev
->next
= newLp
;
904 return setCurNum(num
);
909 * Delete lines from the given range.
911 static void deleteLines(int num1
, int num2
)
913 LINE
*lp
, *nlp
, *plp
;
916 if (bad_nums(num1
, num2
, "delete"))
923 if ((curNum
>= num1
) && (curNum
<= num2
)) {
932 count
= num2
- num1
+ 1;
937 while (count
-- > 0) {
951 * Search for a line which contains the specified string.
952 * If the string is "", then the previously searched for string
953 * is used. The currently searched for string is saved for future use.
954 * Returns the line number which matches, or 0 if there was no match
955 * with an error printed.
957 static NOINLINE
int searchLines(const char *str
, int num1
, int num2
)
962 if (bad_nums(num1
, num2
, "search"))
966 if (searchString
[0] == '\0') {
967 bb_error_msg("no previous search string");
973 if (str
!= searchString
)
974 strcpy(searchString
, str
);
982 while (num1
<= num2
) {
983 if (findString(lp
, str
, len
, 0) >= 0)
989 bb_error_msg("can't find string \"%s\"", str
);
995 * Return a pointer to the specified line number.
997 static LINE
*findLine(int num
)
1002 if ((num
< 1) || (num
> lastNum
)) {
1003 bb_error_msg("line number %d does not exist", num
);
1009 curLine
= lines
.next
;
1017 if (num
< (curNum
/ 2)) {
1020 } else if (num
> ((curNum
+ lastNum
) / 2)) {
1025 while (lnum
< num
) {
1030 while (lnum
> num
) {
1039 * Set the current line number.
1040 * Returns TRUE if successful.
1042 static int setCurNum(int num
)