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