hush/editors/ed.c
Rob Landley 3b89039c25 It was sitting there, in the patches directory, for years. It was delete it
or apply it.  It's small, simple, evil, part of SUSv3, and we can switch it
off.
2006-05-04 20:56:43 +00:00

1364 lines
20 KiB
C

/*
* Copyright (c) 2002 by David I. Bell
* Permission is granted to use, distribute, or modify this source,
* provided that this copyright notice remains intact.
*
* The "ed" built-in command (much simplified)
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <memory.h>
#include <time.h>
#include <ctype.h>
#include <sys/param.h>
#include <malloc.h>
#include "busybox.h"
#define USERSIZE 1024 /* max line length typed in by user */
#define INITBUF_SIZE 1024 /* initial buffer size */
typedef struct LINE {
struct LINE *next;
struct LINE *prev;
int len;
char data[1];
} LINE;
static LINE lines, *curLine;
static int curNum, lastNum, marks[26], dirty;
static char *bufBase, *bufPtr, *fileName, searchString[USERSIZE];
static int bufUsed, bufSize;
static void doCommands(void);
static void subCommand(const char *cmd, int num1, int num2);
static int getNum(const char **retcp, int *retHaveNum, int *retNum);
static int setCurNum(int num);
static int initEdit(void);
static void termEdit(void);
static void addLines(int num);
static int insertLine(int num, const char *data, int len);
static int 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);
int ed_main(int argc, char **argv)
{
if (!initEdit())
return EXIT_FAILURE;
if (argc > 1) {
fileName = strdup(argv[1]);
if (fileName == NULL) {
bb_error_msg("No memory");
termEdit();
return EXIT_SUCCESS;
}
if (!readLines(fileName, 1)) {
termEdit();
return EXIT_SUCCESS;
}
if (lastNum)
setCurNum(1);
dirty = FALSE;
}
doCommands();
termEdit();
return EXIT_SUCCESS;
}
/*
* Read commands until we are told to stop.
*/
static void doCommands(void)
{
const char *cp;
char *endbuf, *newname, buf[USERSIZE];
int len, num1, num2, have1, have2;
while (TRUE)
{
printf(": ");
fflush(stdout);
if (fgets(buf, sizeof(buf), stdin) == NULL)
return;
len = strlen(buf);
if (len == 0)
return;
endbuf = &buf[len - 1];
if (*endbuf != '\n')
{
bb_error_msg("Command line too long");
do
{
len = fgetc(stdin);
}
while ((len != EOF) && (len != '\n'));
continue;
}
while ((endbuf > buf) && isblank(endbuf[-1]))
endbuf--;
*endbuf = '\0';
cp = buf;
while (isblank(*cp))
cp++;
have1 = FALSE;
have2 = FALSE;
if ((curNum == 0) && (lastNum > 0))
{
curNum = 1;
curLine = lines.next;
}
if (!getNum(&cp, &have1, &num1))
continue;
while (isblank(*cp))
cp++;
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;
case 'c':
deleteLines(num1, num2);
addLines(num1);
break;
case 'd':
deleteLines(num1, num2);
break;
case 'f':
if (*cp && !isblank(*cp))
{
bb_error_msg("Bad file command");
break;
}
while (isblank(*cp))
cp++;
if (*cp == '\0')
{
if (fileName)
printf("\"%s\"\n", fileName);
else
printf("No file name\n");
break;
}
newname = strdup(cp);
if (newname == NULL)
{
bb_error_msg("No memory for file name");
break;
}
if (fileName)
free(fileName);
fileName = newname;
break;
case 'i':
addLines(num1);
break;
case 'k':
while (isblank(*cp))
cp++;
if ((*cp < 'a') || (*cp > 'a') || cp[1])
{
bb_error_msg("Bad mark name");
break;
}
marks[*cp - 'a'] = num2;
break;
case 'l':
printLines(num1, num2, TRUE);
break;
case 'p':
printLines(num1, num2, FALSE);
break;
case 'q':
while (isblank(*cp))
cp++;
if (have1 || *cp)
{
bb_error_msg("Bad quit command");
break;
}
if (!dirty)
return;
printf("Really quit? ");
fflush(stdout);
buf[0] = '\0';
fgets(buf, sizeof(buf), stdin);
cp = buf;
while (isblank(*cp))
cp++;
if ((*cp == 'y') || (*cp == 'Y'))
return;
break;
case 'r':
if (*cp && !isblank(*cp))
{
bb_error_msg("Bad read command");
break;
}
while (isblank(*cp))
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 = strdup(cp);
break;
case 's':
subCommand(cp, num1, num2);
break;
case 'w':
if (*cp && !isblank(*cp))
{
bb_error_msg("Bad write command");
break;
}
while (isblank(*cp))
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;
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;
case '.':
if (have1)
{
bb_error_msg("No arguments allowed");
break;
}
printLines(curNum, curNum, FALSE);
break;
case '-':
if (setCurNum(curNum - 1))
printLines(curNum, curNum, FALSE);
break;
case '=':
printf("%d\n", num1);
break;
case '\0':
if (have1)
{
printLines(num2, num2, FALSE);
break;
}
if (setCurNum(curNum + 1))
printLines(curNum, curNum, FALSE);
break;
default:
bb_error_msg("Unimplemented command");
break;
}
}
}
/*
* 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 ((num1 < 1) || (num2 > lastNum) || (num1 > num2))
{
bb_error_msg("Bad line range for substitute");
return;
}
globalFlag = FALSE;
printFlag = FALSE;
didSub = FALSE;
needPrint = FALSE;
/*
* Copy the command so we can modify it.
*/
strcpy(buf, cmd);
cp = buf;
if (isblank(*cp) || (*cp == '\0'))
{
bb_error_msg("Bad delimiter for substitute");
return;
}
delim = *cp++;
oldStr = cp;
cp = strchr(cp, delim);
if (cp == NULL)
{
bb_error_msg("Missing 2nd delimiter for substitute");
return;
}
*cp++ = '\0';
newStr = cp;
cp = strchr(cp, delim);
if (cp)
*cp++ = '\0';
else
cp = "";
while (*cp) switch (*cp++)
{
case 'g':
globalFlag = TRUE;
break;
case 'p':
printFlag = TRUE;
break;
default:
bb_error_msg("Unknown option for substitute");
return;
}
if (*oldStr == '\0')
{
if (searchString[0] == '\0')
{
bb_error_msg("No previous search string");
return;
}
oldStr = searchString;
}
if (oldStr != searchString)
strcpy(searchString, oldStr);
lp = findLine(num1);
if (lp == NULL)
return;
oldLen = strlen(oldStr);
newLen = strlen(newStr);
deltaLen = newLen - oldLen;
offset = 0;
nlp = NULL;
while (num1 <= num2)
{
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;
/*
* 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);
lp->len += deltaLen;
}
offset += newLen;
if (globalFlag)
continue;
if (needPrint)
{
printLines(num1, num1, FALSE);
needPrint = FALSE;
}
lp = lp->next;
num1++;
continue;
}
/*
* The new string is larger, so allocate a new line
* structure and use that. Link it in in place of
* the old line structure.
*/
nlp = (LINE *) malloc(sizeof(LINE) + lp->len + deltaLen);
if (nlp == NULL)
{
bb_error_msg("Cannot get memory for line");
return;
}
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;
free(lp);
lp = nlp;
offset += newLen;
if (globalFlag)
continue;
if (needPrint)
{
printLines(num1, num1, FALSE);
needPrint = FALSE;
}
lp = lp->next;
num1++;
}
if (!didSub)
bb_error_msg("No substitutions found for \"%s\"", oldStr);
}
/*
* 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 int findString( const LINE * lp, const char * str, int len, int offset)
{
int left;
const char *cp, *ncp;
cp = &lp->data[offset];
left = lp->len - offset;
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--;
}
return -1;
}
/*
* 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];
while (fgets(buf, sizeof(buf), stdin))
{
if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0'))
return;
len = strlen(buf);
if (len == 0)
return;
if (buf[len - 1] != '\n')
{
bb_error_msg("Line too long");
do
{
len = fgetc(stdin);
}
while ((len != EOF) && (len != '\n'));
return;
}
if (!insertLine(num++, buf, len))
return;
}
}
/*
* 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, int *retHaveNum, int *retNum)
{
const char *cp;
char *endStr, str[USERSIZE];
int haveNum, value, num, sign;
cp = *retcp;
haveNum = FALSE;
value = 0;
sign = 1;
while (TRUE)
{
while (isblank(*cp))
cp++;
switch (*cp)
{
case '.':
haveNum = TRUE;
num = curNum;
cp++;
break;
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;
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:
if (!isdigit(*cp))
{
*retcp = cp;
*retHaveNum = haveNum;
*retNum = value;
return TRUE;
}
num = 0;
while (isdigit(*cp))
num = num * 10 + *cp++ - '0';
haveNum = TRUE;
break;
}
value += num * sign;
while (isblank(*cp))
cp++;
switch (*cp)
{
case '-':
sign = -1;
cp++;
break;
case '+':
sign = 1;
cp++;
break;
default:
*retcp = cp;
*retHaveNum = haveNum;
*retNum = value;
return TRUE;
}
}
}
/*
* Initialize everything for editing.
*/
static int initEdit(void)
{
int i;
bufSize = INITBUF_SIZE;
bufBase = malloc(bufSize);
if (bufBase == NULL)
{
bb_error_msg("No memory for buffer");
return FALSE;
}
bufPtr = bufBase;
bufUsed = 0;
lines.next = &lines;
lines.prev = &lines;
curLine = NULL;
curNum = 0;
lastNum = 0;
dirty = FALSE;
fileName = NULL;
searchString[0] = '\0';
for (i = 0; i < 26; i++)
marks[i] = 0;
return TRUE;
}
/*
* Finish editing.
*/
static void termEdit(void)
{
if (bufBase)
free(bufBase);
bufBase = NULL;
bufPtr = NULL;
bufSize = 0;
bufUsed = 0;
if (fileName)
free(fileName);
fileName = NULL;
searchString[0] = '\0';
if (lastNum)
deleteLines(1, lastNum);
lastNum = 0;
curNum = 0;
curLine = NULL;
}
/*
* 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;
if ((num < 1) || (num > lastNum + 1))
{
bb_error_msg("Bad line for read");
return FALSE;
}
fd = open(file, 0);
if (fd < 0)
{
perror(file);
return FALSE;
}
bufPtr = bufBase;
bufUsed = 0;
lineCount = 0;
charCount = 0;
cc = 0;
printf("\"%s\", ", file);
fflush(stdout);
do
{
cp = memchr(bufPtr, '\n', bufUsed);
if (cp)
{
len = (cp - bufPtr) + 1;
if (!insertLine(num, bufPtr, len))
{
close(fd);
return FALSE;
}
bufPtr += len;
bufUsed -= len;
charCount += len;
lineCount++;
num++;
continue;
}
if (bufPtr != bufBase)
{
memcpy(bufBase, bufPtr, bufUsed);
bufPtr = bufBase + bufUsed;
}
if (bufUsed >= bufSize)
{
len = (bufSize * 3) / 2;
cp = realloc(bufBase, len);
if (cp == NULL)
{
bb_error_msg("No memory for buffer");
close(fd);
return FALSE;
}
bufBase = cp;
bufPtr = bufBase + bufUsed;
bufSize = len;
}
cc = read(fd, bufPtr, bufSize - bufUsed);
bufUsed += cc;
bufPtr = bufBase;
}
while (cc > 0);
if (cc < 0)
{
perror(file);
close(fd);
return FALSE;
}
if (bufUsed)
{
if (!insertLine(num, bufPtr, bufUsed))
{
close(fd);
return -1;
}
lineCount++;
charCount += bufUsed;
}
close(fd);
printf("%d lines%s, %d chars\n", lineCount,
(bufUsed ? " (incomplete)" : ""), charCount);
return TRUE;
}
/*
* 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;
if ((num1 < 1) || (num2 > lastNum) || (num1 > num2))
{
bb_error_msg("Bad line range for write");
return FALSE;
}
lineCount = 0;
charCount = 0;
fd = creat(file, 0666);
if (fd < 0) {
perror(file);
return FALSE;
}
printf("\"%s\", ", file);
fflush(stdout);
lp = findLine(num1);
if (lp == NULL)
{
close(fd);
return FALSE;
}
while (num1++ <= num2)
{
if (write(fd, lp->data, lp->len) != lp->len)
{
perror(file);
close(fd);
return FALSE;
}
charCount += lp->len;
lineCount++;
lp = lp->next;
}
if (close(fd) < 0)
{
perror(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 ((num1 < 1) || (num2 > lastNum) || (num1 > num2))
{
bb_error_msg("Bad line range for print");
return FALSE;
}
lp = findLine(num1);
if (lp == NULL)
return FALSE;
while (num1 <= num2)
{
if (!expandFlag)
{
write(1, lp->data, lp->len);
setCurNum(num1++);
lp = lp->next;
continue;
}
/*
* Show control characters and characters with the
* high bit set specially.
*/
cp = lp->data;
count = lp->len;
if ((count > 0) && (cp[count - 1] == '\n'))
count--;
while (count-- > 0)
{
ch = *cp++;
if (ch & 0x80)
{
fputs("M-", stdout);
ch &= 0x7f;
}
if (ch < ' ')
{
fputc('^', stdout);
ch += '@';
}
if (ch == 0x7f)
{
fputc('^', stdout);
ch = '?';
}
fputc(ch, stdout);
}
fputs("$\n", stdout);
setCurNum(num1++);
lp = lp->next;
}
return TRUE;
}
/*
* 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 ((num < 1) || (num > lastNum + 1))
{
bb_error_msg("Inserting at bad line number");
return FALSE;
}
newLp = (LINE *) malloc(sizeof(LINE) + len - 1);
if (newLp == NULL)
{
bb_error_msg("Failed to allocate memory for line");
return FALSE;
}
memcpy(newLp->data, data, len);
newLp->len = len;
if (num > lastNum)
lp = &lines;
else
{
lp = findLine(num);
if (lp == NULL)
{
free((char *) newLp);
return FALSE;
}
}
newLp->next = lp;
newLp->prev = lp->prev;
lp->prev->next = newLp;
lp->prev = newLp;
lastNum++;
dirty = TRUE;
return setCurNum(num);
}
/*
* Delete lines from the given range.
*/
static int deleteLines(int num1, int num2)
{
LINE *lp, *nlp, *plp;
int count;
if ((num1 < 1) || (num2 > lastNum) || (num1 > num2))
{
bb_error_msg("Bad line numbers for delete");
return FALSE;
}
lp = findLine(num1);
if (lp == NULL)
return FALSE;
if ((curNum >= num1) && (curNum <= num2))
{
if (num2 < lastNum)
setCurNum(num2 + 1);
else if (num1 > 1)
setCurNum(num1 - 1);
else
curNum = 0;
}
count = num2 - num1 + 1;
if (curNum > num2)
curNum -= count;
lastNum -= count;
while (count-- > 0)
{
nlp = lp->next;
plp = lp->prev;
plp->next = nlp;
nlp->prev = plp;
lp->next = NULL;
lp->prev = NULL;
lp->len = 0;
free(lp);
lp = nlp;
}
dirty = TRUE;
return TRUE;
}
/*
* Search for a line which contains the specified string.
* If the string is NULL, 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 int searchLines(const char *str, int num1, int num2)
{
const LINE *lp;
int len;
if ((num1 < 1) || (num2 > lastNum) || (num1 > num2))
{
bb_error_msg("Bad line numbers for search");
return 0;
}
if (*str == '\0')
{
if (searchString[0] == '\0')
{
bb_error_msg("No previous search string");
return 0;
}
str = searchString;
}
if (str != searchString)
strcpy(searchString, str);
len = strlen(str);
lp = findLine(num1);
if (lp == NULL)
return 0;
while (num1 <= num2)
{
if (findString(lp, str, len, 0) >= 0)
return num1;
num1++;
lp = lp->next;
}
bb_error_msg("Cannot find string \"%s\"", str);
return 0;
}
/*
* Return a pointer to the specified line number.
*/
static LINE *findLine(int num)
{
LINE *lp;
int lnum;
if ((num < 1) || (num > lastNum))
{
bb_error_msg("Line number %d does not exist", num);
return NULL;
}
if (curNum <= 0)
{
curNum = 1;
curLine = lines.next;
}
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;
}
/*
* Set the current line number.
* Returns TRUE if successful.
*/
static int setCurNum(int num)
{
LINE *lp;
lp = findLine(num);
if (lp == NULL)
return FALSE;
curNum = num;
curLine = lp;
return TRUE;
}