busybox/editors/ed.c
<<
>>
Prefs
   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
  12typedef 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
  22enum {
  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
  28struct 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
  58static void doCommands(void);
  59static void subCommand(const char *cmd, int num1, int num2);
  60static int getNum(const char **retcp, smallint *retHaveNum, int *retNum);
  61static int setCurNum(int num);
  62static void addLines(int num);
  63static int insertLine(int num, const char *data, int len);
  64static void deleteLines(int num1, int num2);
  65static int printLines(int num1, int num2, int expandFlag);
  66static int writeLines(const char *file, int num1, int num2);
  67static int readLines(const char *file, int num);
  68static int searchLines(const char *str, int num1, int num2);
  69static LINE *findLine(int num);
  70static int findString(const LINE *lp, const char * str, int len, int offset);
  71
  72
  73static 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
  83static char *skip_blank(const char *cp)
  84{
  85        while (isblank(*cp))
  86                cp++;
  87        return (char *)cp;
  88}
  89
  90
  91int ed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  92int 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 */
 119static 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 */
 331static 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 */
 505static 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 */
 537static 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 */
 570static 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 */
 665static 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 */
 760static 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 */
 813static 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 */
 866static 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 */
 908static 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 */
 954static 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 */
 994static 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 */
1039static 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}
1050