--- vim74/src/vim.h.fsync 2016-07-04 11:40:58.000000000 +0300 +++ vim74/src/vim.h 2016-07-04 12:09:54.860240203 +0300 @@ -1744,6 +1744,8 @@ int vim_memcmp(void *, void *, 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). */ --- vim74/src/fileio.c.fsync 2016-07-04 11:40:58.000000000 +0300 +++ vim74/src/fileio.c 2016-07-04 12:47:55.036669379 +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); + #if (defined(sun) || defined(__FreeBSD__)) && defined(S_ISCHR) # define OPEN_CHR_FILES static int is_dev_fd_file(char_u *fname); @@ -3089,7 +3092,6 @@ check_file_readonly( ); } - /* * buf_write() - write to file "fname" lines "start" through "end" * @@ -3124,8 +3126,11 @@ 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 wftmp[MAXPATHL+1]; char_u *s; char_u *ptr; char_u c; @@ -3174,6 +3179,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; @@ -3533,10 +3543,20 @@ 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((char *)gettail(IObuff), "%s", "vimfiletest.XXXXXX"); + fd = mkstemp(IObuff); + if (fd != -1) { + can_write_dir = 1; + /* Close the file before removing it, on MS-Windows we + * can't delete an open file. */ + close(fd); + mch_remove(IObuff); + } + } else { perm = st_old.st_mode; if (!S_ISREG(st_old.st_mode)) /* not a file */ { @@ -3558,6 +3578,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((char *)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 */ /* @@ -3674,17 +3722,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: @@ -3712,58 +3754,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 != 1) + backup_copy = TRUE; } /* @@ -4008,6 +4000,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) @@ -4118,8 +4112,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; @@ -4323,62 +4322,69 @@ buf_write( } #endif - /* - * Open the file "wfname" for writing. - * 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 = mch_open((char *)wfname, O_WRONLY | O_EXTRA | (append - ? (forceit ? (O_APPEND | O_CREAT) : O_APPEND) - : (O_CREAT | O_TRUNC)) - , perm < 0 ? 0666 : (perm & 0777))) < 0) - { + if ((wfname == fname) && can_write_dir && !append) { + snprintf(wftmp, sizeof(wftmp), "%s.XXXXXX", wfname); + wfname = wftmp; + fd = mkstemp(wftmp); + if (fd == -1) { + errmsg = (char_u *)_("E212: Can't open file for writing"); + } + } else { /* - * 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 mch_remove() will fail. + * Open the file "wfname" for writing. + * 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. */ - if (errmsg == NULL) - { -#ifdef UNIX - stat_T st; + if (backup_linked) mch_remove(fname); - /* Don't delete the file when it's a hard or symbolic link. */ - if ((!newfile && st_old.st_nlink > 1) - || (mch_lstat((char *)fname, &st) == 0 - && (st.st_dev != st_old.st_dev - || st.st_ino != st_old.st_ino))) - errmsg = (char_u *)_("E166: Can't open linked file for writing"); - else -#endif + while ((fd = mch_open((char *)wfname, O_WRONLY | O_EXTRA | (append + ? (forceit ? (O_APPEND | O_CREAT) : O_APPEND) + : (O_CREAT | O_TRUNC)) + , perm < 0 ? 0666 : (perm & 0777))) < 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 mch_remove() will fail. + */ + if (errmsg == NULL) { - errmsg = (char_u *)_("E212: Can't open file for writing"); - if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL - && perm >= 0) + #ifdef UNIX + /* Don't delete the file when it's a hard or symbolic link. */ + if ((!newfile && st_old.st_nlink > 1) + || (mch_lstat((char *)fname, &st) == 0 + && (st.st_dev != st_old.st_dev + || st.st_ino != st_old.st_ino))) + errmsg = (char_u *)_("E166: Can't open linked file for writing"); + else + #endif { -#ifdef UNIX - /* we write to the file, thus it should be marked - writable after all */ - if (!(perm & 0200)) - made_writable = TRUE; - perm |= 0200; - if (st_old.st_uid != getuid() || st_old.st_gid != getgid()) - perm &= 0777; -#endif - if (!append) /* don't remove when appending */ - mch_remove(wfname); - continue; + errmsg = (char_u *)_("E212: Can't open file for writing"); + if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL + && perm >= 0) + { + #ifdef UNIX + /* we write to the file, thus it should be marked + writable after all */ + if (!(perm & 0200)) + made_writable = TRUE; + perm |= 0200; + if (st_old.st_uid != getuid() || st_old.st_gid != getgid()) + perm &= 0777; + #endif + if (!append) /* don't remove when appending */ + mch_remove(wfname); + continue; + } } } } -restore_backup: + 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 @@ -4397,6 +4403,7 @@ restore_backup: /* If file does not exist, put the copy in its place */ if (mch_stat((char *)fname, &st) < 0) vim_rename(backup, fname); + /* if original file does exist throw away the copy */ if (mch_stat((char *)fname, &st) >= 0) mch_remove(backup); @@ -4404,7 +4411,7 @@ restore_backup: else { /* try to put the original file back */ - vim_rename(backup, fname); + vim_rename(backup, fname); } } @@ -4414,7 +4421,7 @@ restore_backup: } #ifdef FEAT_MBYTE - if (wfname != fname) + if ((wfname != fname) && (wfname != wftmp)) vim_free(wfname); #endif goto fail; @@ -4674,8 +4681,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 @@ -4721,7 +4726,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 converted @@ -4741,6 +4746,12 @@ restore_backup: } #endif + if (wfname == wftmp) { + if (mch_rename(wfname, fname) == -1) { + end = 0; + } + } + if (end == 0) { if (errmsg == NULL) @@ -4802,7 +4813,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 */ } @@ -4930,8 +4942,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 @@ -6419,23 +6429,22 @@ 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; + int ret; /* * When the names are identical, there is nothing to do. When they refer @@ -6444,9 +6453,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; } @@ -6456,106 +6462,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 WIN3264 - { - BY_HANDLE_FILE_INFORMATION info1, info2; + 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. - * 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); @@ -6566,20 +6502,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) @@ -6601,32 +6539,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; /*