blob: 209ce99423b0bebb3be1b4abd6cad86ac4e0b19d [file] [log] [blame]
Bernhard Reutner-Fischerd9ed35c2006-05-19 12:38:30 +00001/* vi: set sw=4 ts=4: */
Rob Landley3b890392006-05-04 20:56:43 +00002/*
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.
6 *
7 * The "ed" built-in command (much simplified)
8 */
Denys Vlasenko73225b62013-11-13 12:45:33 +01009//config:config ED
Denys Vlasenkob097a842018-12-28 03:20:17 +010010//config: bool "ed (21 kb)"
Denys Vlasenko73225b62013-11-13 12:45:33 +010011//config: default y
12//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020013//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.
Denys Vlasenko73225b62013-11-13 12:45:33 +010016
17//kbuild:lib-$(CONFIG_ED) += ed.o
18
19//applet:IF_ED(APPLET(ed, BB_DIR_BIN, BB_SUID_DROP))
20
Sören Tempel9173c9c2021-12-29 16:15:50 +010021//usage:#define ed_trivial_usage "[-p PROMPT] [-s] [FILE]"
Pere Orga6a3e01d2011-04-01 22:56:30 +020022//usage:#define ed_full_usage ""
23
Denis Vlasenkob6adbf12007-05-26 19:00:18 +000024#include "libbb.h"
Denys Vlasenkoe6a2f4c2016-04-21 16:26:30 +020025#include "common_bufsiz.h"
Rob Landley3b890392006-05-04 20:56:43 +000026
Denis Vlasenkod9391b12007-09-25 11:55:57 +000027typedef struct LINE {
28 struct LINE *next;
29 struct LINE *prev;
30 int len;
31 char data[1];
32} LINE;
33
Denys Vlasenko9de2e5a2016-04-21 18:38:51 +020034#define searchString bb_common_bufsiz1
Denis Vlasenko74324c82007-06-04 10:16:52 +000035
Denis Vlasenko55f30b02007-03-24 22:42:29 +000036enum {
Denys Vlasenko9de2e5a2016-04-21 18:38:51 +020037 USERSIZE = COMMON_BUFSIZE > 1024 ? 1024
38 : COMMON_BUFSIZE - 1, /* max line length typed in by user */
Denis Vlasenko55f30b02007-03-24 22:42:29 +000039 INITBUF_SIZE = 1024, /* initial buffer size */
40};
41
Denis Vlasenkod9391b12007-09-25 11:55:57 +000042struct globals {
43 int curNum;
44 int lastNum;
45 int bufUsed;
46 int bufSize;
47 LINE *curLine;
48 char *bufBase;
49 char *bufPtr;
50 char *fileName;
Sören Tempelbfd87382021-11-21 12:24:45 +010051 const char *prompt;
Denis Vlasenkod9391b12007-09-25 11:55:57 +000052 LINE lines;
53 smallint dirty;
54 int marks[26];
55};
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 )
Sören Tempelbfd87382021-11-21 12:24:45 +010061#define prompt (G.prompt )
Denis Vlasenkod9391b12007-09-25 11:55:57 +000062#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 { \
Denys Vlasenko9de2e5a2016-04-21 18:38:51 +020070 setup_common_bufsiz(); \
Denis Vlasenko574f2f42008-02-27 18:41:59 +000071 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
Denis Vlasenkod9391b12007-09-25 11:55:57 +000072} while (0)
Rob Landley3b890392006-05-04 20:56:43 +000073
Sören Tempel9173c9c2021-12-29 16:15:50 +010074#define OPTION_STR "sp:"
75enum {
76 OPT_s = (1 << 0),
77};
78
Denis Vlasenkod9391b12007-09-25 11:55:57 +000079static int bad_nums(int num1, int num2, const char *for_what)
80{
81 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
82 bb_error_msg("bad line range for %s", for_what);
83 return 1;
84 }
85 return 0;
86}
87
Denys Vlasenko5ea50692017-07-27 11:17:15 +020088/*
89 * Return a pointer to the specified line number.
90 */
91static LINE *findLine(int num)
Rob Landley3b890392006-05-04 20:56:43 +000092{
Denys Vlasenko5ea50692017-07-27 11:17:15 +020093 LINE *lp;
94 int lnum;
Denis Vlasenkod9391b12007-09-25 11:55:57 +000095
Denys Vlasenko5ea50692017-07-27 11:17:15 +020096 if ((num < 1) || (num > lastNum)) {
97 bb_error_msg("line number %d does not exist", num);
98 return NULL;
Rob Landley3b890392006-05-04 20:56:43 +000099 }
100
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200101 if (curNum <= 0) {
102 curNum = 1;
103 curLine = lines.next;
104 }
105
106 if (num == curNum)
107 return curLine;
108
109 lp = curLine;
110 lnum = curNum;
111 if (num < (curNum / 2)) {
112 lp = lines.next;
113 lnum = 1;
114 } else if (num > ((curNum + lastNum) / 2)) {
115 lp = lines.prev;
116 lnum = lastNum;
117 }
118
119 while (lnum < num) {
120 lp = lp->next;
121 lnum++;
122 }
123
124 while (lnum > num) {
125 lp = lp->prev;
126 lnum--;
127 }
128 return lp;
129}
130
131/*
132 * Search a line for the specified string starting at the specified
133 * offset in the line. Returns the offset of the found string, or -1.
134 */
135static int findString(const LINE *lp, const char *str, int len, int offset)
136{
137 int left;
138 const char *cp, *ncp;
139
140 cp = &lp->data[offset];
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200141 left = lp->len - offset - len;
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200142
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200143 while (left >= 0) {
144 ncp = memchr(cp, str[0], left + 1);
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200145 if (ncp == NULL)
146 return -1;
147 left -= (ncp - cp);
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200148 cp = ncp;
149 if (memcmp(cp, str, len) == 0)
150 return (cp - lp->data);
151 cp++;
152 left--;
153 }
154
155 return -1;
156}
157
158/*
159 * Search for a line which contains the specified string.
160 * If the string is "", then the previously searched for string
161 * is used. The currently searched for string is saved for future use.
162 * Returns the line number which matches, or 0 if there was no match
163 * with an error printed.
164 */
165static NOINLINE int searchLines(const char *str, int num1, int num2)
166{
167 const LINE *lp;
168 int len;
169
170 if (bad_nums(num1, num2, "search"))
171 return 0;
172
173 if (*str == '\0') {
174 if (searchString[0] == '\0') {
James Byrne69374872019-07-02 11:35:03 +0200175 bb_simple_error_msg("no previous search string");
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200176 return 0;
177 }
178 str = searchString;
179 }
180
181 if (str != searchString)
182 strcpy(searchString, str);
183
184 len = strlen(str);
185
186 lp = findLine(num1);
187 if (lp == NULL)
188 return 0;
189
190 while (num1 <= num2) {
191 if (findString(lp, str, len, 0) >= 0)
192 return num1;
193 num1++;
194 lp = lp->next;
195 }
196
197 bb_error_msg("can't find string \"%s\"", str);
198 return 0;
199}
200
201/*
202 * Parse a line number argument if it is present. This is a sum
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200203 * or difference of numbers, ".", "$", "'c", or a search string.
204 * Returns pointer which stopped the scan if successful
205 * (whether or not there was a number).
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200206 * Returns NULL if there was a parsing error, with a message output.
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200207 * Whether there was a number is returned indirectly, as is the number.
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200208 */
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200209static const char* getNum(const char *cp, smallint *retHaveNum, int *retNum)
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200210{
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200211 char *endStr, str[USERSIZE];
212 int value, num;
213 smallint haveNum, minus;
214
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200215 value = 0;
216 haveNum = FALSE;
217 minus = 0;
218
219 while (TRUE) {
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200220 cp = skip_whitespace(cp);
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200221
222 switch (*cp) {
223 case '.':
224 haveNum = TRUE;
225 num = curNum;
226 cp++;
227 break;
228
229 case '$':
230 haveNum = TRUE;
231 num = lastNum;
232 cp++;
233 break;
234
235 case '\'':
236 cp++;
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200237 if ((unsigned)(*cp - 'a') >= 26) {
James Byrne69374872019-07-02 11:35:03 +0200238 bb_simple_error_msg("bad mark name");
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200239 return NULL;
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200240 }
241 haveNum = TRUE;
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200242 num = marks[(unsigned)(*cp - 'a')];
243 cp++;
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200244 break;
245
246 case '/':
247 strcpy(str, ++cp);
248 endStr = strchr(str, '/');
249 if (endStr) {
250 *endStr++ = '\0';
251 cp += (endStr - str);
252 } else
253 cp = "";
254 num = searchLines(str, curNum, lastNum);
255 if (num == 0)
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200256 return NULL;
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200257 haveNum = TRUE;
258 break;
259
260 default:
261 if (!isdigit(*cp)) {
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200262 *retHaveNum = haveNum;
263 *retNum = value;
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200264 return cp;
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200265 }
266 num = 0;
267 while (isdigit(*cp))
268 num = num * 10 + *cp++ - '0';
269 haveNum = TRUE;
270 break;
271 }
272
273 value += (minus ? -num : num);
274
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200275 cp = skip_whitespace(cp);
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200276
277 switch (*cp) {
278 case '-':
279 minus = 1;
280 cp++;
281 break;
282
283 case '+':
284 minus = 0;
285 cp++;
286 break;
287
288 default:
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200289 *retHaveNum = haveNum;
290 *retNum = value;
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200291 return cp;
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200292 }
293 }
294}
295
296/*
297 * Set the current line number.
298 * Returns TRUE if successful.
299 */
300static int setCurNum(int num)
301{
302 LINE *lp;
303
304 lp = findLine(num);
305 if (lp == NULL)
306 return FALSE;
307 curNum = num;
308 curLine = lp;
309 return TRUE;
310}
311
312/*
313 * Insert a new line with the specified text.
314 * The line is inserted so as to become the specified line,
315 * thus pushing any existing and further lines down one.
316 * The inserted line is also set to become the current line.
317 * Returns TRUE if successful.
318 */
319static int insertLine(int num, const char *data, int len)
320{
321 LINE *newLp, *lp;
322
323 if ((num < 1) || (num > lastNum + 1)) {
James Byrne69374872019-07-02 11:35:03 +0200324 bb_simple_error_msg("inserting at bad line number");
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200325 return FALSE;
326 }
327
328 newLp = xmalloc(sizeof(LINE) + len - 1);
329
330 memcpy(newLp->data, data, len);
331 newLp->len = len;
332
333 if (num > lastNum)
334 lp = &lines;
335 else {
336 lp = findLine(num);
337 if (lp == NULL) {
338 free((char *) newLp);
339 return FALSE;
340 }
341 }
342
343 newLp->next = lp;
344 newLp->prev = lp->prev;
345 lp->prev->next = newLp;
346 lp->prev = newLp;
347
348 lastNum++;
349 dirty = TRUE;
350 return setCurNum(num);
351}
352
353/*
354 * Add lines which are typed in by the user.
355 * The lines are inserted just before the specified line number.
356 * The lines are terminated by a line containing a single dot (ugly!),
357 * or by an end of file.
358 */
359static void addLines(int num)
360{
361 int len;
362 char buf[USERSIZE + 1];
363
364 while (1) {
365 /* Returns:
366 * -1 on read errors or EOF, or on bare Ctrl-D.
367 * 0 on ctrl-C,
368 * >0 length of input string, including terminating '\n'
369 */
Denys Vlasenko84ea60e2017-08-02 17:27:28 +0200370 len = read_line_input(NULL, "", buf, sizeof(buf));
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200371 if (len <= 0) {
372 /* Previously, ctrl-C was exiting to shell.
373 * Now we exit to ed prompt. Is in important? */
374 return;
375 }
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200376 if (buf[0] == '.' && buf[1] == '\n' && buf[2] == '\0')
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200377 return;
378 if (!insertLine(num++, buf, len))
379 return;
380 }
381}
382
383/*
384 * Read lines from a file at the specified line number.
385 * Returns TRUE if the file was successfully read.
386 */
387static int readLines(const char *file, int num)
388{
389 int fd, cc;
Sören Tempelc33bbcb2021-07-17 21:45:40 +0200390 int len;
391 unsigned charCount;
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200392 char *cp;
393
394 if ((num < 1) || (num > lastNum + 1)) {
James Byrne69374872019-07-02 11:35:03 +0200395 bb_simple_error_msg("bad line for read");
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200396 return FALSE;
397 }
398
399 fd = open(file, 0);
400 if (fd < 0) {
401 bb_simple_perror_msg(file);
402 return FALSE;
403 }
404
405 bufPtr = bufBase;
406 bufUsed = 0;
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200407 charCount = 0;
408 cc = 0;
409
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200410 do {
411 cp = memchr(bufPtr, '\n', bufUsed);
412
413 if (cp) {
414 len = (cp - bufPtr) + 1;
415 if (!insertLine(num, bufPtr, len)) {
416 close(fd);
417 return FALSE;
418 }
419 bufPtr += len;
420 bufUsed -= len;
421 charCount += len;
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200422 num++;
423 continue;
424 }
425
426 if (bufPtr != bufBase) {
427 memcpy(bufBase, bufPtr, bufUsed);
428 bufPtr = bufBase + bufUsed;
429 }
430
431 if (bufUsed >= bufSize) {
432 len = (bufSize * 3) / 2;
433 cp = xrealloc(bufBase, len);
434 bufBase = cp;
435 bufPtr = bufBase + bufUsed;
436 bufSize = len;
437 }
438
439 cc = safe_read(fd, bufPtr, bufSize - bufUsed);
440 bufUsed += cc;
441 bufPtr = bufBase;
442 } while (cc > 0);
443
444 if (cc < 0) {
445 bb_simple_perror_msg(file);
446 close(fd);
447 return FALSE;
448 }
449
450 if (bufUsed) {
451 if (!insertLine(num, bufPtr, bufUsed)) {
452 close(fd);
453 return -1;
454 }
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200455 charCount += bufUsed;
456 }
457
458 close(fd);
459
Sören Tempelc33bbcb2021-07-17 21:45:40 +0200460 /* https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ed.html
461 * "Read Command"
462 * "...the number of bytes read shall be written to standard output
463 * in the following format:
464 * "%d\n", <number of bytes read>
465 */
Sören Tempel9173c9c2021-12-29 16:15:50 +0100466 if (!(option_mask32 & OPT_s))
467 printf("%u\n", charCount);
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200468 return TRUE;
469}
470
471/*
472 * Write the specified lines out to the specified file.
473 * Returns TRUE if successful, or FALSE on an error with a message output.
474 */
475static int writeLines(const char *file, int num1, int num2)
476{
477 LINE *lp;
Sören Tempelc33bbcb2021-07-17 21:45:40 +0200478 int fd;
479 unsigned charCount;
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200480
481 if (bad_nums(num1, num2, "write"))
482 return FALSE;
483
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200484 charCount = 0;
485
486 fd = creat(file, 0666);
487 if (fd < 0) {
488 bb_simple_perror_msg(file);
489 return FALSE;
490 }
491
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200492 lp = findLine(num1);
493 if (lp == NULL) {
494 close(fd);
495 return FALSE;
496 }
497
498 while (num1++ <= num2) {
499 if (full_write(fd, lp->data, lp->len) != lp->len) {
500 bb_simple_perror_msg(file);
501 close(fd);
502 return FALSE;
503 }
504 charCount += lp->len;
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200505 lp = lp->next;
506 }
507
508 if (close(fd) < 0) {
509 bb_simple_perror_msg(file);
510 return FALSE;
511 }
512
Sören Tempelc33bbcb2021-07-17 21:45:40 +0200513 /* https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ed.html
514 * "Write Command"
515 * "...the number of bytes written shall be written to standard output,
516 * unless the -s option was specified, in the following format:
517 * "%d\n", <number of bytes written>
518 */
Sören Tempel9173c9c2021-12-29 16:15:50 +0100519 if (!(option_mask32 & OPT_s))
520 printf("%u\n", charCount);
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200521 return TRUE;
522}
523
524/*
525 * Print lines in a specified range.
526 * The last line printed becomes the current line.
527 * If expandFlag is TRUE, then the line is printed specially to
528 * show magic characters.
529 */
530static int printLines(int num1, int num2, int expandFlag)
531{
532 const LINE *lp;
533 const char *cp;
534 int ch, count;
535
536 if (bad_nums(num1, num2, "print"))
537 return FALSE;
538
539 lp = findLine(num1);
540 if (lp == NULL)
541 return FALSE;
542
543 while (num1 <= num2) {
544 if (!expandFlag) {
545 write(STDOUT_FILENO, lp->data, lp->len);
546 setCurNum(num1++);
547 lp = lp->next;
548 continue;
549 }
550
551 /*
552 * Show control characters and characters with the
553 * high bit set specially.
554 */
555 cp = lp->data;
556 count = lp->len;
557
558 if ((count > 0) && (cp[count - 1] == '\n'))
559 count--;
560
561 while (count-- > 0) {
562 ch = (unsigned char) *cp++;
563 fputc_printable(ch | PRINTABLE_META, stdout);
564 }
565
Ron Yorstoncad3fc72021-02-03 20:47:14 +0100566 fputs_stdout("$\n");
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200567
568 setCurNum(num1++);
569 lp = lp->next;
570 }
571
572 return TRUE;
573}
574
575/*
576 * Delete lines from the given range.
577 */
578static void deleteLines(int num1, int num2)
579{
580 LINE *lp, *nlp, *plp;
581 int count;
582
583 if (bad_nums(num1, num2, "delete"))
584 return;
585
586 lp = findLine(num1);
587 if (lp == NULL)
588 return;
589
590 if ((curNum >= num1) && (curNum <= num2)) {
591 if (num2 < lastNum)
592 setCurNum(num2 + 1);
593 else if (num1 > 1)
594 setCurNum(num1 - 1);
595 else
596 curNum = 0;
597 }
598
599 count = num2 - num1 + 1;
600 if (curNum > num2)
601 curNum -= count;
602 lastNum -= count;
603
604 while (count-- > 0) {
605 nlp = lp->next;
606 plp = lp->prev;
607 plp->next = nlp;
608 nlp->prev = plp;
609 free(lp);
610 lp = nlp;
611 }
612
613 dirty = TRUE;
614}
615
616/*
617 * Do the substitute command.
618 * The current line is set to the last substitution done.
619 */
620static void subCommand(const char *cmd, int num1, int num2)
621{
622 char *cp, *oldStr, *newStr, buf[USERSIZE];
623 int delim, oldLen, newLen, deltaLen, offset;
624 LINE *lp, *nlp;
625 int globalFlag, printFlag, didSub, needPrint;
626
627 if (bad_nums(num1, num2, "substitute"))
628 return;
629
630 globalFlag = FALSE;
631 printFlag = FALSE;
632 didSub = FALSE;
633 needPrint = FALSE;
634
635 /*
636 * Copy the command so we can modify it.
637 */
638 strcpy(buf, cmd);
639 cp = buf;
640
641 if (isblank(*cp) || (*cp == '\0')) {
James Byrne69374872019-07-02 11:35:03 +0200642 bb_simple_error_msg("bad delimiter for substitute");
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200643 return;
644 }
645
646 delim = *cp++;
647 oldStr = cp;
648
649 cp = strchr(cp, delim);
650 if (cp == NULL) {
James Byrne69374872019-07-02 11:35:03 +0200651 bb_simple_error_msg("missing 2nd delimiter for substitute");
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200652 return;
653 }
654
655 *cp++ = '\0';
656
657 newStr = cp;
658 cp = strchr(cp, delim);
659
660 if (cp)
661 *cp++ = '\0';
662 else
663 cp = (char*)"";
664
665 while (*cp) switch (*cp++) {
666 case 'g':
667 globalFlag = TRUE;
668 break;
669 case 'p':
670 printFlag = TRUE;
671 break;
672 default:
James Byrne69374872019-07-02 11:35:03 +0200673 bb_simple_error_msg("unknown option for substitute");
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200674 return;
675 }
676
677 if (*oldStr == '\0') {
678 if (searchString[0] == '\0') {
James Byrne69374872019-07-02 11:35:03 +0200679 bb_simple_error_msg("no previous search string");
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200680 return;
681 }
682 oldStr = searchString;
683 }
684
685 if (oldStr != searchString)
686 strcpy(searchString, oldStr);
687
688 lp = findLine(num1);
689 if (lp == NULL)
690 return;
691
692 oldLen = strlen(oldStr);
693 newLen = strlen(newStr);
694 deltaLen = newLen - oldLen;
695 offset = 0;
696 nlp = NULL;
697
698 while (num1 <= num2) {
699 offset = findString(lp, oldStr, oldLen, offset);
700
701 if (offset < 0) {
702 if (needPrint) {
703 printLines(num1, num1, FALSE);
704 needPrint = FALSE;
705 }
706 offset = 0;
707 lp = lp->next;
708 num1++;
709 continue;
710 }
711
712 needPrint = printFlag;
713 didSub = TRUE;
714 dirty = TRUE;
715
716 /*
717 * If the replacement string is the same size or shorter
718 * than the old string, then the substitution is easy.
719 */
720 if (deltaLen <= 0) {
721 memcpy(&lp->data[offset], newStr, newLen);
722 if (deltaLen) {
723 memcpy(&lp->data[offset + newLen],
724 &lp->data[offset + oldLen],
725 lp->len - offset - oldLen);
726
727 lp->len += deltaLen;
728 }
729 offset += newLen;
730 if (globalFlag)
731 continue;
732 if (needPrint) {
733 printLines(num1, num1, FALSE);
734 needPrint = FALSE;
735 }
736 lp = lp->next;
737 num1++;
738 continue;
739 }
740
741 /*
742 * The new string is larger, so allocate a new line
743 * structure and use that. Link it in place of
744 * the old line structure.
745 */
746 nlp = xmalloc(sizeof(LINE) + lp->len + deltaLen);
747
748 nlp->len = lp->len + deltaLen;
749
750 memcpy(nlp->data, lp->data, offset);
751 memcpy(&nlp->data[offset], newStr, newLen);
752 memcpy(&nlp->data[offset + newLen],
753 &lp->data[offset + oldLen],
754 lp->len - offset - oldLen);
755
756 nlp->next = lp->next;
757 nlp->prev = lp->prev;
758 nlp->prev->next = nlp;
759 nlp->next->prev = nlp;
760
761 if (curLine == lp)
762 curLine = nlp;
763
764 free(lp);
765 lp = nlp;
766
767 offset += newLen;
768
769 if (globalFlag)
770 continue;
771
772 if (needPrint) {
773 printLines(num1, num1, FALSE);
774 needPrint = FALSE;
775 }
776
777 lp = lp->next;
778 num1++;
779 }
780
781 if (!didSub)
782 bb_error_msg("no substitutions found for \"%s\"", oldStr);
Rob Landley3b890392006-05-04 20:56:43 +0000783}
784
785/*
786 * Read commands until we are told to stop.
787 */
788static void doCommands(void)
789{
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000790 while (TRUE) {
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200791 char buf[USERSIZE];
792 const char *cp;
793 int len;
794 int n, num1, num2;
795 smallint h, have1, have2;
796
Denis Vlasenkod9391b12007-09-25 11:55:57 +0000797 /* Returns:
798 * -1 on read errors or EOF, or on bare Ctrl-D.
799 * 0 on ctrl-C,
800 * >0 length of input string, including terminating '\n'
801 */
Sören Tempelbfd87382021-11-21 12:24:45 +0100802 len = read_line_input(NULL, prompt, buf, sizeof(buf));
Denis Vlasenkod9391b12007-09-25 11:55:57 +0000803 if (len <= 0)
Rob Landley3b890392006-05-04 20:56:43 +0000804 return;
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200805 while (len && isspace(buf[--len]))
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200806 buf[len] = '\0';
Rob Landley3b890392006-05-04 20:56:43 +0000807
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000808 if ((curNum == 0) && (lastNum > 0)) {
Rob Landley3b890392006-05-04 20:56:43 +0000809 curNum = 1;
810 curLine = lines.next;
811 }
812
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200813 have1 = FALSE;
814 have2 = FALSE;
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200815 /* Don't pass &haveN, &numN to getNum() since this forces
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200816 * compiler to keep them on stack, not in registers,
817 * which is usually quite suboptimal.
818 * Using intermediate variables shrinks code by ~150 bytes.
819 */
820 cp = getNum(skip_whitespace(buf), &h, &n);
821 if (!cp)
Rob Landley3b890392006-05-04 20:56:43 +0000822 continue;
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200823 have1 = h;
824 num1 = n;
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200825 cp = skip_whitespace(cp);
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000826 if (*cp == ',') {
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200827 cp = getNum(cp + 1, &h, &n);
828 if (!cp)
Rob Landley3b890392006-05-04 20:56:43 +0000829 continue;
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200830 num2 = n;
Rob Landley3b890392006-05-04 20:56:43 +0000831 if (!have1)
832 num1 = 1;
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200833 if (!h)
Rob Landley3b890392006-05-04 20:56:43 +0000834 num2 = lastNum;
Rob Landley3b890392006-05-04 20:56:43 +0000835 have1 = TRUE;
836 have2 = TRUE;
837 }
Rob Landley3b890392006-05-04 20:56:43 +0000838 if (!have1)
839 num1 = curNum;
Rob Landley3b890392006-05-04 20:56:43 +0000840 if (!have2)
841 num2 = num1;
842
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000843 switch (*cp++) {
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000844 case 'a':
845 addLines(num1 + 1);
846 break;
Rob Landley3b890392006-05-04 20:56:43 +0000847
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000848 case 'c':
849 deleteLines(num1, num2);
850 addLines(num1);
851 break;
Rob Landley3b890392006-05-04 20:56:43 +0000852
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000853 case 'd':
854 deleteLines(num1, num2);
855 break;
Rob Landley3b890392006-05-04 20:56:43 +0000856
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000857 case 'f':
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200858 if (*cp != '\0' && *cp != ' ') {
James Byrne69374872019-07-02 11:35:03 +0200859 bb_simple_error_msg("bad file command");
Rob Landley3b890392006-05-04 20:56:43 +0000860 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000861 }
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200862 cp = skip_whitespace(cp);
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000863 if (*cp == '\0') {
864 if (fileName)
865 printf("\"%s\"\n", fileName);
866 else
Denys Vlasenkod60752f2015-10-07 22:42:45 +0200867 puts("No file name");
Rob Landley3b890392006-05-04 20:56:43 +0000868 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000869 }
870 free(fileName);
871 fileName = xstrdup(cp);
872 break;
Rob Landley3b890392006-05-04 20:56:43 +0000873
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000874 case 'i':
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200875 if (!have1 && lastNum == 0)
876 num1 = 1;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000877 addLines(num1);
878 break;
879
880 case 'k':
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200881 cp = skip_whitespace(cp);
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200882 if ((unsigned)(*cp - 'a') >= 26 || cp[1]) {
James Byrne69374872019-07-02 11:35:03 +0200883 bb_simple_error_msg("bad mark name");
Rob Landley3b890392006-05-04 20:56:43 +0000884 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000885 }
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200886 marks[(unsigned)(*cp - 'a')] = num2;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000887 break;
Rob Landley3b890392006-05-04 20:56:43 +0000888
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000889 case 'l':
890 printLines(num1, num2, TRUE);
891 break;
892
893 case 'p':
894 printLines(num1, num2, FALSE);
895 break;
896
897 case 'q':
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200898 cp = skip_whitespace(cp);
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000899 if (have1 || *cp) {
James Byrne69374872019-07-02 11:35:03 +0200900 bb_simple_error_msg("bad quit command");
Rob Landley3b890392006-05-04 20:56:43 +0000901 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000902 }
903 if (!dirty)
904 return;
Denys Vlasenko84ea60e2017-08-02 17:27:28 +0200905 len = read_line_input(NULL, "Really quit? ", buf, 16);
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000906 /* read error/EOF - no way to continue */
907 if (len < 0)
908 return;
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200909 cp = skip_whitespace(buf);
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000910 if ((*cp | 0x20) == 'y') /* Y or y */
911 return;
912 break;
Rob Landley3b890392006-05-04 20:56:43 +0000913
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000914 case 'r':
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200915 if (*cp != '\0' && *cp != ' ') {
James Byrne69374872019-07-02 11:35:03 +0200916 bb_simple_error_msg("bad read command");
Rob Landley3b890392006-05-04 20:56:43 +0000917 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000918 }
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200919 cp = skip_whitespace(cp);
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000920 if (*cp == '\0') {
James Byrne69374872019-07-02 11:35:03 +0200921 bb_simple_error_msg("no file name");
Rob Landley3b890392006-05-04 20:56:43 +0000922 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000923 }
924 if (!have1)
925 num1 = lastNum;
926 if (readLines(cp, num1 + 1))
Rob Landley3b890392006-05-04 20:56:43 +0000927 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000928 if (fileName == NULL)
929 fileName = xstrdup(cp);
930 break;
Rob Landley3b890392006-05-04 20:56:43 +0000931
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000932 case 's':
933 subCommand(cp, num1, num2);
934 break;
935
936 case 'w':
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200937 if (*cp != '\0' && *cp != ' ') {
James Byrne69374872019-07-02 11:35:03 +0200938 bb_simple_error_msg("bad write command");
Rob Landley3b890392006-05-04 20:56:43 +0000939 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000940 }
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200941 cp = skip_whitespace(cp);
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200942 if (*cp == '\0') {
943 cp = fileName;
944 if (!cp) {
James Byrne69374872019-07-02 11:35:03 +0200945 bb_simple_error_msg("no file name specified");
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200946 break;
947 }
948 }
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000949 if (!have1) {
950 num1 = 1;
951 num2 = lastNum;
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200952 dirty = FALSE;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000953 }
954 writeLines(cp, num1, num2);
955 break;
Rob Landley3b890392006-05-04 20:56:43 +0000956
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000957 case 'z':
958 switch (*cp) {
Rob Landley3b890392006-05-04 20:56:43 +0000959 case '-':
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000960 printLines(curNum - 21, curNum, FALSE);
Rob Landley3b890392006-05-04 20:56:43 +0000961 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000962 case '.':
963 printLines(curNum - 11, curNum + 10, FALSE);
Rob Landley3b890392006-05-04 20:56:43 +0000964 break;
Rob Landley3b890392006-05-04 20:56:43 +0000965 default:
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000966 printLines(curNum, curNum + 21, FALSE);
Rob Landley3b890392006-05-04 20:56:43 +0000967 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000968 }
969 break;
970
971 case '.':
972 if (have1) {
James Byrne69374872019-07-02 11:35:03 +0200973 bb_simple_error_msg("no arguments allowed");
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000974 break;
975 }
976 printLines(curNum, curNum, FALSE);
977 break;
978
979 case '-':
980 if (setCurNum(curNum - 1))
981 printLines(curNum, curNum, FALSE);
982 break;
983
984 case '=':
985 printf("%d\n", num1);
986 break;
987 case '\0':
988 if (have1) {
989 printLines(num2, num2, FALSE);
990 break;
991 }
992 if (setCurNum(curNum + 1))
993 printLines(curNum, curNum, FALSE);
994 break;
995
996 default:
James Byrne69374872019-07-02 11:35:03 +0200997 bb_simple_error_msg("unimplemented command");
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000998 break;
Rob Landley3b890392006-05-04 20:56:43 +0000999 }
1000 }
1001}
1002
Denys Vlasenko5ea50692017-07-27 11:17:15 +02001003int ed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1004int ed_main(int argc UNUSED_PARAM, char **argv)
Rob Landley3b890392006-05-04 20:56:43 +00001005{
Denys Vlasenko5ea50692017-07-27 11:17:15 +02001006 INIT_G();
Rob Landley3b890392006-05-04 20:56:43 +00001007
Denys Vlasenko5ea50692017-07-27 11:17:15 +02001008 bufSize = INITBUF_SIZE;
1009 bufBase = xmalloc(bufSize);
Rob Landley3b890392006-05-04 20:56:43 +00001010 bufPtr = bufBase;
Denys Vlasenko5ea50692017-07-27 11:17:15 +02001011 lines.next = &lines;
1012 lines.prev = &lines;
Rob Landley3b890392006-05-04 20:56:43 +00001013
Sören Tempelbfd87382021-11-21 12:24:45 +01001014 prompt = ""; /* no prompt by default */
Sören Tempel9173c9c2021-12-29 16:15:50 +01001015 getopt32(argv, OPTION_STR, &prompt);
Sören Tempelbfd87382021-11-21 12:24:45 +01001016 argv += optind;
1017
1018 if (argv[0]) {
1019 fileName = xstrdup(argv[0]);
Denys Vlasenko5ea50692017-07-27 11:17:15 +02001020 if (!readLines(fileName, 1)) {
1021 return EXIT_SUCCESS;
Rob Landley3b890392006-05-04 20:56:43 +00001022 }
Denys Vlasenko5ea50692017-07-27 11:17:15 +02001023 dirty = FALSE;
Rob Landley3b890392006-05-04 20:56:43 +00001024 }
1025
Denys Vlasenko5ea50692017-07-27 11:17:15 +02001026 doCommands();
1027 return EXIT_SUCCESS;
Rob Landley3b890392006-05-04 20:56:43 +00001028}