blob: 05797692c2e64139e6d4ee6c038ca53ce9bacec4 [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 Vlasenko4eed2c62017-07-18 22:01:24 +020010//config: bool "ed (25 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
Denys Vlasenko5ea50692017-07-27 11:17:15 +020021//usage:#define ed_trivial_usage "[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;
51 LINE lines;
52 smallint dirty;
53 int marks[26];
54};
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 { \
Denys Vlasenko9de2e5a2016-04-21 18:38:51 +020068 setup_common_bufsiz(); \
Denis Vlasenko574f2f42008-02-27 18:41:59 +000069 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
Denis Vlasenkod9391b12007-09-25 11:55:57 +000070} while (0)
Rob Landley3b890392006-05-04 20:56:43 +000071
Denis Vlasenkod9391b12007-09-25 11:55:57 +000072static int bad_nums(int num1, int num2, const char *for_what)
73{
74 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
75 bb_error_msg("bad line range for %s", for_what);
76 return 1;
77 }
78 return 0;
79}
80
Denys Vlasenko5ea50692017-07-27 11:17:15 +020081/*
82 * Return a pointer to the specified line number.
83 */
84static LINE *findLine(int num)
Rob Landley3b890392006-05-04 20:56:43 +000085{
Denys Vlasenko5ea50692017-07-27 11:17:15 +020086 LINE *lp;
87 int lnum;
Denis Vlasenkod9391b12007-09-25 11:55:57 +000088
Denys Vlasenko5ea50692017-07-27 11:17:15 +020089 if ((num < 1) || (num > lastNum)) {
90 bb_error_msg("line number %d does not exist", num);
91 return NULL;
Rob Landley3b890392006-05-04 20:56:43 +000092 }
93
Denys Vlasenko5ea50692017-07-27 11:17:15 +020094 if (curNum <= 0) {
95 curNum = 1;
96 curLine = lines.next;
97 }
98
99 if (num == curNum)
100 return curLine;
101
102 lp = curLine;
103 lnum = curNum;
104 if (num < (curNum / 2)) {
105 lp = lines.next;
106 lnum = 1;
107 } else if (num > ((curNum + lastNum) / 2)) {
108 lp = lines.prev;
109 lnum = lastNum;
110 }
111
112 while (lnum < num) {
113 lp = lp->next;
114 lnum++;
115 }
116
117 while (lnum > num) {
118 lp = lp->prev;
119 lnum--;
120 }
121 return lp;
122}
123
124/*
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.
127 */
128static int findString(const LINE *lp, const char *str, int len, int offset)
129{
130 int left;
131 const char *cp, *ncp;
132
133 cp = &lp->data[offset];
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200134 left = lp->len - offset - len;
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200135
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200136 while (left >= 0) {
137 ncp = memchr(cp, str[0], left + 1);
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200138 if (ncp == NULL)
139 return -1;
140 left -= (ncp - cp);
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200141 cp = ncp;
142 if (memcmp(cp, str, len) == 0)
143 return (cp - lp->data);
144 cp++;
145 left--;
146 }
147
148 return -1;
149}
150
151/*
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.
157 */
158static NOINLINE int searchLines(const char *str, int num1, int num2)
159{
160 const LINE *lp;
161 int len;
162
163 if (bad_nums(num1, num2, "search"))
164 return 0;
165
166 if (*str == '\0') {
167 if (searchString[0] == '\0') {
168 bb_error_msg("no previous search string");
169 return 0;
170 }
171 str = searchString;
172 }
173
174 if (str != searchString)
175 strcpy(searchString, str);
176
177 len = strlen(str);
178
179 lp = findLine(num1);
180 if (lp == NULL)
181 return 0;
182
183 while (num1 <= num2) {
184 if (findString(lp, str, len, 0) >= 0)
185 return num1;
186 num1++;
187 lp = lp->next;
188 }
189
190 bb_error_msg("can't find string \"%s\"", str);
191 return 0;
192}
193
194/*
195 * Parse a line number argument if it is present. This is a sum
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200196 * 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).
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200199 * Returns NULL if there was a parsing error, with a message output.
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200200 * Whether there was a number is returned indirectly, as is the number.
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200201 */
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200202static const char* getNum(const char *cp, smallint *retHaveNum, int *retNum)
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200203{
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200204 char *endStr, str[USERSIZE];
205 int value, num;
206 smallint haveNum, minus;
207
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200208 value = 0;
209 haveNum = FALSE;
210 minus = 0;
211
212 while (TRUE) {
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200213 cp = skip_whitespace(cp);
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200214
215 switch (*cp) {
216 case '.':
217 haveNum = TRUE;
218 num = curNum;
219 cp++;
220 break;
221
222 case '$':
223 haveNum = TRUE;
224 num = lastNum;
225 cp++;
226 break;
227
228 case '\'':
229 cp++;
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200230 if ((unsigned)(*cp - 'a') >= 26) {
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200231 bb_error_msg("bad mark name");
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200232 return NULL;
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200233 }
234 haveNum = TRUE;
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200235 num = marks[(unsigned)(*cp - 'a')];
236 cp++;
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200237 break;
238
239 case '/':
240 strcpy(str, ++cp);
241 endStr = strchr(str, '/');
242 if (endStr) {
243 *endStr++ = '\0';
244 cp += (endStr - str);
245 } else
246 cp = "";
247 num = searchLines(str, curNum, lastNum);
248 if (num == 0)
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200249 return NULL;
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200250 haveNum = TRUE;
251 break;
252
253 default:
254 if (!isdigit(*cp)) {
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200255 *retHaveNum = haveNum;
256 *retNum = value;
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200257 return cp;
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200258 }
259 num = 0;
260 while (isdigit(*cp))
261 num = num * 10 + *cp++ - '0';
262 haveNum = TRUE;
263 break;
264 }
265
266 value += (minus ? -num : num);
267
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200268 cp = skip_whitespace(cp);
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200269
270 switch (*cp) {
271 case '-':
272 minus = 1;
273 cp++;
274 break;
275
276 case '+':
277 minus = 0;
278 cp++;
279 break;
280
281 default:
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200282 *retHaveNum = haveNum;
283 *retNum = value;
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200284 return cp;
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200285 }
286 }
287}
288
289/*
290 * Set the current line number.
291 * Returns TRUE if successful.
292 */
293static int setCurNum(int num)
294{
295 LINE *lp;
296
297 lp = findLine(num);
298 if (lp == NULL)
299 return FALSE;
300 curNum = num;
301 curLine = lp;
302 return TRUE;
303}
304
305/*
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.
311 */
312static int insertLine(int num, const char *data, int len)
313{
314 LINE *newLp, *lp;
315
316 if ((num < 1) || (num > lastNum + 1)) {
317 bb_error_msg("inserting at bad line number");
318 return FALSE;
319 }
320
321 newLp = xmalloc(sizeof(LINE) + len - 1);
322
323 memcpy(newLp->data, data, len);
324 newLp->len = len;
325
326 if (num > lastNum)
327 lp = &lines;
328 else {
329 lp = findLine(num);
330 if (lp == NULL) {
331 free((char *) newLp);
332 return FALSE;
333 }
334 }
335
336 newLp->next = lp;
337 newLp->prev = lp->prev;
338 lp->prev->next = newLp;
339 lp->prev = newLp;
340
341 lastNum++;
342 dirty = TRUE;
343 return setCurNum(num);
344}
345
346/*
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.
351 */
352static void addLines(int num)
353{
354 int len;
355 char buf[USERSIZE + 1];
356
357 while (1) {
358 /* Returns:
359 * -1 on read errors or EOF, or on bare Ctrl-D.
360 * 0 on ctrl-C,
361 * >0 length of input string, including terminating '\n'
362 */
Denys Vlasenko84ea60e2017-08-02 17:27:28 +0200363 len = read_line_input(NULL, "", buf, sizeof(buf));
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200364 if (len <= 0) {
365 /* Previously, ctrl-C was exiting to shell.
366 * Now we exit to ed prompt. Is in important? */
367 return;
368 }
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200369 if (buf[0] == '.' && buf[1] == '\n' && buf[2] == '\0')
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200370 return;
371 if (!insertLine(num++, buf, len))
372 return;
373 }
374}
375
376/*
377 * Read lines from a file at the specified line number.
378 * Returns TRUE if the file was successfully read.
379 */
380static int readLines(const char *file, int num)
381{
382 int fd, cc;
383 int len, lineCount, charCount;
384 char *cp;
385
386 if ((num < 1) || (num > lastNum + 1)) {
387 bb_error_msg("bad line for read");
388 return FALSE;
389 }
390
391 fd = open(file, 0);
392 if (fd < 0) {
393 bb_simple_perror_msg(file);
394 return FALSE;
395 }
396
397 bufPtr = bufBase;
398 bufUsed = 0;
399 lineCount = 0;
400 charCount = 0;
401 cc = 0;
402
403 printf("\"%s\", ", file);
404 fflush_all();
405
406 do {
407 cp = memchr(bufPtr, '\n', bufUsed);
408
409 if (cp) {
410 len = (cp - bufPtr) + 1;
411 if (!insertLine(num, bufPtr, len)) {
412 close(fd);
413 return FALSE;
414 }
415 bufPtr += len;
416 bufUsed -= len;
417 charCount += len;
418 lineCount++;
419 num++;
420 continue;
421 }
422
423 if (bufPtr != bufBase) {
424 memcpy(bufBase, bufPtr, bufUsed);
425 bufPtr = bufBase + bufUsed;
426 }
427
428 if (bufUsed >= bufSize) {
429 len = (bufSize * 3) / 2;
430 cp = xrealloc(bufBase, len);
431 bufBase = cp;
432 bufPtr = bufBase + bufUsed;
433 bufSize = len;
434 }
435
436 cc = safe_read(fd, bufPtr, bufSize - bufUsed);
437 bufUsed += cc;
438 bufPtr = bufBase;
439 } while (cc > 0);
440
441 if (cc < 0) {
442 bb_simple_perror_msg(file);
443 close(fd);
444 return FALSE;
445 }
446
447 if (bufUsed) {
448 if (!insertLine(num, bufPtr, bufUsed)) {
449 close(fd);
450 return -1;
451 }
452 lineCount++;
453 charCount += bufUsed;
454 }
455
456 close(fd);
457
458 printf("%d lines%s, %d chars\n", lineCount,
459 (bufUsed ? " (incomplete)" : ""), charCount);
460
461 return TRUE;
462}
463
464/*
465 * Write the specified lines out to the specified file.
466 * Returns TRUE if successful, or FALSE on an error with a message output.
467 */
468static int writeLines(const char *file, int num1, int num2)
469{
470 LINE *lp;
471 int fd, lineCount, charCount;
472
473 if (bad_nums(num1, num2, "write"))
474 return FALSE;
475
476 lineCount = 0;
477 charCount = 0;
478
479 fd = creat(file, 0666);
480 if (fd < 0) {
481 bb_simple_perror_msg(file);
482 return FALSE;
483 }
484
485 printf("\"%s\", ", file);
486 fflush_all();
487
488 lp = findLine(num1);
489 if (lp == NULL) {
490 close(fd);
491 return FALSE;
492 }
493
494 while (num1++ <= num2) {
495 if (full_write(fd, lp->data, lp->len) != lp->len) {
496 bb_simple_perror_msg(file);
497 close(fd);
498 return FALSE;
499 }
500 charCount += lp->len;
501 lineCount++;
502 lp = lp->next;
503 }
504
505 if (close(fd) < 0) {
506 bb_simple_perror_msg(file);
507 return FALSE;
508 }
509
510 printf("%d lines, %d chars\n", lineCount, charCount);
511 return TRUE;
512}
513
514/*
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.
519 */
520static int printLines(int num1, int num2, int expandFlag)
521{
522 const LINE *lp;
523 const char *cp;
524 int ch, count;
525
526 if (bad_nums(num1, num2, "print"))
527 return FALSE;
528
529 lp = findLine(num1);
530 if (lp == NULL)
531 return FALSE;
532
533 while (num1 <= num2) {
534 if (!expandFlag) {
535 write(STDOUT_FILENO, lp->data, lp->len);
536 setCurNum(num1++);
537 lp = lp->next;
538 continue;
539 }
540
541 /*
542 * Show control characters and characters with the
543 * high bit set specially.
544 */
545 cp = lp->data;
546 count = lp->len;
547
548 if ((count > 0) && (cp[count - 1] == '\n'))
549 count--;
550
551 while (count-- > 0) {
552 ch = (unsigned char) *cp++;
553 fputc_printable(ch | PRINTABLE_META, stdout);
554 }
555
556 fputs("$\n", stdout);
557
558 setCurNum(num1++);
559 lp = lp->next;
560 }
561
562 return TRUE;
563}
564
565/*
566 * Delete lines from the given range.
567 */
568static void deleteLines(int num1, int num2)
569{
570 LINE *lp, *nlp, *plp;
571 int count;
572
573 if (bad_nums(num1, num2, "delete"))
574 return;
575
576 lp = findLine(num1);
577 if (lp == NULL)
578 return;
579
580 if ((curNum >= num1) && (curNum <= num2)) {
581 if (num2 < lastNum)
582 setCurNum(num2 + 1);
583 else if (num1 > 1)
584 setCurNum(num1 - 1);
585 else
586 curNum = 0;
587 }
588
589 count = num2 - num1 + 1;
590 if (curNum > num2)
591 curNum -= count;
592 lastNum -= count;
593
594 while (count-- > 0) {
595 nlp = lp->next;
596 plp = lp->prev;
597 plp->next = nlp;
598 nlp->prev = plp;
599 free(lp);
600 lp = nlp;
601 }
602
603 dirty = TRUE;
604}
605
606/*
607 * Do the substitute command.
608 * The current line is set to the last substitution done.
609 */
610static void subCommand(const char *cmd, int num1, int num2)
611{
612 char *cp, *oldStr, *newStr, buf[USERSIZE];
613 int delim, oldLen, newLen, deltaLen, offset;
614 LINE *lp, *nlp;
615 int globalFlag, printFlag, didSub, needPrint;
616
617 if (bad_nums(num1, num2, "substitute"))
618 return;
619
620 globalFlag = FALSE;
621 printFlag = FALSE;
622 didSub = FALSE;
623 needPrint = FALSE;
624
625 /*
626 * Copy the command so we can modify it.
627 */
628 strcpy(buf, cmd);
629 cp = buf;
630
631 if (isblank(*cp) || (*cp == '\0')) {
632 bb_error_msg("bad delimiter for substitute");
633 return;
634 }
635
636 delim = *cp++;
637 oldStr = cp;
638
639 cp = strchr(cp, delim);
640 if (cp == NULL) {
641 bb_error_msg("missing 2nd delimiter for substitute");
642 return;
643 }
644
645 *cp++ = '\0';
646
647 newStr = cp;
648 cp = strchr(cp, delim);
649
650 if (cp)
651 *cp++ = '\0';
652 else
653 cp = (char*)"";
654
655 while (*cp) switch (*cp++) {
656 case 'g':
657 globalFlag = TRUE;
658 break;
659 case 'p':
660 printFlag = TRUE;
661 break;
662 default:
663 bb_error_msg("unknown option for substitute");
664 return;
665 }
666
667 if (*oldStr == '\0') {
668 if (searchString[0] == '\0') {
669 bb_error_msg("no previous search string");
670 return;
671 }
672 oldStr = searchString;
673 }
674
675 if (oldStr != searchString)
676 strcpy(searchString, oldStr);
677
678 lp = findLine(num1);
679 if (lp == NULL)
680 return;
681
682 oldLen = strlen(oldStr);
683 newLen = strlen(newStr);
684 deltaLen = newLen - oldLen;
685 offset = 0;
686 nlp = NULL;
687
688 while (num1 <= num2) {
689 offset = findString(lp, oldStr, oldLen, offset);
690
691 if (offset < 0) {
692 if (needPrint) {
693 printLines(num1, num1, FALSE);
694 needPrint = FALSE;
695 }
696 offset = 0;
697 lp = lp->next;
698 num1++;
699 continue;
700 }
701
702 needPrint = printFlag;
703 didSub = TRUE;
704 dirty = TRUE;
705
706 /*
707 * If the replacement string is the same size or shorter
708 * than the old string, then the substitution is easy.
709 */
710 if (deltaLen <= 0) {
711 memcpy(&lp->data[offset], newStr, newLen);
712 if (deltaLen) {
713 memcpy(&lp->data[offset + newLen],
714 &lp->data[offset + oldLen],
715 lp->len - offset - oldLen);
716
717 lp->len += deltaLen;
718 }
719 offset += newLen;
720 if (globalFlag)
721 continue;
722 if (needPrint) {
723 printLines(num1, num1, FALSE);
724 needPrint = FALSE;
725 }
726 lp = lp->next;
727 num1++;
728 continue;
729 }
730
731 /*
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.
735 */
736 nlp = xmalloc(sizeof(LINE) + lp->len + deltaLen);
737
738 nlp->len = lp->len + deltaLen;
739
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);
745
746 nlp->next = lp->next;
747 nlp->prev = lp->prev;
748 nlp->prev->next = nlp;
749 nlp->next->prev = nlp;
750
751 if (curLine == lp)
752 curLine = nlp;
753
754 free(lp);
755 lp = nlp;
756
757 offset += newLen;
758
759 if (globalFlag)
760 continue;
761
762 if (needPrint) {
763 printLines(num1, num1, FALSE);
764 needPrint = FALSE;
765 }
766
767 lp = lp->next;
768 num1++;
769 }
770
771 if (!didSub)
772 bb_error_msg("no substitutions found for \"%s\"", oldStr);
Rob Landley3b890392006-05-04 20:56:43 +0000773}
774
775/*
776 * Read commands until we are told to stop.
777 */
778static void doCommands(void)
779{
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000780 while (TRUE) {
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200781 char buf[USERSIZE];
782 const char *cp;
783 int len;
784 int n, num1, num2;
785 smallint h, have1, have2;
786
Denis Vlasenkod9391b12007-09-25 11:55:57 +0000787 /* Returns:
788 * -1 on read errors or EOF, or on bare Ctrl-D.
789 * 0 on ctrl-C,
790 * >0 length of input string, including terminating '\n'
791 */
Denys Vlasenko84ea60e2017-08-02 17:27:28 +0200792 len = read_line_input(NULL, ": ", buf, sizeof(buf));
Denis Vlasenkod9391b12007-09-25 11:55:57 +0000793 if (len <= 0)
Rob Landley3b890392006-05-04 20:56:43 +0000794 return;
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200795 while (len && isspace(buf[--len]))
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200796 buf[len] = '\0';
Rob Landley3b890392006-05-04 20:56:43 +0000797
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000798 if ((curNum == 0) && (lastNum > 0)) {
Rob Landley3b890392006-05-04 20:56:43 +0000799 curNum = 1;
800 curLine = lines.next;
801 }
802
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200803 have1 = FALSE;
804 have2 = FALSE;
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200805 /* Don't pass &haveN, &numN to getNum() since this forces
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200806 * compiler to keep them on stack, not in registers,
807 * which is usually quite suboptimal.
808 * Using intermediate variables shrinks code by ~150 bytes.
809 */
810 cp = getNum(skip_whitespace(buf), &h, &n);
811 if (!cp)
Rob Landley3b890392006-05-04 20:56:43 +0000812 continue;
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200813 have1 = h;
814 num1 = n;
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200815 cp = skip_whitespace(cp);
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000816 if (*cp == ',') {
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200817 cp = getNum(cp + 1, &h, &n);
818 if (!cp)
Rob Landley3b890392006-05-04 20:56:43 +0000819 continue;
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200820 num2 = n;
Rob Landley3b890392006-05-04 20:56:43 +0000821 if (!have1)
822 num1 = 1;
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200823 if (!h)
Rob Landley3b890392006-05-04 20:56:43 +0000824 num2 = lastNum;
Rob Landley3b890392006-05-04 20:56:43 +0000825 have1 = TRUE;
826 have2 = TRUE;
827 }
Rob Landley3b890392006-05-04 20:56:43 +0000828 if (!have1)
829 num1 = curNum;
Rob Landley3b890392006-05-04 20:56:43 +0000830 if (!have2)
831 num2 = num1;
832
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000833 switch (*cp++) {
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000834 case 'a':
835 addLines(num1 + 1);
836 break;
Rob Landley3b890392006-05-04 20:56:43 +0000837
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000838 case 'c':
839 deleteLines(num1, num2);
840 addLines(num1);
841 break;
Rob Landley3b890392006-05-04 20:56:43 +0000842
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000843 case 'd':
844 deleteLines(num1, num2);
845 break;
Rob Landley3b890392006-05-04 20:56:43 +0000846
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000847 case 'f':
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200848 if (*cp != '\0' && *cp != ' ') {
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000849 bb_error_msg("bad file command");
Rob Landley3b890392006-05-04 20:56:43 +0000850 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000851 }
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200852 cp = skip_whitespace(cp);
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000853 if (*cp == '\0') {
854 if (fileName)
855 printf("\"%s\"\n", fileName);
856 else
Denys Vlasenkod60752f2015-10-07 22:42:45 +0200857 puts("No file name");
Rob Landley3b890392006-05-04 20:56:43 +0000858 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000859 }
860 free(fileName);
861 fileName = xstrdup(cp);
862 break;
Rob Landley3b890392006-05-04 20:56:43 +0000863
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000864 case 'i':
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200865 if (!have1 && lastNum == 0)
866 num1 = 1;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000867 addLines(num1);
868 break;
869
870 case 'k':
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200871 cp = skip_whitespace(cp);
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200872 if ((unsigned)(*cp - 'a') >= 26 || cp[1]) {
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000873 bb_error_msg("bad mark name");
Rob Landley3b890392006-05-04 20:56:43 +0000874 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000875 }
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200876 marks[(unsigned)(*cp - 'a')] = num2;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000877 break;
Rob Landley3b890392006-05-04 20:56:43 +0000878
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000879 case 'l':
880 printLines(num1, num2, TRUE);
881 break;
882
883 case 'p':
884 printLines(num1, num2, FALSE);
885 break;
886
887 case 'q':
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200888 cp = skip_whitespace(cp);
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000889 if (have1 || *cp) {
890 bb_error_msg("bad quit command");
Rob Landley3b890392006-05-04 20:56:43 +0000891 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000892 }
893 if (!dirty)
894 return;
Denys Vlasenko84ea60e2017-08-02 17:27:28 +0200895 len = read_line_input(NULL, "Really quit? ", buf, 16);
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000896 /* read error/EOF - no way to continue */
897 if (len < 0)
898 return;
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200899 cp = skip_whitespace(buf);
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000900 if ((*cp | 0x20) == 'y') /* Y or y */
901 return;
902 break;
Rob Landley3b890392006-05-04 20:56:43 +0000903
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000904 case 'r':
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200905 if (*cp != '\0' && *cp != ' ') {
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000906 bb_error_msg("bad read command");
Rob Landley3b890392006-05-04 20:56:43 +0000907 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000908 }
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200909 cp = skip_whitespace(cp);
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000910 if (*cp == '\0') {
911 bb_error_msg("no file name");
Rob Landley3b890392006-05-04 20:56:43 +0000912 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000913 }
914 if (!have1)
915 num1 = lastNum;
916 if (readLines(cp, num1 + 1))
Rob Landley3b890392006-05-04 20:56:43 +0000917 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000918 if (fileName == NULL)
919 fileName = xstrdup(cp);
920 break;
Rob Landley3b890392006-05-04 20:56:43 +0000921
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000922 case 's':
923 subCommand(cp, num1, num2);
924 break;
925
926 case 'w':
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200927 if (*cp != '\0' && *cp != ' ') {
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000928 bb_error_msg("bad write command");
Rob Landley3b890392006-05-04 20:56:43 +0000929 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000930 }
Denys Vlasenko7d2f33d2017-07-27 11:58:25 +0200931 cp = skip_whitespace(cp);
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200932 if (*cp == '\0') {
933 cp = fileName;
934 if (!cp) {
935 bb_error_msg("no file name specified");
936 break;
937 }
938 }
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000939 if (!have1) {
940 num1 = 1;
941 num2 = lastNum;
Denys Vlasenko6bdcee82017-07-27 12:34:56 +0200942 dirty = FALSE;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000943 }
944 writeLines(cp, num1, num2);
945 break;
Rob Landley3b890392006-05-04 20:56:43 +0000946
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000947 case 'z':
948 switch (*cp) {
Rob Landley3b890392006-05-04 20:56:43 +0000949 case '-':
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000950 printLines(curNum - 21, curNum, FALSE);
Rob Landley3b890392006-05-04 20:56:43 +0000951 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000952 case '.':
953 printLines(curNum - 11, curNum + 10, FALSE);
Rob Landley3b890392006-05-04 20:56:43 +0000954 break;
Rob Landley3b890392006-05-04 20:56:43 +0000955 default:
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000956 printLines(curNum, curNum + 21, FALSE);
Rob Landley3b890392006-05-04 20:56:43 +0000957 break;
Denis Vlasenkoc4523c22008-03-28 02:24:59 +0000958 }
959 break;
960
961 case '.':
962 if (have1) {
963 bb_error_msg("no arguments allowed");
964 break;
965 }
966 printLines(curNum, curNum, FALSE);
967 break;
968
969 case '-':
970 if (setCurNum(curNum - 1))
971 printLines(curNum, curNum, FALSE);
972 break;
973
974 case '=':
975 printf("%d\n", num1);
976 break;
977 case '\0':
978 if (have1) {
979 printLines(num2, num2, FALSE);
980 break;
981 }
982 if (setCurNum(curNum + 1))
983 printLines(curNum, curNum, FALSE);
984 break;
985
986 default:
987 bb_error_msg("unimplemented command");
988 break;
Rob Landley3b890392006-05-04 20:56:43 +0000989 }
990 }
991}
992
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200993int ed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
994int ed_main(int argc UNUSED_PARAM, char **argv)
Rob Landley3b890392006-05-04 20:56:43 +0000995{
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200996 INIT_G();
Rob Landley3b890392006-05-04 20:56:43 +0000997
Denys Vlasenko5ea50692017-07-27 11:17:15 +0200998 bufSize = INITBUF_SIZE;
999 bufBase = xmalloc(bufSize);
Rob Landley3b890392006-05-04 20:56:43 +00001000 bufPtr = bufBase;
Denys Vlasenko5ea50692017-07-27 11:17:15 +02001001 lines.next = &lines;
1002 lines.prev = &lines;
Rob Landley3b890392006-05-04 20:56:43 +00001003
Denys Vlasenko5ea50692017-07-27 11:17:15 +02001004 if (argv[1]) {
1005 fileName = xstrdup(argv[1]);
1006 if (!readLines(fileName, 1)) {
1007 return EXIT_SUCCESS;
Rob Landley3b890392006-05-04 20:56:43 +00001008 }
Denys Vlasenko5ea50692017-07-27 11:17:15 +02001009 if (lastNum)
1010 setCurNum(1);
1011 dirty = FALSE;
Rob Landley3b890392006-05-04 20:56:43 +00001012 }
1013
Denys Vlasenko5ea50692017-07-27 11:17:15 +02001014 doCommands();
1015 return EXIT_SUCCESS;
Rob Landley3b890392006-05-04 20:56:43 +00001016}