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 /* FALLTHROUGH */ 315 316 case 'L': 317 recov++; 318 break; 319 320 case 'V': 321 verbose = 1; 322 break; 323 324 case 't': 325 if (toptseen) { 326 usage(cmdnam); 327 exit(1); 328 } else { 329 toptseen++; 330 } 331 itag = tagflg = 1; /* -t option */ 332 if (strlcpy(lasttag, optarg, 333 sizeof (lasttag)) >= sizeof (lasttag)) { 334 (void) fprintf(stderr, gettext("Tag" 335 " file name too long\n")); 336 exit(1); 337 } 338 break; 339 340 case 'w': 341 defwind = 0; 342 if (optarg[0] == NULL) 343 defwind = 3; 344 else for (cp = (unsigned char *)optarg; 345 isdigit(*cp); cp++) 346 defwind = 10*defwind + *cp - '0'; 347 if (defwind < 0) 348 defwind = 3; 349 break; 350 351 case 'C': 352 crflag = 1; 353 xflag = 1; 354 break; 355 356 case 'x': 357 /* encrypted mode */ 358 xflag = 1; 359 crflag = -1; 360 break; 361 362 case 'v': 363 ivis = 1; 364 break; 365 366 default: 367 usage(cmdnam); 368 exit(1); 369 } 370 if (av[optind] && av[optind][0] == '+' && 371 av[optind-1] && strcmp(av[optind-1], "--")) { 372 firstpat = (unsigned char *)&av[optind][1]; 373 optind++; 374 continue; 375 } else if (av[optind] && av[optind][0] == '-' && 376 av[optind-1] && strcmp(av[optind-1], "--")) { 377 hush = 1; 378 value(vi_AUTOPRINT) = 0; 379 fast++; 380 optind++; 381 continue; 382 } 383 break; 384 } 385 386 if (isatty(0) == 0) { 387 /* 388 * If -V option is set and input is coming in via 389 * stdin then vi behavior should be ignored. The vi 390 * command should act like ex and only process ex commands 391 * and echo the input ex commands to stderr 392 */ 393 if (verbose == 1) { 394 ivis = 0; 395 } 396 397 /* 398 * If the standard input is not a terminal device, 399 * it is as if the -s option has been specified. 400 */ 401 if (ivis == 0) { 402 hush = 1; 403 value(vi_AUTOPRINT) = 0; 404 fast++; 405 } 406 } 407 408 ac -= optind; 409 av = &av[optind]; 410 411 for (argcounter = 0; argcounter < ac; argcounter++) { 412 if ((strlen(av[argcounter])) >= sizeof (savedfile)) { 413 (void) fprintf(stderr, gettext("File argument" 414 " too long\n")); 415 exit(1); 416 } 417 } 418 419 #ifdef SIGTSTP 420 if (!hush && signal(SIGTSTP, SIG_IGN) == SIG_DFL) 421 signal(SIGTSTP, onsusp), dosusp++; 422 #endif 423 424 if (xflag) { 425 permflag = 1; 426 if ((kflag = run_setkey(perm, 427 (key = (unsigned char *)getpass( 428 gettext("Enter key:"))))) == -1) { 429 kflag = 0; 430 xflag = 0; 431 smerror(gettext("Encryption facility not available\n")); 432 } 433 if (kflag == 0) 434 crflag = 0; 435 else { 436 strcpy(cryptkey, "CrYpTkEy=XXXXXXXXX"); 437 strcpy(cryptkey + 9, key); 438 if (putenv((char *)cryptkey) != 0) 439 smerror(gettext(" Cannot copy key to environment")); 440 } 441 442 } 443 #ifndef PRESUNEUC 444 /* 445 * Perform locale-specific initialization 446 */ 447 localize(); 448 #endif /* PRESUNEUC */ 449 450 /* 451 * Initialize end of core pointers. 452 * Normally we avoid breaking back to fendcore after each 453 * file since this can be expensive (much core-core copying). 454 * If your system can scatter load processes you could do 455 * this as ed does, saving a little core, but it will probably 456 * not often make much difference. 457 */ 458 fendcore = (line *) sbrk(0); 459 endcore = fendcore - 2; 460 461 /* 462 * If we are doing a recover and no filename 463 * was given, then execute an exrecover command with 464 * the -r option to type out the list of saved file names. 465 * Otherwise set the remembered file name to the first argument 466 * file name so the "recover" initial command will find it. 467 */ 468 if (recov) { 469 if (ac == 0 && (rcvname == NULL || *rcvname == NULL)) { 470 ppid = 0; 471 setrupt(); 472 execlp(EXRECOVER, "exrecover", "-r", (char *)0); 473 filioerr(EXRECOVER); 474 exit(++errcnt); 475 } 476 if (rcvname && *rcvname) 477 (void) strlcpy(savedfile, rcvname, sizeof (savedfile)); 478 else { 479 (void) strlcpy(savedfile, *av++, sizeof (savedfile)); 480 ac--; 481 } 482 } 483 484 /* 485 * Initialize the argument list. 486 */ 487 argv0 = (unsigned char **)av; 488 argc0 = ac; 489 args0 = (unsigned char *)av[0]; 490 erewind(); 491 492 /* 493 * Initialize a temporary file (buffer) and 494 * set up terminal environment. Read user startup commands. 495 */ 496 if (setexit() == 0) { 497 setrupt(); 498 intty = isatty(0); 499 value(vi_PROMPT) = intty; 500 if (((cp = (unsigned char *)getenv("SHELL")) != NULL) && 501 (*cp != '\0')) { 502 if (strlen(cp) < sizeof (shell)) { 503 (void) strlcpy(shell, cp, sizeof (shell)); 504 } 505 } 506 if (fast) 507 setterm((unsigned char *)"dumb"); 508 else { 509 gettmode(); 510 cp = (unsigned char *)getenv("TERM"); 511 if (cp == NULL || *cp == '\0') 512 cp = (unsigned char *)"unknown"; 513 setterm(cp); 514 } 515 } 516 517 /* 518 * Bring up some code from init() 519 * This is still done in init later. This 520 * avoids null pointer problems 521 */ 522 523 dot = zero = truedol = unddol = dol = fendcore; 524 one = zero+1; 525 { 526 int i; 527 528 for (i = 0; i <= 'z'-'a'+1; i++) 529 names[i] = 1; 530 } 531 532 if (setexit() == 0 && !fast) { 533 if ((globp = 534 (unsigned char *) getenv("EXINIT")) && *globp) { 535 if (ivis) 536 inexrc = 1; 537 commands(1, 1); 538 inexrc = 0; 539 } else { 540 globp = 0; 541 if ((cp = (unsigned char *) getenv("HOME")) != 542 0 && *cp) { 543 strncpy(scratch, cp, sizeof (scratch) - 1); 544 strncat(scratch, "/.exrc", 545 sizeof (scratch) - 1 - strlen(scratch)); 546 if (ivis) 547 inexrc = 1; 548 if ((vret = validate_exrc(scratch)) == 0) { 549 source(scratch, 1); 550 } else { 551 if (vret == -1) { 552 error(gettext( 553 "Not owner of .exrc " 554 "or .exrc is group or " 555 "world writable")); 556 } 557 } 558 inexrc = 0; 559 } 560 } 561 562 /* 563 * Allow local .exrc if the "exrc" option was set. This 564 * loses if . is $HOME, but nobody should notice unless 565 * they do stupid things like putting a version command 566 * in .exrc. 567 * Besides, they should be using EXINIT, not .exrc, right? 568 */ 569 570 if (value(vi_EXRC)) { 571 if (ivis) 572 inexrc = 1; 573 if ((cp = (unsigned char *) getenv("PWD")) != 0 && 574 *cp) { 575 strncpy(exrcpath, cp, sizeof (exrcpath) - 1); 576 strncat(exrcpath, "/.exrc", 577 sizeof (exrcpath) - 1 - strlen(exrcpath)); 578 if (strcmp(scratch, exrcpath) != 0) { 579 if ((vret = 580 validate_exrc(exrcpath)) == 0) { 581 source(exrcpath, 1); 582 } else { 583 if (vret == -1) { 584 error(gettext( 585 "Not owner of " 586 ".exrc or .exrc " 587 "is group or world " 588 "writable")); 589 } 590 } 591 } 592 } 593 inexrc = 0; 594 } 595 } 596 597 init(); /* moved after prev 2 chunks to fix directory option */ 598 599 /* 600 * Initial processing. Handle tag, recover, and file argument 601 * implied next commands. If going in as 'vi', then don't do 602 * anything, just set initev so we will do it later (from within 603 * visual). 604 */ 605 if (setexit() == 0) { 606 if (recov) 607 globp = (unsigned char *)"recover"; 608 else if (itag) { 609 globp = ivis ? (unsigned char *)"tag" : 610 (unsigned char *)"tag|p"; 611 #ifdef XPG4 612 if (firstpat != NULL) { 613 /* 614 * if the user specified the -t and -c 615 * flags together, then we service these 616 * commands here. -t is handled first. 617 */ 618 savepat = firstpat; 619 firstpat = NULL; 620 inglobal = 1; 621 commands(1, 1); 622 623 /* now handle the -c argument: */ 624 globp = savepat; 625 commands(1, 1); 626 inglobal = 0; 627 globp = savepat = NULL; 628 629 /* the above isn't sufficient for ex mode: */ 630 if (!ivis) { 631 setdot(); 632 nonzero(); 633 plines(addr1, addr2, 1); 634 } 635 } 636 #endif /* XPG4 */ 637 } else if (argc) 638 globp = (unsigned char *)"next"; 639 if (ivis) 640 initev = globp; 641 else if (globp) { 642 inglobal = 1; 643 commands(1, 1); 644 inglobal = 0; 645 } 646 } 647 648 /* 649 * Vi command... go into visual. 650 */ 651 if (ivis) { 652 /* 653 * Don't have to be upward compatible 654 * by starting editing at line $. 655 */ 656 #ifdef XPG4 657 if (!itag && (dol > zero)) 658 #else /* XPG4 */ 659 if (dol > zero) 660 #endif /* XPG4 */ 661 dot = one; 662 globp = (unsigned char *)"visual"; 663 if (setexit() == 0) 664 commands(1, 1); 665 } 666 667 /* 668 * Clear out trash in state accumulated by startup, 669 * and then do the main command loop for a normal edit. 670 * If you quit out of a 'vi' command by doing Q or ^\, 671 * you also fall through to here. 672 */ 673 seenprompt = 1; 674 ungetchar(0); 675 globp = 0; 676 initev = 0; 677 setlastchar('\n'); 678 setexit(); 679 commands(0, 0); 680 cleanup(1); 681 return (errcnt); 682 } 683 684 /* 685 * Initialization, before editing a new file. 686 * Main thing here is to get a new buffer (in fileinit), 687 * rest is peripheral state resetting. 688 */ 689 void 690 init(void) 691 { 692 int i; 693 void (*pstat)(); 694 fileinit(); 695 dot = zero = truedol = unddol = dol = fendcore; 696 one = zero+1; 697 undkind = UNDNONE; 698 chng = 0; 699 edited = 0; 700 for (i = 0; i <= 'z'-'a'+1; i++) 701 names[i] = 1; 702 anymarks = 0; 703 if (xflag) { 704 xtflag = 1; 705 /* ignore SIGINT before crypt process */ 706 pstat = signal(SIGINT, SIG_IGN); 707 if (tpermflag) 708 (void) crypt_close(tperm); 709 tpermflag = 1; 710 if (makekey(tperm) != 0) { 711 xtflag = 0; 712 smerror(gettext( 713 "Warning--Cannot encrypt temporary buffer\n")); 714 } 715 signal(SIGINT, pstat); 716 } 717 } 718 719 /* 720 * Return last component of unix path name p. 721 */ 722 unsigned char * 723 tailpath(p) 724 unsigned char *p; 725 { 726 unsigned char *r; 727 728 for (r = p; *p; p++) 729 if (*p == '/') 730 r = p+1; 731 return (r); 732 } 733 734 735 /* 736 * validate_exrc - verify .exrc as belonging to the user. 737 * The file uid should match the process ruid, 738 * and the file should be writable only by the owner. 739 */ 740 static int 741 validate_exrc(unsigned char *exrc_path) 742 { 743 struct stat64 exrc_stat; 744 int process_uid; 745 746 if (stat64((char *)exrc_path, &exrc_stat) == -1) 747 return (0); /* ignore if .exrec is not found */ 748 process_uid = geteuid(); 749 /* if not root, uid must match file owner */ 750 if (process_uid && process_uid != exrc_stat.st_uid) 751 return (-1); 752 if ((exrc_stat.st_mode & (S_IWGRP | S_IWOTH)) != 0) 753 return (-1); 754 return (0); 755 } 756 757 /* 758 * print usage message to stdout 759 */ 760 static void 761 usage(unsigned char *name) 762 { 763 char buf[160]; 764 765 #ifdef TRACE 766 (void) snprintf(buf, sizeof (buf), gettext( 767 "Usage: %s [- | -s] [-l] [-L] [-wn] " 768 "[-R] [-S] [-r [file]] [-t tag] [-T] [-U tracefile]\n" 769 "[-v] [-V] [-x] [-C] [+cmd | -c cmd] file...\n"), name); 770 #else 771 (void) snprintf(buf, sizeof (buf), gettext( 772 "Usage: %s [- | -s] [-l] [-L] [-wn] " 773 "[-R] [-S] [-r [file]] [-t tag]\n" 774 "[-v] [-V] [-x] [-C] [+cmd | -c cmd] file...\n"), name); 775 #endif 776 (void) write(2, buf, strlen(buf)); 777 } 778