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)
10 //config: bool "ed (25 kb)"
13 //config: The original 1970's Unix text editor, from the days of teletypes.
14 //config: Small, simple, evil. Part of SUSv3. If you're not already using
15 //config: this, you don't need it.
17 //kbuild:lib-$(CONFIG_ED) += ed.o
19 //applet:IF_ED(APPLET(ed, BB_DIR_BIN, BB_SUID_DROP))
21 //usage:#define ed_trivial_usage "[FILE]"
22 //usage:#define ed_full_usage ""
25 #include "common_bufsiz.h"
34 #define searchString bb_common_bufsiz1
37 USERSIZE = COMMON_BUFSIZE > 1024 ? 1024
38 : COMMON_BUFSIZE - 1, /* max line length typed in by user */
39 INITBUF_SIZE = 1024, /* initial buffer size */
55 #define G (*ptr_to_globals)
56 #define curLine (G.curLine )
57 #define bufBase (G.bufBase )
58 #define bufPtr (G.bufPtr )
59 #define fileName (G.fileName )
60 #define curNum (G.curNum )
61 #define lastNum (G.lastNum )
62 #define bufUsed (G.bufUsed )
63 #define bufSize (G.bufSize )
64 #define dirty (G.dirty )
65 #define lines (G.lines )
66 #define marks (G.marks )
67 #define INIT_G() do { \
68 setup_common_bufsiz(); \
69 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
72 static int bad_nums(int num1, int num2, const char *for_what)
74 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
75 bb_error_msg("bad line range for %s", for_what);
82 * Return a pointer to the specified line number.
84 static LINE *findLine(int num)
89 if ((num < 1) || (num > lastNum)) {
90 bb_error_msg("line number %d does not exist", num);
104 if (num < (curNum / 2)) {
107 } else if (num > ((curNum + lastNum) / 2)) {
125 * Search a line for the specified string starting at the specified
126 * offset in the line. Returns the offset of the found string, or -1.
128 static int findString(const LINE *lp, const char *str, int len, int offset)
131 const char *cp, *ncp;
133 cp = &lp->data[offset];
134 left = lp->len - offset - len;
137 ncp = memchr(cp, str[0], left + 1);
142 if (memcmp(cp, str, len) == 0)
143 return (cp - lp->data);
152 * Search for a line which contains the specified string.
153 * If the string is "", then the previously searched for string
154 * is used. The currently searched for string is saved for future use.
155 * Returns the line number which matches, or 0 if there was no match
156 * with an error printed.
158 static NOINLINE int searchLines(const char *str, int num1, int num2)
163 if (bad_nums(num1, num2, "search"))
167 if (searchString[0] == '\0') {
168 bb_error_msg("no previous search string");
174 if (str != searchString)
175 strcpy(searchString, str);
183 while (num1 <= num2) {
184 if (findString(lp, str, len, 0) >= 0)
190 bb_error_msg("can't find string \"%s\"", str);
195 * Parse a line number argument if it is present. This is a sum
196 * or difference of numbers, ".", "$", "'c", or a search string.
197 * Returns pointer which stopped the scan if successful
198 * (whether or not there was a number).
199 * Returns NULL if there was a parsing error, with a message output.
200 * Whether there was a number is returned indirectly, as is the number.
202 static const char* getNum(const char *cp, smallint *retHaveNum, int *retNum)
204 char *endStr, str[USERSIZE];
206 smallint haveNum, minus;
213 cp = skip_whitespace(cp);
230 if ((unsigned)(*cp - 'a') >= 26) {
231 bb_error_msg("bad mark name");
235 num = marks[(unsigned)(*cp - 'a')];
241 endStr = strchr(str, '/');
244 cp += (endStr - str);
247 num = searchLines(str, curNum, lastNum);
255 *retHaveNum = haveNum;
261 num = num * 10 + *cp++ - '0';
266 value += (minus ? -num : num);
268 cp = skip_whitespace(cp);
282 *retHaveNum = haveNum;
290 * Set the current line number.
291 * Returns TRUE if successful.
293 static int setCurNum(int num)
306 * Insert a new line with the specified text.
307 * The line is inserted so as to become the specified line,
308 * thus pushing any existing and further lines down one.
309 * The inserted line is also set to become the current line.
310 * Returns TRUE if successful.
312 static int insertLine(int num, const char *data, int len)
316 if ((num < 1) || (num > lastNum + 1)) {
317 bb_error_msg("inserting at bad line number");
321 newLp = xmalloc(sizeof(LINE) + len - 1);
323 memcpy(newLp->data, data, len);
331 free((char *) newLp);
337 newLp->prev = lp->prev;
338 lp->prev->next = newLp;
343 return setCurNum(num);
347 * Add lines which are typed in by the user.
348 * The lines are inserted just before the specified line number.
349 * The lines are terminated by a line containing a single dot (ugly!),
350 * or by an end of file.
352 static void addLines(int num)
355 char buf[USERSIZE + 1];
359 * -1 on read errors or EOF, or on bare Ctrl-D.
361 * >0 length of input string, including terminating '\n'
363 len = read_line_input(NULL, "", buf, sizeof(buf));
365 /* Previously, ctrl-C was exiting to shell.
366 * Now we exit to ed prompt. Is in important? */
369 if (buf[0] == '.' && buf[1] == '\n' && buf[2] == '\0')
371 if (!insertLine(num++, buf, len))
377 * Read lines from a file at the specified line number.
378 * Returns TRUE if the file was successfully read.
380 static int readLines(const char *file, int num)
383 int len, lineCount, charCount;
386 if ((num < 1) || (num > lastNum + 1)) {
387 bb_error_msg("bad line for read");
393 bb_simple_perror_msg(file);
403 printf("\"%s\", ", file);
407 cp = memchr(bufPtr, '\n', bufUsed);
410 len = (cp - bufPtr) + 1;
411 if (!insertLine(num, bufPtr, len)) {
423 if (bufPtr != bufBase) {
424 memcpy(bufBase, bufPtr, bufUsed);
425 bufPtr = bufBase + bufUsed;
428 if (bufUsed >= bufSize) {
429 len = (bufSize * 3) / 2;
430 cp = xrealloc(bufBase, len);
432 bufPtr = bufBase + bufUsed;
436 cc = safe_read(fd, bufPtr, bufSize - bufUsed);
442 bb_simple_perror_msg(file);
448 if (!insertLine(num, bufPtr, bufUsed)) {
453 charCount += bufUsed;
458 printf("%d lines%s, %d chars\n", lineCount,
459 (bufUsed ? " (incomplete)" : ""), charCount);
465 * Write the specified lines out to the specified file.
466 * Returns TRUE if successful, or FALSE on an error with a message output.
468 static int writeLines(const char *file, int num1, int num2)
471 int fd, lineCount, charCount;
473 if (bad_nums(num1, num2, "write"))
479 fd = creat(file, 0666);
481 bb_simple_perror_msg(file);
485 printf("\"%s\", ", file);
494 while (num1++ <= num2) {
495 if (full_write(fd, lp->data, lp->len) != lp->len) {
496 bb_simple_perror_msg(file);
500 charCount += lp->len;
506 bb_simple_perror_msg(file);
510 printf("%d lines, %d chars\n", lineCount, charCount);
515 * Print lines in a specified range.
516 * The last line printed becomes the current line.
517 * If expandFlag is TRUE, then the line is printed specially to
518 * show magic characters.
520 static int printLines(int num1, int num2, int expandFlag)
526 if (bad_nums(num1, num2, "print"))
533 while (num1 <= num2) {
535 write(STDOUT_FILENO, lp->data, lp->len);
542 * Show control characters and characters with the
543 * high bit set specially.
548 if ((count > 0) && (cp[count - 1] == '\n'))
551 while (count-- > 0) {
552 ch = (unsigned char) *cp++;
553 fputc_printable(ch | PRINTABLE_META, stdout);
556 fputs("$\n", stdout);
566 * Delete lines from the given range.
568 static void deleteLines(int num1, int num2)
570 LINE *lp, *nlp, *plp;
573 if (bad_nums(num1, num2, "delete"))
580 if ((curNum >= num1) && (curNum <= num2)) {
589 count = num2 - num1 + 1;
594 while (count-- > 0) {
607 * Do the substitute command.
608 * The current line is set to the last substitution done.
610 static void subCommand(const char *cmd, int num1, int num2)
612 char *cp, *oldStr, *newStr, buf[USERSIZE];
613 int delim, oldLen, newLen, deltaLen, offset;
615 int globalFlag, printFlag, didSub, needPrint;
617 if (bad_nums(num1, num2, "substitute"))
626 * Copy the command so we can modify it.
631 if (isblank(*cp) || (*cp == '\0')) {
632 bb_error_msg("bad delimiter for substitute");
639 cp = strchr(cp, delim);
641 bb_error_msg("missing 2nd delimiter for substitute");
648 cp = strchr(cp, delim);
655 while (*cp) switch (*cp++) {
663 bb_error_msg("unknown option for substitute");
667 if (*oldStr == '\0') {
668 if (searchString[0] == '\0') {
669 bb_error_msg("no previous search string");
672 oldStr = searchString;
675 if (oldStr != searchString)
676 strcpy(searchString, oldStr);
682 oldLen = strlen(oldStr);
683 newLen = strlen(newStr);
684 deltaLen = newLen - oldLen;
688 while (num1 <= num2) {
689 offset = findString(lp, oldStr, oldLen, offset);
693 printLines(num1, num1, FALSE);
702 needPrint = printFlag;
707 * If the replacement string is the same size or shorter
708 * than the old string, then the substitution is easy.
711 memcpy(&lp->data[offset], newStr, newLen);
713 memcpy(&lp->data[offset + newLen],
714 &lp->data[offset + oldLen],
715 lp->len - offset - oldLen);
723 printLines(num1, num1, FALSE);
732 * The new string is larger, so allocate a new line
733 * structure and use that. Link it in place of
734 * the old line structure.
736 nlp = xmalloc(sizeof(LINE) + lp->len + deltaLen);
738 nlp->len = lp->len + deltaLen;
740 memcpy(nlp->data, lp->data, offset);
741 memcpy(&nlp->data[offset], newStr, newLen);
742 memcpy(&nlp->data[offset + newLen],
743 &lp->data[offset + oldLen],
744 lp->len - offset - oldLen);
746 nlp->next = lp->next;
747 nlp->prev = lp->prev;
748 nlp->prev->next = nlp;
749 nlp->next->prev = nlp;
763 printLines(num1, num1, FALSE);
772 bb_error_msg("no substitutions found for \"%s\"", oldStr);
776 * Read commands until we are told to stop.
778 static void doCommands(void)
785 smallint h, have1, have2;
788 * -1 on read errors or EOF, or on bare Ctrl-D.
790 * >0 length of input string, including terminating '\n'
792 len = read_line_input(NULL, ": ", buf, sizeof(buf));
795 while (len && isspace(buf[--len]))
798 if ((curNum == 0) && (lastNum > 0)) {
800 curLine = lines.next;
805 /* Don't pass &haveN, &numN to getNum() since this forces
806 * compiler to keep them on stack, not in registers,
807 * which is usually quite suboptimal.
808 * Using intermediate variables shrinks code by ~150 bytes.
810 cp = getNum(skip_whitespace(buf), &h, &n);
815 cp = skip_whitespace(cp);
817 cp = getNum(cp + 1, &h, &n);
839 deleteLines(num1, num2);
844 deleteLines(num1, num2);
848 if (*cp != '\0' && *cp != ' ') {
849 bb_error_msg("bad file command");
852 cp = skip_whitespace(cp);
855 printf("\"%s\"\n", fileName);
857 puts("No file name");
861 fileName = xstrdup(cp);
865 if (!have1 && lastNum == 0)
871 cp = skip_whitespace(cp);
872 if ((unsigned)(*cp - 'a') >= 26 || cp[1]) {
873 bb_error_msg("bad mark name");
876 marks[(unsigned)(*cp - 'a')] = num2;
880 printLines(num1, num2, TRUE);
884 printLines(num1, num2, FALSE);
888 cp = skip_whitespace(cp);
890 bb_error_msg("bad quit command");
895 len = read_line_input(NULL, "Really quit? ", buf, 16);
896 /* read error/EOF - no way to continue */
899 cp = skip_whitespace(buf);
900 if ((*cp | 0x20) == 'y') /* Y or y */
905 if (*cp != '\0' && *cp != ' ') {
906 bb_error_msg("bad read command");
909 cp = skip_whitespace(cp);
911 bb_error_msg("no file name");
916 if (readLines(cp, num1 + 1))
918 if (fileName == NULL)
919 fileName = xstrdup(cp);
923 subCommand(cp, num1, num2);
927 if (*cp != '\0' && *cp != ' ') {
928 bb_error_msg("bad write command");
931 cp = skip_whitespace(cp);
935 bb_error_msg("no file name specified");
944 writeLines(cp, num1, num2);
950 printLines(curNum - 21, curNum, FALSE);
953 printLines(curNum - 11, curNum + 10, FALSE);
956 printLines(curNum, curNum + 21, FALSE);
963 bb_error_msg("no arguments allowed");
966 printLines(curNum, curNum, FALSE);
970 if (setCurNum(curNum - 1))
971 printLines(curNum, curNum, FALSE);
975 printf("%d\n", num1);
979 printLines(num2, num2, FALSE);
982 if (setCurNum(curNum + 1))
983 printLines(curNum, curNum, FALSE);
987 bb_error_msg("unimplemented command");
993 int ed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
994 int ed_main(int argc UNUSED_PARAM, char **argv)
998 bufSize = INITBUF_SIZE;
999 bufBase = xmalloc(bufSize);
1001 lines.next = &lines;
1002 lines.prev = &lines;
1005 fileName = xstrdup(argv[1]);
1006 if (!readLines(fileName, 1)) {
1007 return EXIT_SUCCESS;
1015 return EXIT_SUCCESS;