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