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
main(int ac,char * av[])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
init(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 *
tailpath(p)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
validate_exrc(unsigned char * exrc_path)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
usage(unsigned char * name)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