예제 #1
0
파일: ex_shell.c 프로젝트: jungle0755/minix
/*
 * ex_shell -- :sh[ell]
 *	Invoke the program named in the SHELL environment variable
 *	with the argument -i.
 *
 * PUBLIC: int ex_shell __P((SCR *, EXCMD *));
 */
int
ex_shell(SCR *sp, EXCMD *cmdp)
{
    int rval;
    char buf[MAXPATHLEN];

    /* We'll need a shell. */
    if (opts_empty(sp, O_SHELL, 0))
        return (1);

    /*
     * XXX
     * Assumes all shells use -i.
     */
    (void)snprintf(buf, sizeof(buf), "%s -i", O_STR(sp, O_SHELL));

    /* Restore the window name. */
    (void)sp->gp->scr_rename(sp, NULL, 0);

    /* If we're still in a vi screen, move out explicitly. */
    rval = ex_exec_proc(sp, cmdp, buf, NULL, !F_ISSET(sp, SC_SCR_EXWROTE));

    /* Set the window name. */
    (void)sp->gp->scr_rename(sp, sp->frp->name, 1);

    /*
     * !!!
     * Historically, vi didn't require a continue message after the
     * return of the shell.  Match it.
     */
    F_SET(sp, SC_EX_WAIT_NO);

    return (rval);
}
예제 #2
0
/*
 * ex_script -- : sc[ript][!] [file]
 *	Switch to script mode.
 *
 * PUBLIC: int ex_script __P((SCR *, EXCMD *));
 */
int
ex_script(SCR *sp, EXCMD *cmdp)
{
	/* Vi only command. */
	if (!F_ISSET(sp, SC_VI)) {
		msgq(sp, M_ERR,
		    "150|The script command is only available in vi mode");
		return (1);
	}

	/* Avoid double run. */
	if (F_ISSET(sp, SC_SCRIPT)) {
		msgq(sp, M_ERR,
		    "The script command is already runninng");
		return (1);
	}

	/* We're going to need a shell. */
	if (opts_empty(sp, O_SHELL, 0))
		return (1);

	/* Switch to the new file. */
	if (cmdp->argc != 0 && ex_edit(sp, cmdp))
		return (1);

	/* Create the shell, figure out the prompt. */
	if (sscr_init(sp))
		return (1);

	return (0);
}
/*
 * rcv_tmp --
 *	Build a file name that will be used as the recovery file.
 *
 * PUBLIC: int rcv_tmp __P((SCR *, EXF *, char *));
 */
int
rcv_tmp(SCR *sp, EXF *ep, char *name)
{
	struct stat sb;
	int fd;
	char path[MAXPATHLEN];
	const char *dp;

	/*
	 * !!!
	 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
	 *
	 *
	 * If the recovery directory doesn't exist, try and create it.  As
	 * the recovery files are themselves protected from reading/writing
	 * by other than the owner, the worst that can happen is that a user
	 * would have permission to remove other user's recovery files.  If
	 * the sticky bit has the BSD semantics, that too will be impossible.
	 */
	if (opts_empty(sp, O_RECDIR, 0))
		goto err;
	dp = O_STR(sp, O_RECDIR);
	if (stat(dp, &sb)) {
		if (errno != ENOENT || mkdir(dp, 0)) {
			msgq(sp, M_SYSERR, "%s", dp);
			goto err;
		}
		(void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
	}

	/* Newlines delimit the mail messages. */
	if (strchr(name, '\n')) {
		msgq(sp, M_ERR,
		    "055|Files with newlines in the name are unrecoverable");
		goto err;
	}

	(void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", dp);
	if ((fd = rcv_mktemp(sp, path, dp, S_IRWXU)) == -1)
		goto err;
	(void)close(fd);

	if ((ep->rcv_path = strdup(path)) == NULL) {
		msgq(sp, M_SYSERR, NULL);
		(void)unlink(path);
err:		msgq(sp, M_ERR,
		    "056|Modifications not recoverable if the session fails");
		return (1);
	}

	/* We believe the file is recoverable. */
	F_SET(ep, F_RCV_ON);
	return (0);
}
예제 #4
0
파일: ex_shell.c 프로젝트: jungle0755/minix
/*
 * ex_exec_proc --
 *	Run a separate process.
 *
 * PUBLIC: int ex_exec_proc __P((SCR *, EXCMD *, const char *, const char *, int));
 */
int
ex_exec_proc(SCR *sp, EXCMD *cmdp, const char *cmd, const char *msg, int need_newline)
{
    GS *gp;
    const char *name;
    pid_t pid;

    gp = sp->gp;

    /* We'll need a shell. */
    if (opts_empty(sp, O_SHELL, 0))
        return (1);

    /* Enter ex mode. */
    if (F_ISSET(sp, SC_VI)) {
        if (gp->scr_screen(sp, SC_EX)) {
            ex_wemsg(sp, cmdp->cmd->name, EXM_NOCANON);
            return (1);
        }
        (void)gp->scr_attr(sp, SA_ALTERNATE, 0);
        F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE);
    }

    /* Put out additional newline, message. */
    if (need_newline)
        (void)ex_puts(sp, "\n");
    if (msg != NULL) {
        (void)ex_puts(sp, msg);
        (void)ex_puts(sp, "\n");
    }
    (void)ex_fflush(sp);

    switch (pid = vfork()) {
    case -1:			/* Error. */
        msgq(sp, M_SYSERR, "vfork");
        return (1);
    case 0:				/* Utility. */
        if (gp->scr_child)
            gp->scr_child(sp);
        if ((name = strrchr(O_STR(sp, O_SHELL), '/')) == NULL)
            name = O_STR(sp, O_SHELL);
        else
            ++name;
        execl(O_STR(sp, O_SHELL), name, "-c", cmd, (char *)NULL);
        msgq_str(sp, M_SYSERR, O_STR(sp, O_SHELL), "execl: %s");
        _exit(127);
    /* NOTREACHED */
    default:			/* Parent. */
        return (proc_wait(sp, (long)pid, cmd, 0, 0));
    }
    /* NOTREACHED */
}
예제 #5
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);
}
예제 #6
0
/*
 * 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);
}
예제 #7
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);
}
예제 #8
0
파일: exf.c 프로젝트: fishman/nvi
/*
 * 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);
}
예제 #9
0
파일: exf.c 프로젝트: fishman/nvi
/*
 * 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);
}
/*
 * rcv_read --
 *	Start a recovered file as the file to edit.
 *
 * PUBLIC: int rcv_read __P((SCR *, FREF *));
 */
int
rcv_read(SCR *sp, FREF *frp)
{
	struct dirent *dp;
	struct stat sb;
	DIR *dirp;
	EXF *ep;
	time_t rec_mtime;
	int fd, found, locked = 0, requested, sv_fd;
	char *name, *p, *t, *recp, *pathp;
	const char *rp;
	char file[MAXPATHLEN], path[MAXPATHLEN], recpath[MAXPATHLEN];

	if (opts_empty(sp, O_RECDIR, 0))
		return (1);
	rp = O_STR(sp, O_RECDIR);
	if ((dirp = opendir(rp)) == NULL) {
		msgq_str(sp, M_ERR, rp, "%s");
		return (1);
	}

	name = frp->name;
	sv_fd = -1;
	rec_mtime = 0;
	recp = pathp = NULL;
	for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
		if (strncmp(dp->d_name, "recover.", 8))
			continue;
		(void)snprintf(recpath,
		    sizeof(recpath), "%s/%s", rp, dp->d_name);

		/*
		 * If it's readable, it's recoverable.  It would be very
		 * nice to use stdio(3), but, we can't because that would
		 * require closing and then reopening the file so that we
		 * could have a lock and still close the FP.  Another tip
		 * of the hat to fcntl(2).
		 *
		 * XXX
		 * Should be O_RDONLY, we don't want to write it.  However,
		 * if we're using fcntl(2), there's no way to lock a file
		 * descriptor that's not open for writing.
		 */
		if ((fd = open(recpath, O_RDWR, 0)) == -1)
			continue;

		switch (file_lock(sp, NULL, NULL, fd, 1)) {
		case LOCK_FAILED:
			/*
			 * XXX
			 * Assume that a lock can't be acquired, but that we
			 * should permit recovery anyway.  If this is wrong,
			 * and someone else is using the file, we're going to
			 * die horribly.
			 */
			locked = 0;
			break;
		case LOCK_SUCCESS:
			locked = 1;
			break;
		case LOCK_UNAVAIL:
			/* If it's locked, it's live. */
			(void)close(fd);
			continue;
		}

		/* Check the headers. */
		if (rcv_gets(file, sizeof(file), fd) == NULL ||
		    strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
		    (p = strchr(file, '\n')) == NULL ||
		    rcv_gets(path, sizeof(path), fd) == NULL ||
		    strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
		    (t = strchr(path, '\n')) == NULL) {
			msgq_str(sp, M_ERR, recpath,
			    "067|%s: malformed recovery file");
			goto next;
		}
		*p = *t = '\0';
		++found;

		/*
		 * If the file doesn't exist, it's an orphaned recovery file,
		 * toss it.
		 *
		 * XXX
		 * This can occur if the backup file was deleted and we crashed
		 * before deleting the email file.
		 */
		errno = 0;
		if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
		    errno == ENOENT) {
			(void)unlink(dp->d_name);
			goto next;
		}

		/* Check the file name. */
		if (strcmp(file + sizeof(VI_FHEADER) - 1, name))
			goto next;

		++requested;

		/*
		 * If we've found more than one, take the most recent.
		 *
		 * XXX
		 * Since we're using st_mtime, for portability reasons,
		 * we only get a single second granularity, instead of
		 * getting it right.
		 */
		(void)fstat(fd, &sb);
		if (recp == NULL || rec_mtime < sb.st_mtime) {
			p = recp;
			t = pathp;
			if ((recp = strdup(recpath)) == NULL) {
				msgq(sp, M_SYSERR, NULL);
				recp = p;
				goto next;
			}
			if ((pathp = strdup(path)) == NULL) {
				msgq(sp, M_SYSERR, NULL);
				free(recp);
				recp = p;
				pathp = t;
				goto next;
			}
			if (p != NULL) {
				free(p);
				free(t);
			}
			rec_mtime = sb.st_mtime;
			if (sv_fd != -1)
				(void)close(sv_fd);
			sv_fd = fd;
		} else
next:			(void)close(fd);
	}
	(void)closedir(dirp);

	if (recp == NULL) {
		msgq_str(sp, M_INFO, name,
		    "068|No files named %s, readable by you, to recover");
		return (1);
	}
	if (found) {
		if (requested > 1)
			msgq(sp, M_INFO,
	    "069|There are older versions of this file for you to recover");
		if (found > requested)
			msgq(sp, M_INFO,
			    "070|There are other files for you to recover");
	}

	/*
	 * Create the FREF structure, start the btree file.
	 *
	 * XXX
	 * file_init() is going to set ep->rcv_path.
	 */
	if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) {
		free(recp);
		free(pathp);
		(void)close(sv_fd);
		return (1);
	}

	/*
	 * We keep an open lock on the file so that the recover option can
	 * distinguish between files that are live and those that need to
	 * be recovered.  The lock is already acquired, just copy it.
	 */
	ep = sp->ep;
	ep->rcv_mpath = recp;
	ep->rcv_fd = sv_fd;
	if (!locked)
		F_SET(frp, FR_UNLOCKED);

	/* We believe the file is recoverable. */
	F_SET(ep, F_RCV_ON);
	free(pathp);
	return (0);
}
/*
 *	people making love
 *	never exactly the same
 *	just like a snowflake
 *
 * rcv_list --
 *	List the files that can be recovered by this user.
 *
 * PUBLIC: int rcv_list __P((SCR *));
 */
int
rcv_list(SCR *sp)
{
	struct dirent *dp;
	struct stat sb;
	DIR *dirp;
	FILE *fp;
	int found;
	char *p, *t;
	const char *d;
	char file[MAXPATHLEN], path[MAXPATHLEN];

	/* Open the recovery directory for reading. */
	if (opts_empty(sp, O_RECDIR, 0))
		return (1);
	d = O_STR(sp, O_RECDIR);
	if (chdir(d) || (dirp = opendir(".")) == NULL) {
		msgq_str(sp, M_SYSERR, d, "recdir: %s");
		return (1);
	}

	/* Read the directory. */
	for (found = 0; (dp = readdir(dirp)) != NULL;) {
		if (strncmp(dp->d_name, "recover.", 8))
			continue;

		/*
		 * If it's readable, it's recoverable.
		 *
		 * XXX
		 * Should be "r", we don't want to write the file.  However,
		 * if we're using fcntl(2), there's no way to lock a file
		 * descriptor that's not open for writing.
		 */
		if ((fp = fopen(dp->d_name, "r+")) == NULL)
			continue;

		switch (file_lock(sp, NULL, NULL, fileno(fp), 1)) {
		case LOCK_FAILED:
			/*
			 * XXX
			 * Assume that a lock can't be acquired, but that we
			 * should permit recovery anyway.  If this is wrong,
			 * and someone else is using the file, we're going to
			 * die horribly.
			 */
			break;
		case LOCK_SUCCESS:
			break;
		case LOCK_UNAVAIL:
			/* If it's locked, it's live. */
			(void)fclose(fp);
			continue;
		}

		/* Check the headers. */
		if (fgets(file, sizeof(file), fp) == NULL ||
		    strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
		    (p = strchr(file, '\n')) == NULL ||
		    fgets(path, sizeof(path), fp) == NULL ||
		    strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
		    (t = strchr(path, '\n')) == NULL) {
			msgq_str(sp, M_ERR, dp->d_name,
			    "066|%s: malformed recovery file");
			goto next;
		}
		*p = *t = '\0';

		/*
		 * If the file doesn't exist, it's an orphaned recovery file,
		 * toss it.
		 *
		 * XXX
		 * This can occur if the backup file was deleted and we crashed
		 * before deleting the email file.
		 */
		errno = 0;
		if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
		    errno == ENOENT) {
			(void)unlink(dp->d_name);
			goto next;
		}

		/* Get the last modification time and display. */
		(void)fstat(fileno(fp), &sb);
		(void)printf("%.24s: %s\n",
		    ctime(&sb.st_mtime), file + sizeof(VI_FHEADER) - 1);
		found = 1;

		/* Close, discarding lock. */
next:		(void)fclose(fp);
	}
	if (found == 0)
		(void)printf("%s: No files to recover\n", sp->gp->progname);
	(void)closedir(dirp);
	return (0);
}
/*
 * rcv_mailfile --
 *	Build the file to mail to the user.
 */
static int
rcv_mailfile(SCR *sp, int issync, char *cp_path)
{
	EXF *ep;
	GS *gp;
	struct passwd *pw;
	size_t len;
	time_t now;
	uid_t uid;
	int fd;
	char *p, *t, buf[4096], mpath[MAXPATHLEN];
	const char *dp;
	char *t1, *t2, *t3;

	/*
	 * XXX
	 * MAXHOSTNAMELEN is in various places on various systems, including
	 * <netdb.h> and <sys/socket.h>.  If not found, use a large default.
	 */
#ifndef MAXHOSTNAMELEN
#define	MAXHOSTNAMELEN	1024
#endif
	char host[MAXHOSTNAMELEN];

	gp = sp->gp;
	if ((pw = getpwuid(uid = getuid())) == NULL) {
		msgq(sp, M_ERR,
		    "062|Information on user id %u not found", uid);
		return (1);
	}

	if (opts_empty(sp, O_RECDIR, 0))
		return (1);
	dp = O_STR(sp, O_RECDIR);
	(void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXX", dp);
	if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1)
		return (1);

	/*
	 * XXX
	 * We keep an open lock on the file so that the recover option can
	 * distinguish between files that are live and those that need to
	 * be recovered.  There's an obvious window between the mkstemp call
	 * and the lock, but it's pretty small.
	 */
	ep = sp->ep;
	if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS)
		msgq(sp, M_SYSERR, "063|Unable to lock recovery file");
	if (!issync) {
		/* Save the recover file descriptor, and mail path. */
		ep->rcv_fd = fd;
		if ((ep->rcv_mpath = strdup(mpath)) == NULL) {
			msgq(sp, M_SYSERR, NULL);
			goto err;
		}
		cp_path = ep->rcv_path;
	}

	/*
	 * XXX
	 * We can't use stdio(3) here.  The problem is that we may be using
	 * fcntl(2), so if ANY file descriptor into the file is closed, the
	 * lock is lost.  So, we could never close the FILE *, even if we
	 * dup'd the fd first.
	 */
	t = sp->frp->name;
	if ((p = strrchr(t, '/')) == NULL)
		p = t;
	else
		++p;
	(void)time(&now);
	(void)gethostname(host, sizeof(host));
	len = snprintf(buf, sizeof(buf),
	    "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
	    VI_FHEADER, t,			/* Non-standard. */
	    VI_PHEADER, cp_path,		/* Non-standard. */
	    "Reply-To: root",
	    "From: root (Nvi recovery program)",
	    "To: ", pw->pw_name,
	    "Subject: Nvi saved the file ", p,
	    "Precedence: bulk");		/* For vacation(1). */
	if (len > sizeof(buf) - 1)
		goto lerr;
	if ((size_t)write(fd, buf, len) != len)
		goto werr;

	len = snprintf(buf, sizeof(buf),
	    "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
	    "On ", ctime(&now), ", the user ", pw->pw_name,
	    " was editing a file named ", t, " on the machine ",
	    host, ", when it was saved for recovery. ",
	    "You can recover most, if not all, of the changes ",
	    "to this file using the -r option to ", gp->progname, ":\n\n\t",
	    gp->progname, " -r ", t);
	if (len > sizeof(buf) - 1) {
lerr:		msgq(sp, M_ERR, "064|Recovery file buffer overrun");
		goto err;
	}

	/*
	 * Format the message.  (Yes, I know it's silly.)
	 * Requires that the message end in a <newline>.
	 */
#define	FMTCOLS	60
	for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {
		/* Check for a short length. */
		if (len <= FMTCOLS) {
			t2 = t1 + (len - 1);
			goto wout;
		}

		/* Check for a required <newline>. */
		t2 = strchr(t1, '\n');
		if (t2 - t1 <= FMTCOLS)
			goto wout;

		/* Find the closest space, if any. */
		for (t3 = t2; t2 > t1; --t2)
			if (*t2 == ' ') {
				if (t2 - t1 <= FMTCOLS)
					goto wout;
				t3 = t2;
			}
		t2 = t3;

		/* t2 points to the last character to display. */
wout:		*t2++ = '\n';

		/* t2 points one after the last character to display. */
		if (write(fd, t1, t2 - t1) != t2 - t1)
			goto werr;
	}

	if (issync) {
		rcv_email(sp, mpath);
		if (close(fd)) {
werr:			msgq(sp, M_SYSERR, "065|Recovery file");
			goto err;
		}
	}
	return (0);

err:	if (!issync)
		ep->rcv_fd = -1;
	if (fd != -1)
		(void)close(fd);
	return (1);
}
/*
 * rcv_sync --
 *	Sync the file, optionally:
 *		flagging the backup file to be preserved
 *		snapshotting the backup file and send email to the user
 *		sending email to the user if the file was modified
 *		ending the file session
 *
 * PUBLIC: int rcv_sync __P((SCR *, u_int));
 */
int
rcv_sync(SCR *sp, u_int flags)
{
	EXF *ep;
	int fd, rval;
	char buf[1024];
	const char *dp;

	/* Make sure that there's something to recover/sync. */
	ep = sp->ep;
	if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
		return (0);

	/* Sync the file if it's been modified. */
	if (F_ISSET(ep, F_MODIFIED)) {
		/*
		 * If we are using a db1 version of the database,
		 * we want to sync the underlying btree not the
		 * recno tree which is transient anyway.
		 */
#ifndef R_RECNOSYNC
#define	R_RECNOSYNC 0
#endif
		if (ep->db->sync(ep->db, R_RECNOSYNC)) {
			F_CLR(ep, F_RCV_ON | F_RCV_NORM);
			msgq_str(sp, M_SYSERR,
			    ep->rcv_path, "060|File backup failed: %s");
			return (1);
		}

		/* REQUEST: don't remove backing file on exit. */
		if (LF_ISSET(RCV_PRESERVE))
			F_SET(ep, F_RCV_NORM);

		/* REQUEST: send email. */
		if (LF_ISSET(RCV_EMAIL))
			rcv_email(sp, ep->rcv_mpath);
	}

	/*
	 * !!!
	 * Each time the user exec's :preserve, we have to snapshot all of
	 * the recovery information, i.e. it's like the user re-edited the
	 * file.  We copy the DB(3) backing file, and then create a new mail
	 * recovery file, it's simpler than exiting and reopening all of the
	 * underlying files.
	 *
	 * REQUEST: snapshot the file.
	 */
	rval = 0;
	if (LF_ISSET(RCV_SNAPSHOT)) {
		if (opts_empty(sp, O_RECDIR, 0))
			goto err;
		dp = O_STR(sp, O_RECDIR);
		(void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXX", dp);
		if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1)
			goto err;
		sp->gp->scr_busy(sp,
		    "061|Copying file for recovery...", BUSY_ON);
		if (rcv_copy(sp, fd, ep->rcv_path) ||
		    close(fd) || rcv_mailfile(sp, 1, buf)) {
			(void)unlink(buf);
			(void)close(fd);
			rval = 1;
		}
		sp->gp->scr_busy(sp, NULL, BUSY_OFF);
	}
	if (0) {
err:		rval = 1;
	}

	/* REQUEST: end the file session. */
	if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
		rval = 1;

	return (rval);
}
예제 #14
0
파일: ex_filter.c 프로젝트: fishman/nvi
/*
 * ex_filter --
 *	Run a range of lines through a filter utility and optionally
 *	replace the original text with the stdout/stderr output of
 *	the utility.
 *
 * PUBLIC: int ex_filter __P((SCR *, 
 * PUBLIC:    EXCMD *, MARK *, MARK *, MARK *, CHAR_T *, enum filtertype));
 */
int
ex_filter(SCR *sp, EXCMD *cmdp, MARK *fm, MARK *tm, MARK *rp, CHAR_T *cmd, enum filtertype ftype)
{
	FILE *ifp, *ofp;
	pid_t parent_writer_pid, utility_pid;
	db_recno_t nread;
	int input[2], output[2], rval;
	char *name;
	char *np;
	size_t nlen;

	rval = 0;

	/* Set return cursor position, which is never less than line 1. */
	*rp = *fm;
	if (rp->lno == 0)
		rp->lno = 1;

	/* We're going to need a shell. */
	if (opts_empty(sp, O_SHELL, 0))
		return (1);

	/*
	 * There are three different processes running through this code.
	 * They are the utility, the parent-writer and the parent-reader.
	 * The parent-writer is the process that writes from the file to
	 * the utility, the parent reader is the process that reads from
	 * the utility.
	 *
	 * Input and output are named from the utility's point of view.
	 * The utility reads from input[0] and the parent(s) write to
	 * input[1].  The parent(s) read from output[0] and the utility
	 * writes to output[1].
	 *
	 * !!!
	 * Historically, in the FILTER_READ case, the utility reads from
	 * the terminal (e.g. :r! cat works).  Otherwise open up utility
	 * input pipe.
	 */
	ofp = NULL;
	input[0] = input[1] = output[0] = output[1] = -1;
	if (ftype != FILTER_READ && pipe(input) < 0) {
		msgq(sp, M_SYSERR, "pipe");
		goto err;
	}

	/* Open up utility output pipe. */
	if (pipe(output) < 0) {
		msgq(sp, M_SYSERR, "pipe");
		goto err;
	}
	if ((ofp = fdopen(output[0], "r")) == NULL) {
		msgq(sp, M_SYSERR, "fdopen");
		goto err;
	}

	/* Fork off the utility process. */
	switch (utility_pid = vfork()) {
	case -1:			/* Error. */
		msgq(sp, M_SYSERR, "vfork");
err:		if (input[0] != -1)
			(void)close(input[0]);
		if (input[1] != -1)
			(void)close(input[1]);
		if (ofp != NULL)
			(void)fclose(ofp);
		else if (output[0] != -1)
			(void)close(output[0]);
		if (output[1] != -1)
			(void)close(output[1]);
		return (1);
	case 0:				/* Utility. */
		/*
		 * Redirect stdin from the read end of the input pipe, and
		 * redirect stdout/stderr to the write end of the output pipe.
		 *
		 * !!!
		 * Historically, ex only directed stdout into the input pipe,
		 * letting stderr come out on the terminal as usual.  Vi did
		 * not, directing both stdout and stderr into the input pipe.
		 * We match that practice in both ex and vi for consistency.
		 */
		if (input[0] != -1)
			(void)dup2(input[0], STDIN_FILENO);
		(void)dup2(output[1], STDOUT_FILENO);
		(void)dup2(output[1], STDERR_FILENO);

		/* Close the utility's file descriptors. */
		if (input[0] != -1)
			(void)close(input[0]);
		if (input[1] != -1)
			(void)close(input[1]);
		(void)close(output[0]);
		(void)close(output[1]);

		if ((name = strrchr(O_STR(sp, O_SHELL), '/')) == NULL)
			name = O_STR(sp, O_SHELL);
		else
			++name;

		INT2SYS(sp, cmd, STRLEN(cmd)+1, np, nlen);
		execl(O_STR(sp, O_SHELL), name, "-c", np, (char *)NULL);
		msgq_str(sp, M_SYSERR, O_STR(sp, O_SHELL), "execl: %s");
		_exit (127);
		/* NOTREACHED */
	default:			/* Parent-reader, parent-writer. */
		/* Close the pipe ends neither parent will use. */
		if (input[0] != -1)
			(void)close(input[0]);
		(void)close(output[1]);
		break;
	}

	/*
	 * FILTER_RBANG, FILTER_READ:
	 *
	 * Reading is the simple case -- we don't need a parent writer,
	 * so the parent reads the output from the read end of the output
	 * pipe until it finishes, then waits for the child.  Ex_readfp
	 * appends to the MARK, and closes ofp.
	 *
	 * For FILTER_RBANG, there is nothing to write to the utility.
	 * Make sure it doesn't wait forever by closing its standard
	 * input.
	 *
	 * !!!
	 * Set the return cursor to the last line read in for FILTER_READ.
	 * Historically, this behaves differently from ":r file" command,
	 * which leaves the cursor at the first line read in.  Check to
	 * make sure that it's not past EOF because we were reading into an
	 * empty file.
	 */
	if (ftype == FILTER_RBANG || ftype == FILTER_READ) {
		if (ftype == FILTER_RBANG)
			(void)close(input[1]);

		if (ex_readfp(sp, "filter", ofp, fm, &nread, 1))
			rval = 1;
		sp->rptlines[L_ADDED] += nread;
		if (ftype == FILTER_READ)
			if (fm->lno == 0)
				rp->lno = nread;
			else
				rp->lno += nread;
		goto uwait;
	}

	/*
	 * FILTER_BANG, FILTER_WRITE
	 *
	 * Here we need both a reader and a writer.  Temporary files are
	 * expensive and we'd like to avoid disk I/O.  Using pipes has the
	 * obvious starvation conditions.  It's done as follows:
	 *
	 *	fork
	 *	child
	 *		write lines out
	 *		exit
	 *	parent
	 *		FILTER_BANG:
	 *			read lines into the file
	 *			delete old lines
	 *		FILTER_WRITE
	 *			read and display lines
	 *		wait for child
	 *
	 * XXX
	 * We get away without locking the underlying database because we know
	 * that none of the records that we're reading will be modified until
	 * after we've read them.  This depends on the fact that the current
	 * B+tree implementation doesn't balance pages or similar things when
	 * it inserts new records.  When the DB code has locking, we should
	 * treat vi as if it were multiple applications sharing a database, and
	 * do the required locking.  If necessary a work-around would be to do
	 * explicit locking in the line.c:db_get() code, based on the flag set
	 * here.
	 */
	F_SET(sp->ep, F_MULTILOCK);
	switch (parent_writer_pid = fork()) {
	case -1:			/* Error. */
		msgq(sp, M_SYSERR, "fork");
		(void)close(input[1]);
		(void)close(output[0]);
		rval = 1;
		break;
	case 0:				/* Parent-writer. */
		/*
		 * Write the selected lines to the write end of the input
		 * pipe.  This instance of ifp is closed by ex_writefp.
		 */
		(void)close(output[0]);
		if ((ifp = fdopen(input[1], "w")) == NULL)
			_exit (1);
		_exit(ex_writefp(sp, "filter", ifp, fm, tm, NULL, NULL, 1));

		/* NOTREACHED */
	default:			/* Parent-reader. */
		(void)close(input[1]);
		if (ftype == FILTER_WRITE) {
			/*
			 * Read the output from the read end of the output
			 * pipe and display it.  Filter_ldisplay closes ofp.
			 */
			if (filter_ldisplay(sp, ofp))
				rval = 1;
		} else {
			/*
			 * Read the output from the read end of the output
			 * pipe.  Ex_readfp appends to the MARK and closes
			 * ofp.
			 */
			if (ex_readfp(sp, "filter", ofp, tm, &nread, 1))
				rval = 1;
			sp->rptlines[L_ADDED] += nread;
		}

		/* Wait for the parent-writer. */
		if (proc_wait(sp,
		    (long)parent_writer_pid, "parent-writer", 0, 1))
			rval = 1;

		/* Delete any lines written to the utility. */
		if (rval == 0 && ftype == FILTER_BANG &&
		    (cut(sp, NULL, fm, tm, CUT_LINEMODE) ||
		    del(sp, fm, tm, 1))) {
			rval = 1;
			break;
		}

		/*
		 * If the filter had no output, we may have just deleted
		 * the cursor.  Don't do any real error correction, we'll
		 * try and recover later.
		 */
		 if (rp->lno > 1 && !db_exist(sp, rp->lno))
			--rp->lno;
		break;
	}
	F_CLR(sp->ep, F_MULTILOCK);

	/*
	 * !!!
	 * Ignore errors on vi file reads, to make reads prettier.  It's
	 * completely inconsistent, and historic practice.
	 */
uwait:	INT2CHAR(sp, cmd, STRLEN(cmd) + 1, np, nlen);
	return (proc_wait(sp, (long)utility_pid, np,
	    ftype == FILTER_READ && F_ISSET(sp, SC_VI) ? 1 : 0, 0) || rval);
}