ash,hush: testcase for "exit" without arguments in a trap
[oweals/busybox.git] / editors / ed.c
1 /* vi: set sw=4 ts=4: */
2 /*
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  */
9 //config:config ED
10 //config:       bool "ed (21 kb)"
11 //config:       default y
12 //config:       help
13 //config:       The original 1970's Unix text editor, from the days of teletypes.
14 //config:       Small, simple, evil. Part of SUSv3. If you're not already using
15 //config:       this, you don't need it.
16
17 //kbuild:lib-$(CONFIG_ED) += ed.o
18
19 //applet:IF_ED(APPLET(ed, BB_DIR_BIN, BB_SUID_DROP))
20
21 //usage:#define ed_trivial_usage "[FILE]"
22 //usage:#define ed_full_usage ""
23
24 #include "libbb.h"
25 #include "common_bufsiz.h"
26
27 typedef struct LINE {
28         struct LINE *next;
29         struct LINE *prev;
30         int len;
31         char data[1];
32 } LINE;
33
34 #define searchString bb_common_bufsiz1
35
36 enum {
37         USERSIZE = COMMON_BUFSIZE > 1024 ? 1024
38                  : COMMON_BUFSIZE - 1, /* max line length typed in by user */
39         INITBUF_SIZE = 1024, /* initial buffer size */
40 };
41
42 struct 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 { \
68         setup_common_bufsiz(); \
69         SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
70 } while (0)
71
72 static 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
81 /*
82  * Return a pointer to the specified line number.
83  */
84 static LINE *findLine(int num)
85 {
86         LINE *lp;
87         int lnum;
88
89         if ((num < 1) || (num > lastNum)) {
90                 bb_error_msg("line number %d does not exist", num);
91                 return NULL;
92         }
93
94         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  */
128 static 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];
134         left = lp->len - offset - len;
135
136         while (left >= 0) {
137                 ncp = memchr(cp, str[0], left + 1);
138                 if (ncp == NULL)
139                         return -1;
140                 left -= (ncp - cp);
141                 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  */
158 static 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_simple_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
196  * or difference of numbers, ".", "$", "'c", or a search string.
197  * Returns pointer which stopped the scan if successful
198  * (whether or not there was a number).
199  * Returns NULL if there was a parsing error, with a message output.
200  * Whether there was a number is returned indirectly, as is the number.
201  */
202 static const char* getNum(const char *cp, smallint *retHaveNum, int *retNum)
203 {
204         char *endStr, str[USERSIZE];
205         int value, num;
206         smallint haveNum, minus;
207
208         value = 0;
209         haveNum = FALSE;
210         minus = 0;
211
212         while (TRUE) {
213                 cp = skip_whitespace(cp);
214
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++;
230                                 if ((unsigned)(*cp - 'a') >= 26) {
231                                         bb_simple_error_msg("bad mark name");
232                                         return NULL;
233                                 }
234                                 haveNum = TRUE;
235                                 num = marks[(unsigned)(*cp - 'a')];
236                                 cp++;
237                                 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)
249                                         return NULL;
250                                 haveNum = TRUE;
251                                 break;
252
253                         default:
254                                 if (!isdigit(*cp)) {
255                                         *retHaveNum = haveNum;
256                                         *retNum = value;
257                                         return cp;
258                                 }
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
268                 cp = skip_whitespace(cp);
269
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:
282                                 *retHaveNum = haveNum;
283                                 *retNum = value;
284                                 return cp;
285                 }
286         }
287 }
288
289 /*
290  * Set the current line number.
291  * Returns TRUE if successful.
292  */
293 static 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  */
312 static int insertLine(int num, const char *data, int len)
313 {
314         LINE *newLp, *lp;
315
316         if ((num < 1) || (num > lastNum + 1)) {
317                 bb_simple_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  */
352 static 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                  */
363                 len = read_line_input(NULL, "", buf, sizeof(buf));
364                 if (len <= 0) {
365                         /* Previously, ctrl-C was exiting to shell.
366                          * Now we exit to ed prompt. Is in important? */
367                         return;
368                 }
369                 if (buf[0] == '.' && buf[1] == '\n' && buf[2] == '\0')
370                         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  */
380 static 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_simple_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  */
468 static 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  */
520 static 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  */
568 static 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  */
610 static 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_simple_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_simple_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_simple_error_msg("unknown option for substitute");
664                         return;
665         }
666
667         if (*oldStr == '\0') {
668                 if (searchString[0] == '\0') {
669                         bb_simple_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);
773 }
774
775 /*
776  * Read commands until we are told to stop.
777  */
778 static void doCommands(void)
779 {
780         while (TRUE) {
781                 char buf[USERSIZE];
782                 const char *cp;
783                 int len;
784                 int n, num1, num2;
785                 smallint h, have1, have2;
786
787                 /* 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                  */
792                 len = read_line_input(NULL, ": ", buf, sizeof(buf));
793                 if (len <= 0)
794                         return;
795                 while (len && isspace(buf[--len]))
796                         buf[len] = '\0';
797
798                 if ((curNum == 0) && (lastNum > 0)) {
799                         curNum = 1;
800                         curLine = lines.next;
801                 }
802
803                 have1 = FALSE;
804                 have2 = FALSE;
805                 /* Don't pass &haveN, &numN to getNum() since this forces
806                  * compiler to keep them on stack, not in registers,
807                  * which is usually quite suboptimal.
808                  * Using intermediate variables shrinks code by ~150 bytes.
809                  */
810                 cp = getNum(skip_whitespace(buf), &h, &n);
811                 if (!cp)
812                         continue;
813                 have1 = h;
814                 num1 = n;
815                 cp = skip_whitespace(cp);
816                 if (*cp == ',') {
817                         cp = getNum(cp + 1, &h, &n);
818                         if (!cp)
819                                 continue;
820                         num2 = n;
821                         if (!have1)
822                                 num1 = 1;
823                         if (!h)
824                                 num2 = lastNum;
825                         have1 = TRUE;
826                         have2 = TRUE;
827                 }
828                 if (!have1)
829                         num1 = curNum;
830                 if (!have2)
831                         num2 = num1;
832
833                 switch (*cp++) {
834                 case 'a':
835                         addLines(num1 + 1);
836                         break;
837
838                 case 'c':
839                         deleteLines(num1, num2);
840                         addLines(num1);
841                         break;
842
843                 case 'd':
844                         deleteLines(num1, num2);
845                         break;
846
847                 case 'f':
848                         if (*cp != '\0' && *cp != ' ') {
849                                 bb_simple_error_msg("bad file command");
850                                 break;
851                         }
852                         cp = skip_whitespace(cp);
853                         if (*cp == '\0') {
854                                 if (fileName)
855                                         printf("\"%s\"\n", fileName);
856                                 else
857                                         puts("No file name");
858                                 break;
859                         }
860                         free(fileName);
861                         fileName = xstrdup(cp);
862                         break;
863
864                 case 'i':
865                         if (!have1 && lastNum == 0)
866                                 num1 = 1;
867                         addLines(num1);
868                         break;
869
870                 case 'k':
871                         cp = skip_whitespace(cp);
872                         if ((unsigned)(*cp - 'a') >= 26 || cp[1]) {
873                                 bb_simple_error_msg("bad mark name");
874                                 break;
875                         }
876                         marks[(unsigned)(*cp - 'a')] = num2;
877                         break;
878
879                 case 'l':
880                         printLines(num1, num2, TRUE);
881                         break;
882
883                 case 'p':
884                         printLines(num1, num2, FALSE);
885                         break;
886
887                 case 'q':
888                         cp = skip_whitespace(cp);
889                         if (have1 || *cp) {
890                                 bb_simple_error_msg("bad quit command");
891                                 break;
892                         }
893                         if (!dirty)
894                                 return;
895                         len = read_line_input(NULL, "Really quit? ", buf, 16);
896                         /* read error/EOF - no way to continue */
897                         if (len < 0)
898                                 return;
899                         cp = skip_whitespace(buf);
900                         if ((*cp | 0x20) == 'y') /* Y or y */
901                                 return;
902                         break;
903
904                 case 'r':
905                         if (*cp != '\0' && *cp != ' ') {
906                                 bb_simple_error_msg("bad read command");
907                                 break;
908                         }
909                         cp = skip_whitespace(cp);
910                         if (*cp == '\0') {
911                                 bb_simple_error_msg("no file name");
912                                 break;
913                         }
914                         if (!have1)
915                                 num1 = lastNum;
916                         if (readLines(cp, num1 + 1))
917                                 break;
918                         if (fileName == NULL)
919                                 fileName = xstrdup(cp);
920                         break;
921
922                 case 's':
923                         subCommand(cp, num1, num2);
924                         break;
925
926                 case 'w':
927                         if (*cp != '\0' && *cp != ' ') {
928                                 bb_simple_error_msg("bad write command");
929                                 break;
930                         }
931                         cp = skip_whitespace(cp);
932                         if (*cp == '\0') {
933                                 cp = fileName;
934                                 if (!cp) {
935                                         bb_simple_error_msg("no file name specified");
936                                         break;
937                                 }
938                         }
939                         if (!have1) {
940                                 num1 = 1;
941                                 num2 = lastNum;
942                                 dirty = FALSE;
943                         }
944                         writeLines(cp, num1, num2);
945                         break;
946
947                 case 'z':
948                         switch (*cp) {
949                         case '-':
950                                 printLines(curNum - 21, curNum, FALSE);
951                                 break;
952                         case '.':
953                                 printLines(curNum - 11, curNum + 10, FALSE);
954                                 break;
955                         default:
956                                 printLines(curNum, curNum + 21, FALSE);
957                                 break;
958                         }
959                         break;
960
961                 case '.':
962                         if (have1) {
963                                 bb_simple_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_simple_error_msg("unimplemented command");
988                         break;
989                 }
990         }
991 }
992
993 int ed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
994 int ed_main(int argc UNUSED_PARAM, char **argv)
995 {
996         INIT_G();
997
998         bufSize = INITBUF_SIZE;
999         bufBase = xmalloc(bufSize);
1000         bufPtr = bufBase;
1001         lines.next = &lines;
1002         lines.prev = &lines;
1003
1004         if (argv[1]) {
1005                 fileName = xstrdup(argv[1]);
1006                 if (!readLines(fileName, 1)) {
1007                         return EXIT_SUCCESS;
1008                 }
1009                 if (lastNum)
1010                         setCurNum(1);
1011                 dirty = FALSE;
1012         }
1013
1014         doCommands();
1015         return EXIT_SUCCESS;
1016 }