--- vim81/src/bufwrite.c.fsync 2019-09-28 22:11:56.000000000 +0200 +++ vim81/src/bufwrite.c 2019-09-29 18:32:49.594025168 +0200 @@ -19,6 +19,8 @@ #define SMALLBUFSIZE 256 // size of emergency write buffer +extern int vim_copy(char_u *from, char_u *to); + /* * Structure to pass arguments from buf_write() to buf_write_bytes(). */ @@ -632,8 +634,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; @@ -677,6 +683,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; @@ -1014,10 +1025,17 @@ buf_write( st_old.st_dev = 0; st_old.st_ino = 0; perm = -1; - if (mch_stat((char *)fname, &st_old) < 0) + if (mch_stat((char *)fname, &st_old) < 0) { newfile = TRUE; - else - { + 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 { @@ -1039,6 +1057,33 @@ 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 // Check for a writable device name. @@ -1141,17 +1186,11 @@ buf_write( // off. This helps when editing large files on almost-full disks. if (!(append && *p_pm == NUL) && !filtering && perm >= 0 && dobackup) { -#if defined(UNIX) || defined(MSWIN) - stat_T st; -#endif - if ((bkc & BKC_YES) || append) // "yes" backup_copy = TRUE; #if defined(UNIX) || defined(MSWIN) else if ((bkc & BKC_AUTO)) // "auto" { - int i; - # ifdef UNIX // Don't rename the file when: // - it's a hard link @@ -1177,56 +1216,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 - vim_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; } // Break symlinks and/or hardlinks if we've been asked to. @@ -1462,6 +1453,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) @@ -1571,8 +1564,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_CLEAR(backup); // don't do the rename below } @@ -1763,6 +1761,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); + #ifdef HAVE_FTRUNCATE # define TRUNC_ON_OPEN 0 #else @@ -1821,8 +1830,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 // try to put the backup in its place. @@ -1855,10 +1862,13 @@ restore_backup: end = 0; } - if (wfname != fname) + if ((wfname != fname) && (wfname != wftmp)) { vim_free(wfname); + wfname = NULL; + } goto fail; } + } write_info.bw_fd = fd; #if defined(UNIX) @@ -2202,7 +2212,7 @@ restore_backup: #endif #if defined(FEAT_EVAL) - if (wfname != fname) + if ((wfname != fname) && (wfname != wftmp)) { // The file was written to a temp file, now it needs to be // converted with 'charconvert' to (overwrite) the output file. @@ -2221,6 +2231,9 @@ restore_backup: #endif } + if (wfname_orig && (mch_rename(wftmp, wfname_orig) == -1)) + end = 0; + if (end == 0) { // Error encountered. @@ -2272,13 +2285,15 @@ restore_backup: write_info.bw_buf = smallbuf; write_info.bw_flags = FIO_NOCONVERT; while ((write_info.bw_len = read_eintr(fd, smallbuf, - SMALLBUFSIZE)) > 0) + SMALLBUFSIZE)) > 0) { if (buf_write_bytes(&write_info) == FAIL) break; + } - if (close(write_info.bw_fd) >= 0 - && write_info.bw_len == 0) - end = 1; // success + if ((fsync(write_info.bw_fd) == 0) + && (close(write_info.bw_fd) >= 0) + && write_info.bw_len == 0) + end = 1; // success } close(fd); // ignore errors for closing read file } @@ -2392,8 +2407,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 if (org == NULL) --- vim81/src/vim.h.fsync 2019-09-28 22:11:56.000000000 +0200 +++ vim81/src/vim.h 2019-09-29 11:27:45.810315054 +0200 @@ -9,6 +9,11 @@ #ifndef VIM__H # define VIM__H +/* O_TMPFILE */ +#if defined(__linux__) && !defined(_GNU_SOURCE) +# define _GNU_SOURCE +#endif + #include "protodef.h" // _WIN32 is defined as 1 when the compilation target is 32-bit or 64-bit. @@ -1741,6 +1746,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). */ --- vim81/src/fileio.c.fsync 2019-12-08 18:41:34.000000000 +0100 +++ vim81/src/fileio.c 2019-12-08 19:00:35.597414621 +0100 @@ -3615,23 +3615,21 @@ vim_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 @@ -3640,9 +3638,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; } @@ -3651,107 +3646,36 @@ vim_rename(char_u *from, char_u *to) */ if (mch_stat((char *)from, &st) < 0) return -1; - -#ifdef UNIX - { - stat_T st_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 MSWIN - { - 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; + if (which == 0) { + if (mch_rename((char *)from, (char *)to) == 0) + return 0; + } else { + mch_remove(to); + if (link(from, to) == 0) + return 1; } -#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; - } - } + totemplen = strlen(to) + 13; + totemp = alloc(totemplen); + if (totemp == NULL) 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 + snprintf(totemp, totemplen, "%s.tmp.XXXXXX", to); - /* - * First try a normal rename, return if it works. - */ - if (mch_rename((char *)from, (char *)to) == 0) - return 0; + fd_in = mch_open((char *)from, O_RDONLY|O_EXTRA, 0); + if (fd_in == -1) { + vim_free(totemp); + return -1; + } /* - * 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); @@ -3762,20 +3686,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); + /* Create the new file with same permissions as the original. */ + 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 = alloc(WRITEBUFSIZE); if (buffer == NULL) @@ -3797,32 +3723,72 @@ 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; } -#ifndef UNIX // for Unix mch_open() already set the permission - mch_setperm(to, perm); +#ifndef UNIX /* for Unix mch_open() already set the permission */ + 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) { semsg(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; /*