--- vim80/src/vim.h.fsync 2017-07-30 22:46:04.000000000 +0300 +++ vim80/src/vim.h 2017-07-31 11:23:45.572308388 +0300 @@ -9,6 +9,11 @@ #ifndef VIM__H # define VIM__H +/* O_TMPFILE */ +#if defined(__linux__) && !defined(_GNU_SOURCE) +# define _GNU_SOURCE +#endif + /* use fastcall for Borland, when compiling for Win32 */ #if defined(__BORLANDC__) && defined(WIN32) && !defined(DEBUG) #if defined(FEAT_PERL) || \ @@ -1769,6 +1774,8 @@ void *vim_memset(void *, int, size_t); # define vim_write(fd, buf, count) write((fd), (char *)(buf), (size_t) (count)) #endif +void mch_copy_sec(char_u*, char_u*); + /* * Enums need a typecast to be used as array index (for Ultrix). */ --- vim80/src/fileio.c.fsync 2017-07-30 22:46:04.000000000 +0300 +++ vim80/src/fileio.c 2017-07-31 12:22:16.113807708 +0300 @@ -27,6 +27,9 @@ /* Is there any system that doesn't have access()? */ #define USE_MCH_ACCESS +static int vim_copy(char_u *from, char_u *to); +static int vim_rename_copy(char_u *from, char_u *to, int which); + #ifdef FEAT_MBYTE static char_u *next_fenc(char_u **pp); # ifdef FEAT_EVAL @@ -3107,7 +3110,6 @@ check_file_readonly( ); } - /* * buf_write() - write to file "fname" lines "start" through "end" * @@ -3142,8 +3144,12 @@ buf_write( char_u *backup = NULL; int backup_copy = FALSE; /* copy the original file? */ int dobackup; + int can_write_dir = 0; /* Can create file on dir where 'fname' resides */ + int backup_linked = 0; /* Backup file is link(2)'ed to 'fname' */ char_u *ffname; char_u *wfname = NULL; /* name of file to write to */ + char_u *wfname_orig = NULL; /* Kill me */ + char_u wftmp[MAXPATHL+1]; char_u *s; char_u *ptr; char_u c; @@ -3193,6 +3199,11 @@ buf_write( #ifdef HAVE_ACL vim_acl_T acl = NULL; /* ACL copied from original file to backup or new file */ + int ret; +#if defined(UNIX) || defined(WIN32) + struct stat st; +#endif + #endif #ifdef FEAT_PERSISTENT_UNDO int write_undo_file = FALSE; @@ -3554,10 +3565,18 @@ buf_write( st_old.st_dev = 0; st_old.st_ino = 0; perm = -1; - if (mch_stat((char *)fname, &st_old) < 0) - newfile = TRUE; - else - { + if (mch_stat((char *)fname, &st_old) < 0) { + newfile = TRUE; + + sprintf(IObuff, "%s", fname); + sprintf(gettail(IObuff), "%s", "vimfiletest.XXXXXX"); + fd = mkstemp(IObuff); + if (fd != -1) { + can_write_dir = 1; + close(fd); + mch_remove(IObuff); + } + } else { perm = st_old.st_mode; if (!S_ISREG(st_old.st_mode)) /* not a file */ { @@ -3579,6 +3598,34 @@ buf_write( newfile = TRUE; perm = -1; } + + /* + * Check if we can create a file and set the owner/group to + * the ones from the original file. + * First find a file name that doesn't exist yet. + */ + sprintf(IObuff, "%s", fname); + sprintf(gettail(IObuff), "%s", "vimfiletest.XXXXXX"); + fd = mkstemp(IObuff); + if (fd != -1) { + # ifdef UNIX + # ifdef HAVE_FCHOWN + fchown(fd, st_old.st_uid, st_old.st_gid); + fchmod(fd, perm); + # endif + if (mch_stat((char *)IObuff, &st) == 0 + && st.st_uid == st_old.st_uid + && st.st_gid == st_old.st_gid + && st.st_mode == perm) + can_write_dir = 1; + # else + can_write_dir = 1; + # endif + /* Close the file before removing it, on MS-Windows we + * can't delete an open file. */ + close(fd); + mch_remove(IObuff); + } } #else /* !UNIX */ /* @@ -3695,17 +3742,11 @@ buf_write( */ if (!(append && *p_pm == NUL) && !filtering && perm >= 0 && dobackup) { -#if defined(UNIX) || defined(WIN32) - stat_T st; -#endif - if ((bkc & BKC_YES) || append) /* "yes" */ backup_copy = TRUE; #if defined(UNIX) || defined(WIN32) else if ((bkc & BKC_AUTO)) /* "auto" */ { - int i; - # ifdef UNIX /* * Don't rename the file when: @@ -3733,58 +3774,8 @@ buf_write( else # endif # endif - { - /* - * Check if we can create a file and set the owner/group to - * the ones from the original file. - * First find a file name that doesn't exist yet (use some - * arbitrary numbers). - */ - STRCPY(IObuff, fname); - for (i = 4913; ; i += 123) - { - sprintf((char *)gettail(IObuff), "%d", i); - if (mch_lstat((char *)IObuff, &st) < 0) - break; - } - fd = mch_open((char *)IObuff, - O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, perm); - if (fd < 0) /* can't write in directory */ - backup_copy = TRUE; - else - { -# ifdef UNIX -# ifdef HAVE_FCHOWN - ignored = fchown(fd, st_old.st_uid, st_old.st_gid); -# endif - if (mch_stat((char *)IObuff, &st) < 0 - || st.st_uid != st_old.st_uid - || st.st_gid != st_old.st_gid - || (long)st.st_mode != perm) - backup_copy = TRUE; -# endif - /* Close the file before removing it, on MS-Windows we - * can't delete an open file. */ - close(fd); - mch_remove(IObuff); -# ifdef MSWIN - /* MS-Windows may trigger a virus scanner to open the - * file, we can't delete it then. Keep trying for half a - * second. */ - { - int try; - - for (try = 0; try < 10; ++try) - { - if (mch_lstat((char *)IObuff, &st) < 0) - break; - ui_delay(50L, TRUE); /* wait 50 msec */ - mch_remove(IObuff); - } - } -# endif - } - } + if (can_write_dir == 0) + backup_copy = TRUE; } /* @@ -4029,6 +4020,8 @@ buf_write( } } + if (fsync(bfd) < 0) + errmsg = _("E999: Error fsyncing \"%s\""); if (close(bfd) < 0 && errmsg == NULL) errmsg = (char_u *)_("E507: Close error for backup file (add ! to override)"); if (write_info.bw_len < 0) @@ -4139,8 +4132,13 @@ buf_write( * If the renaming of the original file to the backup file * works, quit here. */ - if (vim_rename(fname, backup) == 0) + ret = vim_copy(fname, backup); + if (ret == 0) break; + else if (ret == 1) { + backup_linked = 1; + break; + } vim_free(backup); /* don't do the rename below */ backup = NULL; @@ -4370,6 +4368,17 @@ buf_write( } else { + if ((wfname == fname) && can_write_dir && !append) { + snprintf(wftmp, sizeof(wftmp), "%s.vimtemp-XXXXXX", wfname); + fd = mkstemp(wftmp); + if (fd == -1) { + errmsg = (char_u *)_("E212: Can't open file for writing"); + } + wfname_orig = wfname; + wfname = wftmp; + } else { + if (backup_linked) mch_remove(fname); + /* * Open the file "wfname" for writing. * We may try to open the file twice: If we can't write to the file @@ -4428,8 +4437,6 @@ buf_write( restore_backup: { - stat_T st; - /* * If we failed to open the file, we don't need a backup. * Throw it away. If we moved or removed the original file @@ -4468,10 +4475,13 @@ restore_backup: } #ifdef FEAT_MBYTE - if (wfname != fname) + if ((wfname != fname) && (wfname != wftmp)) { vim_free(wfname); + wfname = NULL; + } #endif goto fail; + } } write_info.bw_fd = fd; @@ -4749,8 +4759,6 @@ restore_backup: if (backup != NULL && !backup_copy) { # ifdef HAVE_FCHOWN - stat_T st; - /* don't change the owner when it's already OK, some systems remove * permission or ACL stuff */ if (mch_stat((char *)wfname, &st) < 0 @@ -4804,7 +4812,7 @@ restore_backup: #endif #if defined(FEAT_MBYTE) && defined(FEAT_EVAL) - if (wfname != fname) + if ((wfname != fname) && (wfname != wftmp)) { /* * The file was written to a temp file, now it needs to be @@ -4825,6 +4833,12 @@ restore_backup: #endif } + if (wfname_orig) { + if (mch_rename(wftmp, wfname_orig) == -1) { + end = 0; + } + } + if (end == 0) { /* @@ -4889,7 +4903,8 @@ restore_backup: if (buf_write_bytes(&write_info) == FAIL) break; - if (close(write_info.bw_fd) >= 0 + if ((fsync(write_info.bw_fd) == 0) && + (close(write_info.bw_fd) >= 0) && write_info.bw_len == 0) end = 1; /* success */ } @@ -5017,8 +5032,6 @@ restore_backup: if (backup != NULL) { - stat_T st; - /* * If the original file does not exist yet * the current backup file becomes the original file @@ -6510,23 +6523,21 @@ tag_fgets(char_u *buf, int size, FILE *f * function will (attempts to?) copy the file across if rename fails -- webb * Return -1 for failure, 0 for success. */ - int -vim_rename(char_u *from, char_u *to) +static int +vim_rename_copy(char_u *from, char_u *to, int which) { int fd_in; int fd_out; int n; char *errmsg = NULL; char *buffer; -#ifdef AMIGA - BPTR flock; -#endif stat_T st; long perm; #ifdef HAVE_ACL vim_acl_T acl; /* ACL from original file */ #endif - int use_tmp_file = FALSE; + char_u *totemp; + size_t totemplen; /* * When the names are identical, there is nothing to do. When they refer @@ -6535,9 +6546,6 @@ vim_rename(char_u *from, char_u *to) */ if (fnamecmp(from, to) == 0) { - if (p_fic && STRCMP(gettail(from), gettail(to)) != 0) - use_tmp_file = TRUE; - else return 0; } @@ -6547,106 +6555,36 @@ vim_rename(char_u *from, char_u *to) if (mch_stat((char *)from, &st) < 0) return -1; -#ifdef UNIX - { - stat_T st_to; + if (which == 0) { + if (mch_rename((char *)from, (char *)to) == 0) + return 0; + } else { + mch_remove(to); + if (link(from, to) == 0) + return 1; + } + totemplen = strlen(to) + 13; + totemp = alloc(totemplen); + if (totemp == NULL) + return -1; + snprintf(totemp, totemplen, "%s.tmp.XXXXXX", to); - /* It's possible for the source and destination to be the same file. - * This happens when "from" and "to" differ in case and are on a FAT32 - * filesystem. In that case go through a temp file name. */ - if (mch_stat((char *)to, &st_to) >= 0 - && st.st_dev == st_to.st_dev - && st.st_ino == st_to.st_ino) - use_tmp_file = TRUE; - } -#endif -#ifdef WIN3264 - { - BY_HANDLE_FILE_INFORMATION info1, info2; - - /* It's possible for the source and destination to be the same file. - * In that case go through a temp file name. This makes rename("foo", - * "./foo") a no-op (in a complicated way). */ - if (win32_fileinfo(from, &info1) == FILEINFO_OK - && win32_fileinfo(to, &info2) == FILEINFO_OK - && info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber - && info1.nFileIndexHigh == info2.nFileIndexHigh - && info1.nFileIndexLow == info2.nFileIndexLow) - use_tmp_file = TRUE; - } -#endif - - if (use_tmp_file) - { - char tempname[MAXPATHL + 1]; - - /* - * Find a name that doesn't exist and is in the same directory. - * Rename "from" to "tempname" and then rename "tempname" to "to". - */ - if (STRLEN(from) >= MAXPATHL - 5) - return -1; - STRCPY(tempname, from); - for (n = 123; n < 99999; ++n) - { - sprintf((char *)gettail((char_u *)tempname), "%d", n); - if (mch_stat(tempname, &st) < 0) - { - if (mch_rename((char *)from, tempname) == 0) - { - if (mch_rename(tempname, (char *)to) == 0) - return 0; - /* Strange, the second step failed. Try moving the - * file back and return failure. */ - mch_rename(tempname, (char *)from); - return -1; - } - /* If it fails for one temp name it will most likely fail - * for any temp name, give up. */ - return -1; - } - } - return -1; + fd_in = mch_open((char *)from, O_RDONLY|O_EXTRA, 0); + if (fd_in == -1) { + vim_free(totemp); + return -1; } /* - * Delete the "to" file, this is required on some systems to make the - * mch_rename() work, on other systems it makes sure that we don't have - * two files when the mch_rename() fails. - */ - -#ifdef AMIGA - /* - * With MSDOS-compatible filesystems (crossdos, messydos) it is possible - * that the name of the "to" file is the same as the "from" file, even - * though the names are different. To avoid the chance of accidentally - * deleting the "from" file (horror!) we lock it during the remove. - * - * When used for making a backup before writing the file: This should not - * happen with ":w", because startscript() should detect this problem and - * set buf->b_shortname, causing modname() to return a correct ".bak" file - * name. 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 *)from, (long)ACCESS_READ); -#endif - mch_remove(to); -#ifdef AMIGA - if (flock) - UnLock(flock); -#endif - - /* - * First try a normal rename, return if it works. - */ - if (mch_rename((char *)from, (char *)to) == 0) - return 0; - - /* - * Rename() failed, try copying the file. + * Try copying the file */ perm = mch_getperm(from); + if (perm == -1) { + vim_free(totemp); + close(fd_in); + return -1; + } + #ifdef HAVE_ACL /* For systems that support ACL: get the ACL from the original file. */ acl = mch_get_acl(from); @@ -6657,20 +6595,22 @@ vim_rename(char_u *from, char_u *to) #ifdef HAVE_ACL mch_free_acl(acl); #endif + vim_free(totemp); return -1; } /* Create the new file with same permissions as the original. */ - fd_out = mch_open((char *)to, - O_CREAT|O_EXCL|O_WRONLY|O_EXTRA|O_NOFOLLOW, (int)perm); + fd_out = mkstemp(totemp); if (fd_out == -1) { close(fd_in); #ifdef HAVE_ACL mch_free_acl(acl); #endif + vim_free(totemp); return -1; } + fchmod(fd_out, perm); buffer = (char *)alloc(BUFSIZE); if (buffer == NULL) @@ -6692,32 +6632,73 @@ vim_rename(char_u *from, char_u *to) vim_free(buffer); close(fd_in); - if (close(fd_out) < 0) - errmsg = _("E209: Error closing \"%s\""); if (n < 0) { errmsg = _("E210: Error reading \"%s\""); - to = from; + to = from; /* XXX */ } #ifndef UNIX /* for Unix mch_open() already set the permission */ - mch_setperm(to, perm); + mch_setperm(totemp, perm); #endif #ifdef HAVE_ACL - mch_set_acl(to, acl); + mch_set_acl(totemp, acl); mch_free_acl(acl); #endif #if defined(HAVE_SELINUX) || defined(HAVE_SMACK) - mch_copy_sec(from, to); + mch_copy_sec(from, totemp); #endif + if (fsync(fd_out) != 0) + { + errmsg = _("E999: Error fsyncing \"%s\""); + close(fd_out); + } else { + if (close(fd_out) < 0) + errmsg = _("E209: Error closing \"%s\""); + } + + if (mch_rename(totemp, to) == -1) { + errmsg = _("E999: Error renaming \"%s\""); + mch_remove(totemp); + } + vim_free(totemp); + + if (errmsg == NULL && which == 0) { + if (mch_remove(from) == -1) + errmsg = _("E999: Error removing \"%s\""); + } + if (errmsg != NULL) { EMSG2(errmsg, to); return -1; } - mch_remove(from); + return 0; } +/* + * 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, or 1 for success, + * if which==1 and 'from' was link(2)ed to 'to'. + * + */ + int +vim_rename(from, to) + char_u *from; + char_u *to; +{ + return vim_rename_copy(from, to, 0); +} + + int +vim_copy(from, to) + char_u *from; + char_u *to; +{ + return vim_rename_copy(from, to, 1); +} + static int already_warned = FALSE; /*