1 /*- 2 * Copyright (c) 1992, 1993, 1994 3 * The Regents of the University of California. All rights reserved. 4 * Copyright (c) 1992, 1993, 1994, 1995, 1996 5 * Keith Bostic. All rights reserved. 6 * 7 * See the LICENSE file for redistribution information. 8 */ 9 10 #include "config.h" 11 12 #include <sys/types.h> 13 #include <sys/queue.h> 14 #include <sys/stat.h> 15 16 #include <bitstring.h> 17 #include <err.h> 18 #include <errno.h> 19 #include <fcntl.h> 20 #include <limits.h> 21 #include <stdio.h> 22 #include <stdlib.h> 23 #include <string.h> 24 #include <unistd.h> 25 26 #include "common.h" 27 #include "../vi/vi.h" 28 #include "pathnames.h" 29 30 static void attach(GS *); 31 static int v_obsolete(char *[]); 32 33 /* 34 * editor -- 35 * Main editor routine. 36 * 37 * PUBLIC: int editor(GS *, int, char *[]); 38 */ 39 int 40 editor(GS *gp, int argc, char *argv[]) 41 { 42 extern int optind; 43 extern char *optarg; 44 const char *p; 45 EVENT ev; 46 FREF *frp; 47 SCR *sp; 48 size_t len; 49 u_int flags; 50 int ch, flagchk, lflag, secure, startup, readonly, rval, silent; 51 char *tag_f, *wsizearg; 52 CHAR_T *w, path[256]; 53 size_t wlen; 54 55 /* Initialize the busy routine, if not defined by the screen. */ 56 if (gp->scr_busy == NULL) 57 gp->scr_busy = vs_busy; 58 /* Initialize the message routine, if not defined by the screen. */ 59 if (gp->scr_msg == NULL) 60 gp->scr_msg = vs_msg; 61 gp->catd = (nl_catd)-1; 62 63 /* Common global structure initialization. */ 64 TAILQ_INIT(gp->dq); 65 TAILQ_INIT(gp->hq); 66 SLIST_INIT(gp->ecq); 67 SLIST_INSERT_HEAD(gp->ecq, &gp->excmd, q); 68 gp->noprint = DEFAULT_NOPRINT; 69 70 /* Structures shared by screens so stored in the GS structure. */ 71 TAILQ_INIT(gp->frefq); 72 TAILQ_INIT(gp->dcb_store.textq); 73 SLIST_INIT(gp->cutq); 74 SLIST_INIT(gp->seqq); 75 76 /* Set initial screen type and mode based on the program name. */ 77 readonly = 0; 78 if (!strcmp(getprogname(), "ex") || !strcmp(getprogname(), "nex")) 79 LF_INIT(SC_EX); 80 else { 81 /* Nview, view are readonly. */ 82 if (!strcmp(getprogname(), "nview") || 83 !strcmp(getprogname(), "view")) 84 readonly = 1; 85 86 /* Vi is the default. */ 87 LF_INIT(SC_VI); 88 } 89 90 /* Convert old-style arguments into new-style ones. */ 91 if (v_obsolete(argv)) 92 return (1); 93 94 /* Parse the arguments. */ 95 flagchk = '\0'; 96 tag_f = wsizearg = NULL; 97 lflag = secure = silent = 0; 98 startup = 1; 99 100 /* Set the file snapshot flag. */ 101 F_SET(gp, G_SNAPSHOT); 102 103 #ifdef DEBUG 104 while ((ch = getopt(argc, argv, "c:D:eFlRrSsT:t:vw:")) != EOF) 105 #else 106 while ((ch = getopt(argc, argv, "c:eFlRrSst:vw:")) != EOF) 107 #endif 108 switch (ch) { 109 case 'c': /* Run the command. */ 110 /* 111 * XXX 112 * We should support multiple -c options. 113 */ 114 if (gp->c_option != NULL) { 115 warnx("only one -c command may be specified."); 116 return (1); 117 } 118 gp->c_option = optarg; 119 break; 120 #ifdef DEBUG 121 case 'D': 122 switch (optarg[0]) { 123 case 's': 124 startup = 0; 125 break; 126 case 'w': 127 attach(gp); 128 break; 129 default: 130 warnx("usage: -D requires s or w argument."); 131 return (1); 132 } 133 break; 134 #endif 135 case 'e': /* Ex mode. */ 136 LF_CLR(SC_VI); 137 LF_SET(SC_EX); 138 break; 139 case 'F': /* No snapshot. */ 140 F_CLR(gp, G_SNAPSHOT); 141 break; 142 case 'l': /* Set lisp, showmatch options. */ 143 lflag = 1; 144 break; 145 case 'R': /* Readonly. */ 146 readonly = 1; 147 break; 148 case 'r': /* Recover. */ 149 if (flagchk == 't') { 150 warnx("only one of -r and -t may be specified."); 151 return (1); 152 } 153 flagchk = 'r'; 154 break; 155 case 'S': 156 secure = 1; 157 break; 158 case 's': 159 silent = 1; 160 break; 161 #ifdef DEBUG 162 case 'T': /* Trace. */ 163 if ((gp->tracefp = fopen(optarg, "w")) == NULL) { 164 warn("%s", optarg); 165 goto err; 166 } 167 (void)fprintf(gp->tracefp, 168 "\n===\ntrace: open %s\n", optarg); 169 break; 170 #endif 171 case 't': /* Tag. */ 172 if (flagchk == 'r') { 173 warnx("only one of -r and -t may be specified."); 174 return (1); 175 } 176 if (flagchk == 't') { 177 warnx("only one tag file may be specified."); 178 return (1); 179 } 180 flagchk = 't'; 181 tag_f = optarg; 182 break; 183 case 'v': /* Vi mode. */ 184 LF_CLR(SC_EX); 185 LF_SET(SC_VI); 186 break; 187 case 'w': 188 wsizearg = optarg; 189 break; 190 case '?': 191 default: 192 (void)gp->scr_usage(); 193 return (1); 194 } 195 argc -= optind; 196 argv += optind; 197 198 /* 199 * -s option is only meaningful to ex. 200 * 201 * If not reading from a terminal, it's like -s was specified. 202 */ 203 if (silent && !LF_ISSET(SC_EX)) { 204 warnx("-s option is only applicable to ex."); 205 goto err; 206 } 207 if (LF_ISSET(SC_EX) && F_ISSET(gp, G_SCRIPTED)) 208 silent = 1; 209 210 /* 211 * Build and initialize the first/current screen. This is a bit 212 * tricky. If an error is returned, we may or may not have a 213 * screen structure. If we have a screen structure, put it on a 214 * display queue so that the error messages get displayed. 215 * 216 * !!! 217 * Everything we do until we go interactive is done in ex mode. 218 */ 219 if (screen_init(gp, NULL, &sp)) { 220 if (sp != NULL) 221 TAILQ_INSERT_HEAD(gp->dq, sp, q); 222 goto err; 223 } 224 F_SET(sp, SC_EX); 225 TAILQ_INSERT_HEAD(gp->dq, sp, q); 226 227 if (v_key_init(sp)) /* Special key initialization. */ 228 goto err; 229 230 { int oargs[5], *oargp = oargs; 231 if (lflag) { /* Command-line options. */ 232 *oargp++ = O_LISP; 233 *oargp++ = O_SHOWMATCH; 234 } 235 if (readonly) 236 *oargp++ = O_READONLY; 237 if (secure) 238 *oargp++ = O_SECURE; 239 *oargp = -1; /* Options initialization. */ 240 if (opts_init(sp, oargs)) 241 goto err; 242 } 243 if (wsizearg != NULL) { 244 ARGS *av[2], a, b; 245 (void)SPRINTF(path, SIZE(path), L("window=%s"), wsizearg); 246 a.bp = (CHAR_T *)path; 247 a.len = SIZE(path); 248 b.bp = NULL; 249 b.len = 0; 250 av[0] = &a; 251 av[1] = &b; 252 (void)opts_set(sp, av, NULL); 253 } 254 if (silent) { /* Ex batch mode option values. */ 255 O_CLR(sp, O_AUTOPRINT); 256 O_CLR(sp, O_PROMPT); 257 O_CLR(sp, O_VERBOSE); 258 O_CLR(sp, O_WARN); 259 F_SET(sp, SC_EX_SILENT); 260 } 261 262 sp->rows = O_VAL(sp, O_LINES); /* Make ex formatting work. */ 263 sp->cols = O_VAL(sp, O_COLUMNS); 264 265 if (!silent && startup) { /* Read EXINIT, exrc files. */ 266 if (ex_exrc(sp)) 267 goto err; 268 if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { 269 if (screen_end(sp)) 270 goto err; 271 goto done; 272 } 273 } 274 275 /* 276 * List recovery files if -r specified without file arguments. 277 * Note, options must be initialized and startup information 278 * read before doing this. 279 */ 280 if (flagchk == 'r' && argv[0] == NULL) { 281 if (rcv_list(sp)) 282 goto err; 283 if (screen_end(sp)) 284 goto err; 285 goto done; 286 } 287 288 /* 289 * !!! 290 * Initialize the default ^D, ^U scrolling value here, after the 291 * user has had every opportunity to set the window option. 292 * 293 * It's historic practice that changing the value of the window 294 * option did not alter the default scrolling value, only giving 295 * a count to ^D/^U did that. 296 */ 297 sp->defscroll = (O_VAL(sp, O_WINDOW) + 1) / 2; 298 299 /* 300 * If we don't have a command-line option, switch into the right 301 * editor now, so that we position default files correctly, and 302 * so that any tags file file-already-locked messages are in the 303 * vi screen, not the ex screen. 304 * 305 * XXX 306 * If we have a command-line option, the error message can end 307 * up in the wrong place, but I think that the combination is 308 * unlikely. 309 */ 310 if (gp->c_option == NULL) { 311 F_CLR(sp, SC_EX | SC_VI); 312 F_SET(sp, LF_ISSET(SC_EX | SC_VI)); 313 } 314 315 /* Open a tag file if specified. */ 316 if (tag_f != NULL) { 317 CHAR2INT(sp, tag_f, strlen(tag_f) + 1, w, wlen); 318 if (ex_tag_first(sp, w)) 319 goto err; 320 } 321 322 /* 323 * Append any remaining arguments as file names. Files are recovery 324 * files if -r specified. If the tag option or ex startup commands 325 * loaded a file, then any file arguments are going to come after it. 326 */ 327 if (*argv != NULL) { 328 if (sp->frp != NULL) { 329 /* Cheat -- we know we have an extra argv slot. */ 330 *--argv = strdup(sp->frp->name); 331 if (*argv == NULL) { 332 warn(NULL); 333 goto err; 334 } 335 } 336 sp->argv = sp->cargv = argv; 337 F_SET(sp, SC_ARGNOFREE); 338 if (flagchk == 'r') 339 F_SET(sp, SC_ARGRECOVER); 340 } 341 342 /* 343 * If the ex startup commands and or/the tag option haven't already 344 * created a file, create one. If no command-line files were given, 345 * use a temporary file. 346 */ 347 if (sp->frp == NULL) { 348 if (sp->argv == NULL) { 349 if ((frp = file_add(sp, NULL)) == NULL) 350 goto err; 351 } else { 352 if ((frp = file_add(sp, sp->argv[0])) == NULL) 353 goto err; 354 if (F_ISSET(sp, SC_ARGRECOVER)) 355 F_SET(frp, FR_RECOVER); 356 } 357 358 if (file_init(sp, frp, NULL, 0)) 359 goto err; 360 if (EXCMD_RUNNING(gp)) { 361 (void)ex_cmd(sp); 362 if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { 363 if (screen_end(sp)) 364 goto err; 365 goto done; 366 } 367 } 368 } 369 370 /* 371 * Check to see if we need to wait for ex. If SC_SCR_EX is set, ex 372 * was forced to initialize the screen during startup. We'd like to 373 * wait for a single character from the user, but we can't because 374 * we're not in raw mode. We can't switch to raw mode because the 375 * vi initialization will switch to xterm's alternate screen, causing 376 * us to lose the messages we're pausing to make sure the user read. 377 * So, wait for a complete line. 378 */ 379 if (F_ISSET(sp, SC_SCR_EX)) { 380 p = msg_cmsg(sp, CMSG_CONT_R, &len); 381 (void)write(STDOUT_FILENO, p, len); 382 for (;;) { 383 if (v_event_get(sp, &ev, 0, 0)) 384 goto err; 385 if (ev.e_event == E_INTERRUPT || 386 (ev.e_event == E_CHARACTER && 387 (ev.e_value == K_CR || ev.e_value == K_NL))) 388 break; 389 (void)gp->scr_bell(sp); 390 } 391 } 392 393 /* Switch into the right editor, regardless. */ 394 F_CLR(sp, SC_EX | SC_VI); 395 F_SET(sp, LF_ISSET(SC_EX | SC_VI) | SC_STATUS_CNT); 396 397 /* 398 * Main edit loop. Vi handles split screens itself, we only return 399 * here when switching editor modes or restarting the screen. 400 */ 401 while (sp != NULL) 402 if (F_ISSET(sp, SC_EX) ? ex(&sp) : vi(&sp)) 403 goto err; 404 405 done: rval = 0; 406 if (0) 407 err: rval = 1; 408 409 /* Clean out the global structure. */ 410 v_end(gp); 411 412 return (rval); 413 } 414 415 /* 416 * v_end -- 417 * End the program, discarding screens and most of the global area. 418 * 419 * PUBLIC: void v_end(GS *); 420 */ 421 void 422 v_end(GS *gp) 423 { 424 MSGS *mp; 425 SCR *sp; 426 427 /* If there are any remaining screens, kill them off. */ 428 if (gp->ccl_sp != NULL) { 429 (void)file_end(gp->ccl_sp, NULL, 1); 430 (void)screen_end(gp->ccl_sp); 431 } 432 while ((sp = TAILQ_FIRST(gp->dq)) != NULL) 433 (void)screen_end(sp); 434 while ((sp = TAILQ_FIRST(gp->hq)) != NULL) 435 (void)screen_end(sp); 436 437 #if defined(DEBUG) || defined(PURIFY) 438 { FREF *frp; 439 /* Free FREF's. */ 440 while ((frp = TAILQ_FIRST(gp->frefq)) != NULL) { 441 TAILQ_REMOVE(gp->frefq, frp, q); 442 free(frp->name); 443 free(frp->tname); 444 free(frp); 445 } 446 } 447 448 /* Free key input queue. */ 449 free(gp->i_event); 450 451 /* Free cut buffers. */ 452 cut_close(gp); 453 454 /* Free map sequences. */ 455 seq_close(gp); 456 457 /* Free default buffer storage. */ 458 (void)text_lfree(gp->dcb_store.textq); 459 460 /* Close message catalogs. */ 461 msg_close(gp); 462 #endif 463 464 /* Ring the bell if scheduled. */ 465 if (F_ISSET(gp, G_BELLSCHED)) 466 (void)fprintf(stderr, "\07"); /* \a */ 467 468 /* 469 * Flush any remaining messages. If a message is here, it's almost 470 * certainly the message about the event that killed us (although 471 * it's possible that the user is sourcing a file that exits from the 472 * editor). 473 */ 474 while ((mp = SLIST_FIRST(gp->msgq)) != NULL) { 475 (void)fprintf(stderr, "%s%.*s", 476 mp->mtype == M_ERR ? "ex/vi: " : "", (int)mp->len, mp->buf); 477 SLIST_REMOVE_HEAD(gp->msgq, q); 478 #if defined(DEBUG) || defined(PURIFY) 479 free(mp->buf); 480 free(mp); 481 #endif 482 } 483 484 #if defined(DEBUG) || defined(PURIFY) 485 /* Free any temporary space. */ 486 free(gp->tmp_bp); 487 488 #if defined(DEBUG) 489 /* Close debugging file descriptor. */ 490 if (gp->tracefp != NULL) 491 (void)fclose(gp->tracefp); 492 #endif 493 #endif 494 } 495 496 /* 497 * v_obsolete -- 498 * Convert historic arguments into something getopt(3) will like. 499 */ 500 static int 501 v_obsolete(char *argv[]) 502 { 503 size_t len; 504 char *p; 505 506 /* 507 * Translate old style arguments into something getopt will like. 508 * Make sure it's not text space memory, because ex modifies the 509 * strings. 510 * Change "+" into "-c$". 511 * Change "+<anything else>" into "-c<anything else>". 512 * Change "-" into "-s" 513 * The c, T, t and w options take arguments so they can't be 514 * special arguments. 515 * 516 * Stop if we find "--" as an argument, the user may want to edit 517 * a file named "+foo". 518 */ 519 while (*++argv && strcmp(argv[0], "--")) 520 if (argv[0][0] == '+') { 521 if (argv[0][1] == '\0') { 522 argv[0] = strdup("-c$"); 523 if (argv[0] == NULL) 524 goto nomem; 525 } else { 526 p = argv[0]; 527 len = strlen(argv[0]); 528 argv[0] = malloc(len + 2); 529 if (argv[0] == NULL) 530 goto nomem; 531 argv[0][0] = '-'; 532 argv[0][1] = 'c'; 533 (void)strlcpy(argv[0] + 2, p + 1, len); 534 } 535 } else if (argv[0][0] == '-') { 536 if (argv[0][1] == '\0') { 537 argv[0] = strdup("-s"); 538 if (argv[0] == NULL) { 539 nomem: warn(NULL); 540 return (1); 541 } 542 } else 543 if ((argv[0][1] == 'c' || argv[0][1] == 'T' || 544 argv[0][1] == 't' || argv[0][1] == 'w') && 545 argv[0][2] == '\0') 546 ++argv; 547 } 548 return (0); 549 } 550 551 #ifdef DEBUG 552 static void 553 attach(GS *gp) 554 { 555 int fd; 556 char ch; 557 558 if ((fd = open(_PATH_TTY, O_RDONLY, 0)) < 0) { 559 warn("%s", _PATH_TTY); 560 return; 561 } 562 563 (void)printf("process %lu waiting, enter <CR> to continue: ", 564 (u_long)getpid()); 565 (void)fflush(stdout); 566 567 do { 568 if (read(fd, &ch, 1) != 1) { 569 (void)close(fd); 570 return; 571 } 572 } while (ch != '\n' && ch != '\r'); 573 (void)close(fd); 574 } 575 #endif 576