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