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 }