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