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