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