/* * PUBLIC: int f_print __P((SCR *, OPTION *, char *, u_long *)); */ int f_print( SCR *sp, OPTION *op, char *str, u_long *valp) { int offset = op - sp->opts; /* Preset the value, needed for reinitialization of lookup table. */ if (offset == O_OCTAL) { if (*valp) O_SET(sp, offset); else O_CLR(sp, offset); } else if (o_set(sp, offset, OS_STRDUP, str, 0)) return(1); /* Reinitialize the key fast lookup table. */ v_key_ilookup(sp); /* Reformat the screen. */ F_SET(sp, SC_SCR_REFORMAT); return (0); }
/* * PUBLIC: int f_altwerase __P((SCR *, OPTION *, const char *, u_long *)); */ int f_altwerase(SCR *sp, OPTION *op, const char *str, u_long *valp) { if (*valp) O_CLR(sp, O_TTYWERASE); return (0); }
/* * PUBLIC: int f_ttywerase __P((SCR *, OPTION *, char *, u_long *)); */ int f_ttywerase( SCR *sp, OPTION *op, char *str, u_long *valp) { if (*valp) O_CLR(sp, O_ALTWERASE); return (0); }
/* * editor -- * Main editor routine. * * PUBLIC: int editor(GS *, int, char *[]); */ int editor(GS *gp, int argc, char *argv[]) { extern int optind; extern char *optarg; const char *p; EVENT ev; FREF *frp; SCR *sp; size_t len; u_int flags; int ch, flagchk, lflag, secure, startup, readonly, rval, silent; char *tag_f, *wsizearg, path[256]; CHAR_T *w; size_t wlen; /* Initialize the busy routine, if not defined by the screen. */ if (gp->scr_busy == NULL) gp->scr_busy = vs_busy; /* Initialize the message routine, if not defined by the screen. */ if (gp->scr_msg == NULL) gp->scr_msg = vs_msg; gp->catd = (nl_catd)-1; /* Common global structure initialization. */ TAILQ_INIT(gp->dq); TAILQ_INIT(gp->hq); SLIST_INIT(gp->ecq); SLIST_INSERT_HEAD(gp->ecq, &gp->excmd, q); gp->noprint = DEFAULT_NOPRINT; /* Structures shared by screens so stored in the GS structure. */ TAILQ_INIT(gp->frefq); TAILQ_INIT(gp->dcb_store.textq); SLIST_INIT(gp->cutq); SLIST_INIT(gp->seqq); /* Set initial screen type and mode based on the program name. */ readonly = 0; if (!strcmp(getprogname(), "ex") || !strcmp(getprogname(), "nex")) LF_INIT(SC_EX); else { /* Nview, view are readonly. */ if (!strcmp(getprogname(), "nview") || !strcmp(getprogname(), "view")) readonly = 1; /* Vi is the default. */ LF_INIT(SC_VI); } /* Convert old-style arguments into new-style ones. */ if (v_obsolete(argv)) return (1); /* Parse the arguments. */ flagchk = '\0'; tag_f = wsizearg = NULL; lflag = secure = silent = 0; startup = 1; /* Set the file snapshot flag. */ F_SET(gp, G_SNAPSHOT); #ifdef DEBUG while ((ch = getopt(argc, argv, "c:D:eFlRrSsT:t:vw:")) != EOF) #else while ((ch = getopt(argc, argv, "c:eFlRrSst:vw:")) != EOF) #endif switch (ch) { case 'c': /* Run the command. */ /* * XXX * We should support multiple -c options. */ if (gp->c_option != NULL) { warnx("only one -c command may be specified."); return (1); } gp->c_option = optarg; break; #ifdef DEBUG case 'D': switch (optarg[0]) { case 's': startup = 0; break; case 'w': attach(gp); break; default: warnx("usage: -D requires s or w argument."); return (1); } break; #endif case 'e': /* Ex mode. */ LF_CLR(SC_VI); LF_SET(SC_EX); break; case 'F': /* No snapshot. */ F_CLR(gp, G_SNAPSHOT); break; case 'l': /* Set lisp, showmatch options. */ lflag = 1; break; case 'R': /* Readonly. */ readonly = 1; break; case 'r': /* Recover. */ if (flagchk == 't') { warnx("only one of -r and -t may be specified."); return (1); } flagchk = 'r'; break; case 'S': secure = 1; break; case 's': silent = 1; break; #ifdef DEBUG case 'T': /* Trace. */ if ((gp->tracefp = fopen(optarg, "w")) == NULL) { warn("%s", optarg); goto err; } (void)fprintf(gp->tracefp, "\n===\ntrace: open %s\n", optarg); break; #endif case 't': /* Tag. */ if (flagchk == 'r') { warnx("only one of -r and -t may be specified."); return (1); } if (flagchk == 't') { warnx("only one tag file may be specified."); return (1); } flagchk = 't'; tag_f = optarg; break; case 'v': /* Vi mode. */ LF_CLR(SC_EX); LF_SET(SC_VI); break; case 'w': wsizearg = optarg; break; case '?': default: (void)gp->scr_usage(); return (1); } argc -= optind; argv += optind; /* * -s option is only meaningful to ex. * * If not reading from a terminal, it's like -s was specified. */ if (silent && !LF_ISSET(SC_EX)) { warnx("-s option is only applicable to ex."); goto err; } if (LF_ISSET(SC_EX) && F_ISSET(gp, G_SCRIPTED)) silent = 1; /* * Build and initialize the first/current screen. This is a bit * tricky. If an error is returned, we may or may not have a * screen structure. If we have a screen structure, put it on a * display queue so that the error messages get displayed. * * !!! * Everything we do until we go interactive is done in ex mode. */ if (screen_init(gp, NULL, &sp)) { if (sp != NULL) TAILQ_INSERT_HEAD(gp->dq, sp, q); goto err; } F_SET(sp, SC_EX); TAILQ_INSERT_HEAD(gp->dq, sp, q); if (v_key_init(sp)) /* Special key initialization. */ goto err; { int oargs[5], *oargp = oargs; if (lflag) { /* Command-line options. */ *oargp++ = O_LISP; *oargp++ = O_SHOWMATCH; } if (readonly) *oargp++ = O_READONLY; if (secure) *oargp++ = O_SECURE; *oargp = -1; /* Options initialization. */ if (opts_init(sp, oargs)) goto err; } if (wsizearg != NULL) { ARGS *av[2], a, b; (void)snprintf(path, sizeof(path), "window=%s", wsizearg); a.bp = (CHAR_T *)path; a.len = strlen(path); b.bp = NULL; b.len = 0; av[0] = &a; av[1] = &b; (void)opts_set(sp, av, NULL); } if (silent) { /* Ex batch mode option values. */ O_CLR(sp, O_AUTOPRINT); O_CLR(sp, O_PROMPT); O_CLR(sp, O_VERBOSE); O_CLR(sp, O_WARN); F_SET(sp, SC_EX_SILENT); } sp->rows = O_VAL(sp, O_LINES); /* Make ex formatting work. */ sp->cols = O_VAL(sp, O_COLUMNS); if (!silent && startup) { /* Read EXINIT, exrc files. */ if (ex_exrc(sp)) goto err; if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { if (screen_end(sp)) goto err; goto done; } } /* * List recovery files if -r specified without file arguments. * Note, options must be initialized and startup information * read before doing this. */ if (flagchk == 'r' && argv[0] == NULL) { if (rcv_list(sp)) goto err; if (screen_end(sp)) goto err; goto done; } /* * !!! * Initialize the default ^D, ^U scrolling value here, after the * user has had every opportunity to set the window option. * * It's historic practice that changing the value of the window * option did not alter the default scrolling value, only giving * a count to ^D/^U did that. */ sp->defscroll = (O_VAL(sp, O_WINDOW) + 1) / 2; /* * If we don't have a command-line option, switch into the right * editor now, so that we position default files correctly, and * so that any tags file file-already-locked messages are in the * vi screen, not the ex screen. * * XXX * If we have a command-line option, the error message can end * up in the wrong place, but I think that the combination is * unlikely. */ if (gp->c_option == NULL) { F_CLR(sp, SC_EX | SC_VI); F_SET(sp, LF_ISSET(SC_EX | SC_VI)); } /* Open a tag file if specified. */ if (tag_f != NULL) { CHAR2INT(sp, tag_f, strlen(tag_f) + 1, w, wlen); if (ex_tag_first(sp, w)) goto err; } /* * Append any remaining arguments as file names. Files are recovery * files if -r specified. If the tag option or ex startup commands * loaded a file, then any file arguments are going to come after it. */ if (*argv != NULL) { if (sp->frp != NULL) { /* Cheat -- we know we have an extra argv slot. */ *--argv = strdup(sp->frp->name); if (*argv == NULL) { warn(NULL); goto err; } } sp->argv = sp->cargv = argv; F_SET(sp, SC_ARGNOFREE); if (flagchk == 'r') F_SET(sp, SC_ARGRECOVER); } /* * If the ex startup commands and or/the tag option haven't already * created a file, create one. If no command-line files were given, * use a temporary file. */ if (sp->frp == NULL) { if (sp->argv == NULL) { if ((frp = file_add(sp, NULL)) == NULL) goto err; } else { if ((frp = file_add(sp, sp->argv[0])) == NULL) goto err; if (F_ISSET(sp, SC_ARGRECOVER)) F_SET(frp, FR_RECOVER); } if (file_init(sp, frp, NULL, 0)) goto err; if (EXCMD_RUNNING(gp)) { (void)ex_cmd(sp); if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { if (screen_end(sp)) goto err; goto done; } } } /* * Check to see if we need to wait for ex. If SC_SCR_EX is set, ex * was forced to initialize the screen during startup. We'd like to * wait for a single character from the user, but we can't because * we're not in raw mode. We can't switch to raw mode because the * vi initialization will switch to xterm's alternate screen, causing * us to lose the messages we're pausing to make sure the user read. * So, wait for a complete line. */ if (F_ISSET(sp, SC_SCR_EX)) { p = msg_cmsg(sp, CMSG_CONT_R, &len); (void)write(STDOUT_FILENO, p, len); for (;;) { if (v_event_get(sp, &ev, 0, 0)) goto err; if (ev.e_event == E_INTERRUPT || (ev.e_event == E_CHARACTER && (ev.e_value == K_CR || ev.e_value == K_NL))) break; (void)gp->scr_bell(sp); } } /* Switch into the right editor, regardless. */ F_CLR(sp, SC_EX | SC_VI); F_SET(sp, LF_ISSET(SC_EX | SC_VI) | SC_STATUS_CNT); /* * Main edit loop. Vi handles split screens itself, we only return * here when switching editor modes or restarting the screen. */ while (sp != NULL) if (F_ISSET(sp, SC_EX) ? ex(&sp) : vi(&sp)) goto err; done: rval = 0; if (0) err: rval = 1; /* Clean out the global structure. */ v_end(gp); return (rval); }
/* * editor -- * Main editor routine. * * PUBLIC: int editor __P((WIN *, int, char *[])); */ int editor(WIN *wp, int argc, char **argv) { extern int optind; extern char *optarg; const char *p; EVENT ev; FREF *frp; SCR *sp; GS *gp; size_t len; u_int flags; int ch, flagchk, lflag, secure, startup, readonly, rval, silent; char *tag_f, *wsizearg, path[256]; CHAR_T *w; size_t wlen; gp = wp->gp; /* Initialize the busy routine, if not defined by the screen. */ if (gp->scr_busy == NULL) gp->scr_busy = vs_busy; /* Initialize the message routine, if not defined by the screen. */ if (wp->scr_msg == NULL) wp->scr_msg = vs_msg; /* Set initial screen type and mode based on the program name. */ readonly = 0; if (!strcmp(gp->progname, "ex") || !strcmp(gp->progname, "nex")) LF_INIT(SC_EX); else { /* Nview, view are readonly. */ if (!strcmp(gp->progname, "nview") || !strcmp(gp->progname, "view")) readonly = 1; /* Vi is the default. */ LF_INIT(SC_VI); } /* Convert old-style arguments into new-style ones. */ if (v_obsolete(gp->progname, argv)) return (1); /* Parse the arguments. */ flagchk = '\0'; tag_f = wsizearg = NULL; lflag = secure = silent = 0; startup = 1; /* Set the file snapshot flag. */ F_SET(gp, G_SNAPSHOT); #ifdef DEBUG while ((ch = getopt(argc, argv, "c:D:eFlRrSsT:t:vw:")) != EOF) #else while ((ch = getopt(argc, argv, "c:eFlRrSst:vw:")) != EOF) #endif switch (ch) { case 'c': /* Run the command. */ /* * XXX * We should support multiple -c options. */ if (gp->c_option != NULL) { v_estr(gp->progname, 0, "only one -c command may be specified."); return (1); } gp->c_option = optarg; break; #ifdef DEBUG case 'D': switch (optarg[0]) { case 's': startup = 0; break; case 'w': attach(gp); break; default: v_estr(gp->progname, 0, "usage: -D requires s or w argument."); return (1); } break; #endif case 'e': /* Ex mode. */ LF_CLR(SC_VI); LF_SET(SC_EX); break; case 'F': /* No snapshot. */ v_estr(gp->progname, 0, "-F option no longer supported."); break; case 'l': /* Set lisp, showmatch options. */ lflag = 1; break; case 'R': /* Readonly. */ readonly = 1; break; case 'r': /* Recover. */ if (flagchk == 't') { v_estr(gp->progname, 0, "only one of -r and -t may be specified."); return (1); } flagchk = 'r'; break; case 'S': secure = 1; break; case 's': silent = 1; break; #ifdef TRACE case 'T': /* Trace. */ (void)vtrace_init(optarg); break; #endif case 't': /* Tag. */ if (flagchk == 'r') { v_estr(gp->progname, 0, "only one of -r and -t may be specified."); return (1); } if (flagchk == 't') { v_estr(gp->progname, 0, "only one tag file may be specified."); return (1); } flagchk = 't'; tag_f = optarg; break; case 'v': /* Vi mode. */ LF_CLR(SC_EX); LF_SET(SC_VI); break; case 'w': wsizearg = optarg; break; case '?': default: (void)gp->scr_usage(); return (1); } argc -= optind; argv += optind; /* * -s option is only meaningful to ex. * * If not reading from a terminal, it's like -s was specified. */ if (silent && !LF_ISSET(SC_EX)) { v_estr(gp->progname, 0, "-s option is only applicable to ex."); goto err; } if (LF_ISSET(SC_EX) && F_ISSET(gp, G_SCRIPTED)) silent = 1; /* * Build and initialize the first/current screen. This is a bit * tricky. If an error is returned, we may or may not have a * screen structure. If we have a screen structure, put it on a * display queue so that the error messages get displayed. * * !!! * Everything we do until we go interactive is done in ex mode. */ if (screen_init(gp, NULL, &sp)) { if (sp != NULL) { CIRCLEQ_INSERT_HEAD(&wp->scrq, sp, q); sp->wp = wp; } goto err; } F_SET(sp, SC_EX); CIRCLEQ_INSERT_HEAD(&wp->scrq, sp, q); sp->wp = wp; if (v_key_init(sp)) /* Special key initialization. */ goto err; { int oargs[5], *oargp = oargs; if (lflag) { /* Command-line options. */ *oargp++ = O_LISP; *oargp++ = O_SHOWMATCH; } if (readonly) *oargp++ = O_READONLY; if (secure) *oargp++ = O_SECURE; *oargp = -1; /* Options initialization. */ if (opts_init(sp, oargs)) goto err; } if (wsizearg != NULL) { ARGS *av[2], a, b; (void)snprintf(path, sizeof(path), "window=%s", wsizearg); a.bp = (CHAR_T *)path; a.len = strlen(path); b.bp = NULL; b.len = 0; av[0] = &a; av[1] = &b; (void)opts_set(sp, av, NULL); } if (silent) { /* Ex batch mode option values. */ O_CLR(sp, O_AUTOPRINT); O_CLR(sp, O_PROMPT); O_CLR(sp, O_VERBOSE); O_CLR(sp, O_WARN); F_SET(sp, SC_EX_SILENT); } sp->rows = O_VAL(sp, O_LINES); /* Make ex formatting work. */ sp->cols = O_VAL(sp, O_COLUMNS); if (!silent && startup) { /* Read EXINIT, exrc files. */ if (ex_exrc(sp)) goto err; if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { if (screen_end(sp)) goto err; goto done; } } /* * List recovery files if -r specified without file arguments. * Note, options must be initialized and startup information * read before doing this. */ if (flagchk == 'r' && argv[0] == NULL) { if (rcv_list(sp)) goto err; if (screen_end(sp)) goto err; goto done; } /* * !!! * Initialize the default ^D, ^U scrolling value here, after the * user has had every opportunity to set the window option. * * It's historic practice that changing the value of the window * option did not alter the default scrolling value, only giving * a count to ^D/^U did that. */ sp->defscroll = (O_VAL(sp, O_WINDOW) + 1) / 2; /* * If we don't have a command-line option, switch into the right * editor now, so that we position default files correctly, and * so that any tags file file-already-locked messages are in the * vi screen, not the ex screen. * * XXX * If we have a command-line option, the error message can end * up in the wrong place, but I think that the combination is * unlikely. */ if (gp->c_option == NULL) { F_CLR(sp, SC_EX | SC_VI); F_SET(sp, LF_ISSET(SC_EX | SC_VI)); } /* Open a tag file if specified. */ if (tag_f != NULL) { CHAR2INT(sp, tag_f, strlen(tag_f) + 1, w, wlen); if (ex_tag_first(sp, w)) goto err; } /* * Append any remaining arguments as file names. Files are recovery * files if -r specified. If the tag option or ex startup commands * loaded a file, then any file arguments are going to come after it. */ if (*argv != NULL) { if (sp->frp != NULL) { /* Cheat -- we know we have an extra argv slot. */ MALLOC_NOMSG(sp, *--argv, char *, strlen(sp->frp->name) + 1); if (*argv == NULL) { v_estr(gp->progname, errno, NULL); goto err; } (void)strcpy(*argv, sp->frp->name); } sp->argv = sp->cargv = argv; F_SET(sp, SC_ARGNOFREE); if (flagchk == 'r') F_SET(sp, SC_ARGRECOVER); }
/* * opts_set -- * Change the values of one or more options. * * PUBLIC: int opts_set __P((SCR *, ARGS *[], const char *)); */ int opts_set(SCR *sp, ARGS **argv, const char *usage) { enum optdisp disp; enum nresult nret; OPTLIST const *op; OPTION *spo; u_long isset, turnoff, value; int ch, equals, nf, nf2, offset, qmark, rval; CHAR_T *endp, *name, *p, *sep; char *p2, *t2; const char *np; size_t nlen; disp = NO_DISPLAY; for (rval = 0; argv[0]->len != 0; ++argv) { /* * The historic vi dumped the options for each occurrence of * "all" in the set list. Puhleeze. */ if (!STRCMP(argv[0]->bp, L("all"))) { disp = ALL_DISPLAY; continue; } /* Find equals sign or question mark. */ for (sep = NULL, equals = qmark = 0, p = name = argv[0]->bp; (ch = *p) != '\0'; ++p) if (ch == '=' || ch == '?') { if (p == name) { if (usage != NULL) msgq(sp, M_ERR, "032|Usage: %s", usage); return (1); } sep = p; if (ch == '=') equals = 1; else qmark = 1; break; } turnoff = 0; op = NULL; if (sep != NULL) *sep++ = '\0'; /* Search for the name, then name without any leading "no". */ if ((op = opts_search(name)) == NULL && name[0] == L('n') && name[1] == L('o')) { turnoff = 1; name += 2; op = opts_search(name); } if (op == NULL) { opts_nomatch(sp, name); rval = 1; continue; } /* Find current option values. */ offset = op - optlist; spo = sp->opts + offset; /* * !!! * Historically, the question mark could be a separate * argument. */ if (!equals && !qmark && argv[1]->len == 1 && argv[1]->bp[0] == '?') { ++argv; qmark = 1; } /* Set name, value. */ switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: /* Some options may not be reset. */ if (F_ISSET(op, OPT_NOUNSET) && turnoff) { msgq_wstr(sp, M_ERR, name, "291|set: the %s option may not be turned off"); rval = 1; break; } /* Some options may not be set. */ if (F_ISSET(op, OPT_NOSET) && !turnoff) { msgq_wstr(sp, M_ERR, name, "313|set: the %s option may never be turned on"); rval = 1; break; } if (equals) { msgq_wstr(sp, M_ERR, name, "034|set: [no]%s option doesn't take a value"); rval = 1; break; } if (qmark) { if (!disp) disp = SELECT_DISPLAY; F_SET(spo, OPT_SELECTED); break; } /* * Do nothing if the value is unchanged, the underlying * functions can be expensive. */ isset = !turnoff; if (!F_ISSET(op, OPT_ALWAYS)) { if (isset) { if (O_ISSET(sp, offset)) break; } else if (!O_ISSET(sp, offset)) break; } /* Report to subsystems. */ if ((op->func != NULL && op->func(sp, spo, NULL, &isset)) || ex_optchange(sp, offset, NULL, &isset) || v_optchange(sp, offset, NULL, &isset) || sp->gp->scr_optchange(sp, offset, NULL, &isset)) { rval = 1; break; } /* Set the value. */ if (isset) O_SET(sp, offset); else O_CLR(sp, offset); break; case OPT_NUM: if (turnoff) { msgq_wstr(sp, M_ERR, name, "035|set: %s option isn't a boolean"); rval = 1; break; } if (qmark || !equals) { if (!disp) disp = SELECT_DISPLAY; F_SET(spo, OPT_SELECTED); break; } if (!ISDIGIT((UCHAR_T)sep[0])) goto badnum; if ((nret = nget_uslong(sp, &value, sep, &endp, 10)) != NUM_OK) { INT2CHAR(sp, name, STRLEN(name) + 1, np, nlen); p2 = msg_print(sp, np, &nf); INT2CHAR(sp, sep, STRLEN(sep) + 1, np, nlen); t2 = msg_print(sp, np, &nf2); switch (nret) { case NUM_ERR: msgq(sp, M_SYSERR, "036|set: %s option: %s", p2, t2); break; case NUM_OVER: msgq(sp, M_ERR, "037|set: %s option: %s: value overflow", p2, t2); break; case NUM_OK: case NUM_UNDER: abort(); } if (nf) FREE_SPACE(sp, p2, 0); if (nf2) FREE_SPACE(sp, t2, 0); rval = 1; break; } if (*endp && !ISBLANK(*endp)) { badnum: INT2CHAR(sp, name, STRLEN(name) + 1, np, nlen); p2 = msg_print(sp, np, &nf); INT2CHAR(sp, sep, STRLEN(sep) + 1, np, nlen); t2 = msg_print(sp, np, &nf2); msgq(sp, M_ERR, "038|set: %s option: %s is an illegal number", p2, t2); if (nf) FREE_SPACE(sp, p2, 0); if (nf2) FREE_SPACE(sp, t2, 0); rval = 1; break; } /* Some options may never be set to zero. */ if (F_ISSET(op, OPT_NOZERO) && value == 0) { msgq_wstr(sp, M_ERR, name, "314|set: the %s option may never be set to 0"); rval = 1; break; } /* * Do nothing if the value is unchanged, the underlying * functions can be expensive. */ if (!F_ISSET(op, OPT_ALWAYS) && O_VAL(sp, offset) == value) break; /* Report to subsystems. */ INT2CHAR(sp, sep, STRLEN(sep) + 1, np, nlen); if ((op->func != NULL && op->func(sp, spo, np, &value)) || ex_optchange(sp, offset, np, &value) || v_optchange(sp, offset, np, &value) || sp->gp->scr_optchange(sp, offset, np, &value)) { rval = 1; break; } /* Set the value. */ if (o_set(sp, offset, 0, NULL, value)) rval = 1; break; case OPT_STR: if (turnoff) { msgq_wstr(sp, M_ERR, name, "039|set: %s option isn't a boolean"); rval = 1; break; } if (qmark || !equals) { if (!disp) disp = SELECT_DISPLAY; F_SET(spo, OPT_SELECTED); break; } /* Check for strings that must have even length */ if (F_ISSET(op, OPT_PAIRS) && STRLEN(sep) & 1) { msgq_wstr(sp, M_ERR, name, "047|set: the %s option must be in two character groups"); rval = 1; break; } /* * Do nothing if the value is unchanged, the underlying * functions can be expensive. */ INT2CHAR(sp, sep, STRLEN(sep) + 1, np, nlen); if (!F_ISSET(op, OPT_ALWAYS) && O_STR(sp, offset) != NULL && !strcmp(O_STR(sp, offset), np)) break; /* Report to subsystems. */ if ((op->func != NULL && op->func(sp, spo, np, NULL)) || ex_optchange(sp, offset, np, NULL) || v_optchange(sp, offset, np, NULL) || sp->gp->scr_optchange(sp, offset, np, NULL)) { rval = 1; break; } /* Set the value. */ if (o_set(sp, offset, OS_STRDUP, np, 0)) rval = 1; break; default: abort(); } } if (disp != NO_DISPLAY) opts_dump(sp, disp); return (rval); }
/* * file_init -- * Start editing a file, based on the FREF structure. If successsful, * let go of any previous file. Don't release the previous file until * absolutely sure we have the new one. * * PUBLIC: int file_init __P((SCR *, FREF *, char *, int)); */ int file_init(SCR *sp, FREF *frp, char *rcv_name, int flags) { EXF *ep; struct stat sb; size_t psize; int fd, exists, open_err, readonly, stolen; char *oname, tname[MAXPATHLEN]; stolen = open_err = readonly = 0; /* * If the file is a recovery file, let the recovery code handle it. * Clear the FR_RECOVER flag first -- the recovery code does set up, * and then calls us! If the recovery call fails, it's probably * because the named file doesn't exist. So, move boldly forward, * presuming that there's an error message the user will get to see. */ if (F_ISSET(frp, FR_RECOVER)) { F_CLR(frp, FR_RECOVER); return (rcv_read(sp, frp)); } /* * Required FRP initialization; the only flag we keep is the * cursor information. */ F_CLR(frp, ~FR_CURSORSET); /* * Scan the user's path to find the file that we're going to * try and open. */ if (file_spath(sp, frp, &sb, &exists)) return (1); /* * Check whether we already have this file opened in some * other screen. */ if (exists) { EXF *exfp; for (exfp = sp->gp->exfq.cqh_first; exfp != (EXF *)&sp->gp->exfq; exfp = exfp->q.cqe_next) { if (exfp->mdev == sb.st_dev && exfp->minode == sb.st_ino && (exfp != sp->ep || exfp->refcnt > 1)) { ep = exfp; goto postinit; } } } /* * Required EXF initialization: * Flush the line caches. * Default recover mail file fd to -1. * Set initial EXF flag bits. */ CALLOC_RET(sp, ep, EXF *, 1, sizeof(EXF)); CIRCLEQ_INIT(&ep->scrq); sp->c_lno = ep->c_nlines = OOBLNO; ep->rcv_fd = ep->fcntl_fd = -1; F_SET(ep, F_FIRSTMODIFY); /* * If no name or backing file, for whatever reason, create a backing * temporary file, saving the temp file name so we can later unlink * it. If the user never named this file, copy the temporary file name * to the real name (we display that until the user renames it). */ oname = frp->name; if (LF_ISSET(FS_OPENERR) || oname == NULL || !exists) { if (opts_empty(sp, O_TMP_DIRECTORY, 0)) goto err; (void)snprintf(tname, sizeof(tname), "%s/vi.XXXXXX", O_STR(sp, O_TMP_DIRECTORY)); if ((fd = mkstemp(tname)) == -1) { msgq(sp, M_SYSERR, "237|Unable to create temporary file"); goto err; } (void)close(fd); if (frp->name == NULL) F_SET(frp, FR_TMPFILE); if ((frp->tname = strdup(tname)) == NULL || (frp->name == NULL && (frp->name = strdup(tname)) == NULL)) { if (frp->tname != NULL) { free(frp->tname); } msgq(sp, M_SYSERR, NULL); (void)unlink(tname); goto err; } oname = frp->tname; psize = 1024; if (!LF_ISSET(FS_OPENERR)) F_SET(frp, FR_NEWFILE); time(&ep->mtime); } else { /* * XXX * A seat of the pants calculation: try to keep the file in * 15 pages or less. Don't use a page size larger than 10K * (vi should have good locality) or smaller than 1K. */ psize = ((sb.st_size / 15) + 1023) / 1024; if (psize > 10) psize = 10; if (psize == 0) psize = 1; psize *= 1024; F_SET(ep, F_DEVSET); ep->mdev = sb.st_dev; ep->minode = sb.st_ino; ep->mtime = sb.st_mtime; if (!S_ISREG(sb.st_mode)) msgq_str(sp, M_ERR, oname, "238|Warning: %s is not a regular file"); } /* Set up recovery. */ if (rcv_name == NULL) { /* ep->rcv_path NULL if rcv_tmp fails */ rcv_tmp(sp, ep, frp->name); } else { if ((ep->rcv_path = strdup(rcv_name)) == NULL) { msgq(sp, M_SYSERR, NULL); goto err; } F_SET(ep, F_MODIFIED); } if (db_setup(sp, ep)) goto err; /* Open a db structure. */ if ((sp->db_error = db_create(&ep->db, 0, 0)) != 0) { msgq(sp, M_DBERR, "db_create"); goto err; } ep->db->set_re_delim(ep->db, '\n'); /* Always set. */ ep->db->set_pagesize(ep->db, psize); ep->db->set_flags(ep->db, DB_RENUMBER | DB_SNAPSHOT); if (rcv_name == NULL) ep->db->set_re_source(ep->db, oname); /* * Don't let db use mmap when using fcntl for locking */ #ifdef HAVE_LOCK_FCNTL #define NOMMAPIFFCNTL DB_NOMMAP #else #define NOMMAPIFFCNTL 0 #endif #define _DB_OPEN_MODE S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH if ((sp->db_error = db_open(ep->db, ep->rcv_path, DB_RECNO, ((rcv_name == 0) ? DB_TRUNCATE : 0) | VI_DB_THREAD | NOMMAPIFFCNTL, _DB_OPEN_MODE)) != 0) { msgq_str(sp, M_DBERR, rcv_name == NULL ? oname : rcv_name, "%s"); /* * !!! * Historically, vi permitted users to edit files that couldn't * be read. This isn't useful for single files from a command * line, but it's quite useful for "vi *.c", since you can skip * past files that you can't read. */ ep->db = NULL; /* Don't close it; it wasn't opened */ if (LF_ISSET(FS_OPENERR)) goto err; open_err = 1; goto oerr; } /* re_source is loaded into the database. * Close it and reopen it in the environment. */ if ((sp->db_error = ep->db->close(ep->db, 0))) { msgq(sp, M_DBERR, "close"); goto err; } if ((sp->db_error = db_create(&ep->db, ep->env, 0)) != 0) { msgq(sp, M_DBERR, "db_create 2"); goto err; } if ((sp->db_error = db_open(ep->db, ep->rcv_path, DB_RECNO, VI_DB_THREAD | NOMMAPIFFCNTL, _DB_OPEN_MODE)) != 0) { msgq_str(sp, M_DBERR, ep->rcv_path, "%s"); goto err; } /* * Do the remaining things that can cause failure of the new file, * mark and logging initialization. */ if (mark_init(sp, ep) || log_init(sp, ep)) goto err; postinit: /* * Set the alternate file name to be the file we're discarding. * * !!! * Temporary files can't become alternate files, so there's no file * name. This matches historical practice, although it could only * happen in historical vi as the result of the initial command, i.e. * if vi was executed without a file name. */ if (LF_ISSET(FS_SETALT)) set_alt_name(sp, sp->frp == NULL || F_ISSET(sp->frp, FR_TMPFILE) ? NULL : sp->frp->name); /* * Close the previous file; if that fails, close the new one and run * for the border. * * !!! * There's a nasty special case. If the user edits a temporary file, * and then does an ":e! %", we need to re-initialize the backing * file, but we can't change the name. (It's worse -- we're dealing * with *names* here, we can't even detect that it happened.) Set a * flag so that the file_end routine ignores the backing information * of the old file if it happens to be the same as the new one. * * !!! * Side-effect: after the call to file_end(), sp->frp may be NULL. */ if (sp->ep != NULL) { F_SET(frp, FR_DONTDELETE); if (file_end(sp, NULL, LF_ISSET(FS_FORCE))) { (void)file_end(sp, ep, 1); goto err; } sp->ep = NULL; F_CLR(frp, FR_DONTDELETE); } /* * Lock the file; if it's a recovery file, it should already be * locked. Note, we acquire the lock after the previous file * has been ended, so that we don't get an "already locked" error * for ":edit!". * * XXX * While the user can't interrupt us between the open and here, * there's a race between the dbopen() and the lock. Not much * we can do about it. * * XXX * We don't make a big deal of not being able to lock the file. As * locking rarely works over NFS, and often fails if the file was * mmap(2)'d, it's far too common to do anything like print an error * message, let alone make the file readonly. At some future time, * when locking is a little more reliable, this should change to be * an error. */ if (rcv_name == NULL && ep->refcnt == 0) { if ((ep->fd = open(oname, O_RDWR)) == -1) goto no_lock; switch (file_lock(sp, oname, &ep->fcntl_fd, ep->fd, 1)) { case LOCK_FAILED: no_lock: F_SET(frp, FR_UNLOCKED); break; case LOCK_UNAVAIL: readonly = 1; msgq_str(sp, M_INFO, oname, "239|%s already locked, session is read-only"); break; case LOCK_SUCCESS: break; } } /* * Historically, the readonly edit option was set per edit buffer in * vi, unless the -R command-line option was specified or the program * was executed as "view". (Well, to be truthful, if the letter 'w' * occurred anywhere in the program name, but let's not get into that.) * So, the persistant readonly state has to be stored in the screen * structure, and the edit option value toggles with the contents of * the edit buffer. If the persistant readonly flag is set, set the * readonly edit option. * * Otherwise, try and figure out if a file is readonly. This is a * dangerous thing to do. The kernel is the only arbiter of whether * or not a file is writeable, and the best that a user program can * do is guess. Obvious loopholes are files that are on a file system * mounted readonly (access catches this one on a few systems), or * alternate protection mechanisms, ACL's for example, that we can't * portably check. Lots of fun, and only here because users whined. * * !!! * Historic vi displayed the readonly message if none of the file * write bits were set, or if an an access(2) call on the path * failed. This seems reasonable. If the file is mode 444, root * users may want to know that the owner of the file did not expect * it to be written. * * Historic vi set the readonly bit if no write bits were set for * a file, even if the access call would have succeeded. This makes * the superuser force the write even when vi expects that it will * succeed. I'm less supportive of this semantic, but it's historic * practice and the conservative approach to vi'ing files as root. * * It would be nice if there was some way to update this when the user * does a "^Z; chmod ...". The problem is that we'd first have to * distinguish between readonly bits set because of file permissions * and those set for other reasons. That's not too hard, but deciding * when to reevaluate the permissions is trickier. An alternative * might be to turn off the readonly bit if the user forces a write * and it succeeds. * * XXX * Access(2) doesn't consider the effective uid/gid values. This * probably isn't a problem for vi when it's running standalone. */ if (readonly || F_ISSET(sp, SC_READONLY) || (!F_ISSET(frp, FR_NEWFILE) && (!(sb.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) || access(frp->name, W_OK)))) O_SET(sp, O_READONLY); else O_CLR(sp, O_READONLY); /* Switch... */ ++ep->refcnt; CIRCLEQ_INSERT_HEAD(&ep->scrq, sp, eq); sp->ep = ep; sp->frp = frp; /* Set the initial cursor position, queue initial command. */ file_cinit(sp); /* Report conversion errors again. */ F_CLR(sp, SC_CONV_ERROR); /* Redraw the screen from scratch, schedule a welcome message. */ F_SET(sp, SC_SCR_REFORMAT | SC_STATUS); if (frp->lno == OOBLNO) F_SET(sp, SC_SCR_TOP); /* Append into the chain of file structures. */ if (ep->refcnt == 1) CIRCLEQ_INSERT_TAIL(&sp->gp->exfq, ep, q); return (0); err: if (frp->name != NULL) { free(frp->name); frp->name = NULL; } if (frp->tname != NULL) { (void)unlink(frp->tname); free(frp->tname); frp->tname = NULL; } oerr: if (F_ISSET(ep, F_RCV_ON)) (void)unlink(ep->rcv_path); if (ep->rcv_path != NULL) { free(ep->rcv_path); ep->rcv_path = NULL; } if (ep->db != NULL) { (void)ep->db->close(ep->db, DB_NOSYNC); ep->db = NULL; } free(ep); return (open_err && !LF_ISSET(FS_OPENERR) ? file_init(sp, frp, rcv_name, flags | FS_OPENERR) : 1); }