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