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