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