From 5ea50697fd13eb1caeecf3ceb8d03a1e7f578406 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 27 Jul 2017 11:17:15 +0200 Subject: ed: fix --help and reorder functions, no code changes Signed-off-by: Denys Vlasenko --- editors/ed.c | 1514 ++++++++++++++++++++++++++++------------------------------ 1 file changed, 741 insertions(+), 773 deletions(-) (limited to 'editors') diff --git a/editors/ed.c b/editors/ed.c index c594d3da1..a2a389c2b 100644 --- a/editors/ed.c +++ b/editors/ed.c @@ -6,7 +6,6 @@ * * The "ed" built-in command (much simplified) */ - //config:config ED //config: bool "ed (25 kb)" //config: default y @@ -19,7 +18,7 @@ //applet:IF_ED(APPLET(ed, BB_DIR_BIN, BB_SUID_DROP)) -//usage:#define ed_trivial_usage "" +//usage:#define ed_trivial_usage "[FILE]" //usage:#define ed_full_usage "" #include "libbb.h" @@ -32,7 +31,6 @@ typedef struct LINE { char data[1]; } LINE; - #define searchString bb_common_bufsiz1 enum { @@ -71,22 +69,6 @@ struct globals { SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ } while (0) - -static void doCommands(void); -static void subCommand(const char *cmd, int num1, int num2); -static int getNum(const char **retcp, smallint *retHaveNum, int *retNum); -static int setCurNum(int num); -static void addLines(int num); -static int insertLine(int num, const char *data, int len); -static void deleteLines(int num1, int num2); -static int printLines(int num1, int num2, int expandFlag); -static int writeLines(const char *file, int num1, int num2); -static int readLines(const char *file, int num); -static int searchLines(const char *str, int num1, int num2); -static LINE *findLine(int num); -static int findString(const LINE *lp, const char * str, int len, int offset); - - static int bad_nums(int num1, int num2, const char *for_what) { if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) { @@ -96,7 +78,6 @@ static int bad_nums(int num1, int num2, const char *for_what) return 0; } - static char *skip_blank(const char *cp) { while (isblank(*cp)) @@ -104,949 +85,936 @@ static char *skip_blank(const char *cp) return (char *)cp; } - -int ed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; -int ed_main(int argc UNUSED_PARAM, char **argv) +/* + * Return a pointer to the specified line number. + */ +static LINE *findLine(int num) { - INIT_G(); + LINE *lp; + int lnum; - bufSize = INITBUF_SIZE; - bufBase = xmalloc(bufSize); - bufPtr = bufBase; - lines.next = &lines; - lines.prev = &lines; + if ((num < 1) || (num > lastNum)) { + bb_error_msg("line number %d does not exist", num); + return NULL; + } - if (argv[1]) { - fileName = xstrdup(argv[1]); - if (!readLines(fileName, 1)) { - return EXIT_SUCCESS; - } - if (lastNum) - setCurNum(1); - dirty = FALSE; + if (curNum <= 0) { + curNum = 1; + curLine = lines.next; } - doCommands(); - return EXIT_SUCCESS; + if (num == curNum) + return curLine; + + lp = curLine; + lnum = curNum; + if (num < (curNum / 2)) { + lp = lines.next; + lnum = 1; + } else if (num > ((curNum + lastNum) / 2)) { + lp = lines.prev; + lnum = lastNum; + } + + while (lnum < num) { + lp = lp->next; + lnum++; + } + + while (lnum > num) { + lp = lp->prev; + lnum--; + } + return lp; } /* - * Read commands until we are told to stop. + * Search a line for the specified string starting at the specified + * offset in the line. Returns the offset of the found string, or -1. */ -static void doCommands(void) +static int findString(const LINE *lp, const char *str, int len, int offset) { - const char *cp; - char *endbuf, buf[USERSIZE]; - int len, num1, num2; - smallint have1, have2; + int left; + const char *cp, *ncp; - while (TRUE) { - /* Returns: - * -1 on read errors or EOF, or on bare Ctrl-D. - * 0 on ctrl-C, - * >0 length of input string, including terminating '\n' - */ - len = read_line_input(NULL, ": ", buf, sizeof(buf), /*timeout*/ -1); - if (len <= 0) - return; - endbuf = &buf[len - 1]; - while ((endbuf > buf) && isblank(endbuf[-1])) - endbuf--; - *endbuf = '\0'; + cp = &lp->data[offset]; + left = lp->len - offset; - cp = skip_blank(buf); - have1 = FALSE; - have2 = FALSE; + while (left >= len) { + ncp = memchr(cp, *str, left); + if (ncp == NULL) + return -1; + left -= (ncp - cp); + if (left < len) + return -1; + cp = ncp; + if (memcmp(cp, str, len) == 0) + return (cp - lp->data); + cp++; + left--; + } - if ((curNum == 0) && (lastNum > 0)) { - curNum = 1; - curLine = lines.next; - } + return -1; +} - if (!getNum(&cp, &have1, &num1)) - continue; +/* + * Search for a line which contains the specified string. + * If the string is "", then the previously searched for string + * is used. The currently searched for string is saved for future use. + * Returns the line number which matches, or 0 if there was no match + * with an error printed. + */ +static NOINLINE int searchLines(const char *str, int num1, int num2) +{ + const LINE *lp; + int len; - cp = skip_blank(cp); + if (bad_nums(num1, num2, "search")) + return 0; - if (*cp == ',') { - cp++; - if (!getNum(&cp, &have2, &num2)) - continue; - if (!have1) - num1 = 1; - if (!have2) - num2 = lastNum; - have1 = TRUE; - have2 = TRUE; + if (*str == '\0') { + if (searchString[0] == '\0') { + bb_error_msg("no previous search string"); + return 0; } - if (!have1) - num1 = curNum; - if (!have2) - num2 = num1; + str = searchString; + } - switch (*cp++) { - case 'a': - addLines(num1 + 1); - break; + if (str != searchString) + strcpy(searchString, str); - case 'c': - deleteLines(num1, num2); - addLines(num1); - break; + len = strlen(str); - case 'd': - deleteLines(num1, num2); - break; + lp = findLine(num1); + if (lp == NULL) + return 0; - case 'f': - if (*cp && !isblank(*cp)) { - bb_error_msg("bad file command"); - break; - } - cp = skip_blank(cp); - if (*cp == '\0') { - if (fileName) - printf("\"%s\"\n", fileName); - else - puts("No file name"); - break; - } - free(fileName); - fileName = xstrdup(cp); - break; + while (num1 <= num2) { + if (findString(lp, str, len, 0) >= 0) + return num1; + num1++; + lp = lp->next; + } - case 'i': - addLines(num1); - break; + bb_error_msg("can't find string \"%s\"", str); + return 0; +} - case 'k': - cp = skip_blank(cp); - if ((*cp < 'a') || (*cp > 'z') || cp[1]) { - bb_error_msg("bad mark name"); - break; - } - marks[*cp - 'a'] = num2; - break; +/* + * Parse a line number argument if it is present. This is a sum + * or difference of numbers, '.', '$', 'x, or a search string. + * Returns TRUE if successful (whether or not there was a number). + * Returns FALSE if there was a parsing error, with a message output. + * Whether there was a number is returned indirectly, as is the number. + * The character pointer which stopped the scan is also returned. + */ +static int getNum(const char **retcp, smallint *retHaveNum, int *retNum) +{ + const char *cp; + char *endStr, str[USERSIZE]; + int value, num; + smallint haveNum, minus; - case 'l': - printLines(num1, num2, TRUE); - break; + cp = *retcp; + value = 0; + haveNum = FALSE; + minus = 0; - case 'p': - printLines(num1, num2, FALSE); - break; + while (TRUE) { + cp = skip_blank(cp); - case 'q': - cp = skip_blank(cp); - if (have1 || *cp) { - bb_error_msg("bad quit command"); + switch (*cp) { + case '.': + haveNum = TRUE; + num = curNum; + cp++; break; - } - if (!dirty) - return; - len = read_line_input(NULL, "Really quit? ", buf, 16, /*timeout*/ -1); - /* read error/EOF - no way to continue */ - if (len < 0) - return; - cp = skip_blank(buf); - if ((*cp | 0x20) == 'y') /* Y or y */ - return; - break; - case 'r': - if (*cp && !isblank(*cp)) { - bb_error_msg("bad read command"); - break; - } - cp = skip_blank(cp); - if (*cp == '\0') { - bb_error_msg("no file name"); + case '$': + haveNum = TRUE; + num = lastNum; + cp++; break; - } - if (!have1) - num1 = lastNum; - if (readLines(cp, num1 + 1)) - break; - if (fileName == NULL) - fileName = xstrdup(cp); - break; - case 's': - subCommand(cp, num1, num2); - break; - - case 'w': - if (*cp && !isblank(*cp)) { - bb_error_msg("bad write command"); - break; - } - cp = skip_blank(cp); - if (!have1) { - num1 = 1; - num2 = lastNum; - } - if (*cp == '\0') - cp = fileName; - if (cp == NULL) { - bb_error_msg("no file name specified"); + case '\'': + cp++; + if ((*cp < 'a') || (*cp > 'z')) { + bb_error_msg("bad mark name"); + return FALSE; + } + haveNum = TRUE; + num = marks[*cp++ - 'a']; break; - } - writeLines(cp, num1, num2); - break; - case 'z': - switch (*cp) { - case '-': - printLines(curNum - 21, curNum, FALSE); - break; - case '.': - printLines(curNum - 11, curNum + 10, FALSE); + case '/': + strcpy(str, ++cp); + endStr = strchr(str, '/'); + if (endStr) { + *endStr++ = '\0'; + cp += (endStr - str); + } else + cp = ""; + num = searchLines(str, curNum, lastNum); + if (num == 0) + return FALSE; + haveNum = TRUE; break; + default: - printLines(curNum, curNum + 21, FALSE); + if (!isdigit(*cp)) { + *retcp = cp; + *retHaveNum = haveNum; + *retNum = value; + return TRUE; + } + num = 0; + while (isdigit(*cp)) + num = num * 10 + *cp++ - '0'; + haveNum = TRUE; break; - } - break; + } - case '.': - if (have1) { - bb_error_msg("no arguments allowed"); - break; - } - printLines(curNum, curNum, FALSE); - break; + value += (minus ? -num : num); - case '-': - if (setCurNum(curNum - 1)) - printLines(curNum, curNum, FALSE); - break; + cp = skip_blank(cp); - case '=': - printf("%d\n", num1); - break; - case '\0': - if (have1) { - printLines(num2, num2, FALSE); + switch (*cp) { + case '-': + minus = 1; + cp++; break; - } - if (setCurNum(curNum + 1)) - printLines(curNum, curNum, FALSE); - break; - default: - bb_error_msg("unimplemented command"); - break; + case '+': + minus = 0; + cp++; + break; + + default: + *retcp = cp; + *retHaveNum = haveNum; + *retNum = value; + return TRUE; } } } - /* - * Do the substitute command. - * The current line is set to the last substitution done. + * Set the current line number. + * Returns TRUE if successful. */ -static void subCommand(const char *cmd, int num1, int num2) +static int setCurNum(int num) { - char *cp, *oldStr, *newStr, buf[USERSIZE]; - int delim, oldLen, newLen, deltaLen, offset; - LINE *lp, *nlp; - int globalFlag, printFlag, didSub, needPrint; - - if (bad_nums(num1, num2, "substitute")) - return; + LINE *lp; - globalFlag = FALSE; - printFlag = FALSE; - didSub = FALSE; - needPrint = FALSE; + lp = findLine(num); + if (lp == NULL) + return FALSE; + curNum = num; + curLine = lp; + return TRUE; +} - /* - * Copy the command so we can modify it. - */ - strcpy(buf, cmd); - cp = buf; +/* + * Insert a new line with the specified text. + * The line is inserted so as to become the specified line, + * thus pushing any existing and further lines down one. + * The inserted line is also set to become the current line. + * Returns TRUE if successful. + */ +static int insertLine(int num, const char *data, int len) +{ + LINE *newLp, *lp; - if (isblank(*cp) || (*cp == '\0')) { - bb_error_msg("bad delimiter for substitute"); - return; + if ((num < 1) || (num > lastNum + 1)) { + bb_error_msg("inserting at bad line number"); + return FALSE; } - delim = *cp++; - oldStr = cp; + newLp = xmalloc(sizeof(LINE) + len - 1); - cp = strchr(cp, delim); - if (cp == NULL) { - bb_error_msg("missing 2nd delimiter for substitute"); - return; - } + memcpy(newLp->data, data, len); + newLp->len = len; - *cp++ = '\0'; + if (num > lastNum) + lp = &lines; + else { + lp = findLine(num); + if (lp == NULL) { + free((char *) newLp); + return FALSE; + } + } - newStr = cp; - cp = strchr(cp, delim); + newLp->next = lp; + newLp->prev = lp->prev; + lp->prev->next = newLp; + lp->prev = newLp; - if (cp) - *cp++ = '\0'; - else - cp = (char*)""; + lastNum++; + dirty = TRUE; + return setCurNum(num); +} - while (*cp) switch (*cp++) { - case 'g': - globalFlag = TRUE; - break; - case 'p': - printFlag = TRUE; - break; - default: - bb_error_msg("unknown option for substitute"); - return; - } +/* + * Add lines which are typed in by the user. + * The lines are inserted just before the specified line number. + * The lines are terminated by a line containing a single dot (ugly!), + * or by an end of file. + */ +static void addLines(int num) +{ + int len; + char buf[USERSIZE + 1]; - if (*oldStr == '\0') { - if (searchString[0] == '\0') { - bb_error_msg("no previous search string"); + while (1) { + /* Returns: + * -1 on read errors or EOF, or on bare Ctrl-D. + * 0 on ctrl-C, + * >0 length of input string, including terminating '\n' + */ + len = read_line_input(NULL, "", buf, sizeof(buf), /*timeout*/ -1); + if (len <= 0) { + /* Previously, ctrl-C was exiting to shell. + * Now we exit to ed prompt. Is in important? */ return; } - oldStr = searchString; + if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0')) + return; + if (!insertLine(num++, buf, len)) + return; } +} - if (oldStr != searchString) - strcpy(searchString, oldStr); - - lp = findLine(num1); - if (lp == NULL) - return; +/* + * Read lines from a file at the specified line number. + * Returns TRUE if the file was successfully read. + */ +static int readLines(const char *file, int num) +{ + int fd, cc; + int len, lineCount, charCount; + char *cp; - oldLen = strlen(oldStr); - newLen = strlen(newStr); - deltaLen = newLen - oldLen; - offset = 0; - nlp = NULL; + if ((num < 1) || (num > lastNum + 1)) { + bb_error_msg("bad line for read"); + return FALSE; + } - while (num1 <= num2) { - offset = findString(lp, oldStr, oldLen, offset); + fd = open(file, 0); + if (fd < 0) { + bb_simple_perror_msg(file); + return FALSE; + } - if (offset < 0) { - if (needPrint) { - printLines(num1, num1, FALSE); - needPrint = FALSE; - } - offset = 0; - lp = lp->next; - num1++; - continue; - } + bufPtr = bufBase; + bufUsed = 0; + lineCount = 0; + charCount = 0; + cc = 0; - needPrint = printFlag; - didSub = TRUE; - dirty = TRUE; + printf("\"%s\", ", file); + fflush_all(); - /* - * If the replacement string is the same size or shorter - * than the old string, then the substitution is easy. - */ - if (deltaLen <= 0) { - memcpy(&lp->data[offset], newStr, newLen); - if (deltaLen) { - memcpy(&lp->data[offset + newLen], - &lp->data[offset + oldLen], - lp->len - offset - oldLen); + do { + cp = memchr(bufPtr, '\n', bufUsed); - lp->len += deltaLen; - } - offset += newLen; - if (globalFlag) - continue; - if (needPrint) { - printLines(num1, num1, FALSE); - needPrint = FALSE; + if (cp) { + len = (cp - bufPtr) + 1; + if (!insertLine(num, bufPtr, len)) { + close(fd); + return FALSE; } - lp = lp->next; - num1++; + bufPtr += len; + bufUsed -= len; + charCount += len; + lineCount++; + num++; continue; } - /* - * The new string is larger, so allocate a new line - * structure and use that. Link it in place of - * the old line structure. - */ - nlp = xmalloc(sizeof(LINE) + lp->len + deltaLen); - - nlp->len = lp->len + deltaLen; - - memcpy(nlp->data, lp->data, offset); - memcpy(&nlp->data[offset], newStr, newLen); - memcpy(&nlp->data[offset + newLen], - &lp->data[offset + oldLen], - lp->len - offset - oldLen); - - nlp->next = lp->next; - nlp->prev = lp->prev; - nlp->prev->next = nlp; - nlp->next->prev = nlp; - - if (curLine == lp) - curLine = nlp; + if (bufPtr != bufBase) { + memcpy(bufBase, bufPtr, bufUsed); + bufPtr = bufBase + bufUsed; + } - free(lp); - lp = nlp; + if (bufUsed >= bufSize) { + len = (bufSize * 3) / 2; + cp = xrealloc(bufBase, len); + bufBase = cp; + bufPtr = bufBase + bufUsed; + bufSize = len; + } - offset += newLen; + cc = safe_read(fd, bufPtr, bufSize - bufUsed); + bufUsed += cc; + bufPtr = bufBase; + } while (cc > 0); - if (globalFlag) - continue; + if (cc < 0) { + bb_simple_perror_msg(file); + close(fd); + return FALSE; + } - if (needPrint) { - printLines(num1, num1, FALSE); - needPrint = FALSE; + if (bufUsed) { + if (!insertLine(num, bufPtr, bufUsed)) { + close(fd); + return -1; } - - lp = lp->next; - num1++; + lineCount++; + charCount += bufUsed; } - if (!didSub) - bb_error_msg("no substitutions found for \"%s\"", oldStr); -} + close(fd); + + printf("%d lines%s, %d chars\n", lineCount, + (bufUsed ? " (incomplete)" : ""), charCount); + return TRUE; +} /* - * Search a line for the specified string starting at the specified - * offset in the line. Returns the offset of the found string, or -1. + * Write the specified lines out to the specified file. + * Returns TRUE if successful, or FALSE on an error with a message output. */ -static int findString(const LINE *lp, const char *str, int len, int offset) +static int writeLines(const char *file, int num1, int num2) { - int left; - const char *cp, *ncp; + LINE *lp; + int fd, lineCount, charCount; - cp = &lp->data[offset]; - left = lp->len - offset; + if (bad_nums(num1, num2, "write")) + return FALSE; - while (left >= len) { - ncp = memchr(cp, *str, left); - if (ncp == NULL) - return -1; - left -= (ncp - cp); - if (left < len) - return -1; - cp = ncp; - if (memcmp(cp, str, len) == 0) - return (cp - lp->data); - cp++; - left--; - } + lineCount = 0; + charCount = 0; - return -1; -} + fd = creat(file, 0666); + if (fd < 0) { + bb_simple_perror_msg(file); + return FALSE; + } + printf("\"%s\", ", file); + fflush_all(); -/* - * Add lines which are typed in by the user. - * The lines are inserted just before the specified line number. - * The lines are terminated by a line containing a single dot (ugly!), - * or by an end of file. - */ -static void addLines(int num) -{ - int len; - char buf[USERSIZE + 1]; + lp = findLine(num1); + if (lp == NULL) { + close(fd); + return FALSE; + } - while (1) { - /* Returns: - * -1 on read errors or EOF, or on bare Ctrl-D. - * 0 on ctrl-C, - * >0 length of input string, including terminating '\n' - */ - len = read_line_input(NULL, "", buf, sizeof(buf), /*timeout*/ -1); - if (len <= 0) { - /* Previously, ctrl-C was exiting to shell. - * Now we exit to ed prompt. Is in important? */ - return; + while (num1++ <= num2) { + if (full_write(fd, lp->data, lp->len) != lp->len) { + bb_simple_perror_msg(file); + close(fd); + return FALSE; } - if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0')) - return; - if (!insertLine(num++, buf, len)) - return; + charCount += lp->len; + lineCount++; + lp = lp->next; + } + + if (close(fd) < 0) { + bb_simple_perror_msg(file); + return FALSE; } -} + printf("%d lines, %d chars\n", lineCount, charCount); + return TRUE; +} /* - * Parse a line number argument if it is present. This is a sum - * or difference of numbers, '.', '$', 'x, or a search string. - * Returns TRUE if successful (whether or not there was a number). - * Returns FALSE if there was a parsing error, with a message output. - * Whether there was a number is returned indirectly, as is the number. - * The character pointer which stopped the scan is also returned. + * Print lines in a specified range. + * The last line printed becomes the current line. + * If expandFlag is TRUE, then the line is printed specially to + * show magic characters. */ -static int getNum(const char **retcp, smallint *retHaveNum, int *retNum) +static int printLines(int num1, int num2, int expandFlag) { + const LINE *lp; const char *cp; - char *endStr, str[USERSIZE]; - int value, num; - smallint haveNum, minus; + int ch, count; - cp = *retcp; - value = 0; - haveNum = FALSE; - minus = 0; + if (bad_nums(num1, num2, "print")) + return FALSE; - while (TRUE) { - cp = skip_blank(cp); + lp = findLine(num1); + if (lp == NULL) + return FALSE; - switch (*cp) { - case '.': - haveNum = TRUE; - num = curNum; - cp++; - break; + while (num1 <= num2) { + if (!expandFlag) { + write(STDOUT_FILENO, lp->data, lp->len); + setCurNum(num1++); + lp = lp->next; + continue; + } - case '$': - haveNum = TRUE; - num = lastNum; - cp++; - break; - - case '\'': - cp++; - if ((*cp < 'a') || (*cp > 'z')) { - bb_error_msg("bad mark name"); - return FALSE; - } - haveNum = TRUE; - num = marks[*cp++ - 'a']; - break; + /* + * Show control characters and characters with the + * high bit set specially. + */ + cp = lp->data; + count = lp->len; - case '/': - strcpy(str, ++cp); - endStr = strchr(str, '/'); - if (endStr) { - *endStr++ = '\0'; - cp += (endStr - str); - } else - cp = ""; - num = searchLines(str, curNum, lastNum); - if (num == 0) - return FALSE; - haveNum = TRUE; - break; + if ((count > 0) && (cp[count - 1] == '\n')) + count--; - default: - if (!isdigit(*cp)) { - *retcp = cp; - *retHaveNum = haveNum; - *retNum = value; - return TRUE; - } - num = 0; - while (isdigit(*cp)) - num = num * 10 + *cp++ - '0'; - haveNum = TRUE; - break; + while (count-- > 0) { + ch = (unsigned char) *cp++; + fputc_printable(ch | PRINTABLE_META, stdout); } - value += (minus ? -num : num); - - cp = skip_blank(cp); - - switch (*cp) { - case '-': - minus = 1; - cp++; - break; - - case '+': - minus = 0; - cp++; - break; + fputs("$\n", stdout); - default: - *retcp = cp; - *retHaveNum = haveNum; - *retNum = value; - return TRUE; - } + setCurNum(num1++); + lp = lp->next; } -} + return TRUE; +} /* - * Read lines from a file at the specified line number. - * Returns TRUE if the file was successfully read. + * Delete lines from the given range. */ -static int readLines(const char *file, int num) +static void deleteLines(int num1, int num2) { - int fd, cc; - int len, lineCount, charCount; - char *cp; + LINE *lp, *nlp, *plp; + int count; - if ((num < 1) || (num > lastNum + 1)) { - bb_error_msg("bad line for read"); - return FALSE; - } + if (bad_nums(num1, num2, "delete")) + return; - fd = open(file, 0); - if (fd < 0) { - bb_simple_perror_msg(file); - return FALSE; - } + lp = findLine(num1); + if (lp == NULL) + return; - bufPtr = bufBase; - bufUsed = 0; - lineCount = 0; - charCount = 0; - cc = 0; + if ((curNum >= num1) && (curNum <= num2)) { + if (num2 < lastNum) + setCurNum(num2 + 1); + else if (num1 > 1) + setCurNum(num1 - 1); + else + curNum = 0; + } - printf("\"%s\", ", file); - fflush_all(); + count = num2 - num1 + 1; + if (curNum > num2) + curNum -= count; + lastNum -= count; - do { - cp = memchr(bufPtr, '\n', bufUsed); + while (count-- > 0) { + nlp = lp->next; + plp = lp->prev; + plp->next = nlp; + nlp->prev = plp; + free(lp); + lp = nlp; + } - if (cp) { - len = (cp - bufPtr) + 1; - if (!insertLine(num, bufPtr, len)) { - close(fd); - return FALSE; - } - bufPtr += len; - bufUsed -= len; - charCount += len; - lineCount++; - num++; - continue; - } + dirty = TRUE; +} - if (bufPtr != bufBase) { - memcpy(bufBase, bufPtr, bufUsed); - bufPtr = bufBase + bufUsed; - } +/* + * Do the substitute command. + * The current line is set to the last substitution done. + */ +static void subCommand(const char *cmd, int num1, int num2) +{ + char *cp, *oldStr, *newStr, buf[USERSIZE]; + int delim, oldLen, newLen, deltaLen, offset; + LINE *lp, *nlp; + int globalFlag, printFlag, didSub, needPrint; - if (bufUsed >= bufSize) { - len = (bufSize * 3) / 2; - cp = xrealloc(bufBase, len); - bufBase = cp; - bufPtr = bufBase + bufUsed; - bufSize = len; - } + if (bad_nums(num1, num2, "substitute")) + return; - cc = safe_read(fd, bufPtr, bufSize - bufUsed); - bufUsed += cc; - bufPtr = bufBase; - } while (cc > 0); + globalFlag = FALSE; + printFlag = FALSE; + didSub = FALSE; + needPrint = FALSE; - if (cc < 0) { - bb_simple_perror_msg(file); - close(fd); - return FALSE; - } + /* + * Copy the command so we can modify it. + */ + strcpy(buf, cmd); + cp = buf; - if (bufUsed) { - if (!insertLine(num, bufPtr, bufUsed)) { - close(fd); - return -1; - } - lineCount++; - charCount += bufUsed; + if (isblank(*cp) || (*cp == '\0')) { + bb_error_msg("bad delimiter for substitute"); + return; } - close(fd); - - printf("%d lines%s, %d chars\n", lineCount, - (bufUsed ? " (incomplete)" : ""), charCount); + delim = *cp++; + oldStr = cp; - return TRUE; -} + cp = strchr(cp, delim); + if (cp == NULL) { + bb_error_msg("missing 2nd delimiter for substitute"); + return; + } + *cp++ = '\0'; -/* - * Write the specified lines out to the specified file. - * Returns TRUE if successful, or FALSE on an error with a message output. - */ -static int writeLines(const char *file, int num1, int num2) -{ - LINE *lp; - int fd, lineCount, charCount; + newStr = cp; + cp = strchr(cp, delim); - if (bad_nums(num1, num2, "write")) - return FALSE; + if (cp) + *cp++ = '\0'; + else + cp = (char*)""; - lineCount = 0; - charCount = 0; + while (*cp) switch (*cp++) { + case 'g': + globalFlag = TRUE; + break; + case 'p': + printFlag = TRUE; + break; + default: + bb_error_msg("unknown option for substitute"); + return; + } - fd = creat(file, 0666); - if (fd < 0) { - bb_simple_perror_msg(file); - return FALSE; + if (*oldStr == '\0') { + if (searchString[0] == '\0') { + bb_error_msg("no previous search string"); + return; + } + oldStr = searchString; } - printf("\"%s\", ", file); - fflush_all(); + if (oldStr != searchString) + strcpy(searchString, oldStr); lp = findLine(num1); - if (lp == NULL) { - close(fd); - return FALSE; - } + if (lp == NULL) + return; - while (num1++ <= num2) { - if (full_write(fd, lp->data, lp->len) != lp->len) { - bb_simple_perror_msg(file); - close(fd); - return FALSE; - } - charCount += lp->len; - lineCount++; - lp = lp->next; - } - - if (close(fd) < 0) { - bb_simple_perror_msg(file); - return FALSE; - } - - printf("%d lines, %d chars\n", lineCount, charCount); - return TRUE; -} - - -/* - * Print lines in a specified range. - * The last line printed becomes the current line. - * If expandFlag is TRUE, then the line is printed specially to - * show magic characters. - */ -static int printLines(int num1, int num2, int expandFlag) -{ - const LINE *lp; - const char *cp; - int ch, count; - - if (bad_nums(num1, num2, "print")) - return FALSE; - - lp = findLine(num1); - if (lp == NULL) - return FALSE; + oldLen = strlen(oldStr); + newLen = strlen(newStr); + deltaLen = newLen - oldLen; + offset = 0; + nlp = NULL; while (num1 <= num2) { - if (!expandFlag) { - write(STDOUT_FILENO, lp->data, lp->len); - setCurNum(num1++); + offset = findString(lp, oldStr, oldLen, offset); + + if (offset < 0) { + if (needPrint) { + printLines(num1, num1, FALSE); + needPrint = FALSE; + } + offset = 0; lp = lp->next; + num1++; continue; } + needPrint = printFlag; + didSub = TRUE; + dirty = TRUE; + /* - * Show control characters and characters with the - * high bit set specially. + * If the replacement string is the same size or shorter + * than the old string, then the substitution is easy. */ - cp = lp->data; - count = lp->len; - - if ((count > 0) && (cp[count - 1] == '\n')) - count--; + if (deltaLen <= 0) { + memcpy(&lp->data[offset], newStr, newLen); + if (deltaLen) { + memcpy(&lp->data[offset + newLen], + &lp->data[offset + oldLen], + lp->len - offset - oldLen); - while (count-- > 0) { - ch = (unsigned char) *cp++; - fputc_printable(ch | PRINTABLE_META, stdout); + lp->len += deltaLen; + } + offset += newLen; + if (globalFlag) + continue; + if (needPrint) { + printLines(num1, num1, FALSE); + needPrint = FALSE; + } + lp = lp->next; + num1++; + continue; } - fputs("$\n", stdout); + /* + * The new string is larger, so allocate a new line + * structure and use that. Link it in place of + * the old line structure. + */ + nlp = xmalloc(sizeof(LINE) + lp->len + deltaLen); - setCurNum(num1++); - lp = lp->next; - } + nlp->len = lp->len + deltaLen; - return TRUE; -} + memcpy(nlp->data, lp->data, offset); + memcpy(&nlp->data[offset], newStr, newLen); + memcpy(&nlp->data[offset + newLen], + &lp->data[offset + oldLen], + lp->len - offset - oldLen); + nlp->next = lp->next; + nlp->prev = lp->prev; + nlp->prev->next = nlp; + nlp->next->prev = nlp; -/* - * Insert a new line with the specified text. - * The line is inserted so as to become the specified line, - * thus pushing any existing and further lines down one. - * The inserted line is also set to become the current line. - * Returns TRUE if successful. - */ -static int insertLine(int num, const char *data, int len) -{ - LINE *newLp, *lp; + if (curLine == lp) + curLine = nlp; - if ((num < 1) || (num > lastNum + 1)) { - bb_error_msg("inserting at bad line number"); - return FALSE; - } + free(lp); + lp = nlp; - newLp = xmalloc(sizeof(LINE) + len - 1); + offset += newLen; - memcpy(newLp->data, data, len); - newLp->len = len; + if (globalFlag) + continue; - if (num > lastNum) - lp = &lines; - else { - lp = findLine(num); - if (lp == NULL) { - free((char *) newLp); - return FALSE; + if (needPrint) { + printLines(num1, num1, FALSE); + needPrint = FALSE; } - } - newLp->next = lp; - newLp->prev = lp->prev; - lp->prev->next = newLp; - lp->prev = newLp; + lp = lp->next; + num1++; + } - lastNum++; - dirty = TRUE; - return setCurNum(num); + if (!didSub) + bb_error_msg("no substitutions found for \"%s\"", oldStr); } - /* - * Delete lines from the given range. + * Read commands until we are told to stop. */ -static void deleteLines(int num1, int num2) +static void doCommands(void) { - LINE *lp, *nlp, *plp; - int count; + const char *cp; + char *endbuf, buf[USERSIZE]; + int len, num1, num2; + smallint have1, have2; - if (bad_nums(num1, num2, "delete")) - return; + while (TRUE) { + /* Returns: + * -1 on read errors or EOF, or on bare Ctrl-D. + * 0 on ctrl-C, + * >0 length of input string, including terminating '\n' + */ + len = read_line_input(NULL, ": ", buf, sizeof(buf), /*timeout*/ -1); + if (len <= 0) + return; + endbuf = &buf[len - 1]; + while ((endbuf > buf) && isblank(endbuf[-1])) + endbuf--; + *endbuf = '\0'; - lp = findLine(num1); - if (lp == NULL) - return; + cp = skip_blank(buf); + have1 = FALSE; + have2 = FALSE; - if ((curNum >= num1) && (curNum <= num2)) { - if (num2 < lastNum) - setCurNum(num2 + 1); - else if (num1 > 1) - setCurNum(num1 - 1); - else - curNum = 0; - } + if ((curNum == 0) && (lastNum > 0)) { + curNum = 1; + curLine = lines.next; + } - count = num2 - num1 + 1; - if (curNum > num2) - curNum -= count; - lastNum -= count; + if (!getNum(&cp, &have1, &num1)) + continue; - while (count-- > 0) { - nlp = lp->next; - plp = lp->prev; - plp->next = nlp; - nlp->prev = plp; - free(lp); - lp = nlp; - } + cp = skip_blank(cp); - dirty = TRUE; -} + if (*cp == ',') { + cp++; + if (!getNum(&cp, &have2, &num2)) + continue; + if (!have1) + num1 = 1; + if (!have2) + num2 = lastNum; + have1 = TRUE; + have2 = TRUE; + } + if (!have1) + num1 = curNum; + if (!have2) + num2 = num1; + switch (*cp++) { + case 'a': + addLines(num1 + 1); + break; -/* - * Search for a line which contains the specified string. - * If the string is "", then the previously searched for string - * is used. The currently searched for string is saved for future use. - * Returns the line number which matches, or 0 if there was no match - * with an error printed. - */ -static NOINLINE int searchLines(const char *str, int num1, int num2) -{ - const LINE *lp; - int len; + case 'c': + deleteLines(num1, num2); + addLines(num1); + break; - if (bad_nums(num1, num2, "search")) - return 0; + case 'd': + deleteLines(num1, num2); + break; - if (*str == '\0') { - if (searchString[0] == '\0') { - bb_error_msg("no previous search string"); - return 0; - } - str = searchString; - } + case 'f': + if (*cp && !isblank(*cp)) { + bb_error_msg("bad file command"); + break; + } + cp = skip_blank(cp); + if (*cp == '\0') { + if (fileName) + printf("\"%s\"\n", fileName); + else + puts("No file name"); + break; + } + free(fileName); + fileName = xstrdup(cp); + break; - if (str != searchString) - strcpy(searchString, str); + case 'i': + addLines(num1); + break; - len = strlen(str); + case 'k': + cp = skip_blank(cp); + if ((*cp < 'a') || (*cp > 'z') || cp[1]) { + bb_error_msg("bad mark name"); + break; + } + marks[*cp - 'a'] = num2; + break; - lp = findLine(num1); - if (lp == NULL) - return 0; + case 'l': + printLines(num1, num2, TRUE); + break; - while (num1 <= num2) { - if (findString(lp, str, len, 0) >= 0) - return num1; - num1++; - lp = lp->next; - } + case 'p': + printLines(num1, num2, FALSE); + break; - bb_error_msg("can't find string \"%s\"", str); - return 0; -} + case 'q': + cp = skip_blank(cp); + if (have1 || *cp) { + bb_error_msg("bad quit command"); + break; + } + if (!dirty) + return; + len = read_line_input(NULL, "Really quit? ", buf, 16, /*timeout*/ -1); + /* read error/EOF - no way to continue */ + if (len < 0) + return; + cp = skip_blank(buf); + if ((*cp | 0x20) == 'y') /* Y or y */ + return; + break; + case 'r': + if (*cp && !isblank(*cp)) { + bb_error_msg("bad read command"); + break; + } + cp = skip_blank(cp); + if (*cp == '\0') { + bb_error_msg("no file name"); + break; + } + if (!have1) + num1 = lastNum; + if (readLines(cp, num1 + 1)) + break; + if (fileName == NULL) + fileName = xstrdup(cp); + break; -/* - * Return a pointer to the specified line number. - */ -static LINE *findLine(int num) -{ - LINE *lp; - int lnum; + case 's': + subCommand(cp, num1, num2); + break; - if ((num < 1) || (num > lastNum)) { - bb_error_msg("line number %d does not exist", num); - return NULL; - } + case 'w': + if (*cp && !isblank(*cp)) { + bb_error_msg("bad write command"); + break; + } + cp = skip_blank(cp); + if (!have1) { + num1 = 1; + num2 = lastNum; + } + if (*cp == '\0') + cp = fileName; + if (cp == NULL) { + bb_error_msg("no file name specified"); + break; + } + writeLines(cp, num1, num2); + break; - if (curNum <= 0) { - curNum = 1; - curLine = lines.next; - } + case 'z': + switch (*cp) { + case '-': + printLines(curNum - 21, curNum, FALSE); + break; + case '.': + printLines(curNum - 11, curNum + 10, FALSE); + break; + default: + printLines(curNum, curNum + 21, FALSE); + break; + } + break; - if (num == curNum) - return curLine; + case '.': + if (have1) { + bb_error_msg("no arguments allowed"); + break; + } + printLines(curNum, curNum, FALSE); + break; - lp = curLine; - lnum = curNum; - if (num < (curNum / 2)) { - lp = lines.next; - lnum = 1; - } else if (num > ((curNum + lastNum) / 2)) { - lp = lines.prev; - lnum = lastNum; - } + case '-': + if (setCurNum(curNum - 1)) + printLines(curNum, curNum, FALSE); + break; - while (lnum < num) { - lp = lp->next; - lnum++; - } + case '=': + printf("%d\n", num1); + break; + case '\0': + if (have1) { + printLines(num2, num2, FALSE); + break; + } + if (setCurNum(curNum + 1)) + printLines(curNum, curNum, FALSE); + break; - while (lnum > num) { - lp = lp->prev; - lnum--; + default: + bb_error_msg("unimplemented command"); + break; + } } - return lp; } - -/* - * Set the current line number. - * Returns TRUE if successful. - */ -static int setCurNum(int num) +int ed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int ed_main(int argc UNUSED_PARAM, char **argv) { - LINE *lp; + INIT_G(); - lp = findLine(num); - if (lp == NULL) - return FALSE; - curNum = num; - curLine = lp; - return TRUE; + bufSize = INITBUF_SIZE; + bufBase = xmalloc(bufSize); + bufPtr = bufBase; + lines.next = &lines; + lines.prev = &lines; + + if (argv[1]) { + fileName = xstrdup(argv[1]); + if (!readLines(fileName, 1)) { + return EXIT_SUCCESS; + } + if (lastNum) + setCurNum(1); + dirty = FALSE; + } + + doCommands(); + return EXIT_SUCCESS; } -- cgit v1.2.3