/* * Note a change affecting a lot of lines, or non-visible * lines. If the parameter must is set, then we only want * to do this for open modes now; return and save for later * notification in visual. */ int noteit(bool must) { register int sdl = destline, sdc = destcol; if (notecnt < 2 || (!must && state == VISUAL)) return (0); splitw++; if (WBOT == WECHO) vmoveitup(1, 1); vigoto(WECHO, 0); ex_printf("%d %sline", notecnt, notesgn); if (notecnt > 1) ex_putchar('s'); if (*notenam) { ex_printf(" %s", notenam); if (*(strend(notenam) - 1) != 'e') ex_putchar('e'); ex_putchar('d'); } vclreol(); notecnt = 0; if (state != VISUAL) vcnt = vcline = 0; splitw = 0; if (state == ONEOPEN || state == CRTOPEN) vup1(); destline = sdl; destcol = sdc; return (1); }
/* * ex_prchars -- * Local routine to dump characters to the screen. */ static int ex_prchars(SCR *sp, const CHAR_T *p, size_t *colp, size_t len, u_int flags, int repeatc) { CHAR_T ch; char *kp; GS *gp; size_t col, tlen, ts; if (O_ISSET(sp, O_LIST)) LF_SET(E_C_LIST); gp = sp->gp; ts = O_VAL(sp, O_TABSTOP); for (col = *colp; len--;) if ((ch = *p++) == L('\t') && !LF_ISSET(E_C_LIST)) for (tlen = ts - col % ts; col < sp->cols && tlen--; ++col) { (void)ex_printf(sp, "%c", repeatc ? repeatc : ' '); if (INTERRUPTED(sp)) goto intr; } else { kp = KEY_NAME(sp, ch); tlen = KEY_COL(sp, ch); /* * Start a new line if the last character does not fit * into the current line. The implicit new lines are * not interruptible. */ if (col + tlen > sp->cols) { col = 0; (void)ex_puts(sp, "\n"); } col += tlen; if (!repeatc) { (void)ex_puts(sp, kp); if (INTERRUPTED(sp)) goto intr; } else while (tlen--) { (void)ex_printf(sp, "%c", repeatc); if (INTERRUPTED(sp)) goto intr; } if (col == sp->cols) { col = 0; (void)ex_puts(sp, "\n"); } } intr: *colp = col; return (0); }
static void snote(int total, int lines) { if (!notable(total)) return; ex_printf(mesg("%d subs|%d substitutions"), total); if (lines != 1 && lines != total) ex_printf(" on %d lines", lines); noonl(); flush(); }
/* * ex_viusage -- :viusage [key] * Display vi usage strings. * * PUBLIC: int ex_viusage __P((SCR *, EXCMD *)); */ int ex_viusage(SCR *sp, EXCMD *cmdp) { VIKEYS const *kp; int key; switch (cmdp->argc) { case 1: if (cmdp->argv[0]->len != 1) { ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } key = cmdp->argv[0]->bp[0]; if (key > MAXVIKEY) goto nokey; /* Special case: '[' and ']' commands. */ if ((key == '[' || key == ']') && cmdp->argv[0]->bp[1] != key) goto nokey; /* Special case: ~ command. */ if (key == '~' && O_ISSET(sp, O_TILDEOP)) kp = &tmotion; else kp = &vikeys[key]; if (kp->usage == NULL) nokey: (void)ex_printf(sp, "The %s key has no current meaning\n", KEY_NAME(sp, key)); else (void)ex_printf(sp, " Key:%s%s\nUsage: %s\n", isblank((unsigned char)*kp->help) ? "" : " ", kp->help, kp->usage); break; case 0: for (key = 0; key <= MAXVIKEY && !INTERRUPTED(sp); ++key) { /* Special case: ~ command. */ if (key == '~' && O_ISSET(sp, O_TILDEOP)) kp = &tmotion; else kp = &vikeys[key]; if (kp->help != NULL) (void)ex_printf(sp, "%s\n", kp->help); } break; default: abort(); } return (0); }
/* * ex_prchars -- * Local routine to dump characters to the screen. */ static int ex_prchars(SCR *sp, const CHAR_T *p, size_t *colp, size_t len, u_int flags, int repeatc) { CHAR_T ch; const char *kp; size_t col, tlen, ts; if (O_ISSET(sp, O_LIST)) LF_SET(E_C_LIST); ts = O_VAL(sp, O_TABSTOP); for (col = *colp; len--;) if ((ch = *p++) == L('\t') && !LF_ISSET(E_C_LIST)) for (tlen = ts - col % ts; col < sp->cols && tlen--; ++col) { (void)ex_printf(sp, "%c", repeatc ? repeatc : ' '); if (INTERRUPTED(sp)) goto intr; } else { /* XXXX */ if (INTISWIDE(ch)) { CHAR_T str[2] = {0, 0}; str[0] = ch; INT2CHAR(sp, str, 2, kp, tlen); } else { kp = (char *)KEY_NAME(sp, ch); tlen = KEY_LEN(sp, ch); } if (!repeatc && col + tlen < sp->cols) { (void)ex_puts(sp, kp); col += tlen; } else for (; tlen--; ++kp, ++col) { if (col == sp->cols) { col = 0; (void)ex_puts(sp, "\n"); } (void)ex_printf(sp, "%c", repeatc ? repeatc : *kp); if (INTERRUPTED(sp)) goto intr; } } intr: *colp = col; return (0); }
/* * ex_equal -- :address = * * PUBLIC: int ex_equal __P((SCR *, EXCMD *)); */ int ex_equal(SCR *sp, EXCMD *cmdp) { db_recno_t lno; NEEDFILE(sp, cmdp); /* * Print out the line number matching the specified address, * or the number of the last line in the file if no address * specified. * * !!! * Historically, ":0=" displayed 0, and ":=" or ":1=" in an * empty file displayed 1. Until somebody complains loudly, * we're going to do it right. The tables in excmd.c permit * lno to get away with any address from 0 to the end of the * file, which, in an empty file, is 0. */ if (F_ISSET(cmdp, E_ADDR_DEF)) { if (db_last(sp, &lno)) return (1); } else lno = cmdp->addr1.lno; (void)ex_printf(sp, "%ld\n", lno); return (0); }
/* * txt_prompt -- * Display the ex prompt, line number, ai characters. Characters had * better be printable by the terminal driver, but that's its problem, * not ours. */ static void txt_prompt(SCR *sp, TEXT *tp, ARG_CHAR_T prompt, u_int32_t flags) { /* Display the prompt. */ if (LF_ISSET(TXT_PROMPT)) (void)ex_printf(sp, "%c", prompt); /* Display the line number. */ if (LF_ISSET(TXT_NUMBER) && O_ISSET(sp, O_NUMBER)) (void)ex_printf(sp, "%6lu ", (u_long)tp->lno); /* Print out autoindent string. */ if (LF_ISSET(TXT_AUTOINDENT)) (void)ex_printf(sp, WVS, (int)tp->ai, tp->lb); (void)ex_fflush(sp); }
/* * vs_update -- * Update a command. * * PUBLIC: void vs_update __P((SCR *, const char *, const CHAR_T *)); */ void vs_update(SCR *sp, const char *m1, const CHAR_T *m2) { GS *gp; size_t len, mlen, oldx, oldy; const char *np; size_t nlen; gp = sp->gp; /* * This routine displays a message on the bottom line of the screen, * without updating any of the command structures that would keep it * there for any period of time, i.e. it is overwritten immediately. * * It's used by the ex read and ! commands when the user's command is * expanded, and by the ex substitution confirmation prompt. */ if (F_ISSET(sp, SC_SCR_EXWROTE)) { if (m2 != NULL) INT2CHAR(sp, m2, STRLEN(m2) + 1, np, nlen); (void)ex_printf(sp, "%s%s\n", m1 == NULL? "" : m1, m2 == NULL ? "" : np); (void)ex_fflush(sp); } /* * Save the cursor position, the substitute-with-confirmation code * will have already set it correctly. */ (void)gp->scr_cursor(sp, &oldy, &oldx); /* Clear the bottom line. */ (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_clrtoeol(sp); /* * XXX * Don't let long file names screw up the screen. */ if (m1 != NULL) { mlen = len = strlen(m1); if (len > sp->cols - 2) mlen = len = sp->cols - 2; (void)gp->scr_addstr(sp, m1, mlen); } else len = 0; if (m2 != NULL) { mlen = STRLEN(m2); if (len + mlen > sp->cols - 2) mlen = (sp->cols - 2) - len; (void)gp->scr_waddstr(sp, m2, mlen); } (void)gp->scr_move(sp, oldy, oldx); (void)gp->scr_refresh(sp, 0); }
/* * Print out the argument list, with []'s around the current name. */ void pargs(void) { register char **av = argv0, *as = args0; register int ac; for (ac = 0; ac < argc0; ac++) { if (ac != 0) putchar(' ' | QUOTE); if (ac + argc == argc0 - 1) ex_printf("["); lprintf("%s", as); if (ac + argc == argc0 - 1) ex_printf("]"); as = av ? *++av : strend(as) + 1; } noonl(); }
/* * Print a line with a number. */ void numbline(int i) { if (shudclob) slobber(' '); ex_printf("%6d ", i); normline(); }
int getfile(void) { int c; register char *lp, *fp; lp = linebuf; fp = nextip; do { if (--ninbuf < 0) { ninbuf = read(io, genbuf, bsize) - 1; if (ninbuf < 0) { if (lp != linebuf) { lp++; ex_printf(" [Incomplete last line]"); break; } return (EOF); } #ifdef CRYPT if (kflag) { fp = genbuf; while(fp < &genbuf[ninbuf]) { if (*fp++ & 0200) { crblock(perm, genbuf, ninbuf+1, cntch); break; } } } #endif fp = genbuf; cntch += ninbuf+1; } if (lp >= &linebuf[LBSIZE]) { error(" Line too long"); } c = *fp++; if (c == 0) { cntnull++; continue; } if (c & QUOTE) { cntodd++; #ifndef BIT8 c &= TRIM; if (c == 0) continue; #endif } *lp++ = c; } while (c != '\n'); *--lp = 0; nextip = fp; cntln++; return (0); }
static void splitit(void) { register int l; register char *lp; for (l = COLUMNS == 1000 ? 72 : COLUMNS, lp = linebuf; l > 0; l--) *lp++ = '-'; *lp = 0; ex_printf("%s\n", linebuf); }
/* * Io is finished, close the unit and print statistics. */ static int iostats(void) { close(io); io = -1; if (hush == 0) { if (value(TERSE)) ex_printf(" %d/%D", cntln, cntch); else ex_printf(" %d line%s, %D character%s", cntln, plural((long) cntln), cntch, plural(cntch)); if (cntnull || cntodd) { ex_printf(" ("); if (cntnull) { ex_printf("%D null", cntnull); if (cntodd) ex_printf(", "); } if (cntodd) ex_printf("%D non-ASCII", cntodd); ex_putchar(')'); } noonl(); flush(); } return (cntnull != 0 #ifndef BIT8 || cntodd != 0 #endif ); }
void showmode(int mode) { int sdc = destcol, sdl = destline; char *ocurs, *str; if (value(SHOWMODE) == 0 || TCOLUMNS <= 20 || state == ONEOPEN || state == HARDOPEN /*|| vmacp != NULL*/) return; ocurs = cursor; fixech(); vgoto(WECHO, TCOLUMNS - 20); switch (mode) { case 0: str = catgets(catd, 1, 227, " "); break; case 'A': /*FALLTHROUGH*/ case 'a': str = catgets(catd, 1, 228, "AAPPEND MODE"); break; case 'C': /*FALLTHROUGH*/ case 'c': str = catgets(catd, 1, 229, "CCHANGE MODE"); break; case 'O': /*FALLTHROUGH*/ case 'o': str = catgets(catd, 1, 230, "OOPEN MODE"); break; case 'R': str = catgets(catd, 1, 231, "RREPLACE MODE"); break; case 'r': str = catgets(catd, 1, 232, "rREPLACE 1 CHAR"); break; default: str = catgets(catd, 1, 233, "IINSERT MODE"); } if (value(TERSE)) putchar(str[0]); else ex_printf(&str[1]); vgoto(sdl, sdc); cursor = ocurs; splitw = 0; }
/* * db -- * Display a buffer. */ static void db(SCR *sp, CB *cbp, const char *np) { CHAR_T *p; GS *gp; TEXT *tp; size_t len; const unsigned char *name = (const void *)np; gp = sp->gp; (void)ex_printf(sp, "********** %s%s\n", name == NULL ? KEY_NAME(sp, cbp->name) : name, F_ISSET(cbp, CB_LMODE) ? " (line mode)" : " (character mode)"); for (tp = cbp->textq.cqh_first; tp != (void *)&cbp->textq; tp = tp->q.cqe_next) { for (len = tp->len, p = tp->lb; len--; ++p) { (void)ex_puts(sp, (char *)KEY_NAME(sp, *p)); if (INTERRUPTED(sp)) return; } (void)ex_puts(sp, "\n"); } }
/* * Main procedure. Process arguments and then * transfer control to the main command processing loop * in the routine commands. We are entered as either "ex", "edit" or "vi" * and the distinction is made here. Actually, we are "vi" if * there is a 'v' in our name, and "edit" if there is a 'd' in our * name. For edit we just diddle options; for vi we actually * force an early visual command, setting the external initev so * the q command in visual doesn't give command mode. */ int main(int ac, char **av) { #if 0 char *erpath = EXSTRINGS; #endif register char *cp; register int c; bool recov = 0; bool ivis = any('v', av[0]); bool itag = 0; bool fast = 0; #ifdef TRACE register char *tracef; #endif /* * Immediately grab the tty modes so that we wont * get messed up if an interrupt comes in quickly. */ gTTY(1); normf = tty; #if 0 /* * For debugging take files out of . if name is a.out. * If a 'd' in our name, then set options for edit. */ if (av[0][0] == 'a') erpath += 9; #endif if (ivis) { options[MAGIC].odefault = value(MAGIC) = 0; options[BEAUTIFY].odefault = value(BEAUTIFY) = 1; } else if (any('d', av[0])) { value(OPEN) = 0; value(REPORT) = 1; value(MAGIC) = 0; } /* * Open the error message file. */ draino(); #if 0 erfile = open(erpath, 0); if (erfile < 0) { flush(); exit(1); } #endif pstop(); /* * Initialize interrupt handling. */ oldhup = signal(SIGHUP, SIG_IGN); if (oldhup == SIG_DFL) signal(SIGHUP, onhup); oldquit = signal(SIGQUIT, SIG_IGN); ruptible = signal(SIGINT, SIG_IGN) == SIG_DFL; if (signal(SIGTERM, SIG_IGN) == SIG_DFL) signal(SIGTERM, onhup); /* * Initialize end of core pointers. * Normally we avoid breaking back to fendcore after each * file since this can be expensive (much core-core copying). * If your system can scatter load processes you could do * this as ed does, saving a little core, but it will probably * not often make much difference. */ #ifdef UNIX_SBRK fendcore = (line *) sbrk(0); endcore = fendcore - 2; #else # define LINELIMIT 0x8000 fendcore = malloc(LINELIMIT * sizeof(line *)); endcore = fendcore + LINELIMIT - 1; #endif /* * Process flag arguments. */ ac--, av++; while (ac && av[0][0] == '-') { c = av[0][1]; if (c == 0) { hush = 1; value(AUTOPRINT) = 0; fast++; } else switch (c) { #ifdef TRACE case 'T': if (av[0][2] == 0) tracef = "trace"; else { tracef = tttrace; tracef[8] = av[0][2]; if (tracef[8]) tracef[9] = av[0][3]; else tracef[9] = 0; } trace = fopen(tracef, "w"); if (trace == NULL) ex_printf("Trace create error\n"); setbuf(trace, tracbuf); break; #endif #ifdef LISP case 'l': value(LISP) = 1; value(SHOWMATCH) = 1; break; #endif case 'r': recov++; break; case 't': if (ac > 1 && av[1][0] != '-') { ac--, av++; itag = 1; /* BUG: should check for too long tag. */ CP(lasttag, av[0]); } break; case 'v': globp = ""; ivis = 1; break; default: smerror("Unknown option %s\n", av[0]); break; } ac--, av++; } if (ac && av[0][0] == '+') { firstln = getn(av[0] + 1); if (firstln == 0) firstln = 20000; ac--, av++; } /* * If we are doing a recover and no filename * was given, then execute an exrecover command with * the -r option to type out the list of saved file names. * Otherwise set the remembered file name to the first argument * file name so the "recover" initial command will find it. */ if (recov) { if (ac == 0) { die++; setrupt(); execl(EXRECOVER, "exrecover", "-r", NULL); filioerr(EXRECOVER); exit(1); } CP(savedfile, *av); av++, ac--; } /* * Initialize the argument list. */ argv0 = av; argc0 = ac; args0 = av[0]; erewind(); /* * Initialize a temporary file (buffer) and * set up terminal environment. Read user startup commands. */ init(); if (setexit() == 0) { setrupt(); intty = isatty(0); if (fast || !intty) setterm("dumb"); else { gettmode(); if ((cp = getenv("TERM")) != 0) setterm(cp); if ((cp = getenv("HOME")) != 0) source(strcat(strcpy(genbuf, cp), "/.exrc"), 1); } } /* * Initial processing. Handle tag, recover, and file argument * implied next commands. If going in as 'vi', then don't do * anything, just set initev so we will do it later (from within * visual). */ if (setexit() == 0) { if (recov) globp = "recover"; else if (itag) globp = ivis ? "tag" : "tag|p"; else if (argc) globp = "next"; if (ivis) initev = globp; else if (globp) { inglobal = 1; commands(1, 1); inglobal = 0; } } /* * Vi command... go into visual. * Strange... everything in vi usually happens * before we ever "start". */ if (ivis) { /* * Don't have to be upward compatible with stupidity * of starting editing at line $. */ if (dol > zero) dot = one; globp = "visual"; if (setexit() == 0) commands(1, 1); } /* * Clear out trash in state accumulated by startup, * and then do the main command loop for a normal edit. * If you quit out of a 'vi' command by doing Q or ^\, * you also fall through to here. */ ungetchar(0); globp = 0; initev = 0; setlastchar('\n'); setexit(); commands(0, 0); cleanup(1); return 0; }
/* * Read a file from the world. * C is command, 'e' if this really an edit (or a recover). */ void rop(int c) { struct stat stbuf; #ifndef __sun register int i; struct exec head; #endif static int ovro; /* old value(READONLY) */ static int denied; /* 1 if READONLY was set due to file permissions */ #ifdef FLOCKFILE int *lp, *iop; #endif io = open(file, O_RDONLY); if (io < 0) { if (c == 'e' && errno == ENOENT) { edited++; /* * If the user just did "ex foo" he is probably * creating a new file. Don't be an error, since * this is ugly, and it screws up the + option. */ if (!seenprompt) { ex_printf(" [New file]"); noonl(); return; } } syserror(); } if (fstat(io, &stbuf)) syserror(); switch (stbuf.st_mode & S_IFMT) { case S_IFBLK: error(" Block special file"); case S_IFCHR: if (isatty(io)) error(" Teletype"); if (samei(&stbuf, _PATH_DEVNULL)) break; error(" Character special file"); case S_IFDIR: error(" Directory"); #ifndef __sun case S_IFREG: #ifdef CRYPT if (xflag) break; #endif i = read(io, (char *)&head, sizeof(head)); (void)lseek(io, 0L, L_SET); if (i != sizeof(head)) break; #ifndef vms switch ( #if defined N_MAGIC N_MAGIC( #elif defined N_GETMAGIC N_GETMAGIC( #else # error #endif head)) { case 0405: /* data overlay on exec */ case OMAGIC: /* unshared */ case NMAGIC: /* shared text */ case 0411: /* separate I/D */ case ZMAGIC: /* VM/Unix demand paged */ case 0430: /* PDP-11 Overlay shared */ case 0431: /* PDP-11 Overlay sep I/D */ error(" Executable"); /* * We do not forbid the editing of portable archives * because it is reasonable to edit them, especially * if they are archives of text files. This is * especially useful if you archive source files together * and copy them to another system with ~%take, since * the files sometimes show up munged and must be fixed. */ case 0177545: case 0177555: error(" Archive"); case 070707: error(" Cpio file"); default: { char *bp = (char *)&head; if ((u_char)bp[0] == (u_char)'\037' && (u_char)bp[1] == (u_char)'\235') error(" Compressed file"); #ifdef ARCHIVES_ARE_OK if (!strncmp(bp, "!<arch>\n__.SYMDEF", 17) || !strncmp(bp, "!<arch>\n", 8)) error(" Archive"); #endif } break; } #endif /* __sun */ #endif } if (c != 'r') { if (value(READONLY) && denied) { value(READONLY) = ovro; denied = 0; } if ((stbuf.st_mode & 0222) == 0 || access(file, 2) < 0) { ovro = value(READONLY); denied = 1; value(READONLY) = 1; } } if (value(READONLY)) { ex_printf(" [Read only]"); flush(); } #ifdef FLOCKFILE /* * Attempt to lock the file. We use an sharable lock if reading * the file, and an exclusive lock if editting a file. * The lock will be released when the file is no longer being * referenced. At any time, the editor can have as many as * three files locked, and with different lock statuses. */ /* * if this is either the saved or alternate file or current file, * point to the appropriate descriptor and file lock status. */ if (strcmp (file,savedfile) == 0) { if (!io_savedfile) io_savedfile = dup(io) ; lp = &lock_savedfile ; iop = &io_savedfile ; } else if (strcmp (file,altfile) == 0) { if (!io_altfile) io_altfile = dup(io) ; lp = &lock_altfile ; iop = &io_altfile ; } else { /* throw away current lock, accquire new current lock */ if (io_curr) close (io_curr) ; io_curr = dup(io) ; lp = &lock_curr ; iop = &io_curr ; lock_curr = 0 ; } if (c == 'r' || value(READONLY) || *lp == 0) { /* if we have a lock already, don't bother */ if (!*lp) { /* try for a shared lock */ if (flock(*iop, LOCK_SH|LOCK_NB) < 0 && errno == EWOULDBLOCK) { ex_printf ( " [FILE BEING MODIFIED BY ANOTHER PROCESS]") ; flush(); goto fail_lock ; } else *lp = LOCK_SH ; } } if ( c != 'r' && !value(READONLY) && *lp != LOCK_EX) { /* if we are editting the file, upgrade to an exclusive lock. */ if (flock(*iop, LOCK_EX|LOCK_NB) < 0 && errno == EWOULDBLOCK) { ex_printf (" [File open by another process]") ; flush(); } else *lp = LOCK_EX ; } fail_lock: #endif if (c == 'r') setdot(); else setall(); if (FIXUNDO && inopen && c == 'r') undap1 = undap2 = dot + 1; rop2(); rop3(c); }
/* * 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; char *name; size_t nlen; EX_PRIVATE *exp; FILE *fp; FREF *frp; GS *gp; MARK rm; db_recno_t nlines; size_t arglen; int argc, rval; char *p; char *np; 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)) (void)ex_printf(sp, "!%s\n", cmdp->argv[argc]->bp); 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); }
void vappend(int ch, int cnt, int indent) { register int i; register char *gcursor; bool escape; int repcnt; short oldhold = hold; /* * Before a move in hardopen when the line is dirty * or we are in the middle of the printed representation, * we retype the line to the left of the cursor so the * insert looks clean. */ if (ch != 'o' && state == HARDOPEN && (rubble || !ateopr())) { rubble = 1; wcursor = cursor; vmove(); } vaifirst = indent == 0; /* * Handle replace character by (eventually) * limiting the number of input characters allowed * in the vgetline routine. */ if (ch == 'r') repcnt = 2; else repcnt = 0; /* * If an autoindent is specified, then * generate a mixture of blanks to tabs to implement * it and place the cursor after the indent. * Text read by the vgetline routine will be placed in genbuf, * so the indent is generated there. */ if (value(AUTOINDENT) && indent != 0) { gcursor = genindent(indent); *gcursor = 0; vgotoCL(qcolumn(cursor - 1, genbuf)); } else { gcursor = genbuf; *gcursor = 0; if (ch == 'o') vfixcurs(); } /* * Prepare for undo. Pointers delimit inserted portion of line. */ vUA1 = vUA2 = cursor; /* * If we are not in a repeated command and a ^@ comes in * then this means the previous inserted text. * If there is none or it was too long to be saved, * then beep() and also arrange to undo any damage done * so far (e.g. if we are a change.) */ if ((vglobp && *vglobp == 0) || peekbr()) { if ((INS[0] & (OVERBUF|TRIM)) == OVERBUF) { beep(); if (!splitw) ungetkey('u'); doomed = 0; hold = oldhold; return; } /* * Unread input from INS. * An escape will be generated at end of string. * Hold off n^^2 type update on dumb terminals. */ vglobp = INS; hold |= HOLDQIK; } else if (vglobp == 0) /* * Not a repeated command, get * a new inserted text for repeat. */ INS[0] = 0; /* * For wrapmargin to hack away second space after a '.' * when the first space caused a line break we keep * track that this happened in gobblebl, which says * to gobble up a blank silently. */ gobblebl = 0; /* * Text gathering loop. * New text goes into genbuf starting at gcursor. * cursor preserves place in linebuf where text will eventually go. */ if (*cursor == 0 || state == CRTOPEN) hold |= HOLDROL; for (;;) { if (ch == 'r' && repcnt == 0) escape = 0; else { gcursor = vgetline(repcnt, gcursor, &escape); /* * After an append, stick information * about the ^D's and ^^D's and 0^D's in * the repeated text buffer so repeated * inserts of stuff indented with ^D as backtab's * can work. */ if (HADUP) addtext("^"); else if (HADZERO) addtext("0"); while (CDCNT > 0) addtext("\204"), CDCNT--; if (gobbled) addtext(" "); addtext(ogcursor); } repcnt = 0; /* * Smash the generated and preexisting indents together * and generate one cleanly made out of tabs and spaces * if we are using autoindent. */ if (!vaifirst && value(AUTOINDENT)) { i = fixindent(indent); if (!HADUP) indent = i; gcursor = strend(genbuf); } /* * Limit the repetition count based on maximum * possible line length; do output implied * by further count (> 1) and cons up the new line * in linebuf. */ cnt = vmaxrep(ch, cnt); CP(gcursor + 1, cursor); do { CP(cursor, genbuf); if (cnt > 1) { int oldhold = hold; Outchar = vinschar; hold |= HOLDQIK; ex_printf("%s", genbuf); hold = oldhold; Outchar = vputchar; } cursor += gcursor - genbuf; } while (--cnt > 0); endim(); vUA2 = cursor; if (escape != '\n') CP(cursor, gcursor + 1); /* * If doomed characters remain, clobber them, * and reopen the line to get the display exact. */ if (state != HARDOPEN) { DEPTH(vcline) = 0; if (doomed > 0) { register int cind = cindent(); physdc(cind, cind + doomed); doomed = 0; } i = vreopen(LINE(vcline), lineDOT(), vcline); } /* * All done unless we are continuing on to another line. */ if (escape != '\n') break; /* * Set up for the new line. * First save the current line, then construct a new * first image for the continuation line consisting * of any new autoindent plus the pushed ahead text. */ killU(); addtext(gobblebl ? " " : "\n"); vsave(); cnt = 1; if (value(AUTOINDENT)) { #ifdef LISP if (value(LISP)) indent = lindent(dot + 1); else #endif if (!HADUP && vaifirst) indent = whitecnt(linebuf); vaifirst = 0; strcLIN(vpastwh(gcursor + 1)); gcursor = genindent(indent); *gcursor = 0; if (gcursor + strlen(linebuf) > &genbuf[LBSIZE - 2]) gcursor = genbuf; CP(gcursor, linebuf); } else { CP(genbuf, gcursor + 1); gcursor = genbuf; } /* * If we started out as a single line operation and are now * turning into a multi-line change, then we had better yank * out dot before it changes so that undo will work * correctly later. */ if (vundkind == VCHNG) { vremote(1, (void (*)(int))yank, 0); undap1--; } /* * Now do the append of the new line in the buffer, * and update the display. If slowopen * we don't do very much. */ vdoappend(genbuf); vundkind = VMANYINS; vcline++; if (state != VISUAL) vshow(dot, NOLINE); else { i += LINE(vcline - 1); vopen(dot, i); if (value(SLOWOPEN)) vscrap(); else vsync1(LINE(vcline)); } strcLIN(gcursor); *gcursor = 0; cursor = linebuf; vgotoCL(qcolumn(cursor - 1, genbuf)); } /* * All done with insertion, position the cursor * and sync the screen. */ hold = oldhold; if (cursor > linebuf) cursor--; if (state != HARDOPEN) vsyncCL(); else if (cursor > linebuf) back1(); doomed = 0; wcursor = cursor; vmove(); }
/* * Write a file. */ void wop(bool dofname) /* bool dofname; / * if 1 call filename, else use savedfile */ { register int c, exclam, nonexist; line *saddr1, *saddr2; struct stat stbuf; c = 0; exclam = 0; if (dofname) { if (peekchar() == '!') exclam++, ignchar(); ignore(skipwh()); while (peekchar() == '>') ignchar(), c++, ignore(skipwh()); if (c != 0 && c != 2) error("Write forms are 'w' and 'w>>'"); filename('w'); } else { if (savedfile[0] == 0) error("No file|No current filename"); saddr1=addr1; saddr2=addr2; addr1=one; addr2=dol; CP(file, savedfile); if (inopen) { vclrech(0); splitw++; } lprintf("\"%s\"", file); } nonexist = stat(file, &stbuf); switch (c) { case 0: if (!exclam && (!value(WRITEANY) || value(READONLY))) switch (edfile()) { case NOTEDF: if (nonexist) break; if ((stbuf.st_mode & S_IFMT) == S_IFCHR) { if (samei(&stbuf, "/dev/null")) break; if (samei(&stbuf, "/dev/tty")) break; } io = open(file, O_WRONLY); if (io < 0) syserror(); if (!isatty(io)) serror(" File exists| File exists - use \"w! %s\" to overwrite", file); close(io); break; case EDF: if (value(READONLY)) error(" File is read only"); break; case PARTBUF: if (value(READONLY)) error(" File is read only"); error(" Use \"w!\" to write partial buffer"); } cre: /* synctmp(); */ #ifdef V6 io = creat(file, 0644); #else io = creat(file, 0666); #endif if (io < 0) syserror(); writing = 1; if (hush == 0) { if (nonexist) ex_printf(" [New file]"); else if (value(WRITEANY) && edfile() != EDF) ex_printf(" [Existing file]"); } break; case 2: io = open(file, O_WRONLY); if (io < 0) { if (exclam || value(WRITEANY)) goto cre; syserror(); } lseek(io, 0l, SEEK_END); break; } putfile(); ignore(iostats()); if (c != 2 && addr1 == one && addr2 == dol) { if (eq(file, savedfile)) edited = 1; ex_sync(); } if (!dofname) { addr1 = saddr1; addr2 = saddr2; } writing = 0; }
/* * Parse file name for command encoded by comm. * If comm is E then command is doomed and we are * parsing just so user won't have to retype the name. */ void filename(int comm) { register int c = comm, d; register int i; #ifdef FLOCKFILE int lock ; lock = 0 ; #endif d = ex_getchar(); if (endcmd(d)) { if (savedfile[0] == 0 && comm != 'f') error("No file|No current filename"); CP(file, savedfile); #ifdef FLOCKFILE if (io_curr && io_curr != io_savedfile) close(io_curr) ; lock = lock_curr = lock_savedfile ; io_curr = io_savedfile ; #endif wasalt = (isalt > 0) ? isalt-1 : 0; isalt = 0; oldadot = altdot; if (c == 'e' || c == 'E') altdot = lineDOT(); if (d == EOF) ungetchar(d); } else { ungetchar(d); getone(); eol(); if (savedfile[0] == 0 && c != 'E' && c != 'e') { c = 'e'; edited = 0; } wasalt = strcmp(file, altfile) == 0; oldadot = altdot; switch (c) { case 'f': edited = 0; /* fall into ... */ case 'e': if (savedfile[0]) { #ifdef FLOCKFILE if (strcmp(file,savedfile) == 0) break ; #endif altdot = lineDOT(); CP(altfile, savedfile); #ifdef FLOCKFILE if (io_altfile) close (io_altfile) ; io_altfile = io_savedfile ; lock_altfile = lock_savedfile ; io_savedfile = 0 ; #endif } CP(savedfile, file); #ifdef FLOCKFILE io_savedfile = io_curr ; lock_savedfile = lock_curr ; io_curr = 0 ; lock = lock_curr = 0 ; #endif break; default: if (file[0]) { #ifdef FLOCKFILE if (wasalt) break ; #endif if (c != 'E') altdot = lineDOT(); CP(altfile, file); #ifdef FLOCKFILE if (io_altfile && io_altfile != io_curr) close (io_altfile) ; io_altfile = io_curr ; lock_altfile = lock_curr ; io_curr = 0 ; lock = lock_curr = 0 ; #endif } break; } } if ((hush && comm != 'f') || comm == 'E') return; if (file[0] != 0) { lprintf("\"%s\"", file); if (comm == 'f') { if (value(READONLY)) ex_printf(" [Read only]"); if (!edited) ex_printf(" [Not edited]"); if (tchng) ex_printf(" [Modified]"); #ifdef FLOCKFILE if (lock == LOCK_SH) ex_printf(" [Shared lock]") ; else if (lock == LOCK_EX) ex_printf(" [Exclusive lock]") ; #endif } flush(); } else ex_printf("No file "); if (comm == 'f') { if (!(i = lineDOL())) i++; ex_printf(" line %d of %d --%ld%%--", lineDOT(), lineDOL(), (long) 100 * lineDOT() / i); } }
/* * Read a file from the world. * C is command, 'e' if this really an edit (or a recover). */ void rop(int c) { register int i; struct stat stbuf; short magic; static int ovro; /* old value(READONLY) */ static int denied; /* 1 if READONLY was set due to file permissions */ io = open(file, O_RDONLY); if (io < 0) { if (c == 'e' && errno == ENOENT) { edited++; /* * If the user just did "ex foo" he is probably * creating a new file. Don't be an error, since * this is ugly, and it screws up the + option. */ if (!seenprompt) { ex_printf(" [New file]"); noonl(); return; } } syserror(); } if (fstat(io, &stbuf)) syserror(); switch (stbuf.st_mode & S_IFMT) { case S_IFBLK: error(" Block special file"); case S_IFCHR: if (isatty(io)) error(" Teletype"); if (samei(&stbuf, "/dev/null")) break; error(" Character special file"); case S_IFDIR: error(" Directory"); case S_IFREG: #ifdef CRYPT if (xflag) break; #endif i = read(io, (char *) &magic, sizeof(magic)); lseek(io, 0l, SEEK_SET); if (i != sizeof(magic)) break; switch (magic) { case 0405: /* Interdata? overlay */ case 0407: /* unshared */ case 0410: /* shared text */ case 0411: /* separate I/D */ case 0413: /* VM/Unix demand paged */ case 0430: /* PDP-11 Overlay shared */ case 0431: /* PDP-11 Overlay sep I/D */ error(" Executable"); /* * We do not forbid the editing of portable archives * because it is reasonable to edit them, especially * if they are archives of text files. This is * especially useful if you archive source files together * and copy them to another system with ~%take, since * the files sometimes show up munged and must be fixed. */ #if 0 case 0177545: case 0177555: error(" Archive"); #endif default: #ifndef BIT8 if (magic & 0100200) error(" Non-ascii file"); #endif break; } } if (c != 'r') { if (value(READONLY) && denied) { value(READONLY) = ovro; denied = 0; } if ((stbuf.st_mode & 0222) == 0 || access(file, 2) < 0) { ovro = value(READONLY); denied = 1; value(READONLY) = 1; } } if (value(READONLY)) { ex_printf(" [Read only]"); flush(); } if (c == 'r') setdot(); else setall(); if (FIXUNDO && inopen && c == 'r') undap1 = undap2 = dot + 1; rop2(); rop3(c); }
/* * Parse file name for command encoded by comm. * If comm is E then command is doomed and we are * parsing just so user won't have to retype the name. */ void filename(int comm) { register int c = comm, d; register int i; d = ex_getchar(); if (endcmd(d)) { if (savedfile[0] == 0 && comm != 'f') error("No file|No current filename"); CP(file, savedfile); wasalt = (isalt > 0) ? isalt-1 : 0; isalt = 0; oldadot = altdot; if (c == 'e' || c == 'E') altdot = lineDOT(); if (d == EOF) ungetchar(d); } else { ungetchar(d); getone(); eol(); if (savedfile[0] == 0 && c != 'E' && c != 'e') { c = 'e'; edited = 0; } wasalt = strcmp(file, altfile) == 0; oldadot = altdot; switch (c) { case 'f': edited = 0; /* fall into ... */ case 'e': if (savedfile[0]) { altdot = lineDOT(); CP(altfile, savedfile); } CP(savedfile, file); break; default: if (file[0]) { if (c != 'E') altdot = lineDOT(); CP(altfile, file); } break; } } if ((hush && comm != 'f') || comm == 'E') return; if (file[0] != 0) { lprintf("\"%s\"", file); if (comm == 'f') { if (value(READONLY)) ex_printf(" [Read only]"); if (!edited) ex_printf(" [Not edited]"); if (tchng) ex_printf(" [Modified]"); } flush(); } else ex_printf("No file "); if (comm == 'f') { if (!(i = lineDOL())) i++; ex_printf(" line %d of %d --%ld%%--", lineDOT(), lineDOL(), (long) 100 * lineDOT() / i); } }
/* * ex_bang -- :[line [,line]] ! command * * Pass the rest of the line after the ! character to the program named by * the O_SHELL option. * * Historical vi did NOT do shell expansion on the arguments before passing * them, only file name expansion. This means that the O_SHELL program got * "$t" as an argument if that is what the user entered. Also, there's a * special expansion done for the bang command. Any exclamation points in * the user's argument are replaced by the last, expanded ! command. * * There's some fairly amazing slop in this routine to make the different * ways of getting here display the right things. It took a long time to * get it right (wrong?), so be careful. * * PUBLIC: int ex_bang(SCR *, EXCMD *); */ int ex_bang(SCR *sp, EXCMD *cmdp) { enum filtertype ftype; ARGS *ap; EX_PRIVATE *exp; MARK rm; recno_t lno; int rval; const char *msg; char *np; size_t nlen; ap = cmdp->argv[0]; if (ap->len == 0) { ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } /* Set the "last bang command" remembered value. */ exp = EXP(sp); free(exp->lastbcomm); if ((exp->lastbcomm = v_wstrdup(sp, ap->bp, ap->len)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } /* * If the command was modified by the expansion, it was historically * redisplayed. */ if (F_ISSET(cmdp, E_MODIFY) && !F_ISSET(sp, SC_EX_SILENT)) { /* * Display the command if modified. Historic ex/vi displayed * the command if it was modified due to file name and/or bang * expansion. If piping lines in vi, it would be immediately * overwritten by any error or line change reporting. */ if (F_ISSET(sp, SC_VI)) vs_update(sp, "!", ap->bp); else { (void)ex_printf(sp, "!"WS"\n", ap->bp); (void)ex_fflush(sp); } } /* * If no addresses were specified, run the command. If there's an * underlying file, it's been modified and autowrite is set, write * the file back. If the file has been modified, autowrite is not * set and the warn option is set, tell the user about the file. */ if (cmdp->addrcnt == 0) { msg = NULL; if (sp->ep != NULL && F_ISSET(sp->ep, F_MODIFIED)) if (O_ISSET(sp, O_AUTOWRITE)) { if (file_aw(sp, FS_ALL)) return (0); } else if (O_ISSET(sp, O_WARN) && !F_ISSET(sp, SC_EX_SILENT)) msg = msg_cat(sp, "303|File modified since last write.", NULL); /* If we're still in a vi screen, move out explicitly. */ INT2CHAR(sp, ap->bp, ap->len+1, np, nlen); (void)ex_exec_proc(sp, cmdp, np, msg, !F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)); } /* * If addresses were specified, pipe lines from the file through the * command. * * Historically, vi lines were replaced by both the stdout and stderr * lines of the command, but ex lines by only the stdout lines. This * makes no sense to me, so nvi makes it consistent for both, and * matches vi's historic behavior. */ else { NEEDFILE(sp, cmdp); /* Autoprint is set historically, even if the command fails. */ F_SET(cmdp, E_AUTOPRINT); /* * !!! * Historical vi permitted "!!" in an empty file. When this * happens, we arrive here with two addresses of 1,1 and a * bad attitude. The simple solution is to turn it into a * FILTER_READ operation, with the exception that stdin isn't * opened for the utility, and the cursor position isn't the * same. The only historic glitch (I think) is that we don't * put an empty line into the default cut buffer, as historic * vi did. Imagine, if you can, my disappointment. */ ftype = FILTER_BANG; if (cmdp->addr1.lno == 1 && cmdp->addr2.lno == 1) { if (db_last(sp, &lno)) return (1); if (lno == 0) { cmdp->addr1.lno = cmdp->addr2.lno = 0; ftype = FILTER_RBANG; } } rval = ex_filter(sp, cmdp, &cmdp->addr1, &cmdp->addr2, &rm, ap->bp, ftype); /* * If in vi mode, move to the first nonblank. * * !!! * Historic vi wasn't consistent in this area -- if you used * a forward motion it moved to the first nonblank, but if you * did a backward motion it didn't. And, if you followed a * backward motion with a forward motion, it wouldn't move to * the nonblank for either. Going to the nonblank generally * seems more useful and consistent, so we do it. */ sp->lno = rm.lno; if (F_ISSET(sp, SC_VI)) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } else sp->cno = rm.cno; } /* Ex terminates with a bang, even if the command fails. */ if (!F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_EX_SILENT)) (void)ex_puts(sp, "!\n"); /* * XXX * The ! commands never return an error, so that autoprint always * happens in the ex parser. */ return (0); }
/* * Main procedure. Process arguments and then * transfer control to the main command processing loop * in the routine commands. We are entered as either "ex", "edit", "vi" * or "view" and the distinction is made here. Actually, we are "vi" if * there is a 'v' in our name, "view" is there is a 'w', and "edit" if * there is a 'd' in our name. For edit we just diddle options; * for vi we actually force an early visual command. */ int main(int ac, char **av) { #ifndef VMUNIX char *erpath = EXSTRINGS; #endif register char *cp; register int c; bool recov = 0; bool ivis; bool itag = 0; bool fast = 0; #ifdef TRACE register char *tracef; #endif /* * Immediately grab the tty modes so that we wont * get messed up if an interrupt comes in quickly. */ gTTY(1); #ifndef USG3TTY normf = tty.sg_flags; #else normf = tty; #endif ppid = getpid(); /* * Defend against d's, v's, w's, and a's in directories of * path leading to our true name. */ av[0] = tailpath(av[0]); /* * Figure out how we were invoked: ex, edit, vi, view. */ ivis = any('v', av[0]); /* "vi" */ if (any('w', av[0])) /* "view" */ value(READONLY) = 1; if (any('d', av[0])) { /* "edit" */ value(OPEN) = 0; value(REPORT) = 1; value(MAGIC) = 0; } #ifndef VMUNIX /* * For debugging take files out of . if name is a.out. */ if (av[0][0] == 'a') erpath = tailpath(erpath); #endif /* * Open the error message file. */ draino(); #ifndef VMUNIX erfile = open(erpath+4, O_RDONLY); if (erfile < 0) { erfile = open(erpath, O_RDONLY); } #endif pstop(); /* * Initialize interrupt handling. */ oldhup = signal(SIGHUP, SIG_IGN); if (oldhup == SIG_DFL) signal(SIGHUP, onhup); oldquit = signal(SIGQUIT, SIG_IGN); ruptible = signal(SIGINT, SIG_IGN) == SIG_DFL; if (signal(SIGTERM, SIG_IGN) == SIG_DFL) signal(SIGTERM, onhup); #ifdef SIGEMT if (signal(SIGEMT, SIG_IGN) == SIG_DFL) signal(SIGEMT, onemt); #endif /* * Initialize end of core pointers. * Normally we avoid breaking back to fendcore after each * file since this can be expensive (much core-core copying). * If your system can scatter load processes you could do * this as ed does, saving a little core, but it will probably * not often make much difference. */ #ifdef UNIX_SBRK fendcore = (line *) sbrk(0); endcore = fendcore - 2; #else # define LINELIMIT 0x8000 fendcore = malloc(LINELIMIT * sizeof(line *)); endcore = fendcore + LINELIMIT - 1; #endif /* * Process flag arguments. */ ac--, av++; while (ac && av[0][0] == '-') { c = av[0][1]; if (c == 0) { hush = 1; value(AUTOPRINT) = 0; fast++; } else switch (c) { case 'R': value(READONLY) = 1; break; #ifdef TRACE case 'T': if (av[0][2] == 0) tracef = "trace"; else { tracef = tttrace; tracef[8] = av[0][2]; if (tracef[8]) tracef[9] = av[0][3]; else tracef[9] = 0; } trace = fopen(tracef, "w"); if (trace == NULL) ex_printf("Trace create error\n"); setbuf(trace, tracbuf); break; #endif #ifdef LISPCODE case 'l': value(LISP) = 1; value(SHOWMATCH) = 1; break; #endif case 'r': recov++; break; case 't': if (ac > 1 && av[1][0] != '-') { ac--, av++; itag = 1; /* BUG: should check for too long tag. */ CP(lasttag, av[0]); } break; case 'v': ivis = 1; break; case 'w': defwind = 0; if (av[0][2] == 0) defwind = 3; else for (cp = &av[0][2]; isdigit((int)*cp); cp++) defwind = 10*defwind + *cp - '0'; break; #ifdef CRYPT case 'x': /* -x: encrypted mode */ xflag = 1; break; #endif default: smerror("Unknown option %s\n", av[0]); break; } ac--, av++; } #ifdef SIGTSTP if (!hush && signal(SIGTSTP, SIG_IGN) == SIG_DFL) signal(SIGTSTP, onsusp), dosusp++; #endif if (ac && av[0][0] == '+') { firstpat = &av[0][1]; ac--, av++; } #ifdef CRYPT if(xflag){ key = getpass(KEYPROMPT); kflag = crinit(key, perm); } #endif /* * If we are doing a recover and no filename * was given, then execute an exrecover command with * the -r option to type out the list of saved file names. * Otherwise set the remembered file name to the first argument * file name so the "recover" initial command will find it. */ if (recov) { if (ac == 0) { ppid = 0; setrupt(); execl(EXRECOVER, "exrecover", "-r", NULL); filioerr(EXRECOVER); ex_exit(1); } CP(savedfile, *av); av++, ac--; } /* * Initialize the argument list. */ argv0 = av; argc0 = ac; args0 = av[0]; erewind(); /* * Initialize a temporary file (buffer) and * set up terminal environment. Read user startup commands. */ if (setexit() == 0) { setrupt(); intty = isatty(0); value(PROMPT) = intty; if ((cp = getenv("SHELL"))) CP(shell, cp); if (fast || !intty) setterm("dumb"); else { gettmode(); if ((cp = getenv("TERM")) != 0 && *cp) setterm(cp); } } if (setexit() == 0 && !fast && intty) { if ((globp = getenv("EXINIT")) && *globp) commands(1,1); else { globp = 0; if ((cp = getenv("HOME")) != 0 && *cp) source(strcat(strcpy(genbuf, cp), "/.exrc"), 1); } } init(); /* moved after prev 2 chunks to fix directory option */ /* * Initial processing. Handle tag, recover, and file argument * implied next commands. If going in as 'vi', then don't do * anything, just set initev so we will do it later (from within * visual). */ if (setexit() == 0) { if (recov) globp = "recover"; else if (itag) globp = ivis ? "tag" : "tag|p"; else if (argc) globp = "next"; if (ivis) initev = globp; else if (globp) { inglobal = 1; commands(1, 1); inglobal = 0; } } /* * Vi command... go into visual. * Strange... everything in vi usually happens * before we ever "start". */ if (ivis) { /* * Don't have to be upward compatible with stupidity * of starting editing at line $. */ if (dol > zero) dot = one; globp = "visual"; if (setexit() == 0) commands(1, 1); } /* * Clear out trash in state accumulated by startup, * and then do the main command loop for a normal edit. * If you quit out of a 'vi' command by doing Q or ^\, * you also fall through to here. */ seenprompt = 1; ungetchar(0); globp = 0; initev = 0; setlastchar('\n'); setexit(); commands(0, 0); cleanup(1); return 0; }
/* * Main loop for command mode command decoding. * A few commands are executed here, but main function * is to strip command addresses, do a little address oriented * processing and call command routines to do the real work. */ void commands(bool noprompt, bool exitoneof) { register line *addr; register int c; register int lchng; int given; int seensemi; int cnt; bool hadpr; resetflav(); nochng(); for (;;) { /* * If dot at last command * ended up at zero, advance to one if there is a such. */ if (dot <= zero) { dot = zero; if (dol > zero) dot = one; } shudclob = 0; /* * If autoprint or trailing print flags, * print the line at the specified offset * before the next command. */ if (pflag || (lchng != chng && value(AUTOPRINT) && !inglobal && !inopen && endline)) { pflag = 0; nochng(); if (dol != zero) { addr1 = addr2 = dot + poffset; if (addr1 < one || addr1 > dol) error("Offset out-of-bounds|Offset after command too large"); setdot1(); goto print; } } nochng(); /* * Print prompt if appropriate. * If not in global flush output first to prevent * going into pfast mode unreasonably. */ if (inglobal == 0) { flush(); if (!hush && value(PROMPT) && !globp && !noprompt && endline) { ex_putchar(':'); hadpr = 1; } TSYNC(); } /* * Gobble up the address. * Degenerate addresses yield ".". */ addr2 = 0; given = seensemi = 0; do { addr1 = addr2; addr = address(0); c = getcd(); if (addr == 0) { if (c == ',') addr = dot; else if (addr1 != 0) { addr2 = dot; break; } else break; } addr2 = addr; given++; if (c == ';') { c = ','; dot = addr; seensemi = 1; } } while (c == ','); if (c == '%') { /* %: same as 1,$ */ addr1 = one; addr2 = dol; given = 2; c = ex_getchar(); } if (addr1 == 0) addr1 = addr2; if (c == ':') c = ex_getchar(); /* * Set command name for special character commands. */ tailspec(c); /* * If called via : escape from open or visual, limit * the set of available commands here to save work below. */ if (inopen) { if (c=='\n' || c=='\r' || c==CTRL('d') || c==EOF) { if (addr2) dot = addr2; if (c == EOF) return; continue; } if (any(c, "o")) notinvis: tailprim(Command, 1, 1); } switch (c) { case 'a': switch(peekchar()) { case 'b': /* abbreviate */ tail("abbreviate"); setnoaddr(); mapcmd(0, 1); anyabbrs = 1; continue; case 'r': /* args */ tail("args"); setnoaddr(); eol(); pargs(); continue; } /* append */ if (inopen) goto notinvis; tail("append"); setdot(); aiflag = exclam(); ex_newline(); vmacchng(0); deletenone(); setin(addr2); inappend = 1; ignore(append(gettty, addr2)); inappend = 0; nochng(); continue; case 'c': switch (peekchar()) { /* copy */ case 'o': tail("copy"); vmacchng(0); move(); continue; #ifdef CHDIR /* cd */ case 'd': tail("cd"); goto changdir; /* chdir */ case 'h': ignchar(); if (peekchar() == 'd') { register char *p; tail2of("chdir"); changdir: if (savedfile[0] == '/' || !value(WARN)) ignore(exclam()); else ignore(quickly()); if (skipend()) { p = getenv("HOME"); if (p == NULL) error("Home directory unknown"); } else getone(), p = file; eol(); if (chdir(p) < 0) filioerr(p); if (savedfile[0] != '/') edited = 0; continue; } if (inopen) tailprim("change", 2, 1); tail2of("change"); break; #endif default: if (inopen) goto notinvis; tail("change"); break; } /* change */ aiflag = exclam(); setCNL(); vmacchng(0); setin(addr1); delete(0); inappend = 1; ignore(append(gettty, addr1 - 1)); inappend = 0; nochng(); continue; /* delete */ case 'd': /* * Caution: dp and dl have special meaning already. */ tail("delete"); c = cmdreg(); setCNL(); vmacchng(0); if (c) YANKreg(c); delete(0); appendnone(); continue; /* edit */ /* ex */ case 'e': tail(peekchar() == 'x' ? "ex" : "edit"); editcmd: if (!exclam() && chng) c = 'E'; filename(c); if (c == 'E') { ungetchar(lastchar()); ignore(quickly()); } setnoaddr(); doecmd: init(); addr2 = zero; laste++; ex_sync(); rop(c); nochng(); continue; /* file */ case 'f': tail("file"); setnoaddr(); filename(c); noonl(); /* synctmp(); */ continue; /* global */ case 'g': tail("global"); global(!exclam()); nochng(); continue; /* insert */ case 'i': if (inopen) goto notinvis; tail("insert"); setdot(); nonzero(); aiflag = exclam(); ex_newline(); vmacchng(0); deletenone(); setin(addr2); inappend = 1; ignore(append(gettty, addr2 - 1)); inappend = 0; if (dot == zero && dol > zero) dot = one; nochng(); continue; /* join */ case 'j': tail("join"); c = exclam(); setcount(); nonzero(); ex_newline(); vmacchng(0); if (given < 2 && addr2 != dol) addr2++; join(c); continue; /* k */ case 'k': casek: pastwh(); c = ex_getchar(); if (endcmd(c)) serror("Mark what?|%s requires following letter", Command); ex_newline(); if (!islower(c)) error("Bad mark|Mark must specify a letter"); setdot(); nonzero(); names[c - 'a'] = *addr2 &~ 01; anymarks = 1; continue; /* list */ case 'l': tail("list"); setCNL(); ignorf(setlist(1)); pflag = 0; goto print; case 'm': if (peekchar() == 'a') { ignchar(); if (peekchar() == 'p') { /* map */ tail2of("map"); setnoaddr(); mapcmd(0, 0); continue; } /* mark */ tail2of("mark"); goto casek; } /* move */ tail("move"); vmacchng(0); move(); continue; case 'n': if (peekchar() == 'u') { tail("number"); goto numberit; } /* next */ tail("next"); setnoaddr(); ckaw(); ignore(quickly()); if (getargs()) makargs(); next(); c = 'e'; filename(c); goto doecmd; /* open */ case 'o': tail("open"); oop(); pflag = 0; nochng(); continue; case 'p': case 'P': switch (peekchar()) { /* put */ case 'u': tail("put"); setdot(); c = cmdreg(); eol(); vmacchng(0); if (c) putreg(c); else put(); continue; case 'r': ignchar(); if (peekchar() == 'e') { /* preserve */ tail2of("preserve"); eol(); if (preserve() == 0) error("Preserve failed!"); else error("File preserved."); } tail2of("print"); break; default: tail("print"); break; } /* print */ setCNL(); pflag = 0; print: nonzero(); if (CL && span() > EX_LINES) { flush1(); vclear(); } plines(addr1, addr2, 1); continue; /* quit */ case 'q': tail("quit"); setnoaddr(); c = quickly(); eol(); if (!c) quit: nomore(); if (inopen) { vgoto(WECHO, 0); if (!ateopr()) vnfl(); else { tostop(); } flush(); setty(normf); } cleanup(1); ex_exit(0); case 'r': if (peekchar() == 'e') { ignchar(); switch (peekchar()) { /* rewind */ case 'w': tail2of("rewind"); setnoaddr(); if (!exclam()) { ckaw(); if (chng && dol > zero) error("No write@since last chage (:rewind! overrides)"); } eol(); erewind(); next(); c = 'e'; ungetchar(lastchar()); filename(c); goto doecmd; /* recover */ case 'c': tail2of("recover"); setnoaddr(); c = 'e'; if (!exclam() && chng) c = 'E'; filename(c); if (c == 'E') { ungetchar(lastchar()); ignore(quickly()); } init(); addr2 = zero; laste++; ex_sync(); recover(); rop2(); revocer(); if (status == 0) rop3(c); if (dol != zero) change(); nochng(); continue; } tail2of("read"); } else tail("read"); /* read */ if (savedfile[0] == 0 && dol == zero) c = 'e'; pastwh(); vmacchng(0); if (peekchar() == '!') { setdot(); ignchar(); unix0(0); filter(0); continue; } filename(c); rop(c); nochng(); if (inopen && endline && addr1 > zero && addr1 < dol) dot = addr1 + 1; continue; case 's': switch (peekchar()) { /* * Caution: 2nd char cannot be c, g, or r * because these have meaning to substitute. */ /* set */ case 'e': tail("set"); setnoaddr(); set(); continue; /* shell */ case 'h': tail("shell"); setNAEOL(); vnfl(); putpad(TE); flush(); unixwt(1, unixex("-i", (char *) 0, 0, 0)); vcontin(0); continue; /* source */ case 'o': #ifdef notdef if (inopen) goto notinvis; #endif tail("source"); setnoaddr(); getone(); eol(); source(file, 0); continue; #ifdef SIGTSTP /* stop, suspend */ case 't': tail("stop"); goto suspend; case 'u': tail("suspend"); suspend: if (!dosusp) error("Old tty driver|Not using new tty driver/shell"); c = exclam(); eol(); if (!c) ckaw(); onsusp(0); continue; #endif } /* fall into ... */ /* & */ /* ~ */ /* substitute */ case '&': case '~': Command = "substitute"; if (c == 's') tail(Command); vmacchng(0); if (!substitute(c)) pflag = 0; continue; /* t */ case 't': if (peekchar() == 'a') { tail("tag"); tagfind(exclam()); if (!inopen) lchng = chng - 1; else nochng(); continue; } tail("t"); vmacchng(0); move(); continue; case 'u': if (peekchar() == 'n') { ignchar(); switch(peekchar()) { /* unmap */ case 'm': tail2of("unmap"); setnoaddr(); mapcmd(1, 0); continue; /* unabbreviate */ case 'a': tail2of("unabbreviate"); setnoaddr(); mapcmd(1, 1); anyabbrs = 1; continue; } /* undo */ tail2of("undo"); } else tail("undo"); setnoaddr(); markDOT(); c = exclam(); ex_newline(); undo(c); continue; case 'v': switch (peekchar()) { case 'e': /* version */ tail("version"); setNAEOL(); ex_printf("@(#) Version 3.6, 11/3/80" " (4.0BSD). git " "160803 14:24" +5); noonl(); continue; /* visual */ case 'i': tail("visual"); if (inopen) { c = 'e'; goto editcmd; } vop(); pflag = 0; nochng(); continue; } /* v */ tail("v"); global(0); nochng(); continue; /* write */ case 'w': c = peekchar(); tail(c == 'q' ? "wq" : "write"); wq: if (skipwh() && peekchar() == '!') { pofix(); ignchar(); setall(); unix0(0); filter(1); } else { setall(); wop(1); nochng(); } if (c == 'q') goto quit; continue; /* xit */ case 'x': tail("xit"); if (!chng) goto quit; c = 'q'; goto wq; /* yank */ case 'y': tail("yank"); c = cmdreg(); setcount(); eol(); vmacchng(0); if (c) YANKreg(c); else yank(); continue; /* z */ case 'z': zop(0); pflag = 0; continue; /* * */ /* @ */ case '*': case '@': c = ex_getchar(); if (c=='\n' || c=='\r') ungetchar(c); if (any(c, "@*\n\r")) c = lastmac; if (isupper(c)) c = tolower(c); if (!islower(c)) error("Bad register"); ex_newline(); setdot(); cmdmac(c); continue; /* | */ case '|': endline = 0; goto caseline; /* \n */ case '\n': endline = 1; caseline: notempty(); if (addr2 == 0) { if (UP != NOSTR && c == '\n' && !inglobal) c = CTRL('k'); if (inglobal) addr1 = addr2 = dot; else { if (dot == dol) error("At EOF|At end-of-file"); addr1 = addr2 = dot + 1; } } setdot(); nonzero(); if (seensemi) addr1 = addr2; ex_getline(*addr1); if (c == CTRL('k')) { flush1(); destline--; if (hadpr) shudclob = 1; } plines(addr1, addr2, 1); continue; /* " */ case '"': comment(); continue; /* # */ case '#': numberit: setCNL(); ignorf(setnumb(1)); pflag = 0; goto print; /* = */ case '=': ex_newline(); setall(); if (inglobal == 2) pofix(); ex_printf("%d", lineno(addr2)); noonl(); continue; /* ! */ case '!': if (addr2 != 0) { vmacchng(0); unix0(0); setdot(); filter(2); } else { unix0(1); pofix(); putpad(TE); flush(); unixwt(1, unixex("-c", uxb, 0, 0)); vclrech(1); /* vcontin(0); */ nochng(); } continue; /* < */ /* > */ case '<': case '>': for (cnt = 1; peekchar() == c; cnt++) ignchar(); setCNL(); vmacchng(0); shift(c, cnt); continue; /* ^D */ /* EOF */ case CTRL('d'): case EOF: if (exitoneof) { if (addr2 != 0) dot = addr2; return; } if (!isatty(0)) { if (intty) /* * Chtty sys call at UCB may cause a * input which was a tty to suddenly be * turned into /dev/null. */ onhup(0); return; } if (addr2 != 0) { setlastchar('\n'); putnl(); } if (dol == zero) { if (addr2 == 0) putnl(); notempty(); } ungetchar(EOF); zop(hadpr); continue; default: if (!isalpha(c)) break; ungetchar(c); tailprim("", 0, 0); } ierror("What?|Unknown command character '%c'", c); } }
/* * ex_usage -- :exusage [cmd] * Display ex usage strings. * * PUBLIC: int ex_usage __P((SCR *, EXCMD *)); */ int ex_usage(SCR *sp, EXCMD *cmdp) { ARGS *ap; EXCMDLIST const *cp; int newscreen; CHAR_T *p, nb[MAXCMDNAMELEN + 5]; const CHAR_T *name; switch (cmdp->argc) { case 1: ap = cmdp->argv[0]; if (ISUPPER((UCHAR_T)ap->bp[0])) { newscreen = 1; ap->bp[0] = TOLOWER((UCHAR_T)ap->bp[0]); } else newscreen = 0; for (cp = cmds; cp->name != NULL && memcmp(ap->bp, cp->name, ap->len); ++cp); if (cp->name == NULL || (newscreen && !F_ISSET(cp, E_NEWSCREEN))) { const char *nstr; size_t nlen; if (newscreen) ap->bp[0] = TOUPPER((UCHAR_T)ap->bp[0]); INT2CHAR(sp, ap->bp, ap->len + 1, nstr, nlen); (void)ex_printf(sp, "The %.*s command is unknown\n", (int)ap->len, nstr); } else { (void)ex_printf(sp, "Command: %s\n Usage: %s\n", cp->help, cp->usage); /* * !!! * The "visual" command has two modes, one from ex, * one from the vi colon line. Don't ask. */ if (cp != &cmds[C_VISUAL_EX] && cp != &cmds[C_VISUAL_VI]) break; if (cp == &cmds[C_VISUAL_EX]) cp = &cmds[C_VISUAL_VI]; else cp = &cmds[C_VISUAL_EX]; (void)ex_printf(sp, "Command: %s\n Usage: %s\n", cp->help, cp->usage); } break; case 0: for (cp = cmds; cp->name != NULL && !INTERRUPTED(sp); ++cp) { /* * The ^D command has an unprintable name. * * XXX * We display both capital and lower-case versions of * the appropriate commands -- no need to add in extra * room, they're all short names. */ if (cp == &cmds[C_SCROLL]) name = L("^D"); else if (F_ISSET(cp, E_NEWSCREEN)) { nb[0] = L('['); nb[1] = TOUPPER((UCHAR_T)cp->name[0]); nb[2] = cp->name[0]; nb[3] = L(']'); for (name = cp->name + 1, p = nb + 4; (*p++ = *name++) != '\0';); name = nb; } else name = cp->name; (void)ex_printf(sp, WVS": %s\n", MAXCMDNAMELEN, name, cp->help); } break; default: abort(); } return (0); }
/* * Write a file. */ void wop(bool dofname) /* bool dofname; / * if 1 call filename, else use savedfile */ { register int c, exclam, nonexist; line *saddr1, *saddr2; struct stat stbuf; #ifdef FLOCKFILE int *lp, *iop ; #endif c = 0; exclam = 0; saddr1=addr1; saddr2=addr2; if (dofname) { if (peekchar() == '!') exclam++, ignchar(); ignore(skipwh()); while (peekchar() == '>') ignchar(), c++, ignore(skipwh()); if (c != 0 && c != 2) error("Write forms are 'w' and 'w>>'"); filename('w'); } else { if (savedfile[0] == 0) error("No file|No current filename"); addr1=one; addr2=dol; CP(file, savedfile); if (inopen) { vclrech(0); splitw++; } lprintf("\"%s\"", file); } nonexist = stat(file, &stbuf); #ifdef FLOCKFILE /* * if this is either the saved or alternate file or current file, * point to the appropriate descriptor and file lock status. */ if (strcmp (file,savedfile) == 0) { lp = &lock_savedfile ; iop = &io_savedfile ; } else if (strcmp (file,altfile) == 0) { lp = &lock_altfile ; iop = &io_altfile ; } else { lp = &lock_curr ; iop = &io_curr ; } if (!*iop && !nonexist){ *lp = 0 ; if ((*iop = open(file, O_WRONLY)) < 0) *iop = 0 ; } #endif switch (c) { case 0: if (!exclam && (!value(WRITEANY) || value(READONLY))) switch (edfile()) { case NOTEDF: if (nonexist) break; if ((stbuf.st_mode & S_IFMT) == S_IFCHR) { if (samei(&stbuf, _PATH_DEVNULL)) break; if (samei(&stbuf, _PATH_TTY)) break; } io = open(file, O_WRONLY); if (io < 0) syserror(); if (!isatty(io)) serror(" File exists| File exists - use \"w! %s\" to overwrite", file); close(io); break; case EDF: if (value(READONLY)) error(" File is read only"); break; case PARTBUF: if (value(READONLY)) error(" File is read only"); error(" Use \"w!\" to write partial buffer"); } cre: /* synctmp(); */ #ifdef FLOCKFILE if (*iop && !*lp != LOCK_EX && !exclam) { /* * upgrade to a exclusive lock. if can't get, someone else * has the exclusive lock. bitch to the user. */ if (flock(*iop, LOCK_EX|LOCK_NB) < 0 && errno == EWOULDBLOCK) error (" File being modified by another process - use \"w!\" to write"); else *lp = LOCK_EX ; } #endif #ifdef V6 io = creat(file, 0644); #else io = creat(file, 0666); #ifdef vms /* to retain file protection modes on newer version of file */ if (!nonexist) chmod(file, stbuf.st_mode & 0777); #endif #endif if (io < 0) syserror(); writing = 1; if (hush == 0) { if (nonexist) ex_printf(" [New file]"); else if (value(WRITEANY) && edfile() != EDF) ex_printf(" [Existing file]"); } #ifdef FLOCKFILE if (!*iop) *iop = dup(io) ; #endif break; case 2: io = open(file, O_WRONLY); if (io < 0) { if (exclam || value(WRITEANY)) goto cre; syserror(); } lseek(io, 0l, SEEK_END); #ifdef FLOCKFILE if (!*iop) *iop = dup(io) ; if (*lp != LOCK_EX && !exclam) { /* * upgrade to a exclusive lock. if can't get, * someone else has the exclusive lock. * bitch to the user. */ if (flock(*iop, LOCK_SH|LOCK_NB) < 0 && errno == EWOULDBLOCK) error ( " File being modified by another process - use \"w!>>\" to write"); else *lp = LOCK_EX ; } #endif break; } #ifdef FLOCKFILE if (flock(*iop, LOCK_EX|LOCK_NB) >= 0) *lp = LOCK_EX ; #endif putfile(0); #ifndef vms (void) fsync(io); #endif ignore(iostats()); if (c != 2 && addr1 == one && addr2 == dol) { if (eq(file, savedfile)) edited = 1; ex_sync(); } if (!dofname) { addr1 = saddr1; addr2 = saddr2; } writing = 0; }