/* * v_init -- * Initialize the vi screen. */ static int v_init(SCR *sp) { GS *gp; VI_PRIVATE *vip; gp = sp->gp; vip = VIP(sp); /* Switch into vi. */ if (gp->scr_screen(sp, SC_VI)) return (1); (void)gp->scr_attr(sp, SA_ALTERNATE, 1); F_CLR(sp, SC_EX | SC_SCR_EX); F_SET(sp, SC_VI); /* * Initialize screen values. * * Small windows: see vs_refresh(), section 6a. * * Setup: * t_minrows is the minimum rows to display * t_maxrows is the maximum rows to display (rows - 1) * t_rows is the rows currently being displayed */ sp->rows = vip->srows = O_VAL(sp, O_LINES); sp->cols = O_VAL(sp, O_COLUMNS); sp->t_rows = sp->t_minrows = O_VAL(sp, O_WINDOW); if (sp->rows != 1) { if (sp->t_rows > sp->rows - 1) { sp->t_minrows = sp->t_rows = sp->rows - 1; msgq(sp, M_INFO, "214|Windows option value is too large, max is %u", (u_int)sp->t_rows); } sp->t_maxrows = sp->rows - 1; } else sp->t_maxrows = 1; sp->roff = sp->coff = 0; /* Create a screen map. */ CALLOC_RET(sp, HMAP, SMAP *, SIZE_HMAP(sp), sizeof(SMAP)); TMAP = HMAP + (sp->t_rows - 1); HMAP->lno = sp->lno; HMAP->coff = 0; HMAP->soff = 1; /* * Fill the screen map from scratch -- try and center the line. That * way if we're starting with a file we've seen before, we'll put the * line in the middle, otherwise, it won't work and we'll end up with * the line at the top. */ F_SET(sp, SC_SCR_REFORMAT | SC_SCR_CENTER); /* Invalidate the cursor. */ F_SET(vip, VIP_CUR_INVALID); /* Paint the screen image from scratch. */ F_SET(vip, VIP_N_EX_PAINT); return (0); }
bool ACE_Message_Queue_Vx::is_full_i (void) { ACE_TRACE ("ACE_Message_Queue_Vx::is_full_i"); return ::msgQNumMsgs (msgq ()) >= max_messages_; }
/* * cl_event -- * Return a single event. * * PUBLIC: int cl_event __P((SCR *, EVENT *, u_int32_t, int)); */ int cl_event(SCR *sp, EVENT *evp, u_int32_t flags, int ms) { struct timeval t, *tp; CL_PRIVATE *clp; size_t lines, columns; int changed, nr = 0; CHAR_T *wp; size_t wlen; int rc; /* * Queue signal based events. We never clear SIGHUP or SIGTERM events, * so that we just keep returning them until the editor dies. */ clp = CLP(sp); retest: if (LF_ISSET(EC_INTERRUPT) || F_ISSET(clp, CL_SIGINT)) { if (F_ISSET(clp, CL_SIGINT)) { F_CLR(clp, CL_SIGINT); evp->e_event = E_INTERRUPT; } else evp->e_event = E_TIMEOUT; return (0); } if (F_ISSET(clp, CL_SIGHUP | CL_SIGTERM | CL_SIGWINCH)) { if (F_ISSET(clp, CL_SIGHUP)) { evp->e_event = E_SIGHUP; return (0); } if (F_ISSET(clp, CL_SIGTERM)) { evp->e_event = E_SIGTERM; return (0); } if (F_ISSET(clp, CL_SIGWINCH)) { F_CLR(clp, CL_SIGWINCH); if (cl_ssize(sp, 1, &lines, &columns, &changed)) return (1); if (changed) { (void)cl_resize(sp, lines, columns); evp->e_event = E_WRESIZE; return (0); } /* No real change, ignore the signal. */ } } /* Set timer. */ if (ms == 0) tp = NULL; else { t.tv_sec = ms / 1000; t.tv_usec = (ms % 1000) * 1000; tp = &t; } /* Read input characters. */ read: switch (cl_read(sp, LF_ISSET(EC_QUOTED | EC_RAW), clp->ibuf + clp->skip, SIZE(clp->ibuf) - clp->skip, &nr, tp)) { case INP_OK: rc = INPUT2INT5(sp, clp->cw, clp->ibuf, nr + clp->skip, wp, wlen); evp->e_csp = wp; evp->e_len = wlen; evp->e_event = E_STRING; if (rc < 0) { int n = -rc; memmove(clp->ibuf, clp->ibuf + nr + clp->skip - n, n); clp->skip = n; if (wlen == 0) goto read; } else if (rc == 0) clp->skip = 0; else msgq(sp, M_ERR, "323|Invalid input. Truncated."); break; case INP_EOF: evp->e_event = E_EOF; break; case INP_ERR: evp->e_event = E_ERR; break; case INP_INTR: goto retest; case INP_TIMEOUT: evp->e_event = E_TIMEOUT; break; default: abort(); } return (0); }
/* * cscope_add -- * The cscope add command. */ static int cscope_add(SCR *sp, EXCMD *cmdp, const CHAR_T *dname) { struct stat sb; EX_PRIVATE *exp; CSC *csc; size_t len; int cur_argc; const char *dbname; char path[MAXPATHLEN]; const char *np; char *npp; size_t nlen; exp = EXP(sp); /* * 0 additional args: usage. * 1 additional args: matched a file. * >1 additional args: object, too many args. */ cur_argc = cmdp->argc; if (argv_exp2(sp, cmdp, dname, STRLEN(dname))) { return (1); } if (cmdp->argc == cur_argc) { (void)csc_help(sp, "add"); return (1); } if (cmdp->argc == cur_argc + 1) dname = cmdp->argv[cur_argc]->bp; else { INT2CHAR(sp, dname, STRLEN(dname)+1, np, nlen); ex_emsg(sp, np, EXM_FILECOUNT); return (1); } INT2CHAR(sp, dname, STRLEN(dname)+1, np, nlen); /* * The user can specify a specific file (so they can have multiple * Cscope databases in a single directory) or a directory. If the * file doesn't exist, we're done. If it's a directory, append the * standard database file name and try again. Store the directory * name regardless so that we can use it as a base for searches. */ if (stat(np, &sb)) { msgq(sp, M_SYSERR, "%s", np); return (1); } if (S_ISDIR(sb.st_mode)) { (void)snprintf(path, sizeof(path), "%s/%s", np, CSCOPE_DBFILE); if (stat(path, &sb)) { msgq(sp, M_SYSERR, "%s", path); return (1); } dbname = CSCOPE_DBFILE; } else if ((npp = strrchr(np, '/')) != NULL) { *npp = '\0'; dbname = npp + 1; } else { dbname = np; np = "."; } /* Allocate a cscope connection structure and initialize its fields. */ len = strlen(np); CALLOC_RET(sp, csc, CSC *, 1, sizeof(CSC) + len); csc->dname = csc->buf; csc->dlen = len; memcpy(csc->dname, np, len); csc->mtime = sb.st_mtime; /* Get the search paths for the cscope. */ if (get_paths(sp, csc)) goto err; /* Start the cscope process. */ if (run_cscope(sp, csc, dbname)) goto err; /* * Add the cscope connection to the screen's list. From now on, * on error, we have to call terminate, which expects the csc to * be on the chain. */ LIST_INSERT_HEAD(&exp->cscq, csc, q); /* Read the initial prompt from the cscope to make sure it's okay. */ return read_prompt(sp, csc); err: free(csc); return (1); }
/* * screen_init -- * Do the default initialization of an SCR structure. * * PUBLIC: int screen_init __P((GS *, SCR *, SCR **)); */ int screen_init(GS *gp, SCR *orig, SCR **spp) { SCR *sp; size_t len; *spp = NULL; CALLOC_RET(orig, sp, SCR *, 1, sizeof(SCR)); *spp = sp; /* INITIALIZED AT SCREEN CREATE. */ sp->id = ++gp->id; sp->refcnt = 1; sp->gp = gp; /* All ref the GS structure. */ sp->ccnt = 2; /* Anything > 1 */ /* * XXX * sp->defscroll is initialized by the opts_init() code because * we don't have the option information yet. */ CIRCLEQ_INIT(&sp->tiq); /* PARTIALLY OR COMPLETELY COPIED FROM PREVIOUS SCREEN. */ if (orig == NULL) { sp->searchdir = NOTSET; } else { sp->wp = orig->wp; /* Alternate file name. */ if (orig->alt_name != NULL && (sp->alt_name = strdup(orig->alt_name)) == NULL) goto mem; /* Last executed at buffer. */ if (F_ISSET(orig, SC_AT_SET)) { F_SET(sp, SC_AT_SET); sp->at_lbuf = orig->at_lbuf; } /* Retain searching/substitution information. */ sp->searchdir = orig->searchdir == NOTSET ? NOTSET : FORWARD; if (orig->re != NULL && (sp->re = v_wstrdup(sp, orig->re, orig->re_len)) == NULL) goto mem; sp->re_len = orig->re_len; if (orig->subre != NULL && (sp->subre = v_wstrdup(sp, orig->subre, orig->subre_len)) == NULL) goto mem; sp->subre_len = orig->subre_len; if (orig->repl != NULL && (sp->repl = v_wstrdup(sp, orig->repl, orig->repl_len)) == NULL) goto mem; sp->repl_len = orig->repl_len; if (orig->newl_len) { len = orig->newl_len * sizeof(size_t); MALLOC(sp, sp->newl, size_t *, len); if (sp->newl == NULL) { mem: msgq(orig, M_SYSERR, NULL); goto err; } sp->newl_len = orig->newl_len; sp->newl_cnt = orig->newl_cnt; memcpy(sp->newl, orig->newl, len); } if (opts_copy(orig, sp)) goto err; F_SET(sp, F_ISSET(orig, SC_EX | SC_VI)); }
static int s(SCR *sp, EXCMD *cmdp, char *s, regex_t *re, u_int flags) { EVENT ev; MARK from, to; TEXTH tiq; recno_t elno, lno, slno; regmatch_t match[10]; size_t blen, cnt, last, lbclen, lblen, len, llen; size_t offset, saved_offset, scno; int lflag, nflag, pflag, rflag; int didsub, do_eol_match, eflags, empty_ok, eval; int linechanged, matched, quit, rval; unsigned long ul; char *bp, *lb; NEEDFILE(sp, cmdp); slno = sp->lno; scno = sp->cno; /* * !!! * Historically, the 'g' and 'c' suffices were always toggled as flags, * so ":s/A/B/" was the same as ":s/A/B/ccgg". If O_EDCOMPATIBLE was * not set, they were initialized to 0 for all substitute commands. If * O_EDCOMPATIBLE was set, they were initialized to 0 only if the user * specified substitute/replacement patterns (see ex_s()). */ if (!O_ISSET(sp, O_EDCOMPATIBLE)) sp->c_suffix = sp->g_suffix = 0; /* * Historic vi permitted the '#', 'l' and 'p' options in vi mode, but * it only displayed the last change. I'd disallow them, but they are * useful in combination with the [v]global commands. In the current * model the problem is combining them with the 'c' flag -- the screen * would have to flip back and forth between the confirm screen and the * ex print screen, which would be pretty awful. We do display all * changes, though, for what that's worth. * * !!! * Historic vi was fairly strict about the order of "options", the * count, and "flags". I'm somewhat fuzzy on the difference between * options and flags, anyway, so this is a simpler approach, and we * just take it them in whatever order the user gives them. (The ex * usage statement doesn't reflect this.) */ lflag = nflag = pflag = rflag = 0; if (s == NULL) goto noargs; for (lno = OOBLNO; *s != '\0'; ++s) switch (*s) { case ' ': case '\t': continue; case '+': ++cmdp->flagoff; break; case '-': --cmdp->flagoff; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (lno != OOBLNO) goto usage; errno = 0; if ((ul = strtoul(s, &s, 10)) >= UINT_MAX) errno = ERANGE; if (*s == '\0') /* Loop increment correction. */ --s; if (errno == ERANGE) { if (ul >= UINT_MAX) msgq(sp, M_ERR, "Count overflow"); else msgq(sp, M_SYSERR, NULL); return (1); } lno = (recno_t)ul; /* * In historic vi, the count was inclusive from the * second address. */ cmdp->addr1.lno = cmdp->addr2.lno; cmdp->addr2.lno += lno - 1; if (!db_exist(sp, cmdp->addr2.lno) && db_last(sp, &cmdp->addr2.lno)) return (1); break; case '#': nflag = 1; break; case 'c': sp->c_suffix = !sp->c_suffix; /* Ex text structure initialization. */ if (F_ISSET(sp, SC_EX)) { memset(&tiq, 0, sizeof(TEXTH)); TAILQ_INIT(&tiq); } break; case 'g': sp->g_suffix = !sp->g_suffix; break; case 'l': lflag = 1; break; case 'p': pflag = 1; break; case 'r': if (LF_ISSET(SUB_FIRST)) { msgq(sp, M_ERR, "Regular expression specified; r flag meaningless"); return (1); } if (!F_ISSET(sp, SC_RE_SEARCH)) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } rflag = 1; re = &sp->re_c; break; default: goto usage; } if (*s != '\0' || (!rflag && LF_ISSET(SUB_MUSTSETR))) { usage: ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } noargs: if (F_ISSET(sp, SC_VI) && sp->c_suffix && (lflag || nflag || pflag)) { msgq(sp, M_ERR, "The #, l and p flags may not be combined with the c flag in vi mode"); return (1); } /* * bp: if interactive, line cache * blen: if interactive, line cache length * lb: build buffer pointer. * lbclen: current length of built buffer. * lblen; length of build buffer. */ bp = lb = NULL; blen = lbclen = lblen = 0; /* For each line... */ for (matched = quit = 0, lno = cmdp->addr1.lno, elno = cmdp->addr2.lno; !quit && lno <= elno; ++lno) { /* Someone's unhappy, time to stop. */ if (INTERRUPTED(sp)) break; /* Get the line. */ if (db_get(sp, lno, DBG_FATAL, &s, &llen)) goto err; /* * Make a local copy if doing confirmation -- when calling * the confirm routine we're likely to lose the cached copy. */ if (sp->c_suffix) { if (bp == NULL) { GET_SPACE_RET(sp, bp, blen, llen); } else ADD_SPACE_RET(sp, bp, blen, llen); memcpy(bp, s, llen); s = bp; } /* Start searching from the beginning. */ offset = 0; len = llen; /* Reset the build buffer offset. */ lbclen = 0; /* Reset empty match flag. */ empty_ok = 1; /* * We don't want to have to do a setline if the line didn't * change -- keep track of whether or not this line changed. * If doing confirmations, don't want to keep setting the * line if change is refused -- keep track of substitutions. */ didsub = linechanged = 0; /* New line, do an EOL match. */ do_eol_match = 1; /* It's not nul terminated, but we pretend it is. */ eflags = REG_STARTEND; /* * The search area is from s + offset to the EOL. * * Generally, match[0].rm_so is the offset of the start * of the match from the start of the search, and offset * is the offset of the start of the last search. */ nextmatch: match[0].rm_so = 0; match[0].rm_eo = len; /* Get the next match. */ eval = regexec(re, (char *)s + offset, 10, match, eflags); /* * There wasn't a match or if there was an error, deal with * it. If there was a previous match in this line, resolve * the changes into the database. Otherwise, just move on. */ if (eval == REG_NOMATCH) goto endmatch; if (eval != 0) { re_error(sp, eval, re); goto err; } matched = 1; /* Only the first search can match an anchored expression. */ eflags |= REG_NOTBOL; /* * !!! * It's possible to match 0-length strings -- for example, the * command s;a*;X;, when matched against the string "aabb" will * result in "XbXbX", i.e. the matches are "aa", the space * between the b's and the space between the b's and the end of * the string. There is a similar space between the beginning * of the string and the a's. The rule that we use (because vi * historically used it) is that any 0-length match, occurring * immediately after a match, is ignored. Otherwise, the above * example would have resulted in "XXbXbX". Another example is * incorrectly using " *" to replace groups of spaces with one * space. * * The way we do this is that if we just had a successful match, * the starting offset does not skip characters, and the match * is empty, ignore the match and move forward. If there's no * more characters in the string, we were attempting to match * after the last character, so quit. */ if (!empty_ok && match[0].rm_so == 0 && match[0].rm_eo == 0) { empty_ok = 1; if (len == 0) goto endmatch; BUILD(sp, s + offset, 1) ++offset; --len; goto nextmatch; } /* Confirm change. */ if (sp->c_suffix) { /* * Set the cursor position for confirmation. Note, * if we matched on a '$', the cursor may be past * the end of line. */ from.lno = to.lno = lno; from.cno = match[0].rm_so + offset; to.cno = match[0].rm_eo + offset; /* * Both ex and vi have to correct for a change before * the first character in the line. */ if (llen == 0) from.cno = to.cno = 0; if (F_ISSET(sp, SC_VI)) { /* * Only vi has to correct for a change after * the last character in the line. * * XXX * It would be nice to change the vi code so * that we could display a cursor past EOL. */ if (to.cno >= llen) to.cno = llen - 1; if (from.cno >= llen) from.cno = llen - 1; sp->lno = from.lno; sp->cno = from.cno; if (vs_refresh(sp, 1)) goto err; vs_update(sp, "Confirm change? [n]", NULL); if (v_event_get(sp, &ev, 0, 0)) goto err; switch (ev.e_event) { case E_CHARACTER: break; case E_EOF: case E_ERR: case E_INTERRUPT: goto lquit; default: v_event_err(sp, &ev); goto lquit; } } else { if (ex_print(sp, cmdp, &from, &to, 0) || ex_scprint(sp, &from, &to)) goto lquit; if (ex_txt(sp, &tiq, 0, TXT_CR)) goto err; ev.e_c = TAILQ_FIRST(&tiq)->lb[0]; } switch (ev.e_c) { case CH_YES: break; default: case CH_NO: didsub = 0; BUILD(sp, s +offset, match[0].rm_eo); goto skip; case CH_QUIT: /* Set the quit/interrupted flags. */ lquit: quit = 1; F_SET(sp->gp, G_INTERRUPTED); /* * Resolve any changes, then return to (and * exit from) the main loop. */ goto endmatch; } } /* * Set the cursor to the last position changed, converting * from 1-based to 0-based. */ sp->lno = lno; sp->cno = match[0].rm_so; /* Copy the bytes before the match into the build buffer. */ BUILD(sp, s + offset, match[0].rm_so); /* Substitute the matching bytes. */ didsub = 1; if (re_sub(sp, s + offset, &lb, &lbclen, &lblen, match)) goto err; /* Set the change flag so we know this line was modified. */ linechanged = 1; /* Move past the matched bytes. */ skip: offset += match[0].rm_eo; len -= match[0].rm_eo; /* A match cannot be followed by an empty pattern. */ empty_ok = 0; /* * If doing a global change with confirmation, we have to * update the screen. The basic idea is to store the line * so the screen update routines can find it, and restart. */ if (didsub && sp->c_suffix && sp->g_suffix) { /* * The new search offset will be the end of the * modified line. */ saved_offset = lbclen; /* Copy the rest of the line. */ if (len) BUILD(sp, s + offset, len) /* Set the new offset. */ offset = saved_offset; /* Store inserted lines, adjusting the build buffer. */ last = 0; if (sp->newl_cnt) { for (cnt = 0; cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) { if (db_insert(sp, lno, lb + last, sp->newl[cnt] - last)) goto err; last = sp->newl[cnt] + 1; ++sp->rptlines[L_ADDED]; } lbclen -= last; offset -= last; sp->newl_cnt = 0; } /* Store and retrieve the line. */ if (db_set(sp, lno, lb + last, lbclen)) goto err; if (db_get(sp, lno, DBG_FATAL, &s, &llen)) goto err; ADD_SPACE_RET(sp, bp, blen, llen) memcpy(bp, s, llen); s = bp; len = llen - offset; /* Restart the build. */ lbclen = 0; BUILD(sp, s, offset); /* * If we haven't already done the after-the-string * match, do one. Set REG_NOTEOL so the '$' pattern * only matches once. */ if (!do_eol_match) goto endmatch; if (offset == len) { do_eol_match = 0; eflags |= REG_NOTEOL; } goto nextmatch; } /* * If it's a global: * * If at the end of the string, do a test for the after * the string match. Set REG_NOTEOL so the '$' pattern * only matches once. */ if (sp->g_suffix && do_eol_match) { if (len == 0) { do_eol_match = 0; eflags |= REG_NOTEOL; } goto nextmatch; } endmatch: if (!linechanged) continue; /* Copy any remaining bytes into the build buffer. */ if (len) BUILD(sp, s + offset, len) /* Store inserted lines, adjusting the build buffer. */ last = 0; if (sp->newl_cnt) { for (cnt = 0; cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) { if (db_insert(sp, lno, lb + last, sp->newl[cnt] - last)) goto err; last = sp->newl[cnt] + 1; ++sp->rptlines[L_ADDED]; } lbclen -= last; sp->newl_cnt = 0; } /* Store the changed line. */ if (db_set(sp, lno, lb + last, lbclen)) goto err; /* Update changed line counter. */ if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } /* * !!! * Display as necessary. Historic practice is to only * display the last line of a line split into multiple * lines. */ if (lflag || nflag || pflag) { from.lno = to.lno = lno; from.cno = to.cno = 0; if (lflag) (void)ex_print(sp, cmdp, &from, &to, E_C_LIST); if (nflag) (void)ex_print(sp, cmdp, &from, &to, E_C_HASH); if (pflag) (void)ex_print(sp, cmdp, &from, &to, E_C_PRINT); } } /* * !!! * Historically, vi attempted to leave the cursor at the same place if * the substitution was done at the current cursor position. Otherwise * it moved it to the first non-blank of the last line changed. There * were some problems: for example, :s/$/foo/ with the cursor on the * last character of the line left the cursor on the last character, or * the & command with multiple occurrences of the matching string in the * line usually left the cursor in a fairly random position. * * We try to do the same thing, with the exception that if the user is * doing substitution with confirmation, we move to the last line about * which the user was consulted, as opposed to the last line that they * actually changed. This prevents a screen flash if the user doesn't * change many of the possible lines. */ if (!sp->c_suffix && (sp->lno != slno || sp->cno != scno)) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } /* * If not in a global command, and nothing matched, say so. * Else, if none of the lines displayed, put something up. */ rval = 0; if (!matched) { if (!F_ISSET(sp, SC_EX_GLOBAL)) { msgq(sp, M_ERR, "No match found"); goto err; } } else if (!lflag && !nflag && !pflag) F_SET(cmdp, E_AUTOPRINT); if (0) { err: rval = 1; } if (bp != NULL) FREE_SPACE(sp, bp, blen); if (lb != NULL) free(lb); return (rval); }
/* * v_match -- % * Search to matching character. * * PUBLIC: int v_match(SCR *, VICMD *); */ int v_match(SCR *sp, VICMD *vp) { VCS cs; MARK *mp; size_t cno, len, off; int cnt, isempty, matchc, startc, (*gc)(SCR *, VCS *); CHAR_T *p; CHAR_T *cp; const CHAR_T *match_chars; /* * Historically vi would match (), {} and [] however * an update included <>. This is ok for editing HTML * but a pain in the butt for C source. * Making it an option lets the user decide what is 'right'. */ match_chars = VIP(sp)->mcs; /* * !!! * Historic practice; ignore the count. * * !!! * Historical practice was to search for the initial character in the * forward direction only. */ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (isempty) goto nomatch; return (1); } for (off = vp->m_start.cno;; ++off) { if (off >= len) { nomatch: msgq(sp, M_BERR, "184|No match character on this line"); return (1); } startc = p[off]; cp = STRCHR(match_chars, startc); if (cp != NULL) { cnt = cp - match_chars; matchc = match_chars[cnt ^ 1]; gc = cnt & 1 ? cs_prev : cs_next; break; } } cs.cs_lno = vp->m_start.lno; cs.cs_cno = off; if (cs_init(sp, &cs)) return (1); for (cnt = 1;;) { if (gc(sp, &cs)) return (1); if (cs.cs_flags != 0) { if (cs.cs_flags == CS_EOF || cs.cs_flags == CS_SOF) break; continue; } if (cs.cs_ch == startc) ++cnt; else if (cs.cs_ch == matchc && --cnt == 0) break; } if (cnt) { msgq(sp, M_BERR, "185|Matching character not found"); return (1); } vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; /* * If moving right, non-motion commands move to the end of the range. * Delete and yank stay at the start. * * If moving left, all commands move to the end of the range. * * !!! * Don't correct for leftward movement -- historic vi deleted the * starting cursor position when deleting to a match. */ if (vp->m_start.lno < vp->m_stop.lno || (vp->m_start.lno == vp->m_stop.lno && vp->m_start.cno < vp->m_stop.cno)) vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; else vp->m_final = vp->m_stop; /* * !!! * If the motion is across lines, and the earliest cursor position * is at or before any non-blank characters in the line, i.e. the * movement is cutting all of the line's text, and the later cursor * position has nothing other than whitespace characters between it * and the end of its line, the buffer is in line mode. */ if (!ISMOTION(vp) || vp->m_start.lno == vp->m_stop.lno) return (0); mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_start : &vp->m_stop; if (mp->cno != 0) { cno = 0; if (nonblank(sp, mp->lno, &cno)) return (1); if (cno < mp->cno) return (0); } mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_stop : &vp->m_start; if (db_get(sp, mp->lno, DBG_FATAL, &p, &len)) return (1); for (p += mp->cno + 1, len -= mp->cno; --len; ++p) if (!isblank(*p)) return (0); F_SET(vp, VM_LMODE); return (0); }
/* * sscr_insert -- * Take a line from the shell and insert it into the file. */ static int sscr_insert(SCR *sp) { struct timeval tv; char *endp, *p, *t; SCRIPT *sc; fd_set rdfd; db_recno_t lno; size_t len; ssize_t nr; char bp[1024]; const CHAR_T *ip; size_t ilen = 0; /* Find out where the end of the file is. */ if (db_last(sp, &lno)) return (1); endp = bp; /* Read the characters. */ sc = sp->script; more: switch (nr = read(sc->sh_master, endp, bp + sizeof(bp) - endp)) { case 0: /* EOF; shell just exited. */ sscr_end(sp); return (0); case -1: /* Error or interrupt. */ msgq(sp, M_SYSERR, "shell"); return (1); default: endp += nr; break; } /* Append the lines into the file. */ for (p = t = bp; p < endp; ++p) { if (*p == '\r' || *p == '\n') { len = p - t; if (CHAR2INT(sp, t, len, ip, ilen) || db_append(sp, 1, lno++, ip, ilen)) return (1); t = p + 1; } } /* * If the last thing from the shell isn't another prompt, wait up to * 1/10 of a second for more stuff to show up, so that we don't break * the output into two separate lines. Don't want to hang indefinitely * because some program is hanging, confused the shell, or whatever. * Note that sc->sh_prompt can be NULL here. */ len = p - t; if (sc->sh_prompt == NULL || len != sc->sh_prompt_len || memcmp(t, sc->sh_prompt, len) != 0) { tv.tv_sec = 0; tv.tv_usec = 100000; FD_ZERO(&rdfd); FD_SET(sc->sh_master, &rdfd); if (select(sc->sh_master + 1, &rdfd, NULL, NULL, &tv) == 1) { if (len == sizeof(bp)) { if (CHAR2INT(sp, t, len, ip, ilen) || db_append(sp, 1, lno++, ip, ilen)) return (1); endp = bp; } else { memmove(bp, t, len); endp = bp + len; } goto more; } if (sscr_setprompt(sp, t, len)) return (1); } /* Append the remains into the file, and the cursor moves to EOF. */ if (len > 0) { if (CHAR2INT(sp, t, len, ip, ilen) || db_append(sp, 1, lno++, ip, ilen)) return (1); sp->cno = ilen - 1; } else sp->cno = 0; sp->lno = lno; return (vs_refresh(sp, 1)); }
/* * 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); }
/* * ex_read -- :read [file] * :read [!cmd] * Read from a file or utility. * * !!! * Historical vi wouldn't undo a filter read, for no apparent reason. * * PUBLIC: int ex_read __P((SCR *, EXCMD *)); */ int ex_read(SCR *sp, EXCMD *cmdp) { enum { R_ARG, R_EXPANDARG, R_FILTER } which; struct stat sb; CHAR_T *arg = NULL; const char *name; size_t nlen; EX_PRIVATE *exp; FILE *fp; FREF *frp; GS *gp; MARK rm; db_recno_t nlines; size_t arglen = 0; int argc, rval; char *p; gp = sp->gp; /* * 0 args: read the current pathname. * 1 args: check for "read !arg". */ switch (cmdp->argc) { case 0: which = R_ARG; break; case 1: arg = cmdp->argv[0]->bp; arglen = cmdp->argv[0]->len; if (*arg == '!') { ++arg; --arglen; which = R_FILTER; /* Secure means no shell access. */ if (O_ISSET(sp, O_SECURE)) { ex_wemsg(sp, cmdp->cmd->name, EXM_SECURE_F); return (1); } } else which = R_EXPANDARG; break; default: abort(); /* NOTREACHED */ } /* Load a temporary file if no file being edited. */ if (sp->ep == NULL) { if ((frp = file_add(sp, NULL)) == NULL) return (1); if (file_init(sp, frp, NULL, 0)) return (1); } switch (which) { case R_FILTER: /* * File name and bang expand the user's argument. If * we don't get an additional argument, it's illegal. */ argc = cmdp->argc; if (argv_exp1(sp, cmdp, arg, arglen, 1)) return (1); if (argc == cmdp->argc) { ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } argc = cmdp->argc - 1; /* Set the last bang command. */ exp = EXP(sp); if (exp->lastbcomm != NULL) free(exp->lastbcomm); if ((exp->lastbcomm = v_wstrdup(sp, cmdp->argv[argc]->bp, cmdp->argv[argc]->len)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } /* * Vi redisplayed the user's argument if it changed, ex * always displayed a !, plus the user's argument if it * changed. */ if (F_ISSET(sp, SC_VI)) { if (F_ISSET(cmdp, E_MODIFY)) (void)vs_update(sp, "!", cmdp->argv[argc]->bp); } else { if (F_ISSET(cmdp, E_MODIFY)) { INT2CHAR(sp, cmdp->argv[argc]->bp, cmdp->argv[argc]->len + 1, name, nlen); (void)ex_printf(sp, "!%s\n", name); } else (void)ex_puts(sp, "!\n"); (void)ex_fflush(sp); } /* * Historically, filter reads as the first ex command didn't * wait for the user. If SC_SCR_EXWROTE not already set, set * the don't-wait flag. */ if (!F_ISSET(sp, SC_SCR_EXWROTE)) F_SET(sp, SC_EX_WAIT_NO); /* * Switch into ex canonical mode. The reason to restore the * original terminal modes for read filters is so that users * can do things like ":r! cat /dev/tty". * * !!! * We do not output an extra <newline>, so that we don't touch * the screen on a normal read. */ if (F_ISSET(sp, SC_VI)) { if (gp->scr_screen(sp, SC_EX)) { ex_wemsg(sp, cmdp->cmd->name, EXM_NOCANON_F); return (1); } /* * !!! * Historically, the read command doesn't switch to * the alternate X11 xterm screen, if doing a filter * read -- don't set SA_ALTERNATE. */ F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE); } if (ex_filter(sp, cmdp, &cmdp->addr1, NULL, &rm, cmdp->argv[argc]->bp, FILTER_READ)) return (1); /* The filter version of read set the autoprint flag. */ F_SET(cmdp, E_AUTOPRINT); /* * If in vi mode, move to the first nonblank. Might have * switched into ex mode, so saved the original SC_VI value. */ sp->lno = rm.lno; if (F_ISSET(sp, SC_VI)) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } return (0); case R_ARG: name = sp->frp->name; break; case R_EXPANDARG: if (argv_exp2(sp, cmdp, arg, arglen)) return (1); /* * 0 args: impossible. * 1 args: impossible (I hope). * 2 args: read it. * >2 args: object, too many args. * * The 1 args case depends on the argv_sexp() function refusing * to return success without at least one non-blank character. */ switch (cmdp->argc) { case 0: case 1: abort(); /* NOTREACHED */ case 2: INT2CHAR(sp, cmdp->argv[1]->bp, cmdp->argv[1]->len + 1, name, nlen); /* * !!! * Historically, the read and write commands renamed * "unnamed" files, or, if the file had a name, set * the alternate file name. */ if (F_ISSET(sp->frp, FR_TMPFILE) && !F_ISSET(sp->frp, FR_EXNAMED)) { if ((p = strdup(name)) != NULL) { free(sp->frp->name); sp->frp->name = p; } /* * The file has a real name, it's no longer a * temporary, clear the temporary file flags. */ F_CLR(sp->frp, FR_TMPEXIT | FR_TMPFILE); F_SET(sp->frp, FR_NAMECHANGE | FR_EXNAMED); /* Notify the screen. */ (void)sp->gp->scr_rename(sp, sp->frp->name, 1); name = sp->frp->name; } else { set_alt_name(sp, name); name = sp->alt_name; } break; default: ex_wemsg(sp, cmdp->argv[0]->bp, EXM_FILECOUNT); return (1); } break; } /* * !!! * Historically, vi did not permit reads from non-regular files, nor * did it distinguish between "read !" and "read!", so there was no * way to "force" it. We permit reading from named pipes too, since * they didn't exist when the original implementation of vi was done * and they seem a reasonable addition. */ if ((fp = fopen(name, "r")) == NULL || fstat(fileno(fp), &sb)) { msgq_str(sp, M_SYSERR, name, "%s"); return (1); } if (!S_ISFIFO(sb.st_mode) && !S_ISREG(sb.st_mode)) { (void)fclose(fp); msgq(sp, M_ERR, "145|Only regular files and named pipes may be read"); return (1); } /* Try and get a lock. */ if (file_lock(sp, NULL, NULL, fileno(fp), 0) == LOCK_UNAVAIL) msgq(sp, M_ERR, "146|%s: read lock was unavailable", name); rval = ex_readfp(sp, name, fp, &cmdp->addr1, &nlines, 0); /* * In vi, set the cursor to the first line read in, if anything read * in, otherwise, the address. (Historic vi set it to the line after * the address regardless, but since that line may not exist we don't * bother.) * * In ex, set the cursor to the last line read in, if anything read in, * otherwise, the address. */ if (F_ISSET(sp, SC_VI)) { sp->lno = cmdp->addr1.lno; if (nlines) ++sp->lno; } else sp->lno = cmdp->addr1.lno + nlines; return (rval); }
/* * sscr_exec -- * Take a line and hand it off to the shell. * * PUBLIC: int sscr_exec __P((SCR *, db_recno_t)); */ int sscr_exec(SCR *sp, db_recno_t lno) { SCRIPT *sc; db_recno_t last_lno; size_t blen, len, last_len; int isempty, matchprompt, rval; ssize_t nw; char *bp = NULL; const char *p; const CHAR_T *ip; size_t ilen; sc = sp->script; /* If there's a prompt on the last line, append the command. */ if (db_last(sp, &last_lno)) return (1); if (db_get(sp, last_lno, DBG_FATAL, __UNCONST(&ip), &ilen)) return (1); INT2CHAR(sp, ip, ilen, p, last_len); if (last_len == sc->sh_prompt_len && memcmp(p, sc->sh_prompt, last_len) == 0) { matchprompt = 1; GET_SPACE_RETC(sp, bp, blen, last_len + 128); memmove(bp, p, last_len); } else matchprompt = 0; /* Get something to execute. */ if (db_eget(sp, lno, __UNCONST(&ip), &ilen, &isempty)) { if (isempty) goto empty; goto err1; } /* Empty lines aren't interesting. */ if (ilen == 0) goto empty; INT2CHAR(sp, ip, ilen, p, len); /* Delete any prompt. */ if (len >= sc->sh_prompt_len && memcmp(p, sc->sh_prompt, sc->sh_prompt_len) == 0) { len -= sc->sh_prompt_len; if (len == 0) { empty: msgq(sp, M_BERR, "151|No command to execute"); goto err1; } p += sc->sh_prompt_len; } /* Push the line to the shell. */ if ((size_t)(nw = write(sc->sh_master, p, len)) != len) goto err2; rval = 0; if (write(sc->sh_master, "\n", 1) != 1) { err2: if (nw == 0) errno = EIO; msgq(sp, M_SYSERR, "shell"); goto err1; } if (matchprompt) { ADD_SPACE_GOTO(sp, char, bp, blen, last_len + len); memmove(bp + last_len, p, len); CHAR2INT(sp, bp, last_len + len, ip, ilen); if (db_set(sp, last_lno, ip, ilen)) err1: rval = 1; } if (matchprompt) alloc_err: FREE_SPACE(sp, bp, blen); return (rval); }
/* * ex_readfp -- * Read lines into the file. * * PUBLIC: int ex_readfp __P((SCR *, const char *, FILE *, MARK *, db_recno_t *, int)); */ int ex_readfp(SCR *sp, const char *name, FILE *fp, MARK *fm, db_recno_t *nlinesp, int silent) { EX_PRIVATE *exp; GS *gp; db_recno_t lcnt, lno; size_t len; u_long ccnt; /* XXX: can't print off_t portably. */ int nf, rval; const char *p; size_t wlen; const CHAR_T *wp; gp = sp->gp; exp = EXP(sp); /* * Add in the lines from the output. Insertion starts at the line * following the address. */ ccnt = 0; lcnt = 0; p = "147|Reading..."; for (lno = fm->lno; !ex_getline(sp, fp, &len); ++lno, ++lcnt) { if ((lcnt + 1) % INTERRUPT_CHECK == 0) { if (INTERRUPTED(sp)) break; if (!silent) { gp->scr_busy(sp, p, p == NULL ? BUSY_UPDATE : BUSY_ON); p = NULL; } } FILE2INT5(sp, exp->ibcw, exp->ibp, len, wp, wlen); if (db_append(sp, 1, lno, wp, wlen)) goto err; ccnt += len; } if (ferror(fp) || fclose(fp)) goto err; /* Return the number of lines read in. */ if (nlinesp != NULL) *nlinesp = lcnt; if (!silent) { char *q = msg_print(sp, name, &nf); msgq(sp, M_INFO, "148|%s: %lu lines, %lu characters", q, (unsigned long)lcnt, (unsigned long)ccnt); if (nf) FREE_SPACE(sp, q, 0); } rval = 0; if (0) { err: msgq_str(sp, M_SYSERR, name, "%s"); (void)fclose(fp); rval = 1; } if (!silent) gp->scr_busy(sp, NULL, BUSY_OFF); return (rval); }
/* * put -- * Put text buffer contents into the file. * * PUBLIC: int put __P((SCR *, CB *, CHAR_T *, MARK *, MARK *, int)); */ int put(SCR *sp, CB *cbp, ARG_CHAR_T *namep, MARK *cp, MARK *rp, int append) { ARG_CHAR_T name; TEXT *ltp, *tp; db_recno_t lno; size_t blen, clen, len; int rval; CHAR_T *bp, *t; CHAR_T *p; if (cbp == NULL) { if (namep == NULL) { cbp = sp->wp->dcbp; if (cbp == NULL) { msgq(sp, M_ERR, "053|The default buffer is empty"); return (1); } } else { name = *namep; CBNAME(sp, cbp, name); if (cbp == NULL) { msgq(sp, M_ERR, "054|Buffer %s is empty", KEY_NAME(sp, name)); return (1); } } } tp = TAILQ_FIRST(&cbp->textq); /* * It's possible to do a put into an empty file, meaning that the cut * buffer simply becomes the file. It's a special case so that we can * ignore it in general. * * !!! * Historically, pasting into a file with no lines in vi would preserve * the single blank line. This is surely a result of the fact that the * historic vi couldn't deal with a file that had no lines in it. This * implementation treats that as a bug, and does not retain the blank * line. * * Historical practice is that the cursor ends at the first character * in the file. */ if (cp->lno == 1) { if (db_last(sp, &lno)) return (1); if (lno == 0) { for (; tp != NULL; ++lno, ++sp->rptlines[L_ADDED], tp = TAILQ_NEXT(tp, q)) if (db_append(sp, 1, lno, tp->lb, tp->len)) return (1); rp->lno = 1; rp->cno = 0; return (0); } } /* If a line mode buffer, append each new line into the file. */ if (F_ISSET(cbp, CB_LMODE)) { lno = append ? cp->lno : cp->lno - 1; rp->lno = lno + 1; for (; tp != NULL; ++lno, ++sp->rptlines[L_ADDED], tp = TAILQ_NEXT(tp, q)) if (db_append(sp, 1, lno, tp->lb, tp->len)) return (1); rp->cno = 0; (void)nonblank(sp, rp->lno, &rp->cno); return (0); } /* * If buffer was cut in character mode, replace the current line with * one built from the portion of the first line to the left of the * split plus the first line in the CB. Append each intermediate line * in the CB. Append a line built from the portion of the first line * to the right of the split plus the last line in the CB. * * Get the first line. */ lno = cp->lno; if (db_get(sp, lno, DBG_FATAL, &p, &len)) return (1); GET_SPACE_RETW(sp, bp, blen, tp->len + len + 1); t = bp; /* Original line, left of the split. */ if (len > 0 && (clen = cp->cno + (append ? 1 : 0)) > 0) { MEMCPYW(bp, p, clen); p += clen; t += clen; } /* First line from the CB. */ if (tp->len != 0) { MEMCPYW(t, tp->lb, tp->len); t += tp->len; } /* Calculate length left in the original line. */ clen = len == 0 ? 0 : len - (cp->cno + (append ? 1 : 0)); /* * !!! * In the historical 4BSD version of vi, character mode puts within * a single line have two cursor behaviors: if the put is from the * unnamed buffer, the cursor moves to the character inserted which * appears last in the file. If the put is from a named buffer, * the cursor moves to the character inserted which appears first * in the file. In System III/V, it was changed at some point and * the cursor always moves to the first character. In both versions * of vi, character mode puts that cross line boundaries leave the * cursor on the first character. Nvi implements the System III/V * behavior, and expect POSIX.2 to do so as well. */ rp->lno = lno; rp->cno = len == 0 ? 0 : sp->cno + (append && tp->len ? 1 : 0); /* * If no more lines in the CB, append the rest of the original * line and quit. Otherwise, build the last line before doing * the intermediate lines, because the line changes will lose * the cached line. */ if (TAILQ_NEXT(tp, q) == NULL) { if (clen > 0) { MEMCPYW(t, p, clen); t += clen; } if (db_set(sp, lno, bp, t - bp)) goto err; if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } } else { /* * Have to build both the first and last lines of the * put before doing any sets or we'll lose the cached * line. Build both the first and last lines in the * same buffer, so we don't have to have another buffer * floating around. * * Last part of original line; check for space, reset * the pointer into the buffer. */ ltp = TAILQ_LAST(&cbp->textq, _texth); len = t - bp; ADD_SPACE_RETW(sp, bp, blen, ltp->len + clen); t = bp + len; /* Add in last part of the CB. */ MEMCPYW(t, ltp->lb, ltp->len); if (clen) MEMCPYW(t + ltp->len, p, clen); clen += ltp->len; /* * Now: bp points to the first character of the first * line, t points to the last character of the last * line, t - bp is the length of the first line, and * clen is the length of the last. Just figured you'd * want to know. * * Output the line replacing the original line. */ if (db_set(sp, lno, bp, t - bp)) goto err; if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } /* Output any intermediate lines in the CB. */ for (tp = TAILQ_NEXT(tp, q); TAILQ_NEXT(tp, q) != NULL; ++lno, ++sp->rptlines[L_ADDED], tp = TAILQ_NEXT(tp, q)) if (db_append(sp, 1, lno, tp->lb, tp->len)) goto err; if (db_append(sp, 1, lno, t, clen)) goto err; ++sp->rptlines[L_ADDED]; } rval = 0; if (0) err: rval = 1; FREE_SPACEW(sp, bp, blen); return (rval); }
/* * log_line -- * Log a line change. * * PUBLIC: int log_line __P((SCR *, db_recno_t, u_int)); */ int log_line(SCR *sp, db_recno_t lno, u_int action) { DBT data, key; EXF *ep; size_t len; CHAR_T *lp; db_recno_t lcur; ep = sp->ep; if (F_ISSET(ep, F_NOLOG)) return (0); /* * XXX * * Kluge for vi. Clear the EXF undo flag so that the * next 'u' command does a roll-back, regardless. */ F_CLR(ep, F_UNDO); /* Put out one initial cursor record per set of changes. */ if (ep->l_cursor.lno != OOBLNO) { if (log_cursor1(sp, LOG_CURSOR_INIT)) return (1); ep->l_cursor.lno = OOBLNO; ep->l_win = sp->wp; } /*else if (ep->l_win != sp->wp) { printf("log_line own: %p, this: %p\n", ep->l_win, sp->wp); return 1; }*/ if ((sp->db_error = __vi_change_log(ep->env, NULL, &ep->lsn_cur, 0, action, lno)) != 0) { msgq(sp, M_DBERR, "change_log"); return 1; } #if defined(DEBUG) && 0 switch (action) { case LOG_LINE_APPEND_F: vtrace(sp, "%u: log_line: append_f: %lu {%u}\n", ep->l_cur, lno, len); break; case LOG_LINE_APPEND_B: vtrace(sp, "%u: log_line: append_b: %lu {%u}\n", ep->l_cur, lno, len); break; case LOG_LINE_DELETE_F: vtrace(sp, "%lu: log_line: delete_f: %lu {%u}\n", ep->l_cur, lno, len); break; case LOG_LINE_DELETE_B: vtrace(sp, "%lu: log_line: delete_b: %lu {%u}\n", ep->l_cur, lno, len); break; case LOG_LINE_RESET_F: vtrace(sp, "%lu: log_line: reset_f: %lu {%u}\n", ep->l_cur, lno, len); break; case LOG_LINE_RESET_B: vtrace(sp, "%lu: log_line: reset_b: %lu {%u}\n", ep->l_cur, lno, len); break; } #endif /* Reset high water mark. */ ep->l_high = ++ep->l_cur; return (0); }
/* * cl_ex_init -- * Initialize the ex screen. */ static int cl_ex_init(SCR *sp) { CL_PRIVATE *clp; int error; const char *ttype; clp = CLP(sp); /* If already initialized, just set the terminal modes. */ if (F_ISSET(clp, CL_SCR_EX_INIT)) goto fast; /* If not reading from a file, we're done. */ if (!F_ISSET(clp, CL_STDIN_TTY)) return (0); if (F_ISSET(clp, CL_CHANGE_TERM)) { if (F_ISSET(clp, CL_SETUPTERM) && del_curterm(cur_term)) return (1); F_CLR(clp, CL_SETUPTERM | CL_CHANGE_TERM); } if (!F_ISSET(clp, CL_SETUPTERM)) { /* We'll need a terminal type. */ if (opts_empty(sp, O_TERM, 0)) return (1); ttype = O_STR(sp, O_TERM); (void)setupterm(ttype, STDOUT_FILENO, &error); if (error == 0 || error == -1) return (1); } /* Get the ex termcap/terminfo strings. */ (void)cl_getcap(sp, "cup", &clp->cup); (void)cl_getcap(sp, "smso", &clp->smso); (void)cl_getcap(sp, "rmso", &clp->rmso); (void)cl_getcap(sp, "el", &clp->el); (void)cl_getcap(sp, "cuu1", &clp->cuu1); /* Enter_standout_mode and exit_standout_mode are paired. */ if (clp->smso == NULL || clp->rmso == NULL) { if (clp->smso != NULL) { free(clp->smso); clp->smso = NULL; } if (clp->rmso != NULL) { free(clp->rmso); clp->rmso = NULL; } } /* * Turn on canonical mode, with normal input and output processing. * Start with the original terminal settings as the user probably * had them (including any local extensions) set correctly for the * current terminal. * * !!! * We can't get everything that we need portably; for example, ONLCR, * mapping <newline> to <carriage-return> on output isn't required * by POSIX 1003.1b-1993. If this turns out to be a problem, then * we'll either have to play some games on the mapping, or we'll have * to make all ex printf's output \r\n instead of \n. */ clp->ex_enter = clp->orig; clp->ex_enter.c_lflag |= ECHO | ECHOE | ECHOK | ICANON | IEXTEN | ISIG; #ifdef ECHOCTL clp->ex_enter.c_lflag |= ECHOCTL; #endif #ifdef ECHOKE clp->ex_enter.c_lflag |= ECHOKE; #endif clp->ex_enter.c_iflag |= ICRNL; clp->ex_enter.c_oflag |= OPOST; #ifdef ONLCR clp->ex_enter.c_oflag |= ONLCR; #endif fast: if (tcsetattr(STDIN_FILENO, TCSADRAIN | TCSASOFT, &clp->ex_enter)) { if (errno == EINTR) goto fast; msgq(sp, M_SYSERR, "tcsetattr"); return (1); } return (0); }
/* * file_end -- * Stop editing a file. * * PUBLIC: int file_end __P((SCR *, EXF *, int)); */ int file_end(SCR *sp, EXF *ep, int force) { FREF *frp; /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. * (If argument ep is NULL, use sp->ep.) * * If multiply referenced, just decrement the count and return. */ if (ep == NULL) ep = sp->ep; CIRCLEQ_REMOVE(&ep->scrq, sp, eq); if (--ep->refcnt != 0) return (0); /* * * Clean up the FREF structure. * * Save the cursor location. * * XXX * It would be cleaner to do this somewhere else, but by the time * ex or vi knows that we're changing files it's already happened. */ frp = sp->frp; frp->lno = sp->lno; frp->cno = sp->cno; F_SET(frp, FR_CURSORSET); /* * We may no longer need the temporary backing file, so clean it * up. We don't need the FREF structure either, if the file was * never named, so lose it. * * !!! * Re: FR_DONTDELETE, see the comment above in file_init(). */ if (!F_ISSET(frp, FR_DONTDELETE) && frp->tname != NULL) { if (unlink(frp->tname)) msgq_str(sp, M_SYSERR, frp->tname, "240|%s: remove"); free(frp->tname); frp->tname = NULL; if (F_ISSET(frp, FR_TMPFILE)) { CIRCLEQ_REMOVE(&sp->gp->frefq, frp, q); if (frp->name != NULL) free(frp->name); free(frp); } sp->frp = NULL; } /* * Clean up the EXF structure. * * Close the db structure. */ if (ep->db->close != NULL) { if ((sp->db_error = ep->db->close(ep->db, DB_NOSYNC)) != 0 && !force) { msgq_str(sp, M_DBERR, frp->name, "241|%s: close"); CIRCLEQ_INSERT_HEAD(&ep->scrq, sp, eq); ++ep->refcnt; return (1); } ep->db = NULL; } /* COMMITTED TO THE CLOSE. THERE'S NO GOING BACK... */ /* Stop logging. */ (void)log_end(sp, ep); /* Free up any marks. */ (void)mark_end(sp, ep); if (ep->env) { DB_ENV *env; ep->env->close(ep->env, 0); ep->env = 0; if ((sp->db_error = db_env_create(&env, 0))) msgq(sp, M_DBERR, "env_create"); if ((sp->db_error = db_env_remove(env, ep->env_path, 0))) msgq(sp, M_DBERR, "env->remove"); if (ep->env_path != NULL && rmdir(ep->env_path)) msgq_str(sp, M_SYSERR, ep->env_path, "242|%s: remove"); } /* * Delete recovery files, close the open descriptor, free recovery * memory. See recover.c for a description of the protocol. * * XXX * Unlink backup file first, we can detect that the recovery file * doesn't reference anything when the user tries to recover it. * There's a race, here, obviously, but it's fairly small. */ if (!F_ISSET(ep, F_RCV_NORM)) { if (ep->rcv_path != NULL && unlink(ep->rcv_path)) msgq_str(sp, M_SYSERR, ep->rcv_path, "242|%s: remove"); if (ep->rcv_mpath != NULL && unlink(ep->rcv_mpath)) msgq_str(sp, M_SYSERR, ep->rcv_mpath, "243|%s: remove"); } CIRCLEQ_REMOVE(&sp->gp->exfq, ep, q); if (ep->fd != -1) (void)close(ep->fd); if (ep->fcntl_fd != -1) (void)close(ep->fcntl_fd); if (ep->rcv_fd != -1) (void)close(ep->rcv_fd); if (ep->env_path != NULL) free(ep->env_path); if (ep->rcv_path != NULL) free(ep->rcv_path); if (ep->rcv_mpath != NULL) free(ep->rcv_mpath); free(ep); return (0); }
/* * ex_move -- :[line [,line]] mo[ve] line * Move selected lines. * * PUBLIC: int ex_move(SCR *, EXCMD *); */ int ex_move(SCR *sp, EXCMD *cmdp) { LMARK *lmp; MARK fm1, fm2; recno_t cnt, diff, fl, tl, mfl, mtl; size_t blen, len; int mark_reset; char *bp, *p; NEEDFILE(sp, cmdp); /* * It's not possible to move things into the area that's being * moved. */ fm1 = cmdp->addr1; fm2 = cmdp->addr2; if (cmdp->lineno >= fm1.lno && cmdp->lineno <= fm2.lno) { msgq(sp, M_ERR, "Destination line is inside move range"); return (1); } /* * Log the positions of any marks in the to-be-deleted lines. This * has to work with the logging code. What happens is that we log * the old mark positions, make the changes, then log the new mark * positions. Then the marks end up in the right positions no matter * which way the log is traversed. * * XXX * Reset the MARK_USERSET flag so that the log can undo the mark. * This isn't very clean, and should probably be fixed. */ fl = fm1.lno; tl = cmdp->lineno; /* Log the old positions of the marks. */ mark_reset = 0; LIST_FOREACH(lmp, &sp->ep->marks, q) if (lmp->name != ABSMARK1 && lmp->lno >= fl && lmp->lno <= tl) { mark_reset = 1; F_CLR(lmp, MARK_USERSET); (void)log_mark(sp, lmp); } /* Get memory for the copy. */ GET_SPACE_RET(sp, bp, blen, 256); /* Move the lines. */ diff = (fm2.lno - fm1.lno) + 1; if (tl > fl) { /* Destination > source. */ mfl = tl - diff; mtl = tl; for (cnt = diff; cnt--;) { if (db_get(sp, fl, DBG_FATAL, &p, &len)) return (1); BINC_RET(sp, bp, blen, len); memcpy(bp, p, len); if (db_append(sp, 1, tl, bp, len)) return (1); if (mark_reset) LIST_FOREACH(lmp, &sp->ep->marks, q) if (lmp->name != ABSMARK1 && lmp->lno == fl) lmp->lno = tl + 1; if (db_delete(sp, fl)) return (1); } } else { /* Destination < source. */
/* * file_write -- * Write the file to disk. Historic vi had fairly convoluted * semantics for whether or not writes would happen. That's * why all the flags. * * PUBLIC: int file_write __P((SCR *, MARK *, MARK *, char *, int)); */ int file_write(SCR *sp, MARK *fm, MARK *tm, char *name, int flags) { enum { NEWFILE, OLDFILE } mtype; struct stat sb; EXF *ep; FILE *fp; FREF *frp; MARK from, to; size_t len; u_long nlno, nch; int fd, nf, noname, oflags, rval; char *p, *s, *t, buf[MAXPATHLEN + 64]; const char *msgstr; ep = sp->ep; frp = sp->frp; /* * Writing '%', or naming the current file explicitly, has the * same semantics as writing without a name. */ if (name == NULL || !strcmp(name, frp->name)) { noname = 1; name = frp->name; } else noname = 0; /* Can't write files marked read-only, unless forced. */ if (!LF_ISSET(FS_FORCE) && noname && O_ISSET(sp, O_READONLY)) { msgq(sp, M_ERR, LF_ISSET(FS_POSSIBLE) ? "244|Read-only file, not written; use ! to override" : "245|Read-only file, not written"); return (1); } /* If not forced, not appending, and "writeany" not set ... */ if (!LF_ISSET(FS_FORCE | FS_APPEND) && !O_ISSET(sp, O_WRITEANY)) { /* Don't overwrite anything but the original file. */ if ((!noname || F_ISSET(frp, FR_NAMECHANGE)) && !stat(name, &sb)) { msgq_str(sp, M_ERR, name, LF_ISSET(FS_POSSIBLE) ? "246|%s exists, not written; use ! to override" : "247|%s exists, not written"); return (1); } /* * Don't write part of any existing file. Only test for the * original file, the previous test catches anything else. */ if (!LF_ISSET(FS_ALL) && noname && !stat(name, &sb)) { msgq(sp, M_ERR, LF_ISSET(FS_POSSIBLE) ? "248|Partial file, not written; use ! to override" : "249|Partial file, not written"); return (1); } } /* * Figure out if the file already exists -- if it doesn't, we display * the "new file" message. The stat might not be necessary, but we * just repeat it because it's easier than hacking the previous tests. * The information is only used for the user message and modification * time test, so we can ignore the obvious race condition. * * One final test. If we're not forcing or appending the current file, * and we have a saved modification time, object if the file changed * since we last edited or wrote it, and make them force it. */ if (stat(name, &sb)) mtype = NEWFILE; else { if (noname && !LF_ISSET(FS_FORCE | FS_APPEND) && ((F_ISSET(ep, F_DEVSET) && (sb.st_dev != ep->mdev || sb.st_ino != ep->minode)) || sb.st_mtime != ep->mtime)) { msgq_str(sp, M_ERR, name, LF_ISSET(FS_POSSIBLE) ? "250|%s: file modified more recently than this copy; use ! to override" : "251|%s: file modified more recently than this copy"); return (1); } mtype = OLDFILE; } /* Set flags to create, write, and either append or truncate. */ oflags = O_CREAT | O_WRONLY | (LF_ISSET(FS_APPEND) ? O_APPEND : O_TRUNC); /* Backup the file if requested. */ if (!opts_empty(sp, O_BACKUP, 1) && file_backup(sp, name, O_STR(sp, O_BACKUP)) && !LF_ISSET(FS_FORCE)) return (1); /* Open the file. */ SIGBLOCK; if ((fd = open(name, oflags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) < 0) { msgq_str(sp, M_SYSERR, name, "%s"); SIGUNBLOCK; return (1); } SIGUNBLOCK; /* Try and get a lock. */ if (!noname && file_lock(sp, NULL, NULL, fd, 0) == LOCK_UNAVAIL) msgq_str(sp, M_ERR, name, "252|%s: write lock was unavailable"); #if __linux__ /* * XXX * In libc 4.5.x, fdopen(fd, "w") clears the O_APPEND flag (if set). * This bug is fixed in libc 4.6.x. * * This code works around this problem for libc 4.5.x users. * Note that this code is harmless if you're using libc 4.6.x. */ if (LF_ISSET(FS_APPEND) && lseek(fd, (off_t)0, SEEK_END) < 0) { msgq(sp, M_SYSERR, name); return (1); } #endif /* * Use stdio for buffering. * * XXX * SVR4.2 requires the fdopen mode exactly match the original open * mode, i.e. you have to open with "a" if appending. */ if ((fp = fdopen(fd, LF_ISSET(FS_APPEND) ? "a" : "w")) == NULL) { msgq_str(sp, M_SYSERR, name, "%s"); (void)close(fd); return (1); } /* Build fake addresses, if necessary. */ if (fm == NULL) { from.lno = 1; from.cno = 0; fm = &from; if (db_last(sp, &to.lno)) return (1); to.cno = 0; tm = &to; } rval = ex_writefp(sp, name, fp, fm, tm, &nlno, &nch, 0); /* * Save the new last modification time -- even if the write fails * we re-init the time. That way the user can clean up the disk * and rewrite without having to force it. */ if (noname) { if (stat(name, &sb)) time(&ep->mtime); else { F_SET(ep, F_DEVSET); ep->mdev = sb.st_dev; ep->minode = sb.st_ino; ep->mtime = sb.st_mtime; } } /* * If the write failed, complain loudly. ex_writefp() has already * complained about the actual error, reinforce it if data was lost. */ if (rval) { if (!LF_ISSET(FS_APPEND)) msgq_str(sp, M_ERR, name, "254|%s: WARNING: FILE TRUNCATED"); return (1); } /* * Once we've actually written the file, it doesn't matter that the * file name was changed -- if it was, we've already whacked it. */ F_CLR(frp, FR_NAMECHANGE); /* * If wrote the entire file, and it wasn't by appending it to a file, * clear the modified bit. If the file was written to the original * file name and the file is a temporary, set the "no exit" bit. This * permits the user to write the file and use it in the context of the * filesystem, but still keeps them from discarding their changes by * exiting. */ if (LF_ISSET(FS_ALL) && !LF_ISSET(FS_APPEND)) { F_CLR(ep, F_MODIFIED); if (F_ISSET(frp, FR_TMPFILE)) { if (noname) F_SET(frp, FR_TMPEXIT); else F_CLR(frp, FR_TMPEXIT); } } p = msg_print(sp, name, &nf); switch (mtype) { case NEWFILE: msgstr = msg_cat(sp, "256|%s: new file: %lu lines, %lu characters", NULL); len = snprintf(buf, sizeof(buf), msgstr, p, nlno, nch); break; case OLDFILE: msgstr = msg_cat(sp, LF_ISSET(FS_APPEND) ? "315|%s: appended: %lu lines, %lu characters" : "257|%s: %lu lines, %lu characters", NULL); len = snprintf(buf, sizeof(buf), msgstr, p, nlno, nch); break; default: abort(); } /* * There's a nasty problem with long path names. Cscope and tags files * can result in long paths and vi will request a continuation key from * the user. Unfortunately, the user has typed ahead, and chaos will * result. If we assume that the characters in the filenames only take * a single screen column each, we can trim the filename. */ s = buf; if (len >= sp->cols) { for (s = buf, t = buf + strlen(p); s < t && (*s != '/' || len >= sp->cols - 3); ++s, --len); if (s == t) s = buf; else { *--s = '.'; /* Leading ellipses. */ *--s = '.'; *--s = '.'; } } msgq(sp, M_INFO, s); if (nf) FREE_SPACE(sp, p, 0); return (0); }
/* * ex_s -- * [line [,line]] s[ubstitute] [[/;]pat[/;]/repl[/;] [cgr] [count] [#lp]] * * Substitute on lines matching a pattern. * * PUBLIC: int ex_s(SCR *, EXCMD *); */ int ex_s(SCR *sp, EXCMD *cmdp) { regex_t *re; size_t blen, len; u_int flags; int delim; char *bp, *ptrn, *rep, *p, *t; /* * Skip leading white space. * * !!! * Historic vi allowed any non-alphanumeric to serve as the * substitution command delimiter. * * !!! * If the arguments are empty, it's the same as &, i.e. we * repeat the last substitution. */ if (cmdp->argc == 0) goto subagain; for (p = cmdp->argv[0]->bp, len = cmdp->argv[0]->len; len > 0; --len, ++p) { if (!isblank(*p)) break; } if (len == 0) subagain: return (ex_subagain(sp, cmdp)); delim = *p++; if (isalnum(delim) || delim == '\\') return (s(sp, cmdp, p, &sp->subre_c, SUB_MUSTSETR)); /* * !!! * The full-blown substitute command reset the remembered * state of the 'c' and 'g' suffices. */ sp->c_suffix = sp->g_suffix = 0; /* * Get the pattern string, toss escaping characters. * * !!! * Historic vi accepted any of the following forms: * * :s/abc/def/ change "abc" to "def" * :s/abc/def change "abc" to "def" * :s/abc/ delete "abc" * :s/abc delete "abc" * * QUOTING NOTE: * * Only toss an escaping character if it escapes a delimiter. * This means that "s/A/\\\\f" replaces "A" with "\\f". It * would be nice to be more regular, i.e. for each layer of * escaping a single escaping character is removed, but that's * not how the historic vi worked. */ for (ptrn = t = p;;) { if (p[0] == '\0' || p[0] == delim) { if (p[0] == delim) ++p; /* * !!! * Nul terminate the pattern string -- it's passed * to regcomp which doesn't understand anything else. */ *t = '\0'; break; } if (p[0] == '\\') { if (p[1] == delim) ++p; else if (p[1] == '\\') *t++ = *p++; } *t++ = *p++; } /* * If the pattern string is empty, use the last RE (not just the * last substitution RE). */ if (*ptrn == '\0') { if (sp->re == NULL) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } /* Re-compile the RE if necessary. */ if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH)) return (1); flags = 0; } else { /* * !!! * Compile the RE. Historic practice is that substitutes set * the search direction as well as both substitute and search * RE's. We compile the RE twice, as we don't want to bother * ref counting the pattern string and (opaque) structure. */ if (re_compile(sp, ptrn, t - ptrn, &sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH)) return (1); if (re_compile(sp, ptrn, t - ptrn, &sp->subre, &sp->subre_len, &sp->subre_c, RE_C_SUBST)) return (1); flags = SUB_FIRST; sp->searchdir = FORWARD; } re = &sp->re_c; /* * Get the replacement string. * * The special character & (\& if O_MAGIC not set) matches the * entire RE. No handling of & is required here, it's done by * re_sub(). * * The special character ~ (\~ if O_MAGIC not set) inserts the * previous replacement string into this replacement string. * Count ~'s to figure out how much space we need. We could * special case nonexistent last patterns or whether or not * O_MAGIC is set, but it's probably not worth the effort. * * QUOTING NOTE: * * Only toss an escaping character if it escapes a delimiter or * if O_MAGIC is set and it escapes a tilde. * * !!! * If the entire replacement pattern is "%", then use the last * replacement pattern. This semantic was added to vi in System * V and then percolated elsewhere, presumably around the time * that it was added to their version of ed(1). */ if (p[0] == '\0' || p[0] == delim) { if (p[0] == delim) ++p; if (sp->repl != NULL) free(sp->repl); sp->repl = NULL; sp->repl_len = 0; } else if (p[0] == '%' && (p[1] == '\0' || p[1] == delim)) p += p[1] == delim ? 2 : 1; else { for (rep = p, len = 0; p[0] != '\0' && p[0] != delim; ++p, ++len) if (p[0] == '~') len += sp->repl_len; GET_SPACE_RET(sp, bp, blen, len); for (t = bp, len = 0, p = rep;;) { if (p[0] == '\0' || p[0] == delim) { if (p[0] == delim) ++p; break; } if (p[0] == '\\') { if (p[1] == delim) ++p; else if (p[1] == '\\') { *t++ = *p++; ++len; } else if (p[1] == '~') { ++p; if (!O_ISSET(sp, O_MAGIC)) goto tilde; } } else if (p[0] == '~' && O_ISSET(sp, O_MAGIC)) { tilde: ++p; memcpy(t, sp->repl, sp->repl_len); t += sp->repl_len; len += sp->repl_len; continue; } *t++ = *p++; ++len; } if ((sp->repl_len = len) != 0) { if (sp->repl != NULL) free(sp->repl); if ((sp->repl = malloc(len)) == NULL) { msgq(sp, M_SYSERR, NULL); FREE_SPACE(sp, bp, blen); return (1); } memcpy(sp->repl, bp, len); } FREE_SPACE(sp, bp, blen); } return (s(sp, cmdp, p, re, flags)); }
/* * sscr_getprompt -- * Eat lines printed by the shell until a line with no trailing * carriage return comes; set the prompt from that line. */ static int sscr_getprompt(SCR *sp) { CHAR_T *endp, *p, *t, buf[1024]; SCRIPT *sc; struct pollfd pfd[1]; recno_t lline; size_t llen, len; u_int value; int nr; endp = buf; len = sizeof(buf); /* Wait up to a second for characters to read. */ sc = sp->script; pfd[0].fd = sc->sh_master; pfd[0].events = POLLIN; switch (poll(pfd, 1, 5 * 1000)) { case -1: /* Error or interrupt. */ msgq(sp, M_SYSERR, "poll"); goto prompterr; case 0: /* Timeout */ msgq(sp, M_ERR, "Error: timed out"); goto prompterr; default: /* Characters to read. */ break; } /* Read the characters. */ more: len = sizeof(buf) - (endp - buf); switch (nr = read(sc->sh_master, endp, len)) { case 0: /* EOF. */ msgq(sp, M_ERR, "Error: shell: EOF"); goto prompterr; case -1: /* Error or interrupt. */ msgq(sp, M_SYSERR, "shell"); goto prompterr; default: endp += nr; break; } /* If any complete lines, push them into the file. */ for (p = t = buf; p < endp; ++p) { value = KEY_VAL(sp, *p); if (value == K_CR || value == K_NL) { if (db_last(sp, &lline) || db_append(sp, 0, lline, t, p - t)) goto prompterr; t = p + 1; } } if (p > buf) { memmove(buf, t, endp - t); endp = buf + (endp - t); } if (endp == buf) goto more; /* Wait up 1/10 of a second to make sure that we got it all. */ switch (poll(pfd, 1, 100)) { case -1: /* Error or interrupt. */ msgq(sp, M_SYSERR, "poll"); goto prompterr; case 0: /* Timeout */ break; default: /* Characters to read. */ goto more; } /* Timed out, so theoretically we have a prompt. */ llen = endp - buf; endp = buf; /* Append the line into the file. */ if (db_last(sp, &lline) || db_append(sp, 0, lline, buf, llen)) { prompterr: sscr_end(sp); return (1); } return (sscr_setprompt(sp, buf, llen)); }
int conv_enc (SCR *sp, int option, const char *enc) { #if defined(USE_WIDECHAR) && defined(USE_ICONV) iconv_t id; char2wchar_t *c2w; wchar2char_t *w2c; switch (option) { case O_FILEENCODING: c2w = &sp->conv.file2int; w2c = &sp->conv.int2file; break; case O_INPUTENCODING: c2w = &sp->conv.input2int; w2c = NULL; break; default: c2w = NULL; w2c = NULL; break; } if (!*enc) { if (c2w) *c2w = raw2int; if (w2c) *w2c = int2raw; return 0; } if (!strcmp(enc, "WCHAR_T")) { if (c2w) *c2w = CHAR_T_char2int; if (w2c) *w2c = CHAR_T_int2char; return 0; } id = iconv_open(enc, nl_langinfo(CODESET)); if (id == (iconv_t)-1) goto err; iconv_close(id); id = iconv_open(nl_langinfo(CODESET), enc); if (id == (iconv_t)-1) goto err; iconv_close(id); switch (option) { case O_FILEENCODING: *c2w = fe_char2int; *w2c = fe_int2char; break; case O_INPUTENCODING: *c2w = ie_char2int; break; } F_CLR(sp, SC_CONV_ERROR); F_SET(sp, SC_SCR_REFORMAT); return 0; err: switch (option) { case O_FILEENCODING: msgq(sp, M_ERR, "321|File encoding conversion not supported"); break; case O_INPUTENCODING: msgq(sp, M_ERR, "322|Input encoding conversion not supported"); break; } #endif return 1; }
/* * sscr_exec -- * Take a line and hand it off to the shell. * * PUBLIC: int sscr_exec(SCR *, recno_t); */ int sscr_exec(SCR *sp, recno_t lno) { SCRIPT *sc; recno_t last_lno; size_t blen, len, last_len, tlen; int isempty, matchprompt, nw, rval; char *bp, *p; /* If there's a prompt on the last line, append the command. */ if (db_last(sp, &last_lno)) return (1); if (db_get(sp, last_lno, DBG_FATAL, &p, &last_len)) return (1); if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) { matchprompt = 1; GET_SPACE_RET(sp, bp, blen, last_len + 128); memmove(bp, p, last_len); } else matchprompt = 0; /* Get something to execute. */ if (db_eget(sp, lno, &p, &len, &isempty)) { if (isempty) goto empty; goto err1; } /* Empty lines aren't interesting. */ if (len == 0) goto empty; /* Delete any prompt. */ if (sscr_matchprompt(sp, p, len, &tlen)) { if (tlen == len) { empty: msgq(sp, M_BERR, "No command to execute"); goto err1; } p += (len - tlen); len = tlen; } /* Push the line to the shell. */ sc = sp->script; if ((nw = write(sc->sh_master, p, len)) != len) goto err2; rval = 0; if (write(sc->sh_master, "\n", 1) != 1) { err2: if (nw == 0) errno = EIO; msgq(sp, M_SYSERR, "shell"); goto err1; } if (matchprompt) { ADD_SPACE_RET(sp, bp, blen, last_len + len); memmove(bp + last_len, p, len); if (db_set(sp, last_lno, bp, last_len + len)) err1: rval = 1; } if (matchprompt) FREE_SPACE(sp, bp, blen); return (rval); }
/* * shift -- * Ex shift support. */ static int shift(SCR *sp, EXCMD *cmdp, enum which rl) { db_recno_t from, to; size_t blen, len, newcol, newidx, oldcol, oldidx, sw; int curset; CHAR_T *p; CHAR_T *bp, *tbp; NEEDFILE(sp, cmdp); if (O_VAL(sp, O_SHIFTWIDTH) == 0) { msgq(sp, M_INFO, "152|shiftwidth option set to 0"); return (0); } /* Copy the lines being shifted into the unnamed buffer. */ if (cut(sp, NULL, &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE)) return (1); /* * The historic version of vi permitted the user to string any number * of '>' or '<' characters together, resulting in an indent of the * appropriate levels. There's a special hack in ex_cmd() so that * cmdp->argv[0] points to the string of '>' or '<' characters. * * Q: What's the difference between the people adding features * to vi and the Girl Scouts? * A: The Girl Scouts have mint cookies and adult supervision. */ for (p = cmdp->argv[0]->bp, sw = 0; *p == '>' || *p == '<'; ++p) sw += O_VAL(sp, O_SHIFTWIDTH); GET_SPACE_RETW(sp, bp, blen, 256); curset = 0; for (from = cmdp->addr1.lno, to = cmdp->addr2.lno; from <= to; ++from) { if (db_get(sp, from, DBG_FATAL, &p, &len)) goto err; if (!len) { if (sp->lno == from) curset = 1; continue; } /* * Calculate the old indent amount and the number of * characters it used. */ for (oldidx = 0, oldcol = 0; oldidx < len; ++oldidx) if (p[oldidx] == ' ') ++oldcol; else if (p[oldidx] == '\t') oldcol += O_VAL(sp, O_TABSTOP) - oldcol % O_VAL(sp, O_TABSTOP); else break; /* Calculate the new indent amount. */ if (rl == RIGHT) newcol = oldcol + sw; else { newcol = oldcol < sw ? 0 : oldcol - sw; if (newcol == oldcol) { if (sp->lno == from) curset = 1; continue; } } /* Get a buffer that will hold the new line. */ ADD_SPACE_RETW(sp, bp, blen, newcol + len); /* * Build a new indent string and count the number of * characters it uses. */ tbp = bp; newidx = 0; if (!O_ISSET(sp, O_EXPANDTAB)) { for (; newcol >= O_VAL(sp, O_TABSTOP); ++newidx) { *tbp++ = '\t'; newcol -= O_VAL(sp, O_TABSTOP); } } for (; newcol > 0; --newcol, ++newidx) *tbp++ = ' '; /* Add the original line. */ MEMCPYW(tbp, p + oldidx, len - oldidx); /* Set the replacement line. */ if (db_set(sp, from, bp, (tbp + (len - oldidx)) - bp)) { err: FREE_SPACEW(sp, bp, blen); return (1); } /* * !!! * The shift command in historic vi had the usual bizarre * collection of cursor semantics. If called from vi, the * cursor was repositioned to the first non-blank character * of the lowest numbered line shifted. If called from ex, * the cursor was repositioned to the first non-blank of the * highest numbered line shifted. Here, if the cursor isn't * part of the set of lines that are moved, move it to the * first non-blank of the last line shifted. (This makes * ":3>>" in vi work reasonably.) If the cursor is part of * the shifted lines, it doesn't get moved at all. This * permits shifting of marked areas, i.e. ">'a." shifts the * marked area twice, something that couldn't be done with * historic vi. */ if (sp->lno == from) { curset = 1; if (newidx > oldidx) sp->cno += newidx - oldidx; else if (sp->cno >= oldidx - newidx) sp->cno -= oldidx - newidx; } } if (!curset) { sp->lno = to; sp->cno = 0; (void)nonblank(sp, to, &sp->cno); } FREE_SPACEW(sp, bp, blen); sp->rptlines[L_SHIFT] += cmdp->addr2.lno - cmdp->addr1.lno + 1; return (0); }
/* * sscr_insert -- * Take a line from the shell and insert it into the file. */ static int sscr_insert(SCR *sp) { CHAR_T *endp, *p, *t; SCRIPT *sc; struct pollfd pfd[1]; recno_t lno; size_t blen, len, tlen; u_int value; int nr, rval; char *bp; /* Find out where the end of the file is. */ if (db_last(sp, &lno)) return (1); #define MINREAD 1024 GET_SPACE_RET(sp, bp, blen, MINREAD); endp = bp; /* Read the characters. */ rval = 1; sc = sp->script; more: switch (nr = read(sc->sh_master, endp, MINREAD)) { case 0: /* EOF; shell just exited. */ sscr_end(sp); rval = 0; goto ret; case -1: /* Error or interrupt. */ msgq(sp, M_SYSERR, "shell"); goto ret; default: endp += nr; break; } /* Append the lines into the file. */ for (p = t = bp; p < endp; ++p) { value = KEY_VAL(sp, *p); if (value == K_CR || value == K_NL) { len = p - t; if (db_append(sp, 1, lno++, t, len)) goto ret; t = p + 1; } } if (p > t) { len = p - t; /* * If the last thing from the shell isn't another prompt, wait * up to 1/10 of a second for more stuff to show up, so that * we don't break the output into two separate lines. Don't * want to hang indefinitely because some program is hanging, * confused the shell, or whatever. */ if (!sscr_matchprompt(sp, t, len, &tlen) || tlen != 0) { pfd[0].fd = sc->sh_master; pfd[0].events = POLLIN; if (poll(pfd, 1, 100) > 0) { memmove(bp, t, len); endp = bp + len; goto more; } } if (sscr_setprompt(sp, t, len)) return (1); if (db_append(sp, 1, lno++, t, len)) goto ret; } /* The cursor moves to EOF. */ sp->lno = lno; sp->cno = len ? len - 1 : 0; rval = vs_refresh(sp, 1); ret: FREE_SPACE(sp, bp, blen); return (rval); }
bool ACE_Message_Queue_Vx::is_empty_i (void) { ACE_TRACE ("ACE_Message_Queue_Vx::is_empty_i"); return ::msgQNumMsgs (msgq ()) == 0; }
/* * sscr_init -- * Create a pty setup for a shell. */ static int sscr_init(SCR *sp) { SCRIPT *sc; char *sh, *sh_path; /* We're going to need a shell. */ if (opts_empty(sp, O_SHELL, 0)) return (1); MALLOC_RET(sp, sc, sizeof(SCRIPT)); sp->script = sc; sc->sh_prompt = NULL; sc->sh_prompt_len = 0; /* * There are two different processes running through this code. * They are the shell and the parent. */ sc->sh_master = sc->sh_slave = -1; if (tcgetattr(STDIN_FILENO, &sc->sh_term) == -1) { msgq(sp, M_SYSERR, "tcgetattr"); goto err; } /* * Turn off output postprocessing and echo. */ sc->sh_term.c_oflag &= ~OPOST; sc->sh_term.c_cflag &= ~(ECHO|ECHOE|ECHONL|ECHOK); if (ioctl(STDIN_FILENO, TIOCGWINSZ, &sc->sh_win) == -1) { msgq(sp, M_SYSERR, "tcgetattr"); goto err; } if (openpty(&sc->sh_master, &sc->sh_slave, sc->sh_name, &sc->sh_term, &sc->sh_win) == -1) { msgq(sp, M_SYSERR, "pty"); goto err; } /* * __TK__ huh? * Don't use vfork() here, because the signal semantics differ from * implementation to implementation. */ switch (sc->sh_pid = fork()) { case -1: /* Error. */ msgq(sp, M_SYSERR, "fork"); err: if (sc->sh_master != -1) (void)close(sc->sh_master); if (sc->sh_slave != -1) (void)close(sc->sh_slave); return (1); case 0: /* Utility. */ /* * XXX * So that shells that do command line editing turn it off. */ if (setenv("TERM", "emacs", 1) == -1 || setenv("TERMCAP", "emacs:", 1) == -1 || setenv("EMACS", "t", 1) == -1) _exit(126); (void)setsid(); /* * 4.4BSD allocates a controlling terminal using the TIOCSCTTY * ioctl, not by opening a terminal device file. POSIX 1003.1 * doesn't define a portable way to do this. */ (void)ioctl(sc->sh_slave, TIOCSCTTY, 0); (void)close(sc->sh_master); (void)dup2(sc->sh_slave, STDIN_FILENO); (void)dup2(sc->sh_slave, STDOUT_FILENO); (void)dup2(sc->sh_slave, STDERR_FILENO); (void)close(sc->sh_slave); /* Assumes that all shells have -i. */ sh_path = O_STR(sp, O_SHELL); if ((sh = strrchr(sh_path, '/')) == NULL) sh = sh_path; else ++sh; execl(sh_path, sh, "-i", (char *)NULL); msgq_str(sp, M_SYSERR, sh_path, "execl: %s"); _exit(127); default: /* Parent. */ break; } if (sscr_getprompt(sp)) return (1); F_SET(sp, SC_SCRIPT); F_SET(sp->gp, G_SCRWIN); return (0); }
/* * cl_read -- * Read characters from the input. */ static input_t cl_read(SCR *sp, u_int32_t flags, char *bp, size_t blen, int *nrp, struct timeval *tp) { struct termios term1, term2; CL_PRIVATE *clp; GS *gp; fd_set rdfd; input_t rval; int maxfd, nr, term_reset; gp = sp->gp; clp = CLP(sp); term_reset = 0; /* * 1: A read from a file or a pipe. In this case, the reads * never timeout regardless. This means that we can hang * when trying to complete a map, but we're going to hang * on the next read anyway. */ if (!F_ISSET(clp, CL_STDIN_TTY)) { switch (nr = read(STDIN_FILENO, bp, blen)) { case 0: return (INP_EOF); case -1: goto err; default: *nrp = nr; return (INP_OK); } /* NOTREACHED */ } /* * 2: A read with an associated timeout, e.g., trying to complete * a map sequence. If input exists, we fall into #3. */ if (tp != NULL) { FD_ZERO(&rdfd); FD_SET(STDIN_FILENO, &rdfd); switch (select(STDIN_FILENO + 1, &rdfd, NULL, NULL, tp)) { case 0: return (INP_TIMEOUT); case -1: goto err; default: break; } } /* * The user can enter a key in the editor to quote a character. If we * get here and the next key is supposed to be quoted, do what we can. * Reset the tty so that the user can enter a ^C, ^Q, ^S. There's an * obvious race here, when the key has already been entered, but there's * nothing that we can do to fix that problem. * * The editor can ask for the next literal character even thought it's * generally running in line-at-a-time mode. Do what we can. */ if (LF_ISSET(EC_QUOTED | EC_RAW) && !tcgetattr(STDIN_FILENO, &term1)) { term_reset = 1; if (LF_ISSET(EC_QUOTED)) { term2 = term1; term2.c_lflag &= ~ISIG; term2.c_iflag &= ~(IXON | IXOFF); (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &term2); } else (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &clp->vi_enter); } /* * 3: Wait for input. * * Select on the command input and scripting window file descriptors. * It's ugly that we wait on scripting file descriptors here, but it's * the only way to keep from locking out scripting windows. */ if (F_ISSET(gp, G_SCRWIN)) { loop: FD_ZERO(&rdfd); FD_SET(STDIN_FILENO, &rdfd); maxfd = STDIN_FILENO; if (F_ISSET(sp, SC_SCRIPT)) { FD_SET(sp->script->sh_master, &rdfd); if (sp->script->sh_master > maxfd) maxfd = sp->script->sh_master; } switch (select(maxfd + 1, &rdfd, NULL, NULL, NULL)) { case 0: abort(); case -1: goto err; default: break; } if (!FD_ISSET(STDIN_FILENO, &rdfd)) { if (sscr_input(sp)) return (INP_ERR); goto loop; } } /* * 4: Read the input. * * !!! * What's going on here is some scary stuff. Ex runs the terminal in * canonical mode. So, the <newline> character terminating a line of * input is returned in the buffer, but a trailing <EOF> character is * not similarly included. As ex uses 0<EOF> and ^<EOF> as autoindent * commands, it has to see the trailing <EOF> characters to determine * the difference between the user entering "0ab" and "0<EOF>ab". We * leave an extra slot in the buffer, so that we can add a trailing * <EOF> character if the buffer isn't terminated by a <newline>. We * lose if the buffer is too small for the line and exactly N characters * are entered followed by an <EOF> character. */ #define ONE_FOR_EOF 1 switch (nr = read(STDIN_FILENO, bp, blen - ONE_FOR_EOF)) { case 0: /* EOF. */ /* * ^D in canonical mode returns a read of 0, i.e. EOF. EOF is * a valid command, but we don't want to loop forever because * the terminal driver is returning EOF because the user has * disconnected. The editor will almost certainly try to write * something before this fires, which should kill us, but You * Never Know. */ if (++clp->eof_count < 50) { bp[0] = clp->orig.c_cc[VEOF]; *nrp = 1; rval = INP_OK; } else rval = INP_EOF; break; case -1: /* Error or interrupt. */ err: if (errno == EINTR) rval = INP_INTR; else { rval = INP_ERR; msgq(sp, M_SYSERR, "input"); } break; default: /* Input characters. */ if (F_ISSET(sp, SC_EX) && bp[nr - 1] != '\n') bp[nr++] = clp->orig.c_cc[VEOF]; *nrp = nr; clp->eof_count = 0; rval = INP_OK; break; } /* Restore the terminal state if it was modified. */ if (term_reset) (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &term1); return (rval); }
/* * cl_vi_init -- * Initialize the curses vi screen. */ static int cl_vi_init(SCR *sp) { CL_PRIVATE *clp; char *o_cols, *o_lines, *o_term; const char *ttype; clp = CLP(sp); /* If already initialized, just set the terminal modes. */ if (F_ISSET(clp, CL_SCR_VI_INIT)) goto fast; /* Curses vi always reads from (and writes to) a terminal. */ if (!F_ISSET(clp, CL_STDIN_TTY) || !isatty(STDOUT_FILENO)) { msgq(sp, M_ERR, "016|Vi's standard input and output must be a terminal"); return (1); } /* We'll need a terminal type. */ if (opts_empty(sp, O_TERM, 0)) return (1); ttype = O_STR(sp, O_TERM); /* * XXX * Changing the row/column and terminal values is done by putting them * into the environment, which is then read by curses. What this loses * in ugliness, it makes up for in stupidity. We can't simply put the * values into the environment ourselves, because in the presence of a * kernel mechanism for returning the window size, entering values into * the environment will screw up future screen resizing events, e.g. if * the user enters a :shell command and then resizes their window. So, * if they weren't already in the environment, we make sure to delete * them immediately after setting them. * * XXX * Putting the TERM variable into the environment is necessary, even * though we're using newterm() here. We may be using initscr() as * the underlying function. */ o_term = getenv("TERM"); cl_putenv(sp, "TERM", ttype, 0); o_lines = getenv("LINES"); cl_putenv(sp, "LINES", NULL, (u_long)O_VAL(sp, O_LINES)); o_cols = getenv("COLUMNS"); cl_putenv(sp, "COLUMNS", NULL, (u_long)O_VAL(sp, O_COLUMNS)); /* Delete cur_term if exists. */ if (F_ISSET(clp, CL_SETUPTERM)) { if (del_curterm(cur_term)) return (1); F_CLR(clp, CL_SETUPTERM); } /* * XXX * The SunOS initscr() can't be called twice. Don't even think about * using it. It fails in subtle ways (e.g. select(2) on fileno(stdin) * stops working). (The SVID notes that applications should only call * initscr() once.) * * XXX * The HP/UX newterm doesn't support the NULL first argument, so we * have to specify the terminal type. */ errno = 0; if ((clp->screen = newterm(__UNCONST(ttype), stdout, stdin)) == NULL) { if (errno) msgq(sp, M_SYSERR, "%s", ttype); else msgq(sp, M_ERR, "%s: unknown terminal type", ttype); return (1); } if (o_term == NULL) cl_unsetenv(sp, "TERM"); if (o_lines == NULL) cl_unsetenv(sp, "LINES"); if (o_cols == NULL) cl_unsetenv(sp, "COLUMNS"); /* * XXX * Someone got let out alone without adult supervision -- the SunOS * newterm resets the signal handlers. There's a race, but it's not * worth closing. */ (void)sig_init(sp->gp, sp); /* * We use raw mode. What we want is 8-bit clean, however, signals * and flow control should continue to work. Admittedly, it sounds * like cbreak, but it isn't. Using cbreak() can get you additional * things like IEXTEN, which turns on flags like DISCARD and LNEXT. * * !!! * If raw isn't turning off echo and newlines, something's wrong. * However, it shouldn't hurt. */ noecho(); /* No character echo. */ nonl(); /* No CR/NL translation. */ raw(); /* 8-bit clean. */ idlok(stdscr, 1); /* Use hardware insert/delete line. */ /* Put the cursor keys into application mode. */ (void)keypad(stdscr, TRUE); /* * XXX * The screen TI sequence just got sent. See the comment in * cl_funcs.c:cl_attr(). */ clp->ti_te = TI_SENT; /* * XXX * Historic implementations of curses handled SIGTSTP signals * in one of three ways. They either: * * 1: Set their own handler, regardless. * 2: Did not set a handler if a handler was already installed. * 3: Set their own handler, but then called any previously set * handler after completing their own cleanup. * * We don't try and figure out which behavior is in place, we force * it to SIG_DFL after initializing the curses interface, which means * that curses isn't going to take the signal. Since curses isn't * reentrant (i.e., the whole curses SIGTSTP interface is a fantasy), * we're doing The Right Thing. */ (void)signal(SIGTSTP, SIG_DFL); /* * If flow control was on, turn it back on. Turn signals on. ISIG * turns on VINTR, VQUIT, VDSUSP and VSUSP. The main curses code * already installed a handler for VINTR. We're going to disable the * other three. * * XXX * We want to use ^Y as a vi scrolling command. If the user has the * DSUSP character set to ^Y (common practice) clean it up. As it's * equally possible that the user has VDSUSP set to 'a', we disable * it regardless. It doesn't make much sense to suspend vi at read, * so I don't think anyone will care. Alternatively, we could look * it up in the table of legal command characters and turn it off if * it matches one. VDSUSP wasn't in POSIX 1003.1-1990, so we test for * it. * * XXX * We don't check to see if the user had signals enabled originally. * If they didn't, it's unclear what we're supposed to do here, but * it's also pretty unlikely. */ if (tcgetattr(STDIN_FILENO, &clp->vi_enter)) { msgq(sp, M_SYSERR, "tcgetattr"); goto err; } if (clp->orig.c_iflag & IXON) clp->vi_enter.c_iflag |= IXON; if (clp->orig.c_iflag & IXOFF) clp->vi_enter.c_iflag |= IXOFF; clp->vi_enter.c_lflag |= ISIG; #ifdef VDSUSP clp->vi_enter.c_cc[VDSUSP] = _POSIX_VDISABLE; #endif clp->vi_enter.c_cc[VQUIT] = _POSIX_VDISABLE; clp->vi_enter.c_cc[VSUSP] = _POSIX_VDISABLE; /* * XXX * OSF/1 doesn't turn off the <discard>, <literal-next> or <status> * characters when curses switches into raw mode. It should be OK * to do it explicitly for everyone. */ #ifdef VDISCARD clp->vi_enter.c_cc[VDISCARD] = _POSIX_VDISABLE; #endif #ifdef VLNEXT clp->vi_enter.c_cc[VLNEXT] = _POSIX_VDISABLE; #endif #ifdef VSTATUS clp->vi_enter.c_cc[VSTATUS] = _POSIX_VDISABLE; #endif /* Initialize terminal based information. */ if (cl_term_init(sp)) goto err; fast: /* Set the terminal modes. */ if (tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &clp->vi_enter)) { msgq(sp, M_SYSERR, "tcsetattr"); err: (void)cl_vi_end(sp->gp); return (1); } return (0); }
/* * ex_next -- :next [+cmd] [files] * Edit the next file, optionally setting the list of files. * * !!! * The :next command behaved differently from the :rewind command in * historic vi. See nvi/docs/autowrite for details, but the basic * idea was that it ignored the force flag if the autowrite flag was * set. This implementation handles them all identically. * * PUBLIC: int ex_next(SCR *, EXCMD *); */ int ex_next(SCR *sp, EXCMD *cmdp) { ARGS **argv; FREF *frp; int noargs; char **ap; CHAR_T *wp; size_t wlen; char *np; size_t nlen; /* Check for file to move to. */ if (cmdp->argc == 0 && (sp->cargv == NULL || sp->cargv[1] == NULL)) { msgq(sp, M_ERR, "111|No more files to edit"); return (1); } if (F_ISSET(cmdp, E_NEWSCREEN)) { /* By default, edit the next file in the old argument list. */ if (cmdp->argc == 0) { CHAR2INT(sp, sp->cargv[1], strlen(sp->cargv[1]) + 1, wp, wlen); if (argv_exp0(sp, cmdp, wp, wlen - 1)) return (1); return (ex_edit(sp, cmdp)); } return (ex_N_next(sp, cmdp)); } /* Check modification. */ if (file_m1(sp, FL_ISSET(cmdp->iflags, E_C_FORCE), FS_ALL | FS_POSSIBLE)) return (1); /* Any arguments are a replacement file list. */ if (cmdp->argc) { /* Free the current list. */ if (!F_ISSET(sp, SC_ARGNOFREE) && sp->argv != NULL) { for (ap = sp->argv; *ap != NULL; ++ap) free(*ap); free(sp->argv); } F_CLR(sp, SC_ARGNOFREE | SC_ARGRECOVER); sp->cargv = NULL; /* Create a new list. */ CALLOC_RET(sp, sp->argv, char **, cmdp->argc + 1, sizeof(char *)); for (ap = sp->argv, argv = cmdp->argv; argv[0]->len != 0; ++ap, ++argv) { INT2CHAR(sp, argv[0]->bp, argv[0]->len, np, nlen); if ((*ap = v_strdup(sp, np, nlen)) == NULL) return (1); } *ap = NULL; /* Switch to the first file. */ sp->cargv = sp->argv; if ((frp = file_add(sp, *sp->cargv)) == NULL) return (1); noargs = 0; /* Display a file count with the welcome message. */ F_SET(sp, SC_STATUS_CNT); } else { if ((frp = file_add(sp, sp->cargv[1])) == NULL)
/* * vi -- * Main vi command loop. * * PUBLIC: int vi __P((SCR **)); */ int vi(SCR **spp) { GS *gp; MARK abs; SCR *next, *sp; VICMD cmd = { 0 }, *vp; VI_PRIVATE *vip; int comcount, mapped, rval; /* Get the first screen. */ sp = *spp; gp = sp->gp; /* Point to the command structure. */ vp = &cmd; /* Reset strange attraction. */ F_SET(vp, VM_RCM_SET); /* Initialize the vi screen. */ if (v_init(sp)) return (1); /* Set the focus. */ (void)sp->gp->scr_rename(sp, sp->frp->name, 1); for (vip = VIP(sp), rval = 0;;) { /* Resolve messages. */ if (!MAPPED_KEYS_WAITING(sp) && vs_resolve(sp, NULL, 0)) goto ret; /* * If not skipping a refresh, return to command mode and * refresh the screen. */ if (F_ISSET(vip, VIP_S_REFRESH)) F_CLR(vip, VIP_S_REFRESH); else { sp->showmode = SM_COMMAND; if (vs_refresh(sp, 0)) goto ret; } /* Set the new favorite position. */ if (F_ISSET(vp, VM_RCM_SET | VM_RCM_SETFNB | VM_RCM_SETNNB)) { F_CLR(vip, VIP_RCM_LAST); (void)vs_column(sp, &sp->rcm); } /* * If not currently in a map, log the cursor position, * and set a flag so that this command can become the * DOT command. */ if (MAPPED_KEYS_WAITING(sp)) mapped = 1; else { if (log_cursor(sp)) goto err; mapped = 0; } /* * There may be an ex command waiting, and we returned here * only because we exited a screen or file. In this case, * we simply go back into the ex parser. */ if (EXCMD_RUNNING(gp)) { vp->kp = &vikeys[':']; goto ex_continue; } /* Refresh the command structure. */ memset(vp, 0, sizeof(VICMD)); /* * We get a command, which may or may not have an associated * motion. If it does, we get it too, calling its underlying * function to get the resulting mark. We then call the * command setting the cursor to the resulting mark. * * !!! * Vi historically flushed mapped characters on error, but * entering extra <escape> characters at the beginning of * a map wasn't considered an error -- in fact, users would * put leading <escape> characters in maps to clean up vi * state before the map was interpreted. Beauty! */ switch (v_cmd(sp, DOT, vp, NULL, &comcount, &mapped)) { case GC_ERR: goto err; case GC_ERR_NOFLUSH: goto gc_err_noflush; case GC_EVENT: goto gc_event; case GC_FATAL: goto ret; case GC_INTERRUPT: goto intr; case GC_OK: break; } /* Check for security setting. */ if (F_ISSET(vp->kp, V_SECURE) && O_ISSET(sp, O_SECURE)) { ex_emsg(sp, KEY_NAME(sp, vp->key), EXM_SECURE); goto err; } /* * Historical practice: if a dot command gets a new count, * any motion component goes away, i.e. "d3w2." deletes a * total of 5 words. */ if (F_ISSET(vp, VC_ISDOT) && comcount) DOTMOTION->count = 1; /* Copy the key flags into the local structure. */ F_SET(vp, vp->kp->flags); /* Prepare to set the previous context. */ if (F_ISSET(vp, V_ABS | V_ABS_C | V_ABS_L)) { abs.lno = sp->lno; abs.cno = sp->cno; } /* * Set the three cursor locations to the current cursor. The * underlying routines don't bother if the cursor doesn't move. * This also handles line commands (e.g. Y) defaulting to the * current line. */ vp->m_start.lno = vp->m_stop.lno = vp->m_final.lno = sp->lno; vp->m_start.cno = vp->m_stop.cno = vp->m_final.cno = sp->cno; /* * Do any required motion; v_motion sets the from MARK and the * line mode flag, as well as the VM_RCM flags. */ if (F_ISSET(vp, V_MOTION) && v_motion(sp, DOTMOTION, vp, &mapped)) { if (INTERRUPTED(sp)) goto intr; goto err; } /* * If a count is set and the command is line oriented, set the * to MARK here relative to the cursor/from MARK. This is for * commands that take both counts and motions, i.e. "4yy" and * "y%". As there's no way the command can know which the user * did, we have to do it here. (There are commands that are * line oriented and that take counts ("#G", "#H"), for which * this calculation is either completely meaningless or wrong. * Each command must validate the value for itself. */ if (F_ISSET(vp, VC_C1SET) && F_ISSET(vp, VM_LMODE)) vp->m_stop.lno += vp->count - 1; /* Increment the command count. */ ++sp->ccnt; #if defined(DEBUG) && defined(COMLOG) v_comlog(sp, vp); #endif /* Call the function. */ ex_continue: if (vp->kp->func(sp, vp)) goto err; gc_event: #ifdef DEBUG /* Make sure no function left the temporary space locked. */ if (F_ISSET(gp, G_TMP_INUSE)) { F_CLR(gp, G_TMP_INUSE); msgq(sp, M_ERR, "232|vi: temporary buffer not released"); } #endif /* * If we're exiting this screen, move to the next one, or, if * there aren't any more, return to the main editor loop. The * ordering is careful, don't discard the contents of sp until * the end. */ if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { if (file_end(sp, NULL, F_ISSET(sp, SC_EXIT_FORCE))) goto ret; if (vs_discard(sp, &next)) goto ret; if (next == NULL && vs_swap(sp, &next, NULL)) goto ret; *spp = next; if (screen_end(sp)) goto ret; if (next == NULL) break; /* Switch screens, change focus. */ sp = next; vip = VIP(sp); (void)sp->gp->scr_rename(sp, sp->frp->name, 1); /* Don't trust the cursor. */ F_SET(vip, VIP_CUR_INVALID); continue; } /* * Set the dot command structure. * * !!! * Historically, commands which used mapped keys did not * set the dot command, with the exception of the text * input commands. */ if (F_ISSET(vp, V_DOT) && !mapped) { *DOT = cmd; F_SET(DOT, VC_ISDOT); /* * If a count was supplied for both the command and * its motion, the count was used only for the motion. * Turn the count back on for the dot structure. */ if (F_ISSET(vp, VC_C1RESET)) F_SET(DOT, VC_C1SET); /* VM flags aren't retained. */ F_CLR(DOT, VM_COMMASK | VM_RCM_MASK); } /* * Some vi row movements are "attracted" to the last position * set, i.e. the VM_RCM commands are moths to the VM_RCM_SET * commands' candle. If the movement is to the EOL the vi * command handles it. If it's to the beginning, we handle it * here. * * Note, some commands (e.g. _, ^) don't set the VM_RCM_SETFNB * flag, but do the work themselves. The reason is that they * have to modify the column in case they're being used as a * motion component. Other similar commands (e.g. +, -) don't * have to modify the column because they are always line mode * operations when used as motions, so the column number isn't * of any interest. * * Does this totally violate the screen and editor layering? * You betcha. As they say, if you think you understand it, * you don't. */ switch (F_ISSET(vp, VM_RCM_MASK)) { case 0: case VM_RCM_SET: break; case VM_RCM: vp->m_final.cno = vs_rcm(sp, vp->m_final.lno, F_ISSET(vip, VIP_RCM_LAST)); break; case VM_RCM_SETLAST: F_SET(vip, VIP_RCM_LAST); break; case VM_RCM_SETFNB: vp->m_final.cno = 0; /* FALLTHROUGH */ case VM_RCM_SETNNB: if (nonblank(sp, vp->m_final.lno, &vp->m_final.cno)) goto err; break; default: abort(); } /* Update the cursor. */ sp->lno = vp->m_final.lno; sp->cno = vp->m_final.cno; /* * Set the absolute mark -- set even if a tags or similar * command, since the tag may be moving to the same file. */ if ((F_ISSET(vp, V_ABS) || (F_ISSET(vp, V_ABS_L) && sp->lno != abs.lno) || (F_ISSET(vp, V_ABS_C) && (sp->lno != abs.lno || sp->cno != abs.cno))) && mark_set(sp, ABSMARK1, &abs, 1)) goto err; if (0) { err: if (v_event_flush(sp, CH_MAPPED)) msgq(sp, M_BERR, "110|Vi command failed: mapped keys discarded"); } /* * Check and clear interrupts. There's an obvious race, but * it's not worth fixing. */ gc_err_noflush: if (INTERRUPTED(sp)) { intr: CLR_INTERRUPT(sp); if (v_event_flush(sp, CH_MAPPED)) msgq(sp, M_ERR, "231|Interrupted: mapped keys discarded"); else msgq(sp, M_ERR, "236|Interrupted"); } /* If the last command switched screens, update. */ if (F_ISSET(sp, SC_SSWITCH)) { F_CLR(sp, SC_SSWITCH); /* * If the current screen is still displayed, it will * need a new status line. */ F_SET(sp, SC_STATUS); /* Switch screens, change focus. */ sp = sp->nextdisp; vip = VIP(sp); (void)sp->gp->scr_rename(sp, sp->frp->name, 1); /* Don't trust the cursor. */ F_SET(vip, VIP_CUR_INVALID); /* Refresh so we can display messages. */ if (vs_refresh(sp, 1)) return (1); } /* If the last command switched files, change focus. */ if (F_ISSET(sp, SC_FSWITCH)) { F_CLR(sp, SC_FSWITCH); (void)sp->gp->scr_rename(sp, sp->frp->name, 1); } /* If leaving vi, return to the main editor loop. */ if (F_ISSET(gp, G_SRESTART) || F_ISSET(sp, SC_EX)) { *spp = sp; v_dtoh(sp); gp->scr_discard(sp, NULL); break; } } if (0) ret: rval = 1; return (rval); }