Contents of /tags/mkinitrd-6_1_2/busybox/editors/ed.c
Parent Directory | Revision Log
Revision 844 -
(show annotations)
(download)
Mon May 4 17:23:09 2009 UTC (15 years, 4 months ago) by niro
File MIME type: text/plain
File size: 19415 byte(s)
Mon May 4 17:23:09 2009 UTC (15 years, 4 months ago) by niro
File MIME type: text/plain
File size: 19415 byte(s)
tagged 'mkinitrd-6_1_2'
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 = malloc(sizeof(LINE) + lp->len + deltaLen); |
458 | if (nlp == NULL) { |
459 | bb_error_msg("cannot get memory for line"); |
460 | return; |
461 | } |
462 | |
463 | nlp->len = lp->len + deltaLen; |
464 | |
465 | memcpy(nlp->data, lp->data, offset); |
466 | memcpy(&nlp->data[offset], newStr, newLen); |
467 | memcpy(&nlp->data[offset + newLen], |
468 | &lp->data[offset + oldLen], |
469 | lp->len - offset - oldLen); |
470 | |
471 | nlp->next = lp->next; |
472 | nlp->prev = lp->prev; |
473 | nlp->prev->next = nlp; |
474 | nlp->next->prev = nlp; |
475 | |
476 | if (curLine == lp) |
477 | curLine = nlp; |
478 | |
479 | free(lp); |
480 | lp = nlp; |
481 | |
482 | offset += newLen; |
483 | |
484 | if (globalFlag) |
485 | continue; |
486 | |
487 | if (needPrint) { |
488 | printLines(num1, num1, FALSE); |
489 | needPrint = FALSE; |
490 | } |
491 | |
492 | lp = lp->next; |
493 | num1++; |
494 | } |
495 | |
496 | if (!didSub) |
497 | bb_error_msg("no substitutions found for \"%s\"", oldStr); |
498 | } |
499 | |
500 | |
501 | /* |
502 | * Search a line for the specified string starting at the specified |
503 | * offset in the line. Returns the offset of the found string, or -1. |
504 | */ |
505 | static int findString(const LINE *lp, const char *str, int len, int offset) |
506 | { |
507 | int left; |
508 | const char *cp, *ncp; |
509 | |
510 | cp = &lp->data[offset]; |
511 | left = lp->len - offset; |
512 | |
513 | while (left >= len) { |
514 | ncp = memchr(cp, *str, left); |
515 | if (ncp == NULL) |
516 | return -1; |
517 | left -= (ncp - cp); |
518 | if (left < len) |
519 | return -1; |
520 | cp = ncp; |
521 | if (memcmp(cp, str, len) == 0) |
522 | return (cp - lp->data); |
523 | cp++; |
524 | left--; |
525 | } |
526 | |
527 | return -1; |
528 | } |
529 | |
530 | |
531 | /* |
532 | * Add lines which are typed in by the user. |
533 | * The lines are inserted just before the specified line number. |
534 | * The lines are terminated by a line containing a single dot (ugly!), |
535 | * or by an end of file. |
536 | */ |
537 | static void addLines(int num) |
538 | { |
539 | int len; |
540 | char buf[USERSIZE + 1]; |
541 | |
542 | while (1) { |
543 | /* Returns: |
544 | * -1 on read errors or EOF, or on bare Ctrl-D. |
545 | * 0 on ctrl-C, |
546 | * >0 length of input string, including terminating '\n' |
547 | */ |
548 | len = read_line_input("", buf, sizeof(buf), NULL); |
549 | if (len <= 0) { |
550 | /* Previously, ctrl-C was exiting to shell. |
551 | * Now we exit to ed prompt. Is in important? */ |
552 | return; |
553 | } |
554 | if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0')) |
555 | return; |
556 | if (!insertLine(num++, buf, len)) |
557 | return; |
558 | } |
559 | } |
560 | |
561 | |
562 | /* |
563 | * Parse a line number argument if it is present. This is a sum |
564 | * or difference of numbers, '.', '$', 'x, or a search string. |
565 | * Returns TRUE if successful (whether or not there was a number). |
566 | * Returns FALSE if there was a parsing error, with a message output. |
567 | * Whether there was a number is returned indirectly, as is the number. |
568 | * The character pointer which stopped the scan is also returned. |
569 | */ |
570 | static int getNum(const char **retcp, smallint *retHaveNum, int *retNum) |
571 | { |
572 | const char *cp; |
573 | char *endStr, str[USERSIZE]; |
574 | int value, num; |
575 | smallint haveNum, minus; |
576 | |
577 | cp = *retcp; |
578 | value = 0; |
579 | haveNum = FALSE; |
580 | minus = 0; |
581 | |
582 | while (TRUE) { |
583 | cp = skip_blank(cp); |
584 | |
585 | switch (*cp) { |
586 | case '.': |
587 | haveNum = TRUE; |
588 | num = curNum; |
589 | cp++; |
590 | break; |
591 | |
592 | case '$': |
593 | haveNum = TRUE; |
594 | num = lastNum; |
595 | cp++; |
596 | break; |
597 | |
598 | case '\'': |
599 | cp++; |
600 | if ((*cp < 'a') || (*cp > 'z')) { |
601 | bb_error_msg("bad mark name"); |
602 | return FALSE; |
603 | } |
604 | haveNum = TRUE; |
605 | num = marks[*cp++ - 'a']; |
606 | break; |
607 | |
608 | case '/': |
609 | strcpy(str, ++cp); |
610 | endStr = strchr(str, '/'); |
611 | if (endStr) { |
612 | *endStr++ = '\0'; |
613 | cp += (endStr - str); |
614 | } else |
615 | cp = ""; |
616 | num = searchLines(str, curNum, lastNum); |
617 | if (num == 0) |
618 | return FALSE; |
619 | haveNum = TRUE; |
620 | break; |
621 | |
622 | default: |
623 | if (!isdigit(*cp)) { |
624 | *retcp = cp; |
625 | *retHaveNum = haveNum; |
626 | *retNum = value; |
627 | return TRUE; |
628 | } |
629 | num = 0; |
630 | while (isdigit(*cp)) |
631 | num = num * 10 + *cp++ - '0'; |
632 | haveNum = TRUE; |
633 | break; |
634 | } |
635 | |
636 | value += (minus ? -num : num); |
637 | |
638 | cp = skip_blank(cp); |
639 | |
640 | switch (*cp) { |
641 | case '-': |
642 | minus = 1; |
643 | cp++; |
644 | break; |
645 | |
646 | case '+': |
647 | minus = 0; |
648 | cp++; |
649 | break; |
650 | |
651 | default: |
652 | *retcp = cp; |
653 | *retHaveNum = haveNum; |
654 | *retNum = value; |
655 | return TRUE; |
656 | } |
657 | } |
658 | } |
659 | |
660 | |
661 | /* |
662 | * Read lines from a file at the specified line number. |
663 | * Returns TRUE if the file was successfully read. |
664 | */ |
665 | static int readLines(const char *file, int num) |
666 | { |
667 | int fd, cc; |
668 | int len, lineCount, charCount; |
669 | char *cp; |
670 | |
671 | if ((num < 1) || (num > lastNum + 1)) { |
672 | bb_error_msg("bad line for read"); |
673 | return FALSE; |
674 | } |
675 | |
676 | fd = open(file, 0); |
677 | if (fd < 0) { |
678 | perror(file); |
679 | return FALSE; |
680 | } |
681 | |
682 | bufPtr = bufBase; |
683 | bufUsed = 0; |
684 | lineCount = 0; |
685 | charCount = 0; |
686 | cc = 0; |
687 | |
688 | printf("\"%s\", ", file); |
689 | fflush(stdout); |
690 | |
691 | do { |
692 | cp = memchr(bufPtr, '\n', bufUsed); |
693 | |
694 | if (cp) { |
695 | len = (cp - bufPtr) + 1; |
696 | if (!insertLine(num, bufPtr, len)) { |
697 | close(fd); |
698 | return FALSE; |
699 | } |
700 | bufPtr += len; |
701 | bufUsed -= len; |
702 | charCount += len; |
703 | lineCount++; |
704 | num++; |
705 | continue; |
706 | } |
707 | |
708 | if (bufPtr != bufBase) { |
709 | memcpy(bufBase, bufPtr, bufUsed); |
710 | bufPtr = bufBase + bufUsed; |
711 | } |
712 | |
713 | if (bufUsed >= bufSize) { |
714 | len = (bufSize * 3) / 2; |
715 | cp = realloc(bufBase, len); |
716 | if (cp == NULL) { |
717 | bb_error_msg("no memory for buffer"); |
718 | close(fd); |
719 | return FALSE; |
720 | } |
721 | bufBase = cp; |
722 | bufPtr = bufBase + bufUsed; |
723 | bufSize = len; |
724 | } |
725 | |
726 | cc = safe_read(fd, bufPtr, bufSize - bufUsed); |
727 | bufUsed += cc; |
728 | bufPtr = bufBase; |
729 | |
730 | } while (cc > 0); |
731 | |
732 | if (cc < 0) { |
733 | perror(file); |
734 | close(fd); |
735 | return FALSE; |
736 | } |
737 | |
738 | if (bufUsed) { |
739 | if (!insertLine(num, bufPtr, bufUsed)) { |
740 | close(fd); |
741 | return -1; |
742 | } |
743 | lineCount++; |
744 | charCount += bufUsed; |
745 | } |
746 | |
747 | close(fd); |
748 | |
749 | printf("%d lines%s, %d chars\n", lineCount, |
750 | (bufUsed ? " (incomplete)" : ""), charCount); |
751 | |
752 | return TRUE; |
753 | } |
754 | |
755 | |
756 | /* |
757 | * Write the specified lines out to the specified file. |
758 | * Returns TRUE if successful, or FALSE on an error with a message output. |
759 | */ |
760 | static int writeLines(const char *file, int num1, int num2) |
761 | { |
762 | LINE *lp; |
763 | int fd, lineCount, charCount; |
764 | |
765 | if (bad_nums(num1, num2, "write")) |
766 | return FALSE; |
767 | |
768 | lineCount = 0; |
769 | charCount = 0; |
770 | |
771 | fd = creat(file, 0666); |
772 | if (fd < 0) { |
773 | perror(file); |
774 | return FALSE; |
775 | } |
776 | |
777 | printf("\"%s\", ", file); |
778 | fflush(stdout); |
779 | |
780 | lp = findLine(num1); |
781 | if (lp == NULL) { |
782 | close(fd); |
783 | return FALSE; |
784 | } |
785 | |
786 | while (num1++ <= num2) { |
787 | if (full_write(fd, lp->data, lp->len) != lp->len) { |
788 | perror(file); |
789 | close(fd); |
790 | return FALSE; |
791 | } |
792 | charCount += lp->len; |
793 | lineCount++; |
794 | lp = lp->next; |
795 | } |
796 | |
797 | if (close(fd) < 0) { |
798 | perror(file); |
799 | return FALSE; |
800 | } |
801 | |
802 | printf("%d lines, %d chars\n", lineCount, charCount); |
803 | return TRUE; |
804 | } |
805 | |
806 | |
807 | /* |
808 | * Print lines in a specified range. |
809 | * The last line printed becomes the current line. |
810 | * If expandFlag is TRUE, then the line is printed specially to |
811 | * show magic characters. |
812 | */ |
813 | static int printLines(int num1, int num2, int expandFlag) |
814 | { |
815 | const LINE *lp; |
816 | const char *cp; |
817 | int ch, count; |
818 | |
819 | if (bad_nums(num1, num2, "print")) |
820 | return FALSE; |
821 | |
822 | lp = findLine(num1); |
823 | if (lp == NULL) |
824 | return FALSE; |
825 | |
826 | while (num1 <= num2) { |
827 | if (!expandFlag) { |
828 | write(STDOUT_FILENO, lp->data, lp->len); |
829 | setCurNum(num1++); |
830 | lp = lp->next; |
831 | continue; |
832 | } |
833 | |
834 | /* |
835 | * Show control characters and characters with the |
836 | * high bit set specially. |
837 | */ |
838 | cp = lp->data; |
839 | count = lp->len; |
840 | |
841 | if ((count > 0) && (cp[count - 1] == '\n')) |
842 | count--; |
843 | |
844 | while (count-- > 0) { |
845 | ch = (unsigned char) *cp++; |
846 | fputc_printable(ch | PRINTABLE_META, stdout); |
847 | } |
848 | |
849 | fputs("$\n", stdout); |
850 | |
851 | setCurNum(num1++); |
852 | lp = lp->next; |
853 | } |
854 | |
855 | return TRUE; |
856 | } |
857 | |
858 | |
859 | /* |
860 | * Insert a new line with the specified text. |
861 | * The line is inserted so as to become the specified line, |
862 | * thus pushing any existing and further lines down one. |
863 | * The inserted line is also set to become the current line. |
864 | * Returns TRUE if successful. |
865 | */ |
866 | static int insertLine(int num, const char *data, int len) |
867 | { |
868 | LINE *newLp, *lp; |
869 | |
870 | if ((num < 1) || (num > lastNum + 1)) { |
871 | bb_error_msg("inserting at bad line number"); |
872 | return FALSE; |
873 | } |
874 | |
875 | newLp = malloc(sizeof(LINE) + len - 1); |
876 | if (newLp == NULL) { |
877 | bb_error_msg("failed to allocate memory for line"); |
878 | return FALSE; |
879 | } |
880 | |
881 | memcpy(newLp->data, data, len); |
882 | newLp->len = len; |
883 | |
884 | if (num > lastNum) |
885 | lp = &lines; |
886 | else { |
887 | lp = findLine(num); |
888 | if (lp == NULL) { |
889 | free((char *) newLp); |
890 | return FALSE; |
891 | } |
892 | } |
893 | |
894 | newLp->next = lp; |
895 | newLp->prev = lp->prev; |
896 | lp->prev->next = newLp; |
897 | lp->prev = newLp; |
898 | |
899 | lastNum++; |
900 | dirty = TRUE; |
901 | return setCurNum(num); |
902 | } |
903 | |
904 | |
905 | /* |
906 | * Delete lines from the given range. |
907 | */ |
908 | static void deleteLines(int num1, int num2) |
909 | { |
910 | LINE *lp, *nlp, *plp; |
911 | int count; |
912 | |
913 | if (bad_nums(num1, num2, "delete")) |
914 | return; |
915 | |
916 | lp = findLine(num1); |
917 | if (lp == NULL) |
918 | return; |
919 | |
920 | if ((curNum >= num1) && (curNum <= num2)) { |
921 | if (num2 < lastNum) |
922 | setCurNum(num2 + 1); |
923 | else if (num1 > 1) |
924 | setCurNum(num1 - 1); |
925 | else |
926 | curNum = 0; |
927 | } |
928 | |
929 | count = num2 - num1 + 1; |
930 | if (curNum > num2) |
931 | curNum -= count; |
932 | lastNum -= count; |
933 | |
934 | while (count-- > 0) { |
935 | nlp = lp->next; |
936 | plp = lp->prev; |
937 | plp->next = nlp; |
938 | nlp->prev = plp; |
939 | free(lp); |
940 | lp = nlp; |
941 | } |
942 | |
943 | dirty = TRUE; |
944 | } |
945 | |
946 | |
947 | /* |
948 | * Search for a line which contains the specified string. |
949 | * If the string is "", then the previously searched for string |
950 | * is used. The currently searched for string is saved for future use. |
951 | * Returns the line number which matches, or 0 if there was no match |
952 | * with an error printed. |
953 | */ |
954 | static int searchLines(const char *str, int num1, int num2) |
955 | { |
956 | const LINE *lp; |
957 | int len; |
958 | |
959 | if (bad_nums(num1, num2, "search")) |
960 | return 0; |
961 | |
962 | if (*str == '\0') { |
963 | if (searchString[0] == '\0') { |
964 | bb_error_msg("no previous search string"); |
965 | return 0; |
966 | } |
967 | str = searchString; |
968 | } |
969 | |
970 | if (str != searchString) |
971 | strcpy(searchString, str); |
972 | |
973 | len = strlen(str); |
974 | |
975 | lp = findLine(num1); |
976 | if (lp == NULL) |
977 | return 0; |
978 | |
979 | while (num1 <= num2) { |
980 | if (findString(lp, str, len, 0) >= 0) |
981 | return num1; |
982 | num1++; |
983 | lp = lp->next; |
984 | } |
985 | |
986 | bb_error_msg("cannot find string \"%s\"", str); |
987 | return 0; |
988 | } |
989 | |
990 | |
991 | /* |
992 | * Return a pointer to the specified line number. |
993 | */ |
994 | static LINE *findLine(int num) |
995 | { |
996 | LINE *lp; |
997 | int lnum; |
998 | |
999 | if ((num < 1) || (num > lastNum)) { |
1000 | bb_error_msg("line number %d does not exist", num); |
1001 | return NULL; |
1002 | } |
1003 | |
1004 | if (curNum <= 0) { |
1005 | curNum = 1; |
1006 | curLine = lines.next; |
1007 | } |
1008 | |
1009 | if (num == curNum) |
1010 | return curLine; |
1011 | |
1012 | lp = curLine; |
1013 | lnum = curNum; |
1014 | if (num < (curNum / 2)) { |
1015 | lp = lines.next; |
1016 | lnum = 1; |
1017 | } else if (num > ((curNum + lastNum) / 2)) { |
1018 | lp = lines.prev; |
1019 | lnum = lastNum; |
1020 | } |
1021 | |
1022 | while (lnum < num) { |
1023 | lp = lp->next; |
1024 | lnum++; |
1025 | } |
1026 | |
1027 | while (lnum > num) { |
1028 | lp = lp->prev; |
1029 | lnum--; |
1030 | } |
1031 | return lp; |
1032 | } |
1033 | |
1034 | |
1035 | /* |
1036 | * Set the current line number. |
1037 | * Returns TRUE if successful. |
1038 | */ |
1039 | static int setCurNum(int num) |
1040 | { |
1041 | LINE *lp; |
1042 | |
1043 | lp = findLine(num); |
1044 | if (lp == NULL) |
1045 | return FALSE; |
1046 | curNum = num; |
1047 | curLine = lp; |
1048 | return TRUE; |
1049 | } |