1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 27 /* All Rights Reserved */ 28 29 /* Copyright (c) 1981 Regents of the University of California */ 30 31 #pragma ident "%Z%%M% %I% %E% SMI" 32 33 #include "ex.h" 34 #include "ex_argv.h" 35 #include "ex_temp.h" 36 #include "ex_tty.h" 37 #include <stdlib.h> 38 #include <locale.h> 39 #include <stdio.h> 40 #ifdef TRACE 41 unsigned char tttrace[BUFSIZ]; 42 #endif 43 44 #define EQ(a, b) (strcmp(a, b) == 0) 45 46 char *strrchr(); 47 void init_re(void); 48 49 /* 50 * The code for ex is divided as follows: 51 * 52 * ex.c Entry point and routines handling interrupt, hangup 53 * signals; initialization code. 54 * 55 * ex_addr.c Address parsing routines for command mode decoding. 56 * Routines to set and check address ranges on commands. 57 * 58 * ex_cmds.c Command mode command decoding. 59 * 60 * ex_cmds2.c Subroutines for command decoding and processing of 61 * file names in the argument list. Routines to print 62 * messages and reset state when errors occur. 63 * 64 * ex_cmdsub.c Subroutines which implement command mode functions 65 * such as append, delete, join. 66 * 67 * ex_data.c Initialization of options. 68 * 69 * ex_get.c Command mode input routines. 70 * 71 * ex_io.c General input/output processing: file i/o, unix 72 * escapes, filtering, source commands, preserving 73 * and recovering. 74 * 75 * ex_put.c Terminal driving and optimizing routines for low-level 76 * output (cursor-positioning); output line formatting 77 * routines. 78 * 79 * ex_re.c Global commands, substitute, regular expression 80 * compilation and execution. 81 * 82 * ex_set.c The set command. 83 * 84 * ex_subr.c Loads of miscellaneous subroutines. 85 * 86 * ex_temp.c Editor buffer routines for main buffer and also 87 * for named buffers (Q registers if you will.) 88 * 89 * ex_tty.c Terminal dependent initializations from termcap 90 * data base, grabbing of tty modes (at beginning 91 * and after escapes). 92 * 93 * ex_unix.c Routines for the ! command and its variations. 94 * 95 * ex_v*.c Visual/open mode routines... see ex_v.c for a 96 * guide to the overall organization. 97 */ 98 99 /* 100 * This sets the Version of ex/vi for both the exstrings file and 101 * the version command (":ver"). 102 */ 103 104 /* variable used by ":ver" command */ 105 unsigned char *Version = (unsigned char *)"Version SVR4.0, Solaris 2.5.0"; 106 107 /* 108 * NOTE: when changing the Version number, it must be changed in the 109 * following files: 110 * 111 * port/READ_ME 112 * port/ex.c 113 * port/ex.news 114 * 115 */ 116 #ifdef XPG4 117 unsigned char *savepat = (unsigned char *) NULL; /* firstpat storage */ 118 #endif /* XPG4 */ 119 120 /* 121 * Main procedure. Process arguments and then 122 * transfer control to the main command processing loop 123 * in the routine commands. We are entered as either "ex", "edit", "vi" 124 * or "view" and the distinction is made here. For edit we just diddle options; 125 * for vi we actually force an early visual command. 126 */ 127 static unsigned char cryptkey[19]; /* contents of encryption key */ 128 129 static void usage(unsigned char *); 130 131 static int validate_exrc(unsigned char *); 132 133 void init(void); 134 135 int 136 main(int ac, char *av[]) 137 { 138 extern char *optarg; 139 extern int optind; 140 unsigned char *rcvname = 0; 141 unsigned char *cp; 142 int c; 143 unsigned char *cmdnam; 144 bool recov = 0; 145 bool ivis = 0; 146 bool itag = 0; 147 bool fast = 0; 148 extern int verbose; 149 int argcounter = 0; 150 extern int tags_flag; /* Set if tag file is not sorted (-S flag) */ 151 unsigned char scratch [PATH_MAX+1]; /* temp for sourcing rc file(s) */ 152 int vret = 0; 153 unsigned char exrcpath [PATH_MAX+1]; /* temp for sourcing rc file(s) */ 154 int toptseen = 0; 155 #ifdef TRACE 156 unsigned char *tracef; 157 #endif 158 tagflg = 0; 159 (void) setlocale(LC_ALL, ""); 160 #if !defined(TEXT_DOMAIN) 161 #define TEXT_DOMAIN "SYS_TEST" 162 #endif 163 (void) textdomain(TEXT_DOMAIN); 164 165 /* 166 * Immediately grab the tty modes so that we won't 167 * get messed up if an interrupt comes in quickly. 168 */ 169 (void) gTTY(2); 170 normf = tty; 171 ppid = getpid(); 172 /* Note - this will core dump if you didn't -DSINGLE in CFLAGS */ 173 lines = 24; 174 columns = 80; /* until defined right by setupterm */ 175 /* 176 * Defend against d's, v's, w's, and a's in directories of 177 * path leading to our true name. 178 */ 179 if ((cmdnam = (unsigned char *)strrchr(av[0], '/')) != 0) 180 cmdnam++; 181 else 182 cmdnam = (unsigned char *)av[0]; 183 184 if (EQ((char *)cmdnam, "vi")) 185 ivis = 1; 186 else if (EQ(cmdnam, "view")) { 187 ivis = 1; 188 value(vi_READONLY) = 1; 189 } else if (EQ(cmdnam, "vedit")) { 190 ivis = 1; 191 value(vi_NOVICE) = 1; 192 value(vi_REPORT) = 1; 193 value(vi_MAGIC) = 0; 194 value(vi_SHOWMODE) = 1; 195 } else if (EQ(cmdnam, "edit")) { 196 value(vi_NOVICE) = 1; 197 value(vi_REPORT) = 1; 198 value(vi_MAGIC) = 0; 199 value(vi_SHOWMODE) = 1; 200 } 201 202 #ifdef XPG4 203 { 204 struct winsize jwin; 205 char *envptr; 206 207 envlines = envcolumns = -1; 208 oldlines = oldcolumns = -1; 209 210 if (ioctl(0, TIOCGWINSZ, &jwin) != -1) { 211 oldlines = jwin.ws_row; 212 oldcolumns = jwin.ws_col; 213 } 214 215 if ((envptr = getenv("LINES")) != NULL && 216 *envptr != '\0' && isdigit(*envptr)) { 217 if ((envlines = atoi(envptr)) <= 0) { 218 envlines = -1; 219 } 220 } 221 222 if ((envptr = getenv("COLUMNS")) != NULL && 223 *envptr != '\0' && isdigit(*envptr)) { 224 if ((envcolumns = atoi(envptr)) <= 0) { 225 envcolumns = -1; 226 } 227 } 228 } 229 #endif /* XPG4 */ 230 231 draino(); 232 pstop(); 233 234 /* 235 * Initialize interrupt handling. 236 */ 237 oldhup = signal(SIGHUP, SIG_IGN); 238 if (oldhup == SIG_DFL) 239 signal(SIGHUP, onhup); 240 oldquit = signal(SIGQUIT, SIG_IGN); 241 ruptible = signal(SIGINT, SIG_IGN) == SIG_DFL; 242 if (signal(SIGTERM, SIG_IGN) == SIG_DFL) 243 signal(SIGTERM, onhup); 244 if (signal(SIGEMT, SIG_IGN) == SIG_DFL) 245 signal(SIGEMT, onemt); 246 signal(SIGILL, oncore); 247 signal(SIGTRAP, oncore); 248 signal(SIGIOT, oncore); 249 signal(SIGFPE, oncore); 250 signal(SIGBUS, oncore); 251 signal(SIGSEGV, oncore); 252 signal(SIGPIPE, oncore); 253 init_re(); 254 while (1) { 255 #ifdef TRACE 256 while ((c = getopt(ac, (char **)av, "VU:Lc:Tvt:rlw:xRCsS")) != 257 EOF) 258 #else 259 while ((c = getopt(ac, (char **)av, 260 "VLc:vt:rlw:xRCsS")) != EOF) 261 #endif 262 switch (c) { 263 case 's': 264 hush = 1; 265 value(vi_AUTOPRINT) = 0; 266 fast++; 267 break; 268 269 case 'R': 270 value(vi_READONLY) = 1; 271 break; 272 case 'S': 273 tags_flag = 1; 274 break; 275 #ifdef TRACE 276 case 'T': 277 tracef = (unsigned char *)"trace"; 278 goto trace; 279 280 case 'U': 281 tracef = tttrace; 282 strcpy(tracef, optarg); 283 trace: 284 trace = fopen((char *)tracef, "w"); 285 #define tracbuf NULL 286 if (trace == NULL) 287 viprintf("Trace create error\n"); 288 else 289 setbuf(trace, (char *)tracbuf); 290 break; 291 #endif 292 case 'c': 293 if (optarg != NULL) 294 firstpat = (unsigned char *)optarg; 295 else 296 firstpat = (unsigned char *)""; 297 break; 298 299 case 'l': 300 value(vi_LISP) = 1; 301 value(vi_SHOWMATCH) = 1; 302 break; 303 304 case 'r': 305 if (av[optind] && (c = av[optind][0]) && 306 c != '-') { 307 if ((strlen(av[optind])) >= 308 sizeof (savedfile)) { 309 (void) fprintf(stderr, gettext( 310 "Recovered file name" 311 " too long\n")); 312 exit(1); 313 } 314 315 rcvname = (unsigned char *)av[optind]; 316 optind++; 317 } 318 319 case 'L': 320 recov++; 321 break; 322 323 case 'V': 324 verbose = 1; 325 break; 326 327 case 't': 328 if (toptseen) { 329 usage(cmdnam); 330 exit(1); 331 } else { 332 toptseen++; 333 } 334 itag = tagflg = 1; /* -t option */ 335 if (strlcpy(lasttag, optarg, 336 sizeof (lasttag)) >= sizeof (lasttag)) { 337 (void) fprintf(stderr, gettext("Tag" 338 " file name too long\n")); 339 exit(1); 340 } 341 break; 342 343 case 'w': 344 defwind = 0; 345 if (optarg[0] == NULL) 346 defwind = 3; 347 else for (cp = (unsigned char *)optarg; 348 isdigit(*cp); cp++) 349 defwind = 10*defwind + *cp - '0'; 350 if (defwind < 0) 351 defwind = 3; 352 break; 353 354 case 'C': 355 crflag = 1; 356 xflag = 1; 357 break; 358 359 case 'x': 360 /* encrypted mode */ 361 xflag = 1; 362 crflag = -1; 363 break; 364 365 case 'v': 366 ivis = 1; 367 break; 368 369 default: 370 usage(cmdnam); 371 exit(1); 372 } 373 if (av[optind] && av[optind][0] == '+' && 374 av[optind-1] && strcmp(av[optind-1], "--")) { 375 firstpat = (unsigned char *)&av[optind][1]; 376 optind++; 377 continue; 378 } else if (av[optind] && av[optind][0] == '-' && 379 av[optind-1] && strcmp(av[optind-1], "--")) { 380 hush = 1; 381 value(vi_AUTOPRINT) = 0; 382 fast++; 383 optind++; 384 continue; 385 } 386 break; 387 } 388 389 if (isatty(0) == 0) { 390 /* 391 * If -V option is set and input is coming in via 392 * stdin then vi behavior should be ignored. The vi 393 * command should act like ex and only process ex commands 394 * and echo the input ex commands to stderr 395 */ 396 if (verbose == 1) { 397 ivis = 0; 398 } 399 400 /* 401 * If the standard input is not a terminal device, 402 * it is as if the -s option has been specified. 403 */ 404 if (ivis == 0) { 405 hush = 1; 406 value(vi_AUTOPRINT) = 0; 407 fast++; 408 } 409 } 410 411 ac -= optind; 412 av = &av[optind]; 413 414 for (argcounter = 0; argcounter < ac; argcounter++) { 415 if ((strlen(av[argcounter])) >= sizeof (savedfile)) { 416 (void) fprintf(stderr, gettext("File argument" 417 " too long\n")); 418 exit(1); 419 } 420 } 421 422 #ifdef SIGTSTP 423 if (!hush && signal(SIGTSTP, SIG_IGN) == SIG_DFL) 424 signal(SIGTSTP, onsusp), dosusp++; 425 #endif 426 427 if (xflag) { 428 permflag = 1; 429 if ((kflag = run_setkey(perm, 430 (key = (unsigned char *)getpass( 431 gettext("Enter key:"))))) == -1) { 432 kflag = 0; 433 xflag = 0; 434 smerror(gettext("Encryption facility not available\n")); 435 } 436 if (kflag == 0) 437 crflag = 0; 438 else { 439 strcpy(cryptkey, "CrYpTkEy=XXXXXXXXX"); 440 strcpy(cryptkey + 9, key); 441 if (putenv((char *)cryptkey) != 0) 442 smerror(gettext(" Cannot copy key to environment")); 443 } 444 445 } 446 #ifndef PRESUNEUC 447 /* 448 * Perform locale-specific initialization 449 */ 450 localize(); 451 #endif /* PRESUNEUC */ 452 453 /* 454 * Initialize end of core pointers. 455 * Normally we avoid breaking back to fendcore after each 456 * file since this can be expensive (much core-core copying). 457 * If your system can scatter load processes you could do 458 * this as ed does, saving a little core, but it will probably 459 * not often make much difference. 460 */ 461 fendcore = (line *) sbrk(0); 462 endcore = fendcore - 2; 463 464 /* 465 * If we are doing a recover and no filename 466 * was given, then execute an exrecover command with 467 * the -r option to type out the list of saved file names. 468 * Otherwise set the remembered file name to the first argument 469 * file name so the "recover" initial command will find it. 470 */ 471 if (recov) { 472 if (ac == 0 && (rcvname == NULL || *rcvname == NULL)) { 473 ppid = 0; 474 setrupt(); 475 execlp(EXRECOVER, "exrecover", "-r", (char *)0); 476 filioerr(EXRECOVER); 477 exit(++errcnt); 478 } 479 if (rcvname && *rcvname) 480 (void) strlcpy(savedfile, rcvname, sizeof (savedfile)); 481 else { 482 (void) strlcpy(savedfile, *av++, sizeof (savedfile)); 483 ac--; 484 } 485 } 486 487 /* 488 * Initialize the argument list. 489 */ 490 argv0 = (unsigned char **)av; 491 argc0 = ac; 492 args0 = (unsigned char *)av[0]; 493 erewind(); 494 495 /* 496 * Initialize a temporary file (buffer) and 497 * set up terminal environment. Read user startup commands. 498 */ 499 if (setexit() == 0) { 500 setrupt(); 501 intty = isatty(0); 502 value(vi_PROMPT) = intty; 503 if (((cp = (unsigned char *)getenv("SHELL")) != NULL) && 504 (*cp != '\0')) { 505 if (strlen(cp) < sizeof (shell)) { 506 (void) strlcpy(shell, cp, sizeof (shell)); 507 } 508 } 509 if (fast) 510 setterm((unsigned char *)"dumb"); 511 else { 512 gettmode(); 513 cp = (unsigned char *)getenv("TERM"); 514 if (cp == NULL || *cp == '\0') 515 cp = (unsigned char *)"unknown"; 516 setterm(cp); 517 } 518 } 519 520 /* 521 * Bring up some code from init() 522 * This is still done in init later. This 523 * avoids null pointer problems 524 */ 525 526 dot = zero = truedol = unddol = dol = fendcore; 527 one = zero+1; 528 { 529 int i; 530 531 for (i = 0; i <= 'z'-'a'+1; i++) 532 names[i] = 1; 533 } 534 535 if (setexit() == 0 && !fast) { 536 if ((globp = 537 (unsigned char *) getenv("EXINIT")) && *globp) { 538 if (ivis) 539 inexrc = 1; 540 commands(1, 1); 541 inexrc = 0; 542 } else { 543 globp = 0; 544 if ((cp = (unsigned char *) getenv("HOME")) != 545 0 && *cp) { 546 strncpy(scratch, cp, sizeof (scratch) - 1); 547 strncat(scratch, "/.exrc", 548 sizeof (scratch) - 1 - strlen(scratch)); 549 if (ivis) 550 inexrc = 1; 551 if ((vret = validate_exrc(scratch)) == 0) { 552 source(scratch, 1); 553 } else { 554 if (vret == -1) { 555 error(gettext( 556 "Not owner of .exrc " 557 "or .exrc is group or " 558 "world writable")); 559 } 560 } 561 inexrc = 0; 562 } 563 } 564 565 /* 566 * Allow local .exrc if the "exrc" option was set. This 567 * loses if . is $HOME, but nobody should notice unless 568 * they do stupid things like putting a version command 569 * in .exrc. 570 * Besides, they should be using EXINIT, not .exrc, right? 571 */ 572 573 if (value(vi_EXRC)) { 574 if (ivis) 575 inexrc = 1; 576 if ((cp = (unsigned char *) getenv("PWD")) != 0 && 577 *cp) { 578 strncpy(exrcpath, cp, sizeof (exrcpath) - 1); 579 strncat(exrcpath, "/.exrc", 580 sizeof (exrcpath) - 1 - strlen(exrcpath)); 581 if (strcmp(scratch, exrcpath) != 0) { 582 if ((vret = 583 validate_exrc(exrcpath)) == 0) { 584 source(exrcpath, 1); 585 } else { 586 if (vret == -1) { 587 error(gettext( 588 "Not owner of " 589 ".exrc or .exrc " 590 "is group or world " 591 "writable")); 592 } 593 } 594 } 595 } 596 inexrc = 0; 597 } 598 } 599 600 init(); /* moved after prev 2 chunks to fix directory option */ 601 602 /* 603 * Initial processing. Handle tag, recover, and file argument 604 * implied next commands. If going in as 'vi', then don't do 605 * anything, just set initev so we will do it later (from within 606 * visual). 607 */ 608 if (setexit() == 0) { 609 if (recov) 610 globp = (unsigned char *)"recover"; 611 else if (itag) { 612 globp = ivis ? (unsigned char *)"tag" : 613 (unsigned char *)"tag|p"; 614 #ifdef XPG4 615 if (firstpat != NULL) { 616 /* 617 * if the user specified the -t and -c 618 * flags together, then we service these 619 * commands here. -t is handled first. 620 */ 621 savepat = firstpat; 622 firstpat = NULL; 623 inglobal = 1; 624 commands(1, 1); 625 626 /* now handle the -c argument: */ 627 globp = savepat; 628 commands(1, 1); 629 inglobal = 0; 630 globp = savepat = NULL; 631 632 /* the above isn't sufficient for ex mode: */ 633 if (!ivis) { 634 setdot(); 635 nonzero(); 636 plines(addr1, addr2, 1); 637 } 638 } 639 #endif /* XPG4 */ 640 } else if (argc) 641 globp = (unsigned char *)"next"; 642 if (ivis) 643 initev = globp; 644 else if (globp) { 645 inglobal = 1; 646 commands(1, 1); 647 inglobal = 0; 648 } 649 } 650 651 /* 652 * Vi command... go into visual. 653 */ 654 if (ivis) { 655 /* 656 * Don't have to be upward compatible 657 * by starting editing at line $. 658 */ 659 #ifdef XPG4 660 if (!itag && (dol > zero)) 661 #else /* XPG4 */ 662 if (dol > zero) 663 #endif /* XPG4 */ 664 dot = one; 665 globp = (unsigned char *)"visual"; 666 if (setexit() == 0) 667 commands(1, 1); 668 } 669 670 /* 671 * Clear out trash in state accumulated by startup, 672 * and then do the main command loop for a normal edit. 673 * If you quit out of a 'vi' command by doing Q or ^\, 674 * you also fall through to here. 675 */ 676 seenprompt = 1; 677 ungetchar(0); 678 globp = 0; 679 initev = 0; 680 setlastchar('\n'); 681 setexit(); 682 commands(0, 0); 683 cleanup(1); 684 return (errcnt); 685 } 686 687 /* 688 * Initialization, before editing a new file. 689 * Main thing here is to get a new buffer (in fileinit), 690 * rest is peripheral state resetting. 691 */ 692 void 693 init(void) 694 { 695 int i; 696 void (*pstat)(); 697 fileinit(); 698 dot = zero = truedol = unddol = dol = fendcore; 699 one = zero+1; 700 undkind = UNDNONE; 701 chng = 0; 702 edited = 0; 703 for (i = 0; i <= 'z'-'a'+1; i++) 704 names[i] = 1; 705 anymarks = 0; 706 if (xflag) { 707 xtflag = 1; 708 /* ignore SIGINT before crypt process */ 709 pstat = signal(SIGINT, SIG_IGN); 710 if (tpermflag) 711 (void) crypt_close(tperm); 712 tpermflag = 1; 713 if (makekey(tperm) != 0) { 714 xtflag = 0; 715 smerror(gettext( 716 "Warning--Cannot encrypt temporary buffer\n")); 717 } 718 signal(SIGINT, pstat); 719 } 720 } 721 722 /* 723 * Return last component of unix path name p. 724 */ 725 unsigned char * 726 tailpath(p) 727 unsigned char *p; 728 { 729 unsigned char *r; 730 731 for (r = p; *p; p++) 732 if (*p == '/') 733 r = p+1; 734 return (r); 735 } 736 737 738 /* 739 * validate_exrc - verify .exrc as belonging to the user. 740 * The file uid should match the process ruid, 741 * and the file should be writable only by the owner. 742 */ 743 static int 744 validate_exrc(unsigned char *exrc_path) 745 { 746 struct stat64 exrc_stat; 747 int process_uid; 748 749 if (stat64((char *)exrc_path, &exrc_stat) == -1) 750 return (0); /* ignore if .exrec is not found */ 751 process_uid = geteuid(); 752 /* if not root, uid must match file owner */ 753 if (process_uid && process_uid != exrc_stat.st_uid) 754 return (-1); 755 if ((exrc_stat.st_mode & (S_IWGRP | S_IWOTH)) != 0) 756 return (-1); 757 return (0); 758 } 759 760 /* 761 * print usage message to stdout 762 */ 763 static void 764 usage(unsigned char *name) 765 { 766 char buf[160]; 767 768 #ifdef TRACE 769 (void) snprintf(buf, sizeof (buf), gettext( 770 "Usage: %s [- | -s] [-l] [-L] [-wn] " 771 "[-R] [-S] [-r [file]] [-t tag] [-T] [-U tracefile]\n" 772 "[-v] [-V] [-x] [-C] [+cmd | -c cmd] file...\n"), name); 773 #else 774 (void) snprintf(buf, sizeof (buf), gettext( 775 "Usage: %s [- | -s] [-l] [-L] [-wn] " 776 "[-R] [-S] [-r [file]] [-t tag]\n" 777 "[-v] [-V] [-x] [-C] [+cmd | -c cmd] file...\n"), name); 778 #endif 779 (void) write(2, buf, strlen(buf)); 780 }