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 ""
35 #define searchString bb_common_bufsiz1
38 USERSIZE
= sizeof(searchString
) > 1024 ? 1024
39 : sizeof(searchString
) - 1, /* max line length typed in by user */
40 INITBUF_SIZE
= 1024, /* initial buffer size */
56 #define G (*ptr_to_globals)
57 #define curLine (G.curLine )
58 #define bufBase (G.bufBase )
59 #define bufPtr (G.bufPtr )
60 #define fileName (G.fileName )
61 #define curNum (G.curNum )
62 #define lastNum (G.lastNum )
63 #define bufUsed (G.bufUsed )
64 #define bufSize (G.bufSize )
65 #define dirty (G.dirty )
66 #define lines (G.lines )
67 #define marks (G.marks )
68 #define INIT_G() do { \
69 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
73 static void doCommands(void);
74 static void subCommand(const char *cmd
, int num1
, int num2
);
75 static int getNum(const char **retcp
, smallint
*retHaveNum
, int *retNum
);
76 static int setCurNum(int num
);
77 static void addLines(int num
);
78 static int insertLine(int num
, const char *data
, int len
);
79 static void deleteLines(int num1
, int num2
);
80 static int printLines(int num1
, int num2
, int expandFlag
);
81 static int writeLines(const char *file
, int num1
, int num2
);
82 static int readLines(const char *file
, int num
);
83 static int searchLines(const char *str
, int num1
, int num2
);
84 static LINE
*findLine(int num
);
85 static int findString(const LINE
*lp
, const char * str
, int len
, int offset
);
88 static int bad_nums(int num1
, int num2
, const char *for_what
)
90 if ((num1
< 1) || (num2
> lastNum
) || (num1
> num2
)) {
91 bb_error_msg("bad line range for %s", for_what
);
98 static char *skip_blank(const char *cp
)
106 int ed_main(int argc
, char **argv
) MAIN_EXTERNALLY_VISIBLE
;
107 int ed_main(int argc UNUSED_PARAM
, char **argv
)
111 bufSize
= INITBUF_SIZE
;
112 bufBase
= xmalloc(bufSize
);
118 fileName
= xstrdup(argv
[1]);
119 if (!readLines(fileName
, 1)) {
132 * Read commands until we are told to stop.
134 static void doCommands(void)
137 char *endbuf
, buf
[USERSIZE
];
139 smallint have1
, have2
;
143 * -1 on read errors or EOF, or on bare Ctrl-D.
145 * >0 length of input string, including terminating '\n'
147 len
= read_line_input(NULL
, ": ", buf
, sizeof(buf
), /*timeout*/ -1);
150 endbuf
= &buf
[len
- 1];
151 while ((endbuf
> buf
) && isblank(endbuf
[-1]))
155 cp
= skip_blank(buf
);
159 if ((curNum
== 0) && (lastNum
> 0)) {
161 curLine
= lines
.next
;
164 if (!getNum(&cp
, &have1
, &num1
))
171 if (!getNum(&cp
, &have2
, &num2
))
191 deleteLines(num1
, num2
);
196 deleteLines(num1
, num2
);
200 if (*cp
&& !isblank(*cp
)) {
201 bb_error_msg("bad file command");
207 printf("\"%s\"\n", fileName
);
209 printf("No file name\n");
213 fileName
= xstrdup(cp
);
222 if ((*cp
< 'a') || (*cp
> 'z') || cp
[1]) {
223 bb_error_msg("bad mark name");
226 marks
[*cp
- 'a'] = num2
;
230 printLines(num1
, num2
, TRUE
);
234 printLines(num1
, num2
, FALSE
);
240 bb_error_msg("bad quit command");
245 len
= read_line_input(NULL
, "Really quit? ", buf
, 16, /*timeout*/ -1);
246 /* read error/EOF - no way to continue */
249 cp
= skip_blank(buf
);
250 if ((*cp
| 0x20) == 'y') /* Y or y */
255 if (*cp
&& !isblank(*cp
)) {
256 bb_error_msg("bad read command");
261 bb_error_msg("no file name");
266 if (readLines(cp
, num1
+ 1))
268 if (fileName
== NULL
)
269 fileName
= xstrdup(cp
);
273 subCommand(cp
, num1
, num2
);
277 if (*cp
&& !isblank(*cp
)) {
278 bb_error_msg("bad write command");
289 bb_error_msg("no file name specified");
292 writeLines(cp
, num1
, num2
);
298 printLines(curNum
- 21, curNum
, FALSE
);
301 printLines(curNum
- 11, curNum
+ 10, FALSE
);
304 printLines(curNum
, curNum
+ 21, FALSE
);
311 bb_error_msg("no arguments allowed");
314 printLines(curNum
, curNum
, FALSE
);
318 if (setCurNum(curNum
- 1))
319 printLines(curNum
, curNum
, FALSE
);
323 printf("%d\n", num1
);
327 printLines(num2
, num2
, FALSE
);
330 if (setCurNum(curNum
+ 1))
331 printLines(curNum
, curNum
, FALSE
);
335 bb_error_msg("unimplemented command");
343 * Do the substitute command.
344 * The current line is set to the last substitution done.
346 static void subCommand(const char *cmd
, int num1
, int num2
)
348 char *cp
, *oldStr
, *newStr
, buf
[USERSIZE
];
349 int delim
, oldLen
, newLen
, deltaLen
, offset
;
351 int globalFlag
, printFlag
, didSub
, needPrint
;
353 if (bad_nums(num1
, num2
, "substitute"))
362 * Copy the command so we can modify it.
367 if (isblank(*cp
) || (*cp
== '\0')) {
368 bb_error_msg("bad delimiter for substitute");
375 cp
= strchr(cp
, delim
);
377 bb_error_msg("missing 2nd delimiter for substitute");
384 cp
= strchr(cp
, delim
);
391 while (*cp
) switch (*cp
++) {
399 bb_error_msg("unknown option for substitute");
403 if (*oldStr
== '\0') {
404 if (searchString
[0] == '\0') {
405 bb_error_msg("no previous search string");
408 oldStr
= searchString
;
411 if (oldStr
!= searchString
)
412 strcpy(searchString
, oldStr
);
418 oldLen
= strlen(oldStr
);
419 newLen
= strlen(newStr
);
420 deltaLen
= newLen
- oldLen
;
424 while (num1
<= num2
) {
425 offset
= findString(lp
, oldStr
, oldLen
, offset
);
429 printLines(num1
, num1
, FALSE
);
438 needPrint
= printFlag
;
443 * If the replacement string is the same size or shorter
444 * than the old string, then the substitution is easy.
447 memcpy(&lp
->data
[offset
], newStr
, newLen
);
449 memcpy(&lp
->data
[offset
+ newLen
],
450 &lp
->data
[offset
+ oldLen
],
451 lp
->len
- offset
- oldLen
);
459 printLines(num1
, num1
, FALSE
);
468 * The new string is larger, so allocate a new line
469 * structure and use that. Link it in place of
470 * the old line structure.
472 nlp
= xmalloc(sizeof(LINE
) + lp
->len
+ deltaLen
);
474 nlp
->len
= lp
->len
+ deltaLen
;
476 memcpy(nlp
->data
, lp
->data
, offset
);
477 memcpy(&nlp
->data
[offset
], newStr
, newLen
);
478 memcpy(&nlp
->data
[offset
+ newLen
],
479 &lp
->data
[offset
+ oldLen
],
480 lp
->len
- offset
- oldLen
);
482 nlp
->next
= lp
->next
;
483 nlp
->prev
= lp
->prev
;
484 nlp
->prev
->next
= nlp
;
485 nlp
->next
->prev
= nlp
;
499 printLines(num1
, num1
, FALSE
);
508 bb_error_msg("no substitutions found for \"%s\"", oldStr
);
513 * Search a line for the specified string starting at the specified
514 * offset in the line. Returns the offset of the found string, or -1.
516 static int findString(const LINE
*lp
, const char *str
, int len
, int offset
)
519 const char *cp
, *ncp
;
521 cp
= &lp
->data
[offset
];
522 left
= lp
->len
- offset
;
524 while (left
>= len
) {
525 ncp
= memchr(cp
, *str
, left
);
532 if (memcmp(cp
, str
, len
) == 0)
533 return (cp
- lp
->data
);
543 * Add lines which are typed in by the user.
544 * The lines are inserted just before the specified line number.
545 * The lines are terminated by a line containing a single dot (ugly!),
546 * or by an end of file.
548 static void addLines(int num
)
551 char buf
[USERSIZE
+ 1];
555 * -1 on read errors or EOF, or on bare Ctrl-D.
557 * >0 length of input string, including terminating '\n'
559 len
= read_line_input(NULL
, "", buf
, sizeof(buf
), /*timeout*/ -1);
561 /* Previously, ctrl-C was exiting to shell.
562 * Now we exit to ed prompt. Is in important? */
565 if ((buf
[0] == '.') && (buf
[1] == '\n') && (buf
[2] == '\0'))
567 if (!insertLine(num
++, buf
, len
))
574 * Parse a line number argument if it is present. This is a sum
575 * or difference of numbers, '.', '$', 'x, or a search string.
576 * Returns TRUE if successful (whether or not there was a number).
577 * Returns FALSE if there was a parsing error, with a message output.
578 * Whether there was a number is returned indirectly, as is the number.
579 * The character pointer which stopped the scan is also returned.
581 static int getNum(const char **retcp
, smallint
*retHaveNum
, int *retNum
)
584 char *endStr
, str
[USERSIZE
];
586 smallint haveNum
, minus
;
611 if ((*cp
< 'a') || (*cp
> 'z')) {
612 bb_error_msg("bad mark name");
616 num
= marks
[*cp
++ - 'a'];
621 endStr
= strchr(str
, '/');
624 cp
+= (endStr
- str
);
627 num
= searchLines(str
, curNum
, lastNum
);
636 *retHaveNum
= haveNum
;
642 num
= num
* 10 + *cp
++ - '0';
647 value
+= (minus
? -num
: num
);
664 *retHaveNum
= haveNum
;
673 * Read lines from a file at the specified line number.
674 * Returns TRUE if the file was successfully read.
676 static int readLines(const char *file
, int num
)
679 int len
, lineCount
, charCount
;
682 if ((num
< 1) || (num
> lastNum
+ 1)) {
683 bb_error_msg("bad line for read");
689 bb_simple_perror_msg(file
);
699 printf("\"%s\", ", file
);
703 cp
= memchr(bufPtr
, '\n', bufUsed
);
706 len
= (cp
- bufPtr
) + 1;
707 if (!insertLine(num
, bufPtr
, len
)) {
719 if (bufPtr
!= bufBase
) {
720 memcpy(bufBase
, bufPtr
, bufUsed
);
721 bufPtr
= bufBase
+ bufUsed
;
724 if (bufUsed
>= bufSize
) {
725 len
= (bufSize
* 3) / 2;
726 cp
= xrealloc(bufBase
, len
);
728 bufPtr
= bufBase
+ bufUsed
;
732 cc
= safe_read(fd
, bufPtr
, bufSize
- bufUsed
);
739 bb_simple_perror_msg(file
);
745 if (!insertLine(num
, bufPtr
, bufUsed
)) {
750 charCount
+= bufUsed
;
755 printf("%d lines%s, %d chars\n", lineCount
,
756 (bufUsed
? " (incomplete)" : ""), charCount
);
763 * Write the specified lines out to the specified file.
764 * Returns TRUE if successful, or FALSE on an error with a message output.
766 static int writeLines(const char *file
, int num1
, int num2
)
769 int fd
, lineCount
, charCount
;
771 if (bad_nums(num1
, num2
, "write"))
777 fd
= creat(file
, 0666);
779 bb_simple_perror_msg(file
);
783 printf("\"%s\", ", file
);
792 while (num1
++ <= num2
) {
793 if (full_write(fd
, lp
->data
, lp
->len
) != lp
->len
) {
794 bb_simple_perror_msg(file
);
798 charCount
+= lp
->len
;
804 bb_simple_perror_msg(file
);
808 printf("%d lines, %d chars\n", lineCount
, charCount
);
814 * Print lines in a specified range.
815 * The last line printed becomes the current line.
816 * If expandFlag is TRUE, then the line is printed specially to
817 * show magic characters.
819 static int printLines(int num1
, int num2
, int expandFlag
)
825 if (bad_nums(num1
, num2
, "print"))
832 while (num1
<= num2
) {
834 write(STDOUT_FILENO
, lp
->data
, lp
->len
);
841 * Show control characters and characters with the
842 * high bit set specially.
847 if ((count
> 0) && (cp
[count
- 1] == '\n'))
850 while (count
-- > 0) {
851 ch
= (unsigned char) *cp
++;
852 fputc_printable(ch
| PRINTABLE_META
, stdout
);
855 fputs("$\n", stdout
);
866 * Insert a new line with the specified text.
867 * The line is inserted so as to become the specified line,
868 * thus pushing any existing and further lines down one.
869 * The inserted line is also set to become the current line.
870 * Returns TRUE if successful.
872 static int insertLine(int num
, const char *data
, int len
)
876 if ((num
< 1) || (num
> lastNum
+ 1)) {
877 bb_error_msg("inserting at bad line number");
881 newLp
= xmalloc(sizeof(LINE
) + len
- 1);
883 memcpy(newLp
->data
, data
, len
);
891 free((char *) newLp
);
897 newLp
->prev
= lp
->prev
;
898 lp
->prev
->next
= newLp
;
903 return setCurNum(num
);
908 * Delete lines from the given range.
910 static void deleteLines(int num1
, int num2
)
912 LINE
*lp
, *nlp
, *plp
;
915 if (bad_nums(num1
, num2
, "delete"))
922 if ((curNum
>= num1
) && (curNum
<= num2
)) {
931 count
= num2
- num1
+ 1;
936 while (count
-- > 0) {
950 * Search for a line which contains the specified string.
951 * If the string is "", then the previously searched for string
952 * is used. The currently searched for string is saved for future use.
953 * Returns the line number which matches, or 0 if there was no match
954 * with an error printed.
956 static NOINLINE
int searchLines(const char *str
, int num1
, int num2
)
961 if (bad_nums(num1
, num2
, "search"))
965 if (searchString
[0] == '\0') {
966 bb_error_msg("no previous search string");
972 if (str
!= searchString
)
973 strcpy(searchString
, str
);
981 while (num1
<= num2
) {
982 if (findString(lp
, str
, len
, 0) >= 0)
988 bb_error_msg("can't find string \"%s\"", str
);
994 * Return a pointer to the specified line number.
996 static LINE
*findLine(int num
)
1001 if ((num
< 1) || (num
> lastNum
)) {
1002 bb_error_msg("line number %d does not exist", num
);
1008 curLine
= lines
.next
;
1016 if (num
< (curNum
/ 2)) {
1019 } else if (num
> ((curNum
+ lastNum
) / 2)) {
1024 while (lnum
< num
) {
1029 while (lnum
> num
) {
1038 * Set the current line number.
1039 * Returns TRUE if successful.
1041 static int setCurNum(int num
)