1 /* $OpenBSD: main.c,v 1.41 2017/11/10 18:31:36 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, 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 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 'R': /* Readonly. */ 167 readonly = 1; 168 break; 169 case 'r': /* Recover. */ 170 if (flagchk == 't') { 171 warnx( 172 "only one of -r and -t may be specified."); 173 return (1); 174 } 175 flagchk = 'r'; 176 break; 177 case 'S': 178 secure = 1; 179 break; 180 case 's': 181 silent = 1; 182 break; 183 #ifdef DEBUG 184 case 'T': /* Trace. */ 185 if ((gp->tracefp = fopen(optarg, "w")) == NULL) { 186 warn("%s", optarg); 187 goto err; 188 } 189 (void)fprintf(gp->tracefp, 190 "\n===\ntrace: open %s\n", optarg); 191 fflush(gp->tracefp); 192 break; 193 #endif 194 case 't': /* Tag. */ 195 if (flagchk == 'r') { 196 warnx( 197 "only one of -r and -t may be specified."); 198 return (1); 199 } 200 if (flagchk == 't') { 201 warnx("only one tag file may be specified."); 202 return (1); 203 } 204 flagchk = 't'; 205 tag_f = optarg; 206 break; 207 case 'v': /* Vi mode. */ 208 LF_CLR(SC_EX); 209 LF_SET(SC_VI); 210 break; 211 case 'w': 212 wsizearg = optarg; 213 break; 214 case '?': 215 default: 216 (void)gp->scr_usage(); 217 return (1); 218 } 219 argc -= optind; 220 argv += optind; 221 222 if (secure) 223 if (pledge("stdio rpath wpath cpath fattr flock getpw tty", NULL) == -1) { 224 perror("pledge"); 225 goto err; 226 } 227 228 /* 229 * -s option is only meaningful to ex. 230 * 231 * If not reading from a terminal, it's like -s was specified. 232 */ 233 if (silent && !LF_ISSET(SC_EX)) { 234 warnx("-s option is only applicable to ex."); 235 goto err; 236 } 237 if (LF_ISSET(SC_EX) && F_ISSET(gp, G_SCRIPTED)) 238 silent = 1; 239 240 /* 241 * Build and initialize the first/current screen. This is a bit 242 * tricky. If an error is returned, we may or may not have a 243 * screen structure. If we have a screen structure, put it on a 244 * display queue so that the error messages get displayed. 245 * 246 * !!! 247 * Everything we do until we go interactive is done in ex mode. 248 */ 249 if (screen_init(gp, NULL, &sp)) { 250 if (sp != NULL) 251 TAILQ_INSERT_HEAD(&gp->dq, sp, q); 252 goto err; 253 } 254 F_SET(sp, SC_EX); 255 TAILQ_INSERT_HEAD(&gp->dq, sp, q); 256 257 if (v_key_init(sp)) /* Special key initialization. */ 258 goto err; 259 260 { int oargs[5], *oargp = oargs; 261 if (readonly) /* Command-line options. */ 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 if ((*--argv = malloc(l)) == NULL) { 356 warn(NULL); 357 goto err; 358 } 359 (void)strlcpy(*argv, sp->frp->name, l); 360 } 361 sp->argv = sp->cargv = argv; 362 F_SET(sp, SC_ARGNOFREE); 363 if (flagchk == 'r') 364 F_SET(sp, SC_ARGRECOVER); 365 } 366 367 /* 368 * If the ex startup commands and or/the tag option haven't already 369 * created a file, create one. If no command-line files were given, 370 * use a temporary file. 371 */ 372 if (sp->frp == NULL) { 373 if (sp->argv == NULL) { 374 if ((frp = file_add(sp, NULL)) == NULL) 375 goto err; 376 } else { 377 if ((frp = file_add(sp, (CHAR_T *)sp->argv[0])) == NULL) 378 goto err; 379 if (F_ISSET(sp, SC_ARGRECOVER)) 380 F_SET(frp, FR_RECOVER); 381 } 382 383 if (file_init(sp, frp, NULL, 0)) 384 goto err; 385 if (EXCMD_RUNNING(gp)) { 386 (void)ex_cmd(sp); 387 if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { 388 if (screen_end(sp)) 389 goto err; 390 goto done; 391 } 392 } 393 } 394 395 /* 396 * Check to see if we need to wait for ex. If SC_SCR_EX is set, ex 397 * was forced to initialize the screen during startup. We'd like to 398 * wait for a single character from the user, but we can't because 399 * we're not in raw mode. We can't switch to raw mode because the 400 * vi initialization will switch to xterm's alternate screen, causing 401 * us to lose the messages we're pausing to make sure the user read. 402 * So, wait for a complete line. 403 */ 404 if (F_ISSET(sp, SC_SCR_EX)) { 405 p = msg_cmsg(sp, CMSG_CONT_R, &len); 406 (void)write(STDOUT_FILENO, p, len); 407 for (;;) { 408 if (v_event_get(sp, &ev, 0, 0)) 409 goto err; 410 if (ev.e_event == E_INTERRUPT || 411 (ev.e_event == E_CHARACTER && 412 (ev.e_value == K_CR || ev.e_value == K_NL))) 413 break; 414 (void)gp->scr_bell(sp); 415 } 416 } 417 418 /* Switch into the right editor, regardless. */ 419 F_CLR(sp, SC_EX | SC_VI); 420 F_SET(sp, LF_ISSET(SC_EX | SC_VI) | SC_STATUS_CNT); 421 422 /* 423 * Main edit loop. Vi handles split screens itself, we only return 424 * here when switching editor modes or restarting the screen. 425 */ 426 while (sp != NULL) 427 if (F_ISSET(sp, SC_EX) ? ex(&sp) : vi(&sp)) 428 goto err; 429 430 done: rval = 0; 431 if (0) 432 err: rval = 1; 433 434 /* Clean out the global structure. */ 435 v_end(gp); 436 437 return (rval); 438 } 439 440 /* 441 * v_end -- 442 * End the program, discarding screens and most of the global area. 443 * 444 * PUBLIC: void v_end(GS *); 445 */ 446 void 447 v_end(GS *gp) 448 { 449 MSGS *mp; 450 SCR *sp; 451 452 /* If there are any remaining screens, kill them off. */ 453 if (gp->ccl_sp != NULL) { 454 (void)file_end(gp->ccl_sp, NULL, 1); 455 (void)screen_end(gp->ccl_sp); 456 } 457 while ((sp = TAILQ_FIRST(&gp->dq))) 458 (void)screen_end(sp); /* Removes sp from the queue. */ 459 while ((sp = TAILQ_FIRST(&gp->hq))) 460 (void)screen_end(sp); /* Removes sp from the queue. */ 461 462 #if defined(DEBUG) || defined(PURIFY) 463 { FREF *frp; 464 /* Free FREF's. */ 465 while ((frp = TAILQ_FIRST(&gp->frefq))) { 466 TAILQ_REMOVE(&gp->frefq, frp, q); 467 free(frp->name); 468 free(frp->tname); 469 free(frp); 470 } 471 } 472 473 /* Free key input queue. */ 474 free(gp->i_event); 475 476 /* Free cut buffers. */ 477 cut_close(gp); 478 479 /* Free map sequences. */ 480 seq_close(gp); 481 482 /* Free default buffer storage. */ 483 (void)text_lfree(&gp->dcb_store.textq); 484 #endif 485 486 /* Ring the bell if scheduled. */ 487 if (F_ISSET(gp, G_BELLSCHED)) 488 (void)fprintf(stderr, "\07"); /* \a */ 489 490 /* 491 * Flush any remaining messages. If a message is here, it's almost 492 * certainly the message about the event that killed us (although 493 * it's possible that the user is sourcing a file that exits from the 494 * editor). 495 */ 496 while ((mp = LIST_FIRST(&gp->msgq)) != NULL) { 497 (void)fprintf(stderr, "%s%.*s", 498 mp->mtype == M_ERR ? "ex/vi: " : "", (int)mp->len, mp->buf); 499 LIST_REMOVE(mp, q); 500 #if defined(DEBUG) || defined(PURIFY) 501 free(mp->buf); 502 free(mp); 503 #endif 504 } 505 506 #if defined(DEBUG) || defined(PURIFY) 507 /* Free any temporary space. */ 508 free(gp->tmp_bp); 509 510 #if defined(DEBUG) 511 /* Close debugging file descriptor. */ 512 if (gp->tracefp != NULL) 513 (void)fclose(gp->tracefp); 514 #endif 515 #endif 516 } 517 518 /* 519 * v_obsolete -- 520 * Convert historic arguments into something getopt(3) will like. 521 */ 522 static int 523 v_obsolete(char *argv[]) 524 { 525 size_t len; 526 char *p; 527 528 /* 529 * Translate old style arguments into something getopt will like. 530 * Make sure it's not text space memory, because ex modifies the 531 * strings. 532 * Change "+" into "-c$". 533 * Change "+<anything else>" into "-c<anything else>". 534 * Change "-" into "-s" 535 * The c, T, t and w options take arguments so they can't be 536 * special arguments. 537 * 538 * Stop if we find "--" as an argument, the user may want to edit 539 * a file named "+foo". 540 */ 541 while (*++argv && strcmp(argv[0], "--")) 542 if (argv[0][0] == '+') { 543 if (argv[0][1] == '\0') { 544 argv[0] = strdup("-c$"); 545 if (argv[0] == NULL) 546 goto nomem; 547 } else { 548 p = argv[0]; 549 len = strlen(argv[0]); 550 if ((argv[0] = malloc(len + 2)) == NULL) 551 goto nomem; 552 argv[0][0] = '-'; 553 argv[0][1] = 'c'; 554 (void)strlcpy(argv[0] + 2, p + 1, len); 555 } 556 } else if (argv[0][0] == '-') { 557 if (argv[0][1] == '\0') { 558 argv[0] = strdup("-s"); 559 if (argv[0] == NULL) { 560 nomem: warn(NULL); 561 return (1); 562 } 563 } else 564 if ((argv[0][1] == 'c' || argv[0][1] == 'T' || 565 argv[0][1] == 't' || argv[0][1] == 'w') && 566 argv[0][2] == '\0') 567 ++argv; 568 } 569 return (0); 570 } 571 572 #ifdef DEBUG 573 static void 574 attach(GS *gp) 575 { 576 int fd; 577 char ch; 578 579 if ((fd = open(_PATH_TTY, O_RDONLY, 0)) < 0) { 580 warn("%s", _PATH_TTY); 581 return; 582 } 583 584 (void)printf("process %ld waiting, enter <CR> to continue: ", 585 (long)getpid()); 586 (void)fflush(stdout); 587 588 do { 589 if (read(fd, &ch, 1) != 1) { 590 (void)close(fd); 591 return; 592 } 593 } while (ch != '\n' && ch != '\r'); 594 (void)close(fd); 595 } 596 #endif 597