summaryrefslogtreecommitdiff
path: root/usr.bin/vim/fileio.c
diff options
context:
space:
mode:
authorJason Downs <downsj@cvs.openbsd.org>1996-09-07 21:40:33 +0000
committerJason Downs <downsj@cvs.openbsd.org>1996-09-07 21:40:33 +0000
commitc224fc199c25dd257673c273eb344786b9bf532c (patch)
tree8f8ed1297120c537480d9e5d46bfe7452bd8505b /usr.bin/vim/fileio.c
parentd0d91e2d3d6569e4defdd5178241f28fa678d753 (diff)
Initial import of vim 4.2.
This is meant to replace nvi in the tree. Vim, in general, works better, provides more features, and does not suffer from the license problems being imposed upon nvi. On the other hand, vim lacks a non-visual ex mode, in addition to open mode. This includes the GUI (X11) code, but doesn't try to compile it.
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);
+ }
+ }
+}