gzip: fix a case where tar xzf fails (we use uninitialized fd)
[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
10 #include "libbb.h"
11
12 typedef struct LINE {
13         struct LINE *next;
14         struct LINE *prev;
15         int len;
16         char data[1];
17 } LINE;
18
19
20 #define searchString bb_common_bufsiz1
21
22 enum {
23         USERSIZE = sizeof(searchString) > 1024 ? 1024
24                  : sizeof(searchString) - 1, /* max line length typed in by user */
25         INITBUF_SIZE = 1024, /* initial buffer size */
26 };
27
28 struct globals {
29         int curNum;
30         int lastNum;
31         int bufUsed;
32         int bufSize;
33         LINE *curLine;
34         char *bufBase;
35         char *bufPtr;
36         char *fileName;
37         LINE lines;
38         smallint dirty;
39         int marks[26];
40 };
41 #define G (*ptr_to_globals)
42 #define curLine            (G.curLine           )
43 #define bufBase            (G.bufBase           )
44 #define bufPtr             (G.bufPtr            )
45 #define fileName           (G.fileName          )
46 #define curNum             (G.curNum            )
47 #define lastNum            (G.lastNum           )
48 #define bufUsed            (G.bufUsed           )
49 #define bufSize            (G.bufSize           )
50 #define dirty              (G.dirty             )
51 #define lines              (G.lines             )
52 #define marks              (G.marks             )
53 #define INIT_G() do { \
54         PTR_TO_GLOBALS = xzalloc(sizeof(G)); \
55 } while (0)
56
57
58 static void doCommands(void);
59 static void subCommand(const char *cmd, int num1, int num2);
60 static int getNum(const char **retcp, smallint *retHaveNum, int *retNum);
61 static int setCurNum(int num);
62 static void addLines(int num);
63 static int insertLine(int num, const char *data, int len);
64 static void deleteLines(int num1, int num2);
65 static int printLines(int num1, int num2, int expandFlag);
66 static int writeLines(const char *file, int num1, int num2);
67 static int readLines(const char *file, int num);
68 static int searchLines(const char *str, int num1, int num2);
69 static LINE *findLine(int num);
70 static int findString(const LINE *lp, const char * str, int len, int offset);
71
72
73 static int bad_nums(int num1, int num2, const char *for_what)
74 {
75         if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
76                 bb_error_msg("bad line range for %s", for_what);
77                 return 1;
78         }
79         return 0;
80 }
81
82
83 static char *skip_blank(const char *cp)
84 {
85         while (isblank(*cp))
86                 cp++;
87         return (char *)cp;
88 }
89
90
91 int ed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
92 int ed_main(int argc, char **argv)
93 {
94         INIT_G();
95
96         bufSize = INITBUF_SIZE;
97         bufBase = xmalloc(bufSize);
98         bufPtr = bufBase;
99         lines.next = &lines;
100         lines.prev = &lines;
101
102         if (argc > 1) {
103                 fileName = xstrdup(argv[1]);
104                 if (!readLines(fileName, 1)) {
105                         return EXIT_SUCCESS;
106                 }
107                 if (lastNum)
108                         setCurNum(1);
109                 dirty = FALSE;
110         }
111
112         doCommands();
113         return EXIT_SUCCESS;
114 }
115
116 /*
117  * Read commands until we are told to stop.
118  */
119 static void doCommands(void)
120 {
121         const char *cp;
122         char *endbuf, *newname, buf[USERSIZE];
123         int len, num1, num2;
124         smallint have1, have2;
125
126         while (TRUE) {
127                 /* Returns:
128                  * -1 on read errors or EOF, or on bare Ctrl-D.
129                  * 0  on ctrl-C,
130                  * >0 length of input string, including terminating '\n'
131                  */
132                 len = read_line_input(": ", buf, sizeof(buf), NULL);
133                 if (len <= 0)
134                         return;
135                 endbuf = &buf[len - 1];
136                 while ((endbuf > buf) && isblank(endbuf[-1]))
137                         endbuf--;
138                 *endbuf = '\0';
139
140                 cp = skip_blank(buf);
141                 have1 = FALSE;
142                 have2 = FALSE;
143
144                 if ((curNum == 0) && (lastNum > 0)) {
145                         curNum = 1;
146                         curLine = lines.next;
147                 }
148
149                 if (!getNum(&cp, &have1, &num1))
150                         continue;
151
152                 cp = skip_blank(cp);
153
154                 if (*cp == ',') {
155                         cp++;
156                         if (!getNum(&cp, &have2, &num2))
157                                 continue;
158                         if (!have1)
159                                 num1 = 1;
160                         if (!have2)
161                                 num2 = lastNum;
162                         have1 = TRUE;
163                         have2 = TRUE;
164                 }
165                 if (!have1)
166                         num1 = curNum;
167                 if (!have2)
168                         num2 = num1;
169
170                 switch (*cp++) {
171                         case 'a':
172                                 addLines(num1 + 1);
173                                 break;
174
175                         case 'c':
176                                 deleteLines(num1, num2);
177                                 addLines(num1);
178                                 break;
179
180                         case 'd':
181                                 deleteLines(num1, num2);
182                                 break;
183
184                         case 'f':
185                                 if (*cp && !isblank(*cp)) {
186                                         bb_error_msg("bad file command");
187                                         break;
188                                 }
189                                 cp = skip_blank(cp);
190                                 if (*cp == '\0') {
191                                         if (fileName)
192                                                 printf("\"%s\"\n", fileName);
193                                         else
194                                                 printf("No file name\n");
195                                         break;
196                                 }
197                                 newname = strdup(cp);
198                                 if (newname == NULL) {
199                                         bb_error_msg("no memory for file name");
200                                         break;
201                                 }
202                                 free(fileName);
203                                 fileName = newname;
204                                 break;
205
206                         case 'i':
207                                 addLines(num1);
208                                 break;
209
210                         case 'k':
211                                 cp = skip_blank(cp);
212                                 if ((*cp < 'a') || (*cp > 'z') || cp[1]) {
213                                         bb_error_msg("bad mark name");
214                                         break;
215                                 }
216                                 marks[*cp - 'a'] = num2;
217                                 break;
218
219                         case 'l':
220                                 printLines(num1, num2, TRUE);
221                                 break;
222
223                         case 'p':
224                                 printLines(num1, num2, FALSE);
225                                 break;
226
227                         case 'q':
228                                 cp = skip_blank(cp);
229                                 if (have1 || *cp) {
230                                         bb_error_msg("bad quit command");
231                                         break;
232                                 }
233                                 if (!dirty)
234                                         return;
235                                 len = read_line_input("Really quit? ", buf, 16, NULL);
236                                 /* read error/EOF - no way to continue */
237                                 if (len < 0)
238                                         return;
239                                 cp = skip_blank(buf);
240                                 if ((*cp | 0x20) == 'y') /* Y or y */
241                                         return;
242                                 break;
243
244                         case 'r':
245                                 if (*cp && !isblank(*cp)) {
246                                         bb_error_msg("bad read command");
247                                         break;
248                                 }
249                                 cp = skip_blank(cp);
250                                 if (*cp == '\0') {
251                                         bb_error_msg("no file name");
252                                         break;
253                                 }
254                                 if (!have1)
255                                         num1 = lastNum;
256                                 if (readLines(cp, num1 + 1))
257                                         break;
258                                 if (fileName == NULL)
259                                         fileName = strdup(cp);
260                                 break;
261
262                         case 's':
263                                 subCommand(cp, num1, num2);
264                                 break;
265
266                         case 'w':
267                                 if (*cp && !isblank(*cp)) {
268                                         bb_error_msg("bad write command");
269                                         break;
270                                 }
271                                 cp = skip_blank(cp);
272                                 if (!have1) {
273                                         num1 = 1;
274                                         num2 = lastNum;
275                                 }
276                                 if (*cp == '\0')
277                                         cp = fileName;
278                                 if (cp == NULL) {
279                                         bb_error_msg("no file name specified");
280                                         break;
281                                 }
282                                 writeLines(cp, num1, num2);
283                                 break;
284
285                         case 'z':
286                                 switch (*cp) {
287                                 case '-':
288                                         printLines(curNum - 21, curNum, FALSE);
289                                         break;
290                                 case '.':
291                                         printLines(curNum - 11, curNum + 10, FALSE);
292                                         break;
293                                 default:
294                                         printLines(curNum, curNum + 21, FALSE);
295                                         break;
296                                 }
297                                 break;
298
299                         case '.':
300                                 if (have1) {
301                                         bb_error_msg("no arguments allowed");
302                                         break;
303                                 }
304                                 printLines(curNum, curNum, FALSE);
305                                 break;
306
307                         case '-':
308                                 if (setCurNum(curNum - 1))
309                                         printLines(curNum, curNum, FALSE);
310                                 break;
311
312                         case '=':
313                                 printf("%d\n", num1);
314                                 break;
315                         case '\0':
316                                 if (have1) {
317                                         printLines(num2, num2, FALSE);
318                                         break;
319                                 }
320                                 if (setCurNum(curNum + 1))
321                                         printLines(curNum, curNum, FALSE);
322                                 break;
323
324                         default:
325                                 bb_error_msg("unimplemented command");
326                                 break;
327                 }
328         }
329 }
330
331
332 /*
333  * Do the substitute command.
334  * The current line is set to the last substitution done.
335  */
336 static void subCommand(const char *cmd, int num1, int num2)
337 {
338         char *cp, *oldStr, *newStr, buf[USERSIZE];
339         int delim, oldLen, newLen, deltaLen, offset;
340         LINE *lp, *nlp;
341         int globalFlag, printFlag, didSub, needPrint;
342
343         if (bad_nums(num1, num2, "substitute"))
344                 return;
345
346         globalFlag = FALSE;
347         printFlag = FALSE;
348         didSub = FALSE;
349         needPrint = FALSE;
350
351         /*
352          * Copy the command so we can modify it.
353          */
354         strcpy(buf, cmd);
355         cp = buf;
356
357         if (isblank(*cp) || (*cp == '\0')) {
358                 bb_error_msg("bad delimiter for substitute");
359                 return;
360         }
361
362         delim = *cp++;
363         oldStr = cp;
364
365         cp = strchr(cp, delim);
366         if (cp == NULL) {
367                 bb_error_msg("missing 2nd delimiter for substitute");
368                 return;
369         }
370
371         *cp++ = '\0';
372
373         newStr = cp;
374         cp = strchr(cp, delim);
375
376         if (cp)
377                 *cp++ = '\0';
378         else
379                 cp = (char*)"";
380
381         while (*cp) switch (*cp++) {
382                 case 'g':
383                         globalFlag = TRUE;
384                         break;
385                 case 'p':
386                         printFlag = TRUE;
387                         break;
388                 default:
389                         bb_error_msg("unknown option for substitute");
390                         return;
391         }
392
393         if (*oldStr == '\0') {
394                 if (searchString[0] == '\0') {
395                         bb_error_msg("no previous search string");
396                         return;
397                 }
398                 oldStr = searchString;
399         }
400
401         if (oldStr != searchString)
402                 strcpy(searchString, oldStr);
403
404         lp = findLine(num1);
405         if (lp == NULL)
406                 return;
407
408         oldLen = strlen(oldStr);
409         newLen = strlen(newStr);
410         deltaLen = newLen - oldLen;
411         offset = 0;
412         nlp = NULL;
413
414         while (num1 <= num2) {
415                 offset = findString(lp, oldStr, oldLen, offset);
416
417                 if (offset < 0) {
418                         if (needPrint) {
419                                 printLines(num1, num1, FALSE);
420                                 needPrint = FALSE;
421                         }
422                         offset = 0;
423                         lp = lp->next;
424                         num1++;
425                         continue;
426                 }
427
428                 needPrint = printFlag;
429                 didSub = TRUE;
430                 dirty = TRUE;
431
432                 /*
433                  * If the replacement string is the same size or shorter
434                  * than the old string, then the substitution is easy.
435                  */
436                 if (deltaLen <= 0) {
437                         memcpy(&lp->data[offset], newStr, newLen);
438                         if (deltaLen) {
439                                 memcpy(&lp->data[offset + newLen],
440                                         &lp->data[offset + oldLen],
441                                         lp->len - offset - oldLen);
442
443                                 lp->len += deltaLen;
444                         }
445                         offset += newLen;
446                         if (globalFlag)
447                                 continue;
448                         if (needPrint) {
449                                 printLines(num1, num1, FALSE);
450                                 needPrint = FALSE;
451                         }
452                         lp = lp->next;
453                         num1++;
454                         continue;
455                 }
456
457                 /*
458                  * The new string is larger, so allocate a new line
459                  * structure and use that.  Link it in in place of
460                  * the old line structure.
461                  */
462                 nlp = malloc(sizeof(LINE) + lp->len + deltaLen);
463                 if (nlp == NULL) {
464                         bb_error_msg("cannot get memory for line");
465                         return;
466                 }
467
468                 nlp->len = lp->len + deltaLen;
469
470                 memcpy(nlp->data, lp->data, offset);
471                 memcpy(&nlp->data[offset], newStr, newLen);
472                 memcpy(&nlp->data[offset + newLen],
473                         &lp->data[offset + oldLen],
474                         lp->len - offset - oldLen);
475
476                 nlp->next = lp->next;
477                 nlp->prev = lp->prev;
478                 nlp->prev->next = nlp;
479                 nlp->next->prev = nlp;
480
481                 if (curLine == lp)
482                         curLine = nlp;
483
484                 free(lp);
485                 lp = nlp;
486
487                 offset += newLen;
488
489                 if (globalFlag)
490                         continue;
491
492                 if (needPrint) {
493                         printLines(num1, num1, FALSE);
494                         needPrint = FALSE;
495                 }
496
497                 lp = lp->next;
498                 num1++;
499         }
500
501         if (!didSub)
502                 bb_error_msg("no substitutions found for \"%s\"", oldStr);
503 }
504
505
506 /*
507  * Search a line for the specified string starting at the specified
508  * offset in the line.  Returns the offset of the found string, or -1.
509  */
510 static int findString(const LINE *lp, const char *str, int len, int offset)
511 {
512         int left;
513         const char *cp, *ncp;
514
515         cp = &lp->data[offset];
516         left = lp->len - offset;
517
518         while (left >= len) {
519                 ncp = memchr(cp, *str, left);
520                 if (ncp == NULL)
521                         return -1;
522                 left -= (ncp - cp);
523                 if (left < len)
524                         return -1;
525                 cp = ncp;
526                 if (memcmp(cp, str, len) == 0)
527                         return (cp - lp->data);
528                 cp++;
529                 left--;
530         }
531
532         return -1;
533 }
534
535
536 /*
537  * Add lines which are typed in by the user.
538  * The lines are inserted just before the specified line number.
539  * The lines are terminated by a line containing a single dot (ugly!),
540  * or by an end of file.
541  */
542 static void addLines(int num)
543 {
544         int len;
545         char buf[USERSIZE + 1];
546
547         while (1) {
548                 /* Returns:
549                  * -1 on read errors or EOF, or on bare Ctrl-D.
550                  * 0  on ctrl-C,
551                  * >0 length of input string, including terminating '\n'
552                  */
553                 len = read_line_input("", buf, sizeof(buf), NULL);
554                 if (len <= 0) {
555                         /* Previously, ctrl-C was exiting to shell.
556                          * Now we exit to ed prompt. Is in important? */
557                         return;
558                 }
559                 if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0'))
560                         return;
561                 if (!insertLine(num++, buf, len))
562                         return;
563         }
564 }
565
566
567 /*
568  * Parse a line number argument if it is present.  This is a sum
569  * or difference of numbers, '.', '$', 'x, or a search string.
570  * Returns TRUE if successful (whether or not there was a number).
571  * Returns FALSE if there was a parsing error, with a message output.
572  * Whether there was a number is returned indirectly, as is the number.
573  * The character pointer which stopped the scan is also returned.
574  */
575 static int getNum(const char **retcp, smallint *retHaveNum, int *retNum)
576 {
577         const char *cp;
578         char *endStr, str[USERSIZE];
579         int value, num;
580         smallint haveNum, minus;
581
582         cp = *retcp;
583         value = 0;
584         haveNum = FALSE;
585         minus = 0;
586
587         while (TRUE) {
588                 cp = skip_blank(cp);
589
590                 switch (*cp) {
591                         case '.':
592                                 haveNum = TRUE;
593                                 num = curNum;
594                                 cp++;
595                                 break;
596
597                         case '$':
598                                 haveNum = TRUE;
599                                 num = lastNum;
600                                 cp++;
601                                 break;
602
603                         case '\'':
604                                 cp++;
605                                 if ((*cp < 'a') || (*cp > 'z')) {
606                                         bb_error_msg("bad mark name");
607                                         return FALSE;
608                                 }
609                                 haveNum = TRUE;
610                                 num = marks[*cp++ - 'a'];
611                                 break;
612
613                         case '/':
614                                 strcpy(str, ++cp);
615                                 endStr = strchr(str, '/');
616                                 if (endStr) {
617                                         *endStr++ = '\0';
618                                         cp += (endStr - str);
619                                 } else
620                                         cp = "";
621                                 num = searchLines(str, curNum, lastNum);
622                                 if (num == 0)
623                                         return FALSE;
624                                 haveNum = TRUE;
625                                 break;
626
627                         default:
628                                 if (!isdigit(*cp)) {
629                                         *retcp = cp;
630                                         *retHaveNum = haveNum;
631                                         *retNum = value;
632                                         return TRUE;
633                                 }
634                                 num = 0;
635                                 while (isdigit(*cp))
636                                         num = num * 10 + *cp++ - '0';
637                                 haveNum = TRUE;
638                                 break;
639                 }
640
641                 value += (minus ? -num : num);
642
643                 cp = skip_blank(cp);
644
645                 switch (*cp) {
646                         case '-':
647                                 minus = 1;
648                                 cp++;
649                                 break;
650
651                         case '+':
652                                 minus = 0;
653                                 cp++;
654                                 break;
655
656                         default:
657                                 *retcp = cp;
658                                 *retHaveNum = haveNum;
659                                 *retNum = value;
660                                 return TRUE;
661                 }
662         }
663 }
664
665
666 /*
667  * Read lines from a file at the specified line number.
668  * Returns TRUE if the file was successfully read.
669  */
670 static int readLines(const char *file, int num)
671 {
672         int fd, cc;
673         int len, lineCount, charCount;
674         char *cp;
675
676         if ((num < 1) || (num > lastNum + 1)) {
677                 bb_error_msg("bad line for read");
678                 return FALSE;
679         }
680
681         fd = open(file, 0);
682         if (fd < 0) {
683                 perror(file);
684                 return FALSE;
685         }
686
687         bufPtr = bufBase;
688         bufUsed = 0;
689         lineCount = 0;
690         charCount = 0;
691         cc = 0;
692
693         printf("\"%s\", ", file);
694         fflush(stdout);
695
696         do {
697                 cp = memchr(bufPtr, '\n', bufUsed);
698
699                 if (cp) {
700                         len = (cp - bufPtr) + 1;
701                         if (!insertLine(num, bufPtr, len)) {
702                                 close(fd);
703                                 return FALSE;
704                         }
705                         bufPtr += len;
706                         bufUsed -= len;
707                         charCount += len;
708                         lineCount++;
709                         num++;
710                         continue;
711                 }
712
713                 if (bufPtr != bufBase) {
714                         memcpy(bufBase, bufPtr, bufUsed);
715                         bufPtr = bufBase + bufUsed;
716                 }
717
718                 if (bufUsed >= bufSize) {
719                         len = (bufSize * 3) / 2;
720                         cp = realloc(bufBase, len);
721                         if (cp == NULL) {
722                                 bb_error_msg("no memory for buffer");
723                                 close(fd);
724                                 return FALSE;
725                         }
726                         bufBase = cp;
727                         bufPtr = bufBase + bufUsed;
728                         bufSize = len;
729                 }
730
731                 cc = safe_read(fd, bufPtr, bufSize - bufUsed);
732                 bufUsed += cc;
733                 bufPtr = bufBase;
734
735         } while (cc > 0);
736
737         if (cc < 0) {
738                 perror(file);
739                 close(fd);
740                 return FALSE;
741         }
742
743         if (bufUsed) {
744                 if (!insertLine(num, bufPtr, bufUsed)) {
745                         close(fd);
746                         return -1;
747                 }
748                 lineCount++;
749                 charCount += bufUsed;
750         }
751
752         close(fd);
753
754         printf("%d lines%s, %d chars\n", lineCount,
755                 (bufUsed ? " (incomplete)" : ""), charCount);
756
757         return TRUE;
758 }
759
760
761 /*
762  * Write the specified lines out to the specified file.
763  * Returns TRUE if successful, or FALSE on an error with a message output.
764  */
765 static int writeLines(const char *file, int num1, int num2)
766 {
767         LINE *lp;
768         int fd, lineCount, charCount;
769
770         if (bad_nums(num1, num2, "write"))
771                 return FALSE;
772
773         lineCount = 0;
774         charCount = 0;
775
776         fd = creat(file, 0666);
777         if (fd < 0) {
778                 perror(file);
779                 return FALSE;
780         }
781
782         printf("\"%s\", ", file);
783         fflush(stdout);
784
785         lp = findLine(num1);
786         if (lp == NULL) {
787                 close(fd);
788                 return FALSE;
789         }
790
791         while (num1++ <= num2) {
792                 if (full_write(fd, lp->data, lp->len) != lp->len) {
793                         perror(file);
794                         close(fd);
795                         return FALSE;
796                 }
797                 charCount += lp->len;
798                 lineCount++;
799                 lp = lp->next;
800         }
801
802         if (close(fd) < 0) {
803                 perror(file);
804                 return FALSE;
805         }
806
807         printf("%d lines, %d chars\n", lineCount, charCount);
808         return TRUE;
809 }
810
811
812 /*
813  * Print lines in a specified range.
814  * The last line printed becomes the current line.
815  * If expandFlag is TRUE, then the line is printed specially to
816  * show magic characters.
817  */
818 static int printLines(int num1, int num2, int expandFlag)
819 {
820         const LINE *lp;
821         const char *cp;
822         int ch, count;
823
824         if (bad_nums(num1, num2, "print"))
825                 return FALSE;
826
827         lp = findLine(num1);
828         if (lp == NULL)
829                 return FALSE;
830
831         while (num1 <= num2) {
832                 if (!expandFlag) {
833                         write(1, lp->data, lp->len);
834                         setCurNum(num1++);
835                         lp = lp->next;
836                         continue;
837                 }
838
839                 /*
840                  * Show control characters and characters with the
841                  * high bit set specially.
842                  */
843                 cp = lp->data;
844                 count = lp->len;
845
846                 if ((count > 0) && (cp[count - 1] == '\n'))
847                         count--;
848
849                 while (count-- > 0) {
850                         ch = *cp++;
851                         if (ch & 0x80) {
852                                 fputs("M-", stdout);
853                                 ch &= 0x7f;
854                         }
855                         if (ch < ' ') {
856                                 bb_putchar('^');
857                                 ch += '@';
858                         }
859                         if (ch == 0x7f) {
860                                 bb_putchar('^');
861                                 ch = '?';
862                         }
863                         bb_putchar(ch);
864                 }
865
866                 fputs("$\n", stdout);
867
868                 setCurNum(num1++);
869                 lp = lp->next;
870         }
871
872         return TRUE;
873 }
874
875
876 /*
877  * Insert a new line with the specified text.
878  * The line is inserted so as to become the specified line,
879  * thus pushing any existing and further lines down one.
880  * The inserted line is also set to become the current line.
881  * Returns TRUE if successful.
882  */
883 static int insertLine(int num, const char *data, int len)
884 {
885         LINE *newLp, *lp;
886
887         if ((num < 1) || (num > lastNum + 1)) {
888                 bb_error_msg("inserting at bad line number");
889                 return FALSE;
890         }
891
892         newLp = malloc(sizeof(LINE) + len - 1);
893         if (newLp == NULL) {
894                 bb_error_msg("failed to allocate memory for line");
895                 return FALSE;
896         }
897
898         memcpy(newLp->data, data, len);
899         newLp->len = len;
900
901         if (num > lastNum)
902                 lp = &lines;
903         else {
904                 lp = findLine(num);
905                 if (lp == NULL) {
906                         free((char *) newLp);
907                         return FALSE;
908                 }
909         }
910
911         newLp->next = lp;
912         newLp->prev = lp->prev;
913         lp->prev->next = newLp;
914         lp->prev = newLp;
915
916         lastNum++;
917         dirty = TRUE;
918         return setCurNum(num);
919 }
920
921
922 /*
923  * Delete lines from the given range.
924  */
925 static void deleteLines(int num1, int num2)
926 {
927         LINE *lp, *nlp, *plp;
928         int count;
929
930         if (bad_nums(num1, num2, "delete"))
931                 return;
932
933         lp = findLine(num1);
934         if (lp == NULL)
935                 return;
936
937         if ((curNum >= num1) && (curNum <= num2)) {
938                 if (num2 < lastNum)
939                         setCurNum(num2 + 1);
940                 else if (num1 > 1)
941                         setCurNum(num1 - 1);
942                 else
943                         curNum = 0;
944         }
945
946         count = num2 - num1 + 1;
947         if (curNum > num2)
948                 curNum -= count;
949         lastNum -= count;
950
951         while (count-- > 0) {
952                 nlp = lp->next;
953                 plp = lp->prev;
954                 plp->next = nlp;
955                 nlp->prev = plp;
956                 free(lp);
957                 lp = nlp;
958         }
959
960         dirty = TRUE;
961 }
962
963
964 /*
965  * Search for a line which contains the specified string.
966  * If the string is "", then the previously searched for string
967  * is used.  The currently searched for string is saved for future use.
968  * Returns the line number which matches, or 0 if there was no match
969  * with an error printed.
970  */
971 static int searchLines(const char *str, int num1, int num2)
972 {
973         const LINE *lp;
974         int len;
975
976         if (bad_nums(num1, num2, "search"))
977                 return 0;
978
979         if (*str == '\0') {
980                 if (searchString[0] == '\0') {
981                         bb_error_msg("no previous search string");
982                         return 0;
983                 }
984                 str = searchString;
985         }
986
987         if (str != searchString)
988                 strcpy(searchString, str);
989
990         len = strlen(str);
991
992         lp = findLine(num1);
993         if (lp == NULL)
994                 return 0;
995
996         while (num1 <= num2) {
997                 if (findString(lp, str, len, 0) >= 0)
998                         return num1;
999                 num1++;
1000                 lp = lp->next;
1001         }
1002
1003         bb_error_msg("cannot find string \"%s\"", str);
1004         return 0;
1005 }
1006
1007
1008 /*
1009  * Return a pointer to the specified line number.
1010  */
1011 static LINE *findLine(int num)
1012 {
1013         LINE *lp;
1014         int lnum;
1015
1016         if ((num < 1) || (num > lastNum)) {
1017                 bb_error_msg("line number %d does not exist", num);
1018                 return NULL;
1019         }
1020
1021         if (curNum <= 0) {
1022                 curNum = 1;
1023                 curLine = lines.next;
1024         }
1025
1026         if (num == curNum)
1027                 return curLine;
1028
1029         lp = curLine;
1030         lnum = curNum;
1031         if (num < (curNum / 2)) {
1032                 lp = lines.next;
1033                 lnum = 1;
1034         } else if (num > ((curNum + lastNum) / 2)) {
1035                 lp = lines.prev;
1036                 lnum = lastNum;
1037         }
1038
1039         while (lnum < num) {
1040                 lp = lp->next;
1041                 lnum++;
1042         }
1043
1044         while (lnum > num) {
1045                 lp = lp->prev;
1046                 lnum--;
1047         }
1048         return lp;
1049 }
1050
1051
1052 /*
1053  * Set the current line number.
1054  * Returns TRUE if successful.
1055  */
1056 static int setCurNum(int num)
1057 {
1058         LINE *lp;
1059
1060         lp = findLine(num);
1061         if (lp == NULL)
1062                 return FALSE;
1063         curNum = num;
1064         curLine = lp;
1065         return TRUE;
1066 }