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