summaryrefslogtreecommitdiff
path: root/usr.bin/vim/fileio.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/vim/fileio.c')
-rw-r--r--usr.bin/vim/fileio.c1991
1 files changed, 1991 insertions, 0 deletions
diff --git a/usr.bin/vim/fileio.c b/usr.bin/vim/fileio.c
new file mode 100644
index 00000000000..db0656cc057
--- /dev/null
+++ b/usr.bin/vim/fileio.c
@@ -0,0 +1,1991 @@
+/* $OpenBSD: fileio.c,v 1.1 1996/09/07 21:40:26 downsj Exp $ */
+/* vi:set ts=4 sw=4:
+ *
+ * VIM - Vi IMproved by Bram Moolenaar
+ *
+ * Do ":help uganda" in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ */
+
+/*
+ * fileio.c: read from and write to a file
+ */
+
+#if defined MSDOS || defined WIN32
+# include <io.h> /* for lseek(), must be before vim.h */
+#endif
+
+#include "vim.h"
+#include "globals.h"
+#include "proto.h"
+#include "option.h"
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif
+
+#ifdef LATTICE
+# include <proto/dos.h> /* for Lock() and UnLock() */
+#endif
+
+#define BUFSIZE 8192 /* size of normal write buffer */
+#define SBUFSIZE 256 /* size of emergency write buffer */
+
+#ifdef VIMINFO
+static void check_marks_read __ARGS((void));
+#endif
+static void msg_add_fname __ARGS((BUF *, char_u *));
+static int msg_add_textmode __ARGS((int));
+static void msg_add_lines __ARGS((int, long, long));
+static int write_buf __ARGS((int, char_u *, int));
+
+static linenr_t write_no_eol_lnum = 0; /* non-zero lnum when last line of
+ next binary write should not have
+ an eol */
+
+ void
+filemess(buf, name, s)
+ BUF *buf;
+ char_u *name;
+ char_u *s;
+{
+ msg_add_fname(buf, name); /* put file name in IObuff with quotes */
+ STRCAT(IObuff, s);
+ /*
+ * For the first message may have to start a new line.
+ * For further ones overwrite the previous one, reset msg_scroll before
+ * calling filemess().
+ */
+ msg_start();
+ msg_outtrans(IObuff);
+ stop_highlight();
+ msg_clr_eos();
+ flushbuf();
+}
+
+/*
+ * Read lines from file 'fname' into the buffer after line 'from'.
+ *
+ * 1. We allocate blocks with lalloc, as big as possible.
+ * 2. Each block is filled with characters from the file with a single read().
+ * 3. The lines are inserted in the buffer with ml_append().
+ *
+ * (caller must check that fname != NULL)
+ *
+ * lines_to_skip is the number of lines that must be skipped
+ * lines_to_read is the number of lines that are appended
+ * When not recovering lines_to_skip is 0 and lines_to_read MAXLNUM.
+ *
+ * return FAIL for failure, OK otherwise
+ */
+ int
+readfile(fname, sfname, from, newfile, lines_to_skip, lines_to_read, filtering)
+ char_u *fname;
+ char_u *sfname;
+ linenr_t from;
+ int newfile;
+ linenr_t lines_to_skip;
+ linenr_t lines_to_read;
+ int filtering;
+{
+ int fd;
+ register char_u c;
+ register linenr_t lnum = from;
+ register char_u *ptr = NULL; /* pointer into read buffer */
+ register char_u *buffer = NULL; /* read buffer */
+ char_u *new_buffer = NULL; /* init to shut up gcc */
+ char_u *line_start = NULL; /* init to shut up gcc */
+ colnr_t len;
+ register long size;
+ register char_u *p;
+ long filesize = 0;
+ int split = 0; /* number of split lines */
+#define UNKNOWN 0x0fffffff /* file size is unknown */
+ linenr_t linecnt = curbuf->b_ml.ml_line_count;
+ int error = FALSE; /* errors encountered */
+ int tx_error = FALSE; /* textmode, but no CR */
+ long linerest = 0; /* remaining characters in line */
+ int firstpart = TRUE; /* reading first part */
+#ifdef UNIX
+ int perm;
+#endif
+ int textmode; /* accept CR-LF linebreak */
+ struct stat st;
+ int file_readonly;
+ linenr_t skip_count = lines_to_skip;
+ linenr_t read_count = lines_to_read;
+ int msg_save = msg_scroll;
+ linenr_t read_no_eol_lnum = 0; /* non-zero lnum when last
+ line of last read was
+ missing the eol */
+
+
+ /*
+ * If there is no file name yet, use the one for the read file.
+ * b_notedited is set to reflect this.
+ * Don't do this for a read from a filter.
+ * Only do this when 'cpoptions' contains the 'f' flag.
+ */
+ if (curbuf->b_filename == NULL && !filtering &&
+ vim_strchr(p_cpo, CPO_FNAMER) != NULL)
+ {
+ if (setfname(fname, sfname, FALSE) == OK)
+ curbuf->b_notedited = TRUE;
+ }
+
+ if (shortmess(SHM_OVER) || curbuf->b_help)
+ msg_scroll = FALSE; /* overwrite previous file message */
+ else
+ msg_scroll = TRUE; /* don't overwrite previous file message */
+ if (sfname == NULL)
+ sfname = fname;
+ /*
+ * For Unix: Use the short filename whenever possible.
+ * Avoids problems with networks and when directory names are changed.
+ * Don't do this for MS-DOS, a "cd" in a sub-shell may have moved us to
+ * another directory, which we don't detect.
+ */
+#if defined(UNIX) || defined(__EMX__)
+ if (!did_cd)
+ fname = sfname;
+#endif
+
+#ifdef UNIX
+ /*
+ * On Unix it is possible to read a directory, so we have to
+ * check for it before the open().
+ */
+ perm = getperm(fname);
+# ifdef _POSIX_SOURCE
+ if (perm >= 0 && !S_ISREG(perm) /* not a regular file ... */
+# ifdef S_ISFIFO
+ && !S_ISFIFO(perm) /* ... or fifo or socket */
+# endif
+ )
+# else
+ if (perm >= 0 && (perm & S_IFMT) != S_IFREG /* not a regular file ... */
+# ifdef S_IFIFO
+ && (perm & S_IFMT) != S_IFIFO /* ... or fifo ... */
+# endif
+# ifdef S_IFSOCK
+ && (perm & S_IFMT) != S_IFSOCK /* ... or socket */
+# endif
+ )
+# endif
+ {
+# ifdef _POSIX_SOURCE
+ if (S_ISDIR(perm))
+# else
+ if ((perm & S_IFMT) == S_IFDIR)
+# endif
+ filemess(curbuf, fname, (char_u *)"is a directory");
+ else
+ filemess(curbuf, fname, (char_u *)"is not a file");
+ msg_scroll = msg_save;
+ return FAIL;
+ }
+#endif
+
+ /*
+ * When opening a new file we take the readonly flag from the file.
+ * Default is r/w, can be set to r/o below.
+ * Don't reset it when in readonly mode
+ */
+ if (newfile && !readonlymode) /* default: set file not readonly */
+ curbuf->b_p_ro = FALSE;
+
+ if (newfile)
+ {
+ if (stat((char *)fname, &st) >= 0) /* remember time of file */
+ {
+ curbuf->b_mtime = st.st_mtime;
+ curbuf->b_mtime_read = st.st_mtime;
+#ifdef UNIX
+ /*
+ * Set the protection bits of the swap file equal to the original
+ * file. This makes it possible for others to read the name of the
+ * original file from the swapfile.
+ */
+ if (curbuf->b_ml.ml_mfp->mf_fname != NULL)
+ (void)setperm(curbuf->b_ml.ml_mfp->mf_fname,
+ (st.st_mode & 0777) | 0600);
+#endif
+ }
+ else
+ {
+ curbuf->b_mtime = 0;
+ curbuf->b_mtime_read = 0;
+ }
+ }
+
+/*
+ * for UNIX: check readonly with perm and access()
+ * for MSDOS and Amiga: check readonly by trying to open the file for writing
+ */
+ file_readonly = FALSE;
+#if defined(UNIX) || defined(DJGPP) || defined(__EMX__)
+ if (
+# ifdef UNIX
+ !(perm & 0222) ||
+# endif
+ access((char *)fname, W_OK))
+ file_readonly = TRUE;
+ fd = open((char *)fname, O_RDONLY | O_EXTRA);
+#else
+ if (!newfile || readonlymode || (fd =
+ open((char *)fname, O_RDWR | O_EXTRA)) < 0)
+ {
+ file_readonly = TRUE;
+ fd = open((char *)fname, O_RDONLY | O_EXTRA); /* try to open ro */
+ }
+#endif
+
+ if (fd < 0) /* cannot open at all */
+ {
+#ifndef UNIX
+ int isdir_f;
+#endif
+ msg_scroll = msg_save;
+#ifndef UNIX
+ /*
+ * On MSDOS and Amiga we can't open a directory, check here.
+ */
+ isdir_f = (mch_isdir(fname));
+ /* replace with short name now, for the messages */
+ if (!did_cd)
+ fname = sfname;
+ if (isdir_f)
+ filemess(curbuf, fname, (char_u *)"is a directory");
+ else
+#endif
+ if (newfile)
+ {
+#ifdef UNIX
+ if (perm < 0)
+#endif
+ {
+ filemess(curbuf, fname, (char_u *)"[New File]");
+#ifdef AUTOCMD
+ apply_autocmds(EVENT_BUFNEWFILE, fname, fname);
+#endif
+ return OK; /* a new file is not an error */
+ }
+#ifdef UNIX
+ else
+ filemess(curbuf, fname, (char_u *)"[Permission Denied]");
+#endif
+ }
+
+ return FAIL;
+ }
+
+ /*
+ * Only set the 'ro' flag for readonly files the first time they are
+ * loaded.
+ * Help files always get readonly mode
+ */
+ if ((newfile && file_readonly) || curbuf->b_help)
+ curbuf->b_p_ro = TRUE;
+
+ if (newfile)
+ curbuf->b_p_eol = TRUE;
+
+#ifndef UNIX
+ /* replace with short name now, for the messages */
+ if (!did_cd)
+ fname = sfname;
+#endif
+ ++no_wait_return; /* don't wait for return yet */
+
+ /*
+ * Set '[ mark to the line above where the lines go (line 1 if zero).
+ */
+ curbuf->b_op_start.lnum = ((from == 0) ? 1 : from);
+ curbuf->b_op_start.col = 0;
+
+#ifdef AUTOCMD
+ {
+ int m = msg_scroll;
+ int n = msg_scrolled;
+
+ /*
+ * The output from the autocommands should not overwrite anything and
+ * should not be overwritten: Set msg_scroll, restore its value if no
+ * output was done.
+ */
+ msg_scroll = TRUE;
+ if (filtering)
+ apply_autocmds(EVENT_FILTERREADPRE, NULL, fname);
+ else if (newfile)
+ apply_autocmds(EVENT_BUFREADPRE, NULL, fname);
+ else
+ apply_autocmds(EVENT_FILEREADPRE, fname, fname);
+ if (msg_scrolled == n)
+ msg_scroll = m;
+ }
+#endif
+
+ if (!recoverymode && !filtering)
+ filemess(curbuf, fname, (char_u *)""); /* show that we are busy */
+
+ msg_scroll = FALSE; /* overwrite the file message */
+
+ /*
+ * Set textmode now, before the "retry" caused by 'textauto' and after the
+ * autocommands, that may reset it.
+ */
+ textmode = curbuf->b_p_tx;
+
+retry:
+ while (!error && !got_int)
+ {
+ /*
+ * We allocate as much space for the file as we can get, plus
+ * space for the old line plus room for one terminating NUL.
+ * The amount is limited by the fact that read() only can read
+ * upto max_unsigned characters (and other things).
+ */
+#if SIZEOF_INT <= 2
+ if (linerest >= 0x7ff0)
+ {
+ ++split;
+ *ptr = NL; /* split line by inserting a NL */
+ size = 1;
+ }
+ else
+#endif
+ {
+#if SIZEOF_INT > 2
+ size = 0x10000L; /* use buffer >= 64K */
+#else
+ size = 0x7ff0L - linerest; /* limit buffer to 32K */
+#endif
+
+ for ( ; size >= 10; size >>= 1)
+ {
+ if ((new_buffer = lalloc((long_u)(size + linerest + 1), FALSE)) != NULL)
+ break;
+ }
+ if (new_buffer == NULL)
+ {
+ do_outofmem_msg();
+ error = TRUE;
+ break;
+ }
+ if (linerest) /* copy characters from the previous buffer */
+ vim_memmove(new_buffer, ptr - linerest, (size_t)linerest);
+ vim_free(buffer);
+ buffer = new_buffer;
+ ptr = buffer + linerest;
+ line_start = buffer;
+
+ if ((size = read(fd, (char *)ptr, (size_t)size)) <= 0)
+ {
+ if (size < 0) /* read error */
+ error = TRUE;
+ break;
+ }
+ filesize += size; /* count the number of characters */
+
+ /*
+ * when reading the first part of a file: guess EOL type
+ */
+ if (firstpart && p_ta)
+ {
+ for (p = ptr; p < ptr + size; ++p)
+ if (*p == NL)
+ {
+ if (p > ptr && p[-1] == CR) /* found CR-NL */
+ textmode = TRUE;
+ else /* found a single NL */
+ textmode = FALSE;
+ /* if editing a new file: may set p_tx */
+ if (newfile)
+ curbuf->b_p_tx = textmode;
+ break;
+ }
+ }
+ }
+
+ firstpart = FALSE;
+
+ /*
+ * This loop is executed once for every character read.
+ * Keep it fast!
+ */
+ --ptr;
+ while (++ptr, --size >= 0)
+ {
+ if ((c = *ptr) != NUL && c != NL) /* catch most common case */
+ continue;
+ if (c == NUL)
+ *ptr = NL; /* NULs are replaced by newlines! */
+ else
+ {
+ if (skip_count == 0)
+ {
+ *ptr = NUL; /* end of line */
+ len = ptr - line_start + 1;
+ if (textmode)
+ {
+ if (ptr[-1] == CR) /* remove CR */
+ {
+ ptr[-1] = NUL;
+ --len;
+ }
+ /*
+ * Reading in textmode, but no CR-LF found!
+ * When 'textauto' set, delete all the lines read so
+ * far and start all over again.
+ * Otherwise give an error message later.
+ */
+ else if (!tx_error)
+ {
+ if (p_ta && lseek(fd, 0L, SEEK_SET) == 0)
+ {
+ while (lnum > from)
+ ml_delete(lnum--, FALSE);
+ textmode = FALSE;
+ if (newfile)
+ curbuf->b_p_tx = FALSE;
+ linerest = 0;
+ filesize = 0;
+ skip_count = lines_to_skip;
+ read_count = lines_to_read;
+ goto retry;
+ }
+ else
+ tx_error = TRUE;
+ }
+ }
+ if (ml_append(lnum, line_start, len, newfile) == FAIL)
+ {
+ error = TRUE;
+ break;
+ }
+ ++lnum;
+ if (--read_count == 0)
+ {
+ error = TRUE; /* break loop */
+ line_start = ptr; /* nothing left to write */
+ break;
+ }
+ }
+ else
+ --skip_count;
+ line_start = ptr + 1;
+ }
+ }
+ linerest = ptr - line_start;
+ mch_breakcheck();
+ }
+
+ /* not an error, max. number of lines reached */
+ if (error && read_count == 0)
+ error = FALSE;
+
+ /*
+ * If we get EOF in the middle of a line, note the fact and
+ * complete the line ourselves.
+ * In textmode ignore a trailing CTRL-Z, unless 'binary' set.
+ */
+ if (!error && !got_int && linerest != 0 &&
+ !(!curbuf->b_p_bin && textmode &&
+ *line_start == Ctrl('Z') && ptr == line_start + 1))
+ {
+ if (newfile) /* remember for when writing */
+ curbuf->b_p_eol = FALSE;
+ *ptr = NUL;
+ if (ml_append(lnum, line_start,
+ (colnr_t)(ptr - line_start + 1), newfile) == FAIL)
+ error = TRUE;
+ else
+ read_no_eol_lnum = ++lnum;
+ }
+ if (lnum != from && !newfile) /* added at least one line */
+ CHANGED;
+
+ close(fd); /* errors are ignored */
+ vim_free(buffer);
+
+ --no_wait_return; /* may wait for return now */
+
+ /* in recovery mode everything but autocommands are skipped */
+ if (!recoverymode)
+ {
+
+ /* need to delete the last line, which comes from the empty buffer */
+ if (newfile && !(curbuf->b_ml.ml_flags & ML_EMPTY))
+ {
+ ml_delete(curbuf->b_ml.ml_line_count, FALSE);
+ --linecnt;
+ }
+ linecnt = curbuf->b_ml.ml_line_count - linecnt;
+ if (filesize == 0)
+ linecnt = 0;
+ if (!newfile)
+ mark_adjust(from + 1, MAXLNUM, (long)linecnt, 0L);
+
+ if (got_int)
+ {
+ filemess(curbuf, fname, e_interr);
+ msg_scroll = msg_save;
+#ifdef VIMINFO
+ check_marks_read();
+#endif /* VIMINFO */
+ return OK; /* an interrupt isn't really an error */
+ }
+
+ if (!filtering)
+ {
+ msg_add_fname(curbuf, fname); /* fname in IObuff with quotes */
+ c = FALSE;
+
+#ifdef UNIX
+# ifdef S_ISFIFO
+ if (S_ISFIFO(perm)) /* fifo or socket */
+ {
+ STRCAT(IObuff, "[fifo/socket]");
+ c = TRUE;
+ }
+# else
+# ifdef S_IFIFO
+ if ((perm & S_IFMT) == S_IFIFO) /* fifo */
+ {
+ STRCAT(IObuff, "[fifo]");
+ c = TRUE;
+ }
+# endif
+# ifdef S_IFSOCK
+ if ((perm & S_IFMT) == S_IFSOCK) /* or socket */
+ {
+ STRCAT(IObuff, "[socket]");
+ c = TRUE;
+ }
+# endif
+# endif
+#endif
+ if (curbuf->b_p_ro)
+ {
+ STRCAT(IObuff, shortmess(SHM_RO) ? "[RO]" : "[readonly]");
+ c = TRUE;
+ }
+ if (read_no_eol_lnum)
+ {
+ STRCAT(IObuff, shortmess(SHM_LAST) ? "[noeol]" :
+ "[Incomplete last line]");
+ c = TRUE;
+ }
+ if (tx_error)
+ {
+ STRCAT(IObuff, "[CR missing]");
+ c = TRUE;
+ }
+ if (split)
+ {
+ STRCAT(IObuff, "[long lines split]");
+ c = TRUE;
+ }
+ if (error)
+ {
+ STRCAT(IObuff, "[READ ERRORS]");
+ c = TRUE;
+ }
+ if (msg_add_textmode(textmode))
+ c = TRUE;
+ msg_add_lines(c, (long)linecnt, filesize);
+ msg_trunc(IObuff);
+ }
+
+ if (error && newfile) /* with errors we should not write the file */
+ curbuf->b_p_ro = TRUE;
+
+ u_clearline(); /* cannot use "U" command after adding lines */
+
+ if (from < curbuf->b_ml.ml_line_count)
+ {
+ curwin->w_cursor.lnum = from + 1; /* cursor at first new line */
+ beginline(TRUE); /* on first non-blank */
+ }
+
+ /*
+ * Set '[ and '] marks to the newly read lines.
+ */
+ curbuf->b_op_start.lnum = from + 1;
+ curbuf->b_op_start.col = 0;
+ curbuf->b_op_end.lnum = from + linecnt;
+ curbuf->b_op_end.col = 0;
+ }
+ msg_scroll = msg_save;
+
+#ifdef AUTOCMD
+ {
+ int m = msg_scroll;
+ int n = msg_scrolled;
+
+ /*
+ * Trick: We remember if the last line of the read didn't have
+ * an eol for when writing it again. This is required for
+ * ":autocmd FileReadPost *.gz set bin|%!gunzip" to work.
+ */
+ write_no_eol_lnum = read_no_eol_lnum;
+
+ /*
+ * The output from the autocommands should not overwrite anything and
+ * should not be overwritten: Set msg_scroll, restore its value if no
+ * output was done.
+ */
+ msg_scroll = TRUE;
+ if (filtering)
+ apply_autocmds(EVENT_FILTERREADPOST, NULL, fname);
+ else if (newfile)
+ apply_autocmds(EVENT_BUFREADPOST, NULL, fname);
+ else
+ apply_autocmds(EVENT_FILEREADPOST, fname, fname);
+ if (msg_scrolled == n)
+ msg_scroll = m;
+
+ write_no_eol_lnum = 0;
+ }
+#endif
+
+#ifdef VIMINFO
+ check_marks_read();
+#endif /* VIMINFO */
+
+ if (recoverymode && error)
+ return FAIL;
+ return OK;
+}
+
+#ifdef VIMINFO
+ static void
+check_marks_read()
+{
+ if (!curbuf->b_marks_read && get_viminfo_parameter('\'') > 0)
+ {
+ read_viminfo(NULL, FALSE, TRUE, FALSE);
+ curbuf->b_marks_read = TRUE;
+ }
+}
+#endif /* VIMINFO */
+
+/*
+ * writeit - write to file 'fname' lines 'start' through 'end'
+ *
+ * We do our own buffering here because fwrite() is so slow.
+ *
+ * If forceit is true, we don't care for errors when attempting backups (jw).
+ * In case of an error everything possible is done to restore the original file.
+ * But when forceit is TRUE, we risk loosing it.
+ * When reset_changed is TRUE and start == 1 and end ==
+ * curbuf->b_ml.ml_line_count, reset curbuf->b_changed.
+ *
+ * This function must NOT use NameBuff (because it's called by autowrite()).
+ *
+ * return FAIL for failure, OK otherwise
+ */
+ int
+buf_write(buf, fname, sfname, start, end, append, forceit,
+ reset_changed, filtering)
+ BUF *buf;
+ char_u *fname;
+ char_u *sfname;
+ linenr_t start, end;
+ int append;
+ int forceit;
+ int reset_changed;
+ int filtering;
+{
+ int fd;
+ char_u *backup = NULL;
+ char_u *ffname;
+#ifdef AUTOCMD
+ BUF *save_buf;
+#endif
+ register char_u *s;
+ register char_u *ptr;
+ register char_u c;
+ register int len;
+ register linenr_t lnum;
+ long nchars;
+ char_u *errmsg = NULL;
+ char_u *buffer;
+ char_u smallbuf[SBUFSIZE];
+ char_u *backup_ext;
+ int bufsize;
+ long perm = -1; /* file permissions */
+ int retval = OK;
+ int newfile = FALSE; /* TRUE if file doesn't exist yet */
+ int msg_save = msg_scroll;
+ int overwriting; /* TRUE if writing over original */
+#if defined(UNIX) || defined(__EMX__XX) /*XXX fix me sometime? */
+ struct stat st_old;
+ int made_writable = FALSE; /* 'w' bit has been set */
+#endif
+#ifdef AMIGA
+ BPTR flock;
+#endif
+ /* writing everything */
+ int whole = (start == 1 && end == buf->b_ml.ml_line_count);
+#ifdef AUTOCMD
+ linenr_t old_line_count = buf->b_ml.ml_line_count;
+#endif
+
+ if (fname == NULL || *fname == NUL) /* safety check */
+ return FAIL;
+
+ /*
+ * If there is no file name yet, use the one for the written file.
+ * b_notedited is set to reflect this (in case the write fails).
+ * Don't do this when the write is for a filter command.
+ * Only do this when 'cpoptions' contains the 'f' flag.
+ */
+ if (reset_changed && whole && buf == curbuf &&
+ curbuf->b_filename == NULL && !filtering &&
+ vim_strchr(p_cpo, CPO_FNAMEW) != NULL)
+ {
+ if (setfname(fname, sfname, FALSE) == OK)
+ curbuf->b_notedited = TRUE;
+ }
+
+ if (sfname == NULL)
+ sfname = fname;
+ /*
+ * For Unix: Use the short filename whenever possible.
+ * Avoids problems with networks and when directory names are changed.
+ * Don't do this for MS-DOS, a "cd" in a sub-shell may have moved us to
+ * another directory, which we don't detect
+ */
+ ffname = fname; /* remember full fname */
+#ifdef UNIX
+ if (!did_cd)
+ fname = sfname;
+#endif
+
+ /* make sure we have a valid backup extension to use */
+ if (*p_bex == NUL)
+ backup_ext = (char_u *)".bak";
+ else
+ backup_ext = p_bex;
+
+ if (buf->b_filename != NULL && fnamecmp(ffname, buf->b_filename) == 0)
+ overwriting = TRUE;
+ else
+ overwriting = FALSE;
+
+ /*
+ * Disallow writing from .exrc and .vimrc in current directory for
+ * security reasons.
+ */
+ if (secure)
+ {
+ secure = 2;
+ emsg(e_curdir);
+ return FAIL;
+ }
+
+ if (exiting)
+ settmode(0); /* when exiting allow typahead now */
+
+ ++no_wait_return; /* don't wait for return yet */
+
+ /*
+ * Set '[ and '] marks to the lines to be written.
+ */
+ buf->b_op_start.lnum = start;
+ buf->b_op_start.col = 0;
+ buf->b_op_end.lnum = end;
+ buf->b_op_end.col = 0;
+
+#ifdef AUTOCMD
+ /*
+ * Apply PRE aucocommands.
+ * Careful: The autocommands may call buf_write() recursively!
+ */
+ save_buf = curbuf;
+ curbuf = buf;
+ curwin->w_buffer = buf;
+ if (append)
+ apply_autocmds(EVENT_FILEAPPENDPRE, fname, fname);
+ else if (filtering)
+ apply_autocmds(EVENT_FILTERWRITEPRE, NULL, fname);
+ else if (reset_changed && whole)
+ apply_autocmds(EVENT_BUFWRITEPRE, fname, fname);
+ else
+ apply_autocmds(EVENT_FILEWRITEPRE, fname, fname);
+ curbuf = save_buf;
+ curwin->w_buffer = save_buf;
+
+ /*
+ * The autocommands may have changed the number of lines in the file.
+ * When writing the whole file, adjust the end.
+ * When writing part of the file, assume that the autocommands only
+ * changed the number of lines that are to be written (tricky!).
+ */
+ if (buf->b_ml.ml_line_count != old_line_count)
+ {
+ if (whole) /* writing all */
+ end = buf->b_ml.ml_line_count;
+ else if (buf->b_ml.ml_line_count > old_line_count) /* more lines */
+ end += buf->b_ml.ml_line_count - old_line_count;
+ else /* less lines */
+ {
+ end -= old_line_count - buf->b_ml.ml_line_count;
+ if (end < start)
+ {
+ --no_wait_return;
+ EMSG("Autocommand changed number of lines in unexpected way");
+ return FAIL;
+ }
+ }
+ }
+#endif
+
+ if (shortmess(SHM_OVER))
+ msg_scroll = FALSE; /* overwrite previous file message */
+ else
+ msg_scroll = TRUE; /* don't overwrite previous file message */
+ if (!filtering)
+ filemess(buf,
+#ifndef UNIX
+ did_cd ? fname : sfname,
+#else
+ fname,
+#endif
+ (char_u *)""); /* show that we are busy */
+ msg_scroll = FALSE; /* always overwrite the file message now */
+
+ buffer = alloc(BUFSIZE);
+ if (buffer == NULL) /* can't allocate big buffer, use small
+ * one (to be able to write when out of
+ * memory) */
+ {
+ buffer = smallbuf;
+ bufsize = SBUFSIZE;
+ }
+ else
+ bufsize = BUFSIZE;
+
+#if defined(UNIX) && !defined(ARCHIE)
+ /* get information about original file (if there is one) */
+ st_old.st_dev = st_old.st_ino = 0;
+ if (stat((char *)fname, &st_old))
+ newfile = TRUE;
+ else
+ {
+#ifdef _POSIX_SOURCE
+ if (!S_ISREG(st_old.st_mode)) /* not a file */
+#else
+ if ((st_old.st_mode & S_IFMT) != S_IFREG) /* not a file */
+#endif
+ {
+#ifdef _POSIX_SOURCE
+ if (S_ISDIR(st_old.st_mode))
+#else
+ if ((st_old.st_mode & S_IFMT) == S_IFDIR)
+#endif
+ errmsg = (char_u *)"is a directory";
+ else
+ errmsg = (char_u *)"is not a file";
+ goto fail;
+ }
+ if (buf->b_mtime_read != 0 &&
+ buf->b_mtime_read != st_old.st_mtime && overwriting)
+ {
+ msg_scroll = TRUE; /* don't overwrite messages here */
+ (void)set_highlight('e'); /* set highlight for error messages */
+ msg_highlight = TRUE;
+ /* don't use emsg() here, don't want to flush the buffers */
+ MSG("WARNING: The file has been changed since reading it!!!");
+ if (ask_yesno((char_u *)"Do you really want to write to it",
+ TRUE) == 'n')
+ {
+ retval = FAIL;
+ goto fail;
+ }
+ msg_scroll = FALSE; /* always overwrite the file message now */
+ }
+ perm = st_old.st_mode;
+ }
+/*
+ * If we are not appending, the file exists, and the 'writebackup', 'backup'
+ * or 'patchmode' option is set, try to make a backup copy of the file.
+ */
+ if (!append && perm >= 0 && (p_wb || p_bk || *p_pm != NUL) &&
+ (fd = open((char *)fname, O_RDONLY | O_EXTRA)) >= 0)
+ {
+ int bfd, buflen;
+ char_u copybuf[BUFSIZE + 1], *wp;
+ int some_error = FALSE;
+ struct stat st_new;
+ char_u *dirp;
+#ifndef SHORT_FNAME
+ int did_set_shortname;
+#endif
+
+ /*
+ * Try to make the backup in each directory in the 'bdir' option.
+ *
+ * Unix semantics has it, that we may have a writable file,
+ * that cannot be recreated with a simple open(..., O_CREAT, ) e.g:
+ * - the directory is not writable,
+ * - the file may be a symbolic link,
+ * - the file may belong to another user/group, etc.
+ *
+ * For these reasons, the existing writable file must be truncated
+ * and reused. Creation of a backup COPY will be attempted.
+ */
+ dirp = p_bdir;
+ while (*dirp)
+ {
+ st_new.st_dev = st_new.st_ino = 0;
+ st_new.st_gid = 0;
+
+ /*
+ * Isolate one directory name.
+ */
+ len = copy_option_part(&dirp, copybuf, BUFSIZE, ",");
+
+ if (*copybuf == '.') /* use same dir as file */
+ STRCPY(copybuf, fname);
+ else /* use dir from 'bdir' option */
+ {
+ if (!ispathsep(copybuf[len - 1]))
+ copybuf[len++] = PATHSEP;
+ STRCPY(copybuf + len, gettail(fname));
+ }
+
+#ifndef SHORT_FNAME
+ did_set_shortname = FALSE;
+#endif
+
+ /*
+ * May try twice if 'shortname' not set.
+ */
+ for (;;)
+ {
+ /*
+ * Make backup file name.
+ */
+ backup = buf_modname(buf, copybuf, backup_ext);
+ if (backup == NULL)
+ {
+ some_error = TRUE; /* out of memory */
+ goto nobackup;
+ }
+
+ /*
+ * Check if backup file already exists.
+ */
+ if (!stat((char *)backup, &st_new))
+ {
+ /*
+ * Check if backup file is same as original file.
+ * May happen when modname gave the same file back.
+ * E.g. silly link, or filename-length reached.
+ * If we don't check here, we either ruin the file when
+ * copying or erase it after writing. jw.
+ */
+ if (st_new.st_dev == st_old.st_dev &&
+ st_new.st_ino == st_old.st_ino)
+ {
+ vim_free(backup);
+ backup = NULL; /* there is no backup file to delete */
+#ifndef SHORT_FNAME
+ /*
+ * may try again with 'shortname' set
+ */
+ if (!(buf->b_shortname || buf->b_p_sn))
+ {
+ buf->b_shortname = TRUE;
+ did_set_shortname = TRUE;
+ continue;
+ }
+ /* setting shortname didn't help */
+ if (did_set_shortname)
+ buf->b_shortname = FALSE;
+#endif
+ break;
+ }
+
+ /*
+ * If we are not going to keep the backup file, don't
+ * delete an existing one, try to use another name.
+ * Change one character, just before the extension.
+ */
+ if (!p_bk)
+ {
+ wp = backup + STRLEN(backup) - 1 - STRLEN(backup_ext);
+ if (wp < backup) /* empty file name ??? */
+ wp = backup;
+ *wp = 'z';
+ while (*wp > 'a' && !stat((char *)backup, &st_new))
+ --*wp;
+ /* They all exist??? Must be something wrong. */
+ if (*wp == 'a')
+ {
+ vim_free(backup);
+ backup = NULL;
+ }
+ }
+ }
+ break;
+ }
+
+ /*
+ * Try to create the backup file
+ */
+ if (backup != NULL)
+ {
+ /* remove old backup, if present */
+ vim_remove(backup);
+ bfd = open((char *)backup, O_WRONLY | O_CREAT | O_EXTRA, 0666);
+ if (bfd < 0)
+ {
+ vim_free(backup);
+ backup = NULL;
+ }
+ else
+ {
+ /* set file protection same as original file, but strip
+ * s-bit */
+ (void)setperm(backup, perm & 0777);
+
+ /*
+ * Try to set the group of the backup same as the original
+ * file. If this fails, set the protection bits for the
+ * group same as the protection bits for others.
+ */
+ if (st_new.st_gid != st_old.st_gid &&
+#ifdef HAVE_FCHOWN /* sequent-ptx lacks fchown() */
+ fchown(bfd, -1, st_old.st_gid) != 0)
+#else
+ chown(backup, -1, st_old.st_gid) != 0)
+#endif
+ setperm(backup, (perm & 0707) | ((perm & 07) << 3));
+
+ /* copy the file. */
+ while ((buflen = read(fd, (char *)copybuf, BUFSIZE)) > 0)
+ {
+ if (write_buf(bfd, copybuf, buflen) == FAIL)
+ {
+ errmsg = (char_u *)"Can't write to backup file (use ! to override)";
+ break;
+ }
+ }
+ if (close(bfd) < 0 && errmsg == NULL)
+ errmsg = (char_u *)"Close error for backup file (use ! to override)";
+ if (buflen < 0)
+ errmsg = (char_u *)"Can't read file for backup (use ! to override)";
+ break;
+ }
+ }
+ }
+nobackup:
+ close(fd); /* ignore errors for closing read file */
+
+ if (backup == NULL && errmsg == NULL)
+ errmsg = (char_u *)"Cannot create backup file (use ! to override)";
+ /* ignore errors when forceit is TRUE */
+ if ((some_error || errmsg) && !forceit)
+ {
+ retval = FAIL;
+ goto fail;
+ }
+ errmsg = NULL;
+ }
+ /* When using ":w!" and the file was read-only: make it writable */
+ if (forceit && (st_old.st_uid == getuid()) && perm >= 0 && !(perm & 0200))
+ {
+ perm |= 0200;
+ (void)setperm(fname, perm);
+ made_writable = TRUE;
+ }
+
+#else /* end of UNIX, start of the rest */
+
+/*
+ * If we are not appending, the file exists, and the 'writebackup' or
+ * 'backup' option is set, make a backup.
+ * Do not make any backup, if "writebackup" and "backup" are
+ * both switched off. This helps when editing large files on
+ * almost-full disks. (jw)
+ */
+ perm = getperm(fname);
+ if (perm < 0)
+ newfile = TRUE;
+ else if (mch_isdir(fname))
+ {
+ errmsg = (char_u *)"is a directory";
+ goto fail;
+ }
+ if (!append && perm >= 0 && (p_wb || p_bk || *p_pm != NUL))
+ {
+ char_u *dirp;
+ char_u *p;
+
+ /*
+ * Form the backup file name - change path/fo.o.h to path/fo.o.h.bak
+ * Try all directories in 'backupdir', first one that works is used.
+ */
+ dirp = p_bdir;
+ while (*dirp)
+ {
+ /*
+ * Isolate one directory name.
+ */
+ len = copy_option_part(&dirp, IObuff, IOSIZE, ",");
+
+#ifdef VMS
+ if (!memcmp(IObuff, "sys$disk:", 9))
+#else
+ if (*IObuff == '.') /* use same dir as file */
+#endif
+ backup = buf_modname(buf, fname, backup_ext);
+ else /* use dir from 'bdir' option */
+ {
+ if (!ispathsep(IObuff[len - 1]))
+ IObuff[len++] = PATHSEP;
+ STRCPY(IObuff + len, gettail(fname));
+ backup = buf_modname(buf, IObuff, backup_ext);
+ }
+ if (backup != NULL)
+ {
+ /*
+ * If we are not going to keep the backup file, don't
+ * delete an existing one, try to use another name.
+ * Change one character, just before the extension.
+ */
+ if (!p_bk && getperm(backup) >= 0)
+ {
+ p = backup + STRLEN(backup) - 1 - STRLEN(backup_ext);
+ if (p < backup) /* empty file name ??? */
+ p = backup;
+ *p = 'z';
+ while (*p > 'a' && getperm(backup) >= 0)
+ --*p;
+ /* They all exist??? Must be something wrong! */
+ if (*p == 'a')
+ {
+ vim_free(backup);
+ backup = NULL;
+ }
+ }
+ }
+ if (backup != NULL)
+ {
+
+ /*
+ * Delete any existing backup and move the current version to
+ * the backup. For safety, we don't remove the backup until
+ * the write has finished successfully. And if the 'backup'
+ * option is set, leave it around.
+ */
+#ifdef AMIGA
+ /*
+ * With MSDOS-compatible filesystems (crossdos, messydos) it is
+ * possible that the name of the backup file is the same as the
+ * original file. To avoid the chance of accidently deleting the
+ * original file (horror!) we lock it during the remove.
+ * This should not happen with ":w", because startscript()
+ * should detect this problem and set buf->b_shortname,
+ * causing modname to return a correct ".bak" filename. This
+ * problem does exist with ":w filename", but then the
+ * original file will be somewhere else so the backup isn't
+ * really important. If autoscripting is off the rename may
+ * fail.
+ */
+ flock = Lock((UBYTE *)fname, (long)ACCESS_READ);
+#endif
+ vim_remove(backup);
+#ifdef AMIGA
+ if (flock)
+ UnLock(flock);
+#endif
+ /*
+ * If the renaming of the original file to the backup file
+ * works, quit here.
+ */
+ if (vim_rename(fname, backup) == 0)
+ break;
+
+ vim_free(backup); /* don't do the rename below */
+ backup = NULL;
+ }
+ }
+ if (backup == NULL && !forceit)
+ {
+ errmsg = (char_u *)"Can't make backup file (use ! to override)";
+ goto fail;
+ }
+ }
+#endif /* UNIX */
+
+ /* When using ":w!" and writing to the current file, readonly makes no
+ * sense, reset it */
+ if (forceit && overwriting)
+ buf->b_p_ro = FALSE;
+
+ /*
+ * If the original file is being overwritten, there is a small chance that
+ * we crash in the middle of writing. Therefore the file is preserved now.
+ * This makes all block numbers positive so that recovery does not need
+ * the original file.
+ * Don't do this if there is a backup file and we are exiting.
+ */
+ if (reset_changed && !newfile && !otherfile(ffname) &&
+ !(exiting && backup != NULL))
+ ml_preserve(buf, FALSE);
+
+ /*
+ * We may try to open the file twice: If we can't write to the
+ * file and forceit is TRUE we delete the existing file and try to create
+ * a new one. If this still fails we may have lost the original file!
+ * (this may happen when the user reached his quotum for number of files).
+ * Appending will fail if the file does not exist and forceit is FALSE.
+ */
+ while ((fd = open((char *)fname, O_WRONLY | O_EXTRA | (append ?
+ (forceit ? (O_APPEND | O_CREAT) : O_APPEND) :
+ (O_CREAT | O_TRUNC)), 0666)) < 0)
+ {
+ /*
+ * A forced write will try to create a new file if the old one is
+ * still readonly. This may also happen when the directory is
+ * read-only. In that case the vim_remove() will fail.
+ */
+ if (!errmsg)
+ {
+ errmsg = (char_u *)"Can't open file for writing";
+ if (forceit)
+ {
+#ifdef UNIX
+ /* we write to the file, thus it should be marked
+ writable after all */
+ perm |= 0200;
+ made_writable = TRUE;
+ if (st_old.st_uid != getuid() || st_old.st_gid != getgid())
+ perm &= 0777;
+#endif /* UNIX */
+ if (!append) /* don't remove when appending */
+ vim_remove(fname);
+ continue;
+ }
+ }
+/*
+ * If we failed to open the file, we don't need a backup. Throw it away.
+ * If we moved or removed the original file try to put the backup in its place.
+ */
+ if (backup != NULL)
+ {
+#ifdef UNIX
+ struct stat st;
+
+ /*
+ * There is a small chance that we removed the original, try
+ * to move the copy in its place.
+ * This may not work if the vim_rename() fails.
+ * In that case we leave the copy around.
+ */
+ /* file does not exist */
+ if (stat((char *)fname, &st) < 0)
+ /* put the copy in its place */
+ vim_rename(backup, fname);
+ /* original file does exist */
+ if (stat((char *)fname, &st) >= 0)
+ vim_remove(backup); /* throw away the copy */
+#else
+ /* try to put the original file back */
+ vim_rename(backup, fname);
+#endif
+ }
+ goto fail;
+ }
+ errmsg = NULL;
+
+ if (end > buf->b_ml.ml_line_count)
+ end = buf->b_ml.ml_line_count;
+ len = 0;
+ s = buffer;
+ nchars = 0;
+ if (buf->b_ml.ml_flags & ML_EMPTY)
+ start = end + 1;
+ for (lnum = start; lnum <= end; ++lnum)
+ {
+ /*
+ * The next while loop is done once for each character written.
+ * Keep it fast!
+ */
+ ptr = ml_get_buf(buf, lnum, FALSE) - 1;
+ while ((c = *++ptr) != NUL)
+ {
+ if (c == NL)
+ *s = NUL; /* replace newlines with NULs */
+ else
+ *s = c;
+ ++s;
+ if (++len != bufsize)
+ continue;
+ if (write_buf(fd, buffer, bufsize) == FAIL)
+ {
+ end = 0; /* write error: break loop */
+ break;
+ }
+ nchars += bufsize;
+ s = buffer;
+ len = 0;
+ }
+ /* write failed or last line has no EOL: stop here */
+ if (end == 0 || (lnum == end && buf->b_p_bin &&
+ (lnum == write_no_eol_lnum ||
+ (lnum == buf->b_ml.ml_line_count && !buf->b_p_eol))))
+ break;
+ if (buf->b_p_tx) /* write CR-NL */
+ {
+ *s = CR;
+ ++s;
+ if (++len == bufsize)
+ {
+ if (write_buf(fd, buffer, bufsize) == FAIL)
+ {
+ end = 0; /* write error: break loop */
+ break;
+ }
+ nchars += bufsize;
+ s = buffer;
+ len = 0;
+ }
+ }
+ *s = NL;
+ ++s;
+ if (++len == bufsize && end)
+ {
+ if (write_buf(fd, buffer, bufsize) == FAIL)
+ {
+ end = 0; /* write error: break loop */
+ break;
+ }
+ nchars += bufsize;
+ s = buffer;
+ len = 0;
+ }
+ }
+ if (len && end)
+ {
+ if (write_buf(fd, buffer, len) == FAIL)
+ end = 0; /* write error */
+ nchars += len;
+ }
+
+ if (close(fd) != 0)
+ {
+ errmsg = (char_u *)"Close failed";
+ goto fail;
+ }
+#ifdef UNIX
+ if (made_writable)
+ perm &= ~0200; /* reset 'w' bit for security reasons */
+#endif
+ if (perm >= 0)
+ (void)setperm(fname, perm); /* set permissions of new file same as old file */
+
+ if (end == 0)
+ {
+ errmsg = (char_u *)"write error (file system full?)";
+ /*
+ * If we have a backup file, try to put it in place of the new file,
+ * because it is probably corrupt. This avoids loosing the original
+ * file when trying to make a backup when writing the file a second
+ * time.
+ * For unix this means copying the backup over the new file.
+ * For others this means renaming the backup file.
+ * If this is OK, don't give the extra warning message.
+ */
+ if (backup != NULL)
+ {
+#ifdef UNIX
+ char_u copybuf[BUFSIZE + 1];
+ int bfd, buflen;
+
+ if ((bfd = open((char *)backup, O_RDONLY | O_EXTRA)) >= 0)
+ {
+ if ((fd = open((char *)fname,
+ O_WRONLY | O_CREAT | O_TRUNC | O_EXTRA, 0666)) >= 0)
+ {
+ /* copy the file. */
+ while ((buflen = read(bfd, (char *)copybuf, BUFSIZE)) > 0)
+ if (write_buf(fd, copybuf, buflen) == FAIL)
+ break;
+ if (close(fd) >= 0 && buflen == 0) /* success */
+ end = 1;
+ }
+ close(bfd); /* ignore errors for closing read file */
+ }
+#else
+ if (vim_rename(backup, fname) == 0)
+ end = 1;
+#endif
+ }
+ goto fail;
+ }
+
+ lnum -= start; /* compute number of written lines */
+ --no_wait_return; /* may wait for return now */
+
+#ifndef UNIX
+ /* use shortname now, for the messages */
+ if (!did_cd)
+ fname = sfname;
+#endif
+ if (!filtering)
+ {
+ msg_add_fname(buf, fname); /* put fname in IObuff with quotes */
+ c = FALSE;
+ if (newfile)
+ {
+ STRCAT(IObuff, shortmess(SHM_NEW) ? "[New]" : "[New File]");
+ c = TRUE;
+ }
+ if (msg_add_textmode(buf->b_p_tx)) /* may add [textmode] */
+ c = TRUE;
+ msg_add_lines(c, (long)lnum, nchars); /* add line/char count */
+ if (!shortmess(SHM_WRITE))
+ STRCAT(IObuff, shortmess(SHM_WRI) ? " [w]" : " written");
+
+ msg_trunc(IObuff);
+ }
+
+ if (reset_changed && whole) /* when written everything */
+ {
+ UNCHANGED(buf);
+ u_unchanged(buf);
+ }
+
+ /*
+ * If written to the current file, update the timestamp of the swap file
+ * and reset the 'notedited' flag. Also sets buf->b_mtime.
+ */
+ if (!exiting && overwriting)
+ {
+ ml_timestamp(buf);
+ buf->b_notedited = FALSE;
+ }
+
+ /*
+ * If we kept a backup until now, and we are in patch mode, then we make
+ * the backup file our 'original' file.
+ */
+ if (*p_pm)
+ {
+ char *org = (char *)buf_modname(buf, fname, p_pm);
+
+ if (backup != NULL)
+ {
+ struct stat st;
+
+ /*
+ * If the original file does not exist yet
+ * the current backup file becomes the original file
+ */
+ if (org == NULL)
+ EMSG("patchmode: can't save original file");
+ else if (stat(org, &st) < 0)
+ {
+ vim_rename(backup, (char_u *)org);
+ vim_free(backup); /* don't delete the file */
+ backup = NULL;
+ }
+ }
+ /*
+ * If there is no backup file, remember that a (new) file was
+ * created.
+ */
+ else
+ {
+ int empty_fd;
+
+ if (org == NULL || (empty_fd =
+ open(org, O_CREAT | O_EXTRA, 0666)) < 0)
+ EMSG("patchmode: can't touch empty original file");
+ else
+ close(empty_fd);
+ }
+ if (org != NULL)
+ {
+ setperm((char_u *)org, getperm(fname) & 0777);
+ vim_free(org);
+ }
+ }
+
+ /*
+ * Remove the backup unless 'backup' option is set
+ */
+ if (!p_bk && backup != NULL && vim_remove(backup) != 0)
+ EMSG("Can't delete backup file");
+
+ goto nofail;
+
+fail:
+ --no_wait_return; /* may wait for return now */
+nofail:
+
+ vim_free(backup);
+ if (buffer != smallbuf)
+ vim_free(buffer);
+
+ if (errmsg != NULL)
+ {
+ /* can't use emsg() here, do something alike */
+ if (p_eb)
+ beep_flush(); /* also includes flush_buffers() */
+ else
+ flush_buffers(FALSE); /* flush internal buffers */
+ (void)set_highlight('e'); /* set highlight mode for error messages */
+ start_highlight();
+ filemess(buf,
+#ifndef UNIX
+ did_cd ? fname : sfname,
+#else
+ fname,
+#endif
+ errmsg);
+ retval = FAIL;
+ if (end == 0)
+ {
+ MSG_OUTSTR("\nWARNING: Original file may be lost or damaged\n");
+ MSG_OUTSTR("don't quit the editor until the file is sucessfully written!");
+ }
+ }
+ msg_scroll = msg_save;
+
+#ifdef AUTOCMD
+ /*
+ * Apply POST aucocommands.
+ * Careful: The autocommands may call buf_write() recursively!
+ */
+ save_buf = curbuf;
+ curbuf = buf;
+ curwin->w_buffer = buf;
+ if (append)
+ apply_autocmds(EVENT_FILEAPPENDPOST, fname, fname);
+ else if (filtering)
+ apply_autocmds(EVENT_FILTERWRITEPOST, NULL, fname);
+ else if (reset_changed && whole)
+ apply_autocmds(EVENT_BUFWRITEPOST, fname, fname);
+ else
+ apply_autocmds(EVENT_FILEWRITEPOST, fname, fname);
+ curbuf = save_buf;
+ curwin->w_buffer = save_buf;
+#endif
+
+ return retval;
+}
+
+/*
+ * Put file name into IObuff with quotes.
+ */
+ static void
+msg_add_fname(buf, fname)
+ BUF *buf;
+ char_u *fname;
+{
+ /* careful: home_replace calls vim_getenv(), which also uses IObuff! */
+ home_replace(buf, fname, IObuff + 1, IOSIZE - 1);
+ IObuff[0] = '"';
+ STRCAT(IObuff, "\" ");
+}
+
+/*
+ * Append message for text mode to IObuff.
+ * Return TRUE if something appended.
+ */
+ static int
+msg_add_textmode(textmode)
+ int textmode;
+{
+#ifdef USE_CRNL
+ if (!textmode)
+ {
+ STRCAT(IObuff, shortmess(SHM_TEXT) ? "[notx]" : "[notextmode]");
+ return TRUE;
+ }
+#else
+ if (textmode)
+ {
+ STRCAT(IObuff, shortmess(SHM_TEXT) ? "[tx]" : "[textmode]");
+ return TRUE;
+ }
+#endif
+ return FALSE;
+}
+
+/*
+ * Append line and character count to IObuff.
+ */
+ static void
+msg_add_lines(insert_space, lnum, nchars)
+ int insert_space;
+ long lnum;
+ long nchars;
+{
+ char_u *p;
+
+ p = IObuff + STRLEN(IObuff);
+
+ if (insert_space)
+ *p++ = ' ';
+ if (shortmess(SHM_LINES))
+ sprintf((char *)p, "%ldL, %ldC", lnum, nchars);
+ else
+ sprintf((char *)p, "%ld line%s, %ld character%s",
+ lnum, plural(lnum),
+ nchars, plural(nchars));
+}
+
+/*
+ * write_buf: call write() to write a buffer
+ *
+ * return FAIL for failure, OK otherwise
+ */
+ static int
+write_buf(fd, buf, len)
+ int fd;
+ char_u *buf;
+ int len;
+{
+ int wlen;
+
+ while (len)
+ {
+ wlen = write(fd, (char *)buf, (size_t)len);
+ if (wlen <= 0) /* error! */
+ return FAIL;
+ len -= wlen;
+ buf += wlen;
+ }
+ return OK;
+}
+
+/*
+ * add extention to filename - change path/fo.o.h to path/fo.o.h.ext or
+ * fo_o_h.ext for MSDOS or when shortname option set.
+ *
+ * Assumed that fname is a valid name found in the filesystem we assure that
+ * the return value is a different name and ends in 'ext'.
+ * "ext" MUST be at most 4 characters long if it starts with a dot, 3
+ * characters otherwise.
+ * Space for the returned name is allocated, must be freed later.
+ */
+
+ char_u *
+modname(fname, ext)
+ char_u *fname, *ext;
+{
+ return buf_modname(curbuf, fname, ext);
+}
+
+ char_u *
+buf_modname(buf, fname, ext)
+ BUF *buf;
+ char_u *fname, *ext;
+{
+ char_u *retval;
+ register char_u *s;
+ register char_u *e;
+ register char_u *ptr;
+ register int fnamelen, extlen;
+
+ extlen = STRLEN(ext);
+
+ /*
+ * if there is no filename we must get the name of the current directory
+ * (we need the full path in case :cd is used)
+ */
+ if (fname == NULL || *fname == NUL)
+ {
+ retval = alloc((unsigned)(MAXPATHL + extlen + 3));
+ if (retval == NULL)
+ return NULL;
+ if (mch_dirname(retval, MAXPATHL) == FAIL ||
+ (fnamelen = STRLEN(retval)) == 0)
+ {
+ vim_free(retval);
+ return NULL;
+ }
+ if (!ispathsep(retval[fnamelen - 1]))
+ {
+ retval[fnamelen++] = PATHSEP;
+ retval[fnamelen] = NUL;
+ }
+ }
+ else
+ {
+ fnamelen = STRLEN(fname);
+ retval = alloc((unsigned)(fnamelen + extlen + 2));
+ if (retval == NULL)
+ return NULL;
+ STRCPY(retval, fname);
+ }
+
+ /*
+ * search backwards until we hit a '/', '\' or ':' replacing all '.'
+ * by '_' for MSDOS or when shortname option set and ext starts with a dot.
+ * Then truncate what is after the '/', '\' or ':' to 8 characters for
+ * MSDOS and 26 characters for AMIGA, a lot more for UNIX.
+ */
+ for (ptr = retval + fnamelen; ptr >= retval; ptr--)
+ {
+ if (*ext == '.'
+#ifdef USE_LONG_FNAME
+ && (!USE_LONG_FNAME || buf->b_p_sn || buf->b_shortname)
+#else
+# ifndef SHORT_FNAME
+ && (buf->b_p_sn || buf->b_shortname)
+# endif
+#endif
+ )
+ if (*ptr == '.') /* replace '.' by '_' */
+ *ptr = '_';
+ if (ispathsep(*ptr))
+ break;
+ }
+ ptr++;
+
+ /* the filename has at most BASENAMELEN characters. */
+#ifndef SHORT_FNAME
+ if (STRLEN(ptr) > (unsigned)BASENAMELEN)
+ ptr[BASENAMELEN] = '\0';
+#endif
+
+ s = ptr + STRLEN(ptr);
+
+ /*
+ * For 8.3 filenames we may have to reduce the length.
+ */
+#ifdef USE_LONG_FNAME
+ if (!USE_LONG_FNAME || buf->b_p_sn || buf->b_shortname)
+#else
+# ifndef SHORT_FNAME
+ if (buf->b_p_sn || buf->b_shortname)
+# endif
+#endif
+ {
+ /*
+ * If there is no file name, and the extension starts with '.', put a
+ * '_' before the dot, because just ".ext" is invalid.
+ */
+ if (fname == NULL || *fname == NUL)
+ {
+ if (*ext == '.')
+ *s++ = '_';
+ }
+ /*
+ * If the extension starts with '.', truncate the base name at 8
+ * characters
+ */
+ else if (*ext == '.')
+ {
+ if (s - ptr > (size_t)8)
+ {
+ s = ptr + 8;
+ *s = '\0';
+ }
+ }
+ /*
+ * If the extension doesn't start with '.', and the file name
+ * doesn't have an extension yet, append a '.'
+ */
+ else if ((e = vim_strchr(ptr, '.')) == NULL)
+ *s++ = '.';
+ /*
+ * If If the extension doesn't start with '.', and there already is an
+ * extension, it may need to be tructated
+ */
+ else if ((int)STRLEN(e) + extlen > 4)
+ s = e + 4 - extlen;
+ }
+#ifdef OS2
+ /*
+ * If there is no file name, and the extension starts with '.', put a
+ * '_' before the dot, because just ".ext" may be invalid if it's on a
+ * FAT partition, and on HPFS it doesn't matter.
+ */
+ else if ((fname == NULL || *fname == NUL) && *ext == '.')
+ *s++ = '_';
+#endif
+
+ /*
+ * Append the extention.
+ * ext can start with '.' and cannot exceed 3 more characters.
+ */
+ STRCPY(s, ext);
+
+ /*
+ * Check that, after appending the extension, the file name is really
+ * different.
+ */
+ if (fname != NULL && STRCMP(fname, retval) == 0)
+ {
+ /* we search for a character that can be replaced by '_' */
+ while (--s >= ptr)
+ {
+ if (*s != '_')
+ {
+ *s = '_';
+ break;
+ }
+ }
+ if (s < ptr) /* fname was "________.<ext>" how tricky! */
+ *ptr = 'v';
+ }
+ return retval;
+}
+
+/* vim_fgets();
+ *
+ * Like fgets(), but if the file line is too long, it is truncated and the
+ * rest of the line is thrown away. Returns TRUE for end-of-file.
+ * Note: do not pass IObuff as the buffer since this is used to read and
+ * discard the extra part of any long lines.
+ */
+ int
+vim_fgets(buf, size, fp)
+ char_u *buf;
+ int size;
+ FILE *fp;
+{
+ char *eof;
+
+ buf[size - 2] = NUL;
+ eof = fgets((char *)buf, size, fp);
+ if (buf[size - 2] != NUL && buf[size - 2] != '\n')
+ {
+ buf[size - 1] = NUL; /* Truncate the line */
+
+ /* Now throw away the rest of the line: */
+ do
+ {
+ IObuff[IOSIZE - 2] = NUL;
+ fgets((char *)IObuff, IOSIZE, fp);
+ } while (IObuff[IOSIZE - 2] != NUL && IObuff[IOSIZE - 2] != '\n');
+ }
+ return (eof == NULL);
+}
+
+/*
+ * rename() only works if both files are on the same file system, this
+ * function will (attempts to?) copy the file across if rename fails -- webb
+ * Return -1 for failure, 0 for success.
+ */
+ int
+vim_rename(from, to)
+ char_u *from;
+ char_u *to;
+{
+ int fd_in;
+ int fd_out;
+ int n;
+ char *errmsg = NULL;
+
+ /*
+ * First delete the "to" file, this is required on some systems to make
+ * the rename() work, on other systems it makes sure that we don't have
+ * two files when the rename() fails.
+ */
+ vim_remove(to);
+
+ /*
+ * First try a normal rename, return if it works.
+ */
+ if (rename((char *)from, (char *)to) == 0)
+ return 0;
+
+ /*
+ * Rename() failed, try copying the file.
+ */
+ fd_in = open((char *)from, O_RDONLY | O_EXTRA);
+ if (fd_in == -1)
+ return -1;
+ fd_out = open((char *)to, O_CREAT | O_TRUNC | O_WRONLY | O_EXTRA, 0666);
+ if (fd_out == -1)
+ {
+ close(fd_in);
+ return -1;
+ }
+ while ((n = read(fd_in, (char *)IObuff, (size_t)IOSIZE)) > 0)
+ if (write(fd_out, (char *)IObuff, (size_t)n) != n)
+ {
+ errmsg = "writing to";
+ break;
+ }
+ close(fd_in);
+ if (close(fd_out) < 0)
+ errmsg = "closing";
+ if (n < 0)
+ {
+ errmsg = "reading";
+ to = from;
+ }
+ if (errmsg != NULL)
+ {
+ sprintf((char *)IObuff, "Error %s '%s'", errmsg, to);
+ emsg(IObuff);
+ return -1;
+ }
+ vim_remove(from);
+ return 0;
+}
+
+/*
+ * Check if any not hidden buffer has been changed.
+ * Postpone the check if there are characters in the stuff buffer, a global
+ * command is being executed, a mapping is being executed or an autocommand is
+ * busy.
+ */
+ void
+check_timestamps()
+{
+ BUF *buf;
+
+ if (!stuff_empty() || global_busy || !typebuf_typed()
+#ifdef AUTOCMD
+ || autocmd_busy
+#endif
+ )
+ need_check_timestamps = TRUE; /* check later */
+ else
+ {
+ ++no_wait_return;
+ for (buf = firstbuf; buf != NULL; buf = buf->b_next)
+ buf_check_timestamp(buf);
+ --no_wait_return;
+ need_check_timestamps = FALSE;
+ }
+}
+
+/*
+ * Check if buffer "buf" has been changed.
+ */
+ void
+buf_check_timestamp(buf)
+ BUF *buf;
+{
+ struct stat st;
+ char_u *path;
+
+ if ( buf->b_filename != NULL &&
+ buf->b_ml.ml_mfp != NULL &&
+ !buf->b_notedited &&
+ buf->b_mtime != 0 &&
+ stat((char *)buf->b_filename, &st) >= 0 &&
+ buf->b_mtime != st.st_mtime)
+ {
+ path = home_replace_save(buf, buf->b_xfilename);
+ if (path != NULL)
+ {
+ EMSG2("Warning: File \"%s\" has changed since editing started",
+ path);
+ buf->b_mtime = st.st_mtime;
+ vim_free(path);
+ }
+ }
+}