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