This is fileio.c in view mode; [Download] [Up]
/* 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 #ifdef UNIX static void set_file_time __ARGS((char_u *fname, time_t atime, time_t mtime)); #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 void msg_add_eol __ARGS((void)); 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 end-of-line */ void filemess(buf, name, s, attr) BUF *buf; char_u *name; char_u *s; int attr; { 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_attr(IObuff, attr); 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, unless READ_STDIN is used) * * 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. * * flags: * READ_NEW starting to edit a new buffer * READ_FILTER reading filter output * READ_STDIN read from stdin instead of a file * * return FAIL for failure, OK otherwise */ int readfile(fname, sfname, from, lines_to_skip, lines_to_read, flags) char_u *fname; char_u *sfname; linenr_t from; linenr_t lines_to_skip; linenr_t lines_to_read; int flags; { int fd; int newfile = (flags & READ_NEW); int filtering = (flags & READ_FILTER); int read_stdin = (flags & READ_STDIN); char_u c; linenr_t lnum = from; char_u *ptr = NULL; /* pointer into read buffer */ 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; long size; char_u *p; long filesize = 0; int split = 0; /* number of split lines */ #define UNKNOWN 0x0fffffff /* file size is unknown */ linenr_t linecnt; int error = FALSE; /* errors encountered */ int tx_error = FALSE; /* textmode, but no CR */ long linerest = 0; /* remaining chars in line */ int firstpart = TRUE; /* reading first part */ #ifdef UNIX int perm = 0; #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 */ #ifdef AUTOCMD write_no_eol_lnum = 0; /* in case it was set by the previous read */ #endif /* * 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_ffname == NULL && !filtering && !read_stdin && 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__) fname = sfname; #endif #ifdef UNIX /* * On Unix it is possible to read a directory, so we have to * check for it before the open(). */ if (!read_stdin) { 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", 0); else filemess(curbuf, fname, (char_u *)"is not a file", 0); 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 && !read_stdin) { 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 (read_stdin) fd = 0; else { #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)); fname = sfname; /* use short name now, for the messages */ if (isdir_f) filemess(curbuf, fname, (char_u *)"is a directory", 0); else #endif if (newfile) { #ifdef UNIX if (perm < 0) #endif { filemess(curbuf, fname, (char_u *)"[New File]", 0); #ifdef AUTOCMD apply_autocmds(EVENT_BUFNEWFILE, fname, fname, FALSE); #endif return OK; /* a new file is not an error */ } #ifdef UNIX else filemess(curbuf, fname, (char_u *)"[Permission Denied]", 0); #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 fname = sfname; /* replace with short name now, for the messages */ #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; BUF *old_curbuf = curbuf; /* * The file must be closed again, the autocommands may want to change * the file before reading it. */ if (!read_stdin) close(fd); /* ignore errors */ /* * 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, FALSE); else if (read_stdin) apply_autocmds(EVENT_STDINREADPRE, NULL, fname, FALSE); else if (newfile) apply_autocmds(EVENT_BUFREADPRE, NULL, fname, FALSE); else apply_autocmds(EVENT_FILEREADPRE, fname, fname, FALSE); if (msg_scrolled == n) msg_scroll = m; /* * Don't allow the autocommands to change the current buffer. * Try to re-open the file. */ if (!read_stdin && (curbuf != old_curbuf || (fd = open((char *)fname, O_RDONLY | O_EXTRA)) < 0)) { --no_wait_return; msg_scroll = msg_save; if (fd < 0) EMSG("*ReadPre autocommands made the file unreadable"); else EMSG("*ReadPre autocommands must not change current buffer"); return FAIL; } } #endif if (!recoverymode && !filtering) filemess(curbuf, fname, (char_u *)"", 0); /* show that we are busy */ msg_scroll = FALSE; /* overwrite the file message */ /* * Set textmode and linecnt now, before the "retry" caused by 'textauto' * and after the autocommands, which may change them. */ textmode = curbuf->b_p_tx; linecnt = curbuf->b_ml.ml_line_count; 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 && !read_stdin && 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; ui_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; invalidate_botline(); /* need to recompute w_botline */ changed_line_abv_curs(); /* need to recompute cursor posn */ 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, 0); 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) { msg_add_eol(); 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 VIMINFO /* * Get the marks before executing autocommands, so they can be used there. */ check_marks_read(); #endif /* VIMINFO */ #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, FALSE); else if (read_stdin) apply_autocmds(EVENT_STDINREADPOST, NULL, fname, FALSE); else if (newfile) apply_autocmds(EVENT_BUFREADPOST, NULL, fname, FALSE); else apply_autocmds(EVENT_FILEREADPOST, fname, fname, FALSE); if (msg_scrolled == n) msg_scroll = m; } #endif 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 */ #ifdef UNIX static void set_file_time(fname, atime, mtime) char_u *fname; time_t atime; /* access time */ time_t mtime; /* modification time */ { # if defined(HAVE_UTIME) && defined(HAVE_UTIME_H) # include <utime.h> struct utimbuf buf; int result; buf.actime = atime; buf.modtime = mtime; result = utime((char *)fname, &buf); # else # if defined(HAVE_UTIMES) struct timeval tvp[2]; int result; tvp[0].tv_sec = atime; tvp[0].tv_usec = 0; tvp[1].tv_sec = mtime; tvp[1].tv_usec = 0; result = utimes((char *)fname, &tvp); # endif # endif } #endif /* UNIX */ /* * buf_write() - 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 char_u *s; char_u *ptr; char_u c; int len; 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 */ int no_eol = FALSE; /* no end-of-line written */ #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 int attr; 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_ffname == 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 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_ffname != NULL && fnamecmp(ffname, buf->b_ffname) == 0) overwriting = TRUE; else overwriting = FALSE; /* * Disallow writing from .exrc and .vimrc in current directory for * security reasons. */ if (check_secure()) return FAIL; if (exiting) settmode(TMODE_COOK); /* 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. * Set curbuf to the buffer to be written. * 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, FALSE); else if (filtering) apply_autocmds(EVENT_FILTERWRITEPRE, NULL, fname, FALSE); else if (reset_changed && whole) apply_autocmds(EVENT_BUFWRITEPRE, fname, fname, FALSE); else apply_autocmds(EVENT_FILEWRITEPRE, fname, fname, FALSE); /* * If the autocommands deleted or unloaded the buffer, give an error * message. */ if (!buf_valid(buf) || buf->b_ml.ml_mfp == NULL) { --no_wait_return; msg_scroll = msg_save; EMSG("Autocommands deleted or unloaded buffer to be written"); return FAIL; } /* * If the autocommands didn't change the current buffer, go back to the * original current buffer, if it still exists. */ if (curbuf == buf && buf_valid(save_buf)) { 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; msg_scroll = msg_save; 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 sfname, #else fname, #endif (char_u *)"", 0); /* 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 */ /* don't use emsg() here, don't want to flush the buffers */ MSG_ATTR("WARNING: The file has been changed since reading it!!!", highlight_attr[HLF_E]); 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; char_u *rootname; #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, using an entry in 'bdir'. */ (void)copy_option_part(&dirp, copybuf, BUFSIZE, ","); rootname = get_file_in_dir(fname, copybuf); if (rootname == NULL) { some_error = TRUE; /* out of memory */ goto nobackup; } #ifndef SHORT_FNAME did_set_shortname = FALSE; #endif /* * May try twice if 'shortname' not set. */ for (;;) { /* * Make backup file name. */ backup = buf_modname( #ifdef SHORT_FNAME TRUE, #else (buf->b_p_sn || buf->b_shortname), #endif rootname, backup_ext); if (backup == NULL) { some_error = TRUE; /* out of memory */ vim_free(rootname); 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; } vim_free(rootname); /* * 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((char *)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)"; set_file_time(backup, st_old.st_atime, st_old.st_mtime); 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; char_u *rootname; /* * 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 and make the backup file name. */ (void)copy_option_part(&dirp, IObuff, IOSIZE, ","); rootname = get_file_in_dir(fname, IObuff); if (rootname == NULL) backup = NULL; else { backup = buf_modname( #ifdef SHORT_FNAME TRUE, #else (buf->b_p_sn || buf->b_shortname), #endif rootname, backup_ext); vim_free(rootname); } 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)))) { ++lnum; /* written the line, count it */ no_eol = TRUE; 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 fname = sfname; /* use shortname now, for the messages */ #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 (no_eol) { msg_add_eol(); 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( #ifdef SHORT_FNAME TRUE, #else (buf->b_p_sn || buf->b_shortname), #endif 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; #ifdef UNIX set_file_time((char_u *)org, st_old.st_atime, st_old.st_mtime); #endif } } /* * 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 */ attr = highlight_attr[HLF_E]; /* set highlight for error messages */ filemess(buf, #ifndef UNIX sfname, #else fname, #endif errmsg, attr); retval = FAIL; if (end == 0) { MSG_PUTS_ATTR("\nWARNING: Original file may be lost or damaged\n", attr); MSG_PUTS_ATTR("don't quit the editor until the file is successfully written!", attr); } } msg_scroll = msg_save; #ifdef AUTOCMD write_no_eol_lnum = 0; /* in case it was set by the previous read */ /* * Apply POST autocommands. * 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, FALSE); else if (filtering) apply_autocmds(EVENT_FILTERWRITEPOST, NULL, fname, FALSE); else if (reset_changed && whole) apply_autocmds(EVENT_BUFWRITEPOST, fname, fname, FALSE); else apply_autocmds(EVENT_FILEWRITEPOST, fname, fname, FALSE); /* * If the autocommands didn't change the current buffer, go back to the * original current buffer, if it still exists. */ if (curbuf == buf && buf_valid(save_buf)) { 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; { if (fname == NULL) fname = (char_u *)"-stdin-"; 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)); } /* * Append message for missing line separator to IObuff. */ static void msg_add_eol() { STRCAT(IObuff, shortmess(SHM_LAST) ? "[noeol]" : "[Incomplete last line]"); } /* * 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; } /* * shorten_fname: try to find a shortname by comparing the fullname with the * current directory. Uses IObuff. */ char_u * shorten_fname(full_path, dir_name) char_u *full_path; char_u *dir_name; { int len; char_u *p; if (full_path == NULL) return NULL; len = STRLEN(dir_name); if (fnamencmp(dir_name, full_path, len) == 0) { p = full_path + len; if (ispathsep(*p)) ++p; else p = NULL; } else p = NULL; return p; } /* * 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( #ifdef SHORT_FNAME TRUE, #else (curbuf->b_p_sn || curbuf->b_shortname), #endif fname, ext); } char_u * buf_modname(shortname, fname, ext) int shortname; /* use 8.3 filename */ char_u *fname, *ext; { char_u *retval; char_u *s; char_u *e; char_u *ptr; 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 || shortname) #else # ifndef SHORT_FNAME && 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 || shortname) #else # ifndef SHORT_FNAME if (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_ffname != NULL && buf->b_ml.ml_mfp != NULL && !buf->b_notedited && buf->b_mtime != 0 && stat((char *)buf->b_ffname, &st) >= 0 && buf->b_mtime != st.st_mtime) { #ifdef AUTOCMD /* * Only give the warning if there are no FileChangedShell autocommands. */ if (!apply_autocmds(EVENT_FILECHANGEDSHELL, NULL, NULL, FALSE)) #endif { path = home_replace_save(buf, buf->b_fname); if (path != NULL) { EMSG2("Warning: File \"%s\" has changed since editing started", path); vim_free(path); } } buf->b_mtime = st.st_mtime; } } /* * Adjust the line with missing eol, used for the next write. * Used for do_filter(), when the input lines for the filter are deleted. */ void write_lnum_adjust(offset) linenr_t offset; { if (write_no_eol_lnum) /* only if there is a missing eol */ write_no_eol_lnum += offset; } #ifdef USE_TMPNAM extern char *tmpnam __ARGS((char *)); #else extern char *mktemp __ARGS((char *)); #endif /* * vim_tempname(): Return a unique name that can be used for a temp file. * * The temp file is NOT created. * * The returned pointer is to allocated memory. * The returned pointer is NULL if no valid name was found. */ char_u * vim_tempname(extra_char) int extra_char; /* character to use in the name instead of '?' */ { #ifdef USE_TMPNAM char_u itmp[L_tmpnam]; /* use tmpnam() */ #else char_u itmp[TEMPNAMELEN]; #endif #if defined(TEMPDIRNAMES) || !defined(USE_TMPNAM) char_u *p; #endif #if defined(TEMPDIRNAMES) static char *(tempdirs[]) = {TEMPDIRNAMES}; static int first_dir = 0; int first_try = TRUE; int i; /* * Try a few places to put the temp file. * To avoid waisting time with non-existing environment variables and * directories, they are skipped next time. */ for (i = first_dir; i < sizeof(tempdirs) / sizeof(char *); ++i) { /* expand $TMP, leave room for '/', "v?XXXXXX" and NUL */ expand_env((char_u *)tempdirs[i], itmp, TEMPNAMELEN - 10); if (mch_isdir(itmp)) /* directory exists */ { if (first_try) first_dir = i; /* start here next time */ first_try = FALSE; #ifdef __EMX__ /* * if $TMP contains a forward slash (perhaps because we're using * bash or tcsh, right Stefan?), don't add a backslash to the * directory before tacking on the filename; use a forward slash! * I first tried adding 2 backslashes, but somehow that didn't * work (something in the EMX system() ate them, I think). */ if (vim_strchr(itmp, '/')) STRCAT(itmp, "/"); else #endif STRCAT(itmp, PATHSEPSTR); STRCAT(itmp, TEMPNAME); if ((p = vim_strchr(itmp, '?')) != NULL) *p = extra_char; if (*mktemp((char *)itmp) == NUL) continue; return vim_strsave(itmp); } } return NULL; #else # ifdef USE_TMPNAM /* tmpnam() will make its own name */ if (*tmpnam((char *)itmp) == NUL) # else STRCPY(itmp, TEMPNAME); if ((p = vim_strchr(itmp, '?')) != NULL) *p = extra_char; if (*mktemp((char *)itmp) == NUL) # endif return NULL; return vim_strsave(itmp); #endif } /* * Code for automatic commands. * * Only included when "AUTOCMD" has been defined. */ #ifdef AUTOCMD typedef struct AutoCmd { char_u *cmd; /* The command to be executed */ int nested; /* If autocommands nest here */ struct AutoCmd *next; /* Next AutoCmd in list */ } AutoCmd; typedef struct AutoPat { char_u *pat; /* pattern as typed */ char_u *reg_pat; /* pattern converted to regexp */ int allow_directories; /* Pattern may match whole path */ AutoCmd *cmds; /* list of commands to do */ struct AutoPat *next; /* next AutoPat in AutoPat list */ } AutoPat; static struct event_name { char *name; /* event name */ int event; /* event number */ } event_names[] = { {"BufEnter", EVENT_BUFENTER}, {"BufLeave", EVENT_BUFLEAVE}, {"BufNewFile", EVENT_BUFNEWFILE}, {"BufReadPost", EVENT_BUFREADPOST}, {"BufReadPre", EVENT_BUFREADPRE}, {"BufRead", EVENT_BUFREADPOST}, {"BufWritePost", EVENT_BUFWRITEPOST}, {"BufWritePre", EVENT_BUFWRITEPRE}, {"BufWrite", EVENT_BUFWRITEPRE}, {"FileAppendPost", EVENT_FILEAPPENDPOST}, {"FileAppendPre", EVENT_FILEAPPENDPRE}, {"FileChangedShell", EVENT_FILECHANGEDSHELL}, {"FileReadPost", EVENT_FILEREADPOST}, {"FileReadPre", EVENT_FILEREADPRE}, {"FileWritePost", EVENT_FILEWRITEPOST}, {"FileWritePre", EVENT_FILEWRITEPRE}, {"FilterReadPost", EVENT_FILTERREADPOST}, {"FilterReadPre", EVENT_FILTERREADPRE}, {"FilterWritePost", EVENT_FILTERWRITEPOST}, {"FilterWritePre", EVENT_FILTERWRITEPRE}, {"StdinReadPost", EVENT_STDINREADPOST}, {"StdinReadPre", EVENT_STDINREADPRE}, {"TermChanged", EVENT_TERMCHANGED}, {"VimLeave", EVENT_VIMLEAVE}, {"WinEnter", EVENT_WINENTER}, {"WinLeave", EVENT_WINLEAVE}, {NULL, 0} }; static AutoPat *first_autopat[NUM_EVENTS] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; static void show_autocmd __ARGS((AutoPat *ap, int event)); static void del_autocmd __ARGS((AutoPat *ap)); static void del_autocmd_cmds __ARGS((AutoPat *ap)); static int event_name2nr __ARGS((char_u *start, char_u **end)); static char *event_nr2name __ARGS((int event)); static char_u *find_end_event __ARGS((char_u *arg)); static int do_autocmd_event __ARGS((int event, char_u *pat, int nested, char_u *cmd, int forceit)); static char_u *getnextac __ARGS((int c, void *cookie, int indent)); static void show_autocmd(ap, event) AutoPat *ap; int event; { AutoCmd *ac; if (got_int) /* "q" hit for "--more--" */ return; msg_putchar('\n'); if (got_int) /* "q" hit for "--more--" */ return; msg_putchar('\n'); if (got_int) /* "q" hit for "--more--" */ return; MSG_PUTS(event_nr2name(event)); MSG_PUTS(" "); msg_outtrans(ap->pat); for (ac = ap->cmds; ac != NULL; ac = ac->next) { MSG_PUTS("\n "); if (got_int) /* hit "q" at "--more--" prompt */ return; msg_outtrans(ac->cmd); } } /* * Delete an autocommand pattern. */ static void del_autocmd(ap) AutoPat *ap; { vim_free(ap->pat); vim_free(ap->reg_pat); del_autocmd_cmds(ap); vim_free(ap); } /* * Delete the commands from a pattern. */ static void del_autocmd_cmds(ap) AutoPat *ap; { AutoCmd *ac; while (ap->cmds != NULL) { ac = ap->cmds; ap->cmds = ac->next; vim_free(ac->cmd); vim_free(ac); } } /* * Return the event number for event name "start". * Return -1 if the event name was not found. * Return a pointer to the next event name in "end". */ static int event_name2nr(start, end) char_u *start; char_u **end; { char_u *p; int i; int len; /* the event name ends with end of line, a blank or a comma */ for (p = start; *p && !vim_iswhite(*p) && *p != ','; ++p) ; for (i = 0; event_names[i].name != NULL; ++i) { len = strlen(event_names[i].name); if (len == p - start && STRNICMP(event_names[i].name, start, len) == 0) break; } if (*p == ',') ++p; *end = p; if (event_names[i].name == NULL) return -1; return event_names[i].event; } /* * Return the name for event "event". */ static char * event_nr2name(event) int event; { int i; for (i = 0; event_names[i].name != NULL; ++i) if (event_names[i].event == event) return event_names[i].name; return "Unknown"; } /* * Scan over the events. "*" stands for all events. */ static char_u * find_end_event(arg) char_u *arg; { char_u *pat; char_u *p; if (*arg == '*') { if (arg[1] && !vim_iswhite(arg[1])) { EMSG2("Illegal character after *: %s", arg); return NULL; } pat = arg + 1; } else { for (pat = arg; *pat && !vim_iswhite(*pat); pat = p) { if (event_name2nr(pat, &p) < 0) { EMSG2("No such event: %s", pat); return NULL; } } } return pat; } /* * do_autocmd() -- implements the :autocmd command. Can be used in the * following ways: * * :autocmd <event> <pat> <cmd> Add <cmd> to the list of commands that * will be automatically executed for <event> * when editing a file matching <pat>. * :autocmd <event> <pat> Show the auto-commands associated with * <event> and <pat>. * :autocmd <event> Show the auto-commands associated with * <event>. * :autocmd Show all auto-commands. * :autocmd! <event> <pat> <cmd> Remove all auto-commands associated with * <event> and <pat>, and add the command * <cmd>. * :autocmd! <event> <pat> Remove all auto-commands associated with * <event> and <pat>. * :autocmd! <event> Remove all auto-commands associated with * <event>. * :autocmd! Remove ALL auto-commands. * * Multiple events and patterns may be given separated by commas. Here are * some examples: * :autocmd bufread,bufenter *.c,*.h set tw=0 smartindent noic * :autocmd bufleave * set tw=79 nosmartindent ic infercase * * :autocmd * *.c show all autocommands for *.c files. */ void do_autocmd(arg, forceit) char_u *arg; int forceit; { char_u *pat; char_u *cmd; int event; int need_free = FALSE; int nested = TRUE; /* * Don't change autocommands while executing one. */ if (autocmd_busy) return; /* * Scan over the events. * If we find an illegal name, return here, don't do anything. */ pat = find_end_event(arg); if (pat == NULL) return; /* * Scan over the pattern. Put a NUL at the end. */ pat = skipwhite(pat); cmd = pat; while (*cmd && (!vim_iswhite(*cmd) || cmd[-1] == '\\')) cmd++; if (*cmd) *cmd++ = NUL; /* * Check for "nested" flag. */ cmd = skipwhite(cmd); if (*cmd != NUL && STRNCMP(cmd, "nested", 6) == 0 && vim_iswhite(cmd[6])) { nested = TRUE; cmd = skipwhite(cmd + 6); } /* * Find the start of the commands. * Expand <sfile> in it. */ if (*cmd != NUL) { cmd = expand_sfile(cmd); if (cmd == NULL) /* some error */ return; need_free = TRUE; } /* * Print header when showing autocommands. */ if (!forceit && *cmd == NUL) { /* Highlight title */ MSG_PUTS_TITLE("\n--- Auto-Commands ---"); } /* * Loop over the events. */ if (*arg == '*' || *arg == NUL) { for (event = 0; event < NUM_EVENTS; ++event) if (do_autocmd_event(event, pat, nested, cmd, forceit) == FAIL) break; } else { while (*arg && !vim_iswhite(*arg)) if (do_autocmd_event(event_name2nr(arg, &arg), pat, nested, cmd, forceit) == FAIL) break; } if (need_free) vim_free(cmd); } /* * do_autocmd() for one event. * If *pat == NUL do for all patterns. * If *cmd == NUL show entries. * If forceit == TRUE delete entries. */ static int do_autocmd_event(event, pat, nested, cmd, forceit) int event; char_u *pat; int nested; char_u *cmd; int forceit; { AutoPat *ap; AutoPat *ap2; AutoPat **final_ap; AutoCmd *ac; AutoCmd **final_ac; int brace_level; char_u *endpat; int len; /* * Show or delete all patterns for an event. */ if (*pat == NUL) { for (ap = first_autopat[event]; ap != NULL; ap = ap2) { ap2 = ap->next; if (forceit) del_autocmd(ap); else show_autocmd(ap, event); } if (forceit) first_autopat[event] = NULL; } /* * Loop through all the specified patterns. */ for ( ; *pat; pat = (*endpat == ',' ? endpat + 1 : endpat)) { /* * Find end of the pattern. * Watch out for a comma in braces, like "*.\{obj,o\}". */ brace_level = 0; for (endpat = pat; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\'); ++endpat) { if (*endpat == '{') brace_level++; else if (*endpat == '}') brace_level--; } if (pat == endpat) /* ignore single comma */ continue; /* * Find entry with same pattern. */ final_ap = &first_autopat[event]; for (ap = first_autopat[event]; ap != NULL; ap = *final_ap) { len = STRLEN(ap->pat); if (len == endpat - pat && STRNCMP(pat, ap->pat, len) == 0) break; final_ap = &ap->next; } /* * Add a new pattern. * Show and delete are ignored if pattern is not found. */ if (ap == NULL) { if (*cmd == NUL) continue; /* Add the autocmd at the end of the list */ ap = (AutoPat *)alloc((unsigned)sizeof(AutoPat)); if (ap == NULL) return FAIL; ap->pat = vim_strnsave(pat, (int)(endpat - pat)); if (ap->pat == NULL) { vim_free(ap); return FAIL; } ap->reg_pat = file_pat_to_reg_pat(pat, endpat, &ap->allow_directories); if (ap->reg_pat == NULL) { vim_free(ap->pat); vim_free(ap); return FAIL; } ap->cmds = NULL; *final_ap = ap; ap->next = NULL; } /* * Remove existing autocommands. * If not adding any new autocmd's for this pattern, delete the * pattern from the autopat list */ else if (forceit) { del_autocmd_cmds(ap); if (*cmd == NUL) { if (ap == first_autopat[event]) first_autopat[event] = ap->next; else { for (ap2 = first_autopat[event]; ap2->next != ap; ap2 = ap2->next) ; ap2->next = ap->next; } del_autocmd(ap); } } /* * Show autocmd's for this autopat */ if (*cmd == NUL && !forceit) { show_autocmd(ap, event); } /* * Add the autocmd at the end if it's not already there. */ else if (*cmd != NUL) { final_ac = &(ap->cmds); for (ac = ap->cmds; ac != NULL && STRCMP(cmd, ac->cmd) != 0; ac = ac->next) final_ac = &ac->next; if (ac == NULL) { ac = (AutoCmd *)alloc((unsigned)sizeof(AutoCmd)); if (ac == NULL) return FAIL; ac->cmd = vim_strsave(cmd); if (ac->cmd == NULL) { vim_free(ac); return FAIL; } ac->next = NULL; *final_ac = ac; } ac->nested = nested; } } return OK; } /* * Implementation of ":doautocmd event [fname]". */ void do_doautocmd(arg) char_u *arg; { char_u *fname; int nothing_done = TRUE; if (*arg == '*') { EMSG("Can't execute autocommands for ALL events"); return; } /* * Scan over the events. * If we find an illegal name, return here, don't do anything. */ fname = find_end_event(arg); if (fname == NULL) return; fname = skipwhite(fname); /* * Loop over the events. */ while (*arg && !vim_iswhite(*arg)) if (apply_autocmds(event_name2nr(arg, &arg), fname, NULL, TRUE)) nothing_done = FALSE; if (nothing_done) MSG("No matching autocommands"); } static int autocmd_nested = FALSE; /* * Execute autocommands for "event" and file name "fname". * Return TRUE if some commands were executed. */ int apply_autocmds(event, fname, fname_io, force) int event; char_u *fname; /* NULL or empty means use actual file name */ char_u *fname_io; /* fname to use for "^Vf" on cmdline */ int force; /* when TRUE, ignore autocmd_busy */ { vim_regexp *prog; char_u *tail; AutoPat *ap; AutoCmd *ac; int temp; int save_changed = curbuf->b_changed; BUF *old_curbuf = curbuf; char_u *save_name; char_u *full_fname = NULL; int retval = FALSE; int old_autocmd_busy = autocmd_busy; int old_autocmd_nested = autocmd_nested; static int nesting = 0; /* * When autocommands are busy, new autocommands are only executed when * explicitly enabled with the "nested" flag. */ if (autocmd_busy && !(force || autocmd_nested)) return retval; /* * Allow nesting of autocommands, but restrict the depth, because it's * possible to create an endless loop. */ if (nesting == 10) { EMSG("autocommand nesting too deep"); return retval; } /* * Check if these autocommands are disabled. Used when doing ":all" or * ":ball". */ if ( (autocmd_no_enter && (event == EVENT_WINENTER || event == EVENT_BUFENTER)) || (autocmd_no_leave && (event == EVENT_WINLEAVE || event == EVENT_BUFLEAVE))) return retval; /* Don't redraw while doing auto commands. */ temp = RedrawingDisabled; RedrawingDisabled = TRUE; save_name = sourcing_name; /* may be called from .vimrc */ autocmd_fname = fname_io; /* * Note that we are applying autocmds. Some commands need to know. */ autocmd_busy = TRUE; ++nesting; /* * When the file name is NULL or empty, use the file name of the current * buffer. Always use the full path of the file name to match with, in * case "allow_directories" is set. */ if (fname == NULL || *fname == NUL) { fname = curbuf->b_ffname; if (fname == NULL) fname = (char_u *)""; } else { full_fname = FullName_save(fname, FALSE); fname = full_fname; } tail = gettail(fname); for (ap = first_autopat[event]; ap != NULL && !got_int; ap = ap->next) { #ifdef CASE_INSENSITIVE_FILENAME reg_ic = TRUE; /* Always ignore case */ #else reg_ic = FALSE; /* Don't ever ignore case */ #endif prog = vim_regcomp(ap->reg_pat, TRUE); /* Always use magic */ if ( prog != NULL && ((ap->allow_directories && vim_regexec(prog, fname, TRUE)) || (!ap->allow_directories && vim_regexec(prog, tail, TRUE)))) { sprintf((char *)IObuff, "%s Auto commands for \"%s\"", event_nr2name(event), (char *)ap->pat); sourcing_name = vim_strsave(IObuff); ac = ap->cmds; if (ac != NULL) { retval = TRUE; do_cmdline(NULL, getnextac, (void *)&ac, DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); } vim_free(sourcing_name); } vim_free(prog); ui_breakcheck(); } RedrawingDisabled = temp; autocmd_busy = old_autocmd_busy; autocmd_nested = old_autocmd_nested; sourcing_name = save_name; autocmd_fname = NULL; vim_free(full_fname); --nesting; /* * Some events don't set or reset the Changed flag. * Check if still in the same buffer! */ if (curbuf == old_curbuf && (event == EVENT_BUFREADPOST || event == EVENT_BUFWRITEPOST || event == EVENT_FILEAPPENDPOST || event == EVENT_VIMLEAVE)) curbuf->b_changed = save_changed; return retval; } /* * Get next autocommand command. * Called by do_cmdline() to get the next line for ":if". * Returns allocated string, or NULL for end of autocommands. */ static char_u * getnextac(c, cookie, indent) int c; /* not used */ void *cookie; int indent; /* not used */ { AutoCmd **ap = (AutoCmd **)cookie; char_u *retval; if (*ap == NULL) return NULL; retval = vim_strsave((*ap)->cmd); autocmd_nested = (*ap)->nested; *ap = (*ap)->next; return retval; } char_u * set_context_in_autocmd(arg, doautocmd) char_u *arg; int doautocmd; /* TRUE for :doautocmd, FALSE for :autocmd */ { char_u *p; /* skip over event name */ for (p = arg; *p && !vim_iswhite(*p); ++p) if (*p == ',') arg = p + 1; if (*p == NUL) { expand_context = EXPAND_EVENTS; /* expand event name */ expand_pattern = arg; return NULL; } /* skip over pattern */ arg = skipwhite(p); while (*arg && (!vim_iswhite(*arg) || arg[-1] == '\\')) arg++; if (*arg) return arg; /* expand (next) command */ if (doautocmd) expand_context = EXPAND_FILES; /* expand file names */ else expand_context = EXPAND_NOTHING; /* pattern is not expanded */ return NULL; } int ExpandEvents(prog, num_file, file) vim_regexp *prog; int *num_file; char_u ***file; { int i; int count; int round; /* * round == 1: Count the matches. * round == 2: Save the matches into the array. */ for (round = 1; round <= 2; ++round) { count = 0; for (i = 0; event_names[i].name != NULL; i++) if (vim_regexec(prog, (char_u *)event_names[i].name, TRUE)) { if (round == 1) count++; else (*file)[count++] = vim_strsave((char_u *)event_names[i].name); } if (round == 1) { *num_file = count; if (count == 0 || (*file = (char_u **) alloc((unsigned)(count * sizeof(char_u *)))) == NULL) return FAIL; } } return OK; } #endif /* AUTOCMD */
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.