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