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