1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24#include "libbb.h"
25#include "common_bufsiz.h"
26
27typedef 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
36enum {
37 USERSIZE = COMMON_BUFSIZE > 1024 ? 1024
38 : COMMON_BUFSIZE - 1,
39 INITBUF_SIZE = 1024,
40};
41
42struct 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
72static 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
83
84static 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
126
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];
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
153
154
155
156
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_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
196
197
198
199
200
201
202static 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
291
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
307
308
309
310
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_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
348
349
350
351
352static void addLines(int num)
353{
354 int len;
355 char buf[USERSIZE + 1];
356
357 while (1) {
358
359
360
361
362
363 len = read_line_input(NULL, "", buf, sizeof(buf));
364 if (len <= 0) {
365
366
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
378
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_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
466
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
516
517
518
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
543
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_stdout("$\n");
557
558 setCurNum(num1++);
559 lp = lp->next;
560 }
561
562 return TRUE;
563}
564
565
566
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
608
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
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
708
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
733
734
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
777
778static 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
788
789
790
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
806
807
808
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
897 if (len < 0)
898 return;
899 cp = skip_whitespace(buf);
900 if ((*cp | 0x20) == '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
993int ed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
994int 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}
1017