--- vim73/src/fileio.c.orig 2011-01-28 17:36:55.591902162 +0200 +++ vim73/src/fileio.c 2011-01-28 17:36:58.270261028 +0200 @@ -41,6 +41,9 @@ static int crypt_seed_len[] = {0, 8}; /* 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(S_ISCHR) # define OPEN_CHR_FILES static int is_dev_fd_file(char_u *fname); @@ -3104,7 +3107,6 @@ check_file_readonly(fname, perm) ); } - /* * buf_write() - write to file "fname" lines "start" through "end" * @@ -3139,8 +3141,11 @@ buf_write(buf, fname, sfname, start, end 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; @@ -3194,6 +3199,10 @@ buf_write(buf, fname, sfname, start, end int write_undo_file = FALSE; context_sha256_T sha_ctx; #endif + int ret; +#if defined(UNIX) || defined(WIN32) + struct stat st; +#endif if (fname == NULL || *fname == NUL) /* safety check */ return FAIL; @@ -3530,10 +3539,20 @@ buf_write(buf, fname, sfname, start, end 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 */ { @@ -3555,6 +3574,35 @@ buf_write(buf, fname, sfname, start, end 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 */ /* @@ -3671,17 +3719,11 @@ buf_write(buf, fname, sfname, start, end */ if (!(append && *p_pm == NUL) && !filtering && perm >= 0 && dobackup) { -#if defined(UNIX) || defined(WIN32) - struct stat st; -#endif - if ((bkc_flags & BKC_YES) || append) /* "yes" */ backup_copy = TRUE; #if defined(UNIX) || defined(WIN32) else if ((bkc_flags & BKC_AUTO)) /* "auto" */ { - int i; - # ifdef UNIX /* * Don't rename the file when: @@ -3709,58 +3751,8 @@ buf_write(buf, fname, sfname, start, end 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; } # ifdef UNIX @@ -3813,7 +3805,6 @@ buf_write(buf, fname, sfname, start, end #if defined(UNIX) && !defined(SHORT_FNAME) int did_set_shortname; #endif - copybuf = alloc(BUFSIZE + 1); if (copybuf == NULL) { @@ -4008,6 +3999,8 @@ buf_write(buf, fname, sfname, start, end } } + 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) @@ -4123,8 +4116,13 @@ buf_write(buf, fname, sfname, start, end * 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; @@ -4328,101 +4326,109 @@ buf_write(buf, fname, sfname, start, end } #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 - struct stat 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: - { - struct stat 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. - */ - if (backup != NULL && wfname == fname) + restore_backup: { - if (backup_copy) + /* + * 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 && wfname == fname) { - /* - * 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. - */ - /* If file does not exist, put the copy in its place */ - if (mch_stat((char *)fname, &st) < 0) + if (backup_copy) + { + /* + * 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. + */ + /* 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); + } + else + { + /* try to put the original file back */ vim_rename(backup, fname); - /* if original file does exist throw away the copy */ - if (mch_stat((char *)fname, &st) >= 0) - mch_remove(backup); - } - else - { - /* try to put the original file back */ - vim_rename(backup, fname); + } } + + /* if original file no longer exists give an extra warning */ + if (!newfile && mch_stat((char *)fname, &st) < 0) + end = 0; } - /* if original file no longer exists give an extra warning */ - if (!newfile && mch_stat((char *)fname, &st) < 0) - end = 0; + #ifdef FEAT_MBYTE + if ((wfname != fname) && (wfname != wftmp)) + vim_free(wfname); + #endif + goto fail; } - -#ifdef FEAT_MBYTE - if (wfname != fname) - vim_free(wfname); -#endif - goto fail; } errmsg = NULL; @@ -4679,8 +4685,6 @@ restore_backup: if (backup != NULL && !backup_copy) { # ifdef HAVE_FCHOWN - struct stat 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 @@ -4729,7 +4733,7 @@ restore_backup: #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 @@ -4749,6 +4753,12 @@ restore_backup: } #endif + if (wfname == wftmp) { + if (mch_rename(wfname, fname) == -1) { + end = 0; + } + } + if (end == 0) { if (errmsg == NULL) @@ -4810,7 +4820,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 */ } @@ -4936,8 +4947,6 @@ restore_backup: if (backup != NULL) { - struct stat st; - /* * If the original file does not exist yet * the current backup file becomes the original file @@ -6475,32 +6484,25 @@ tag_fgets(buf, size, fp) } #endif -/* - * 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) +static int +vim_rename_copy(from, to, which) 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 struct stat st; long perm; #ifdef HAVE_ACL vim_acl_T acl; /* ACL from original file */ #endif -#if defined(UNIX) || defined(CASE_INSENSITIVE_FILENAME) - int use_tmp_file = FALSE; -#endif + char_u *totemp; + size_t totemplen; + int ret; /* * When the names are identical, there is nothing to do. When they refer @@ -6509,11 +6511,6 @@ vim_rename(from, to) */ if (fnamecmp(from, to) == 0) { -#ifdef CASE_INSENSITIVE_FILENAME - if (STRCMP(gettail(from), gettail(to)) != 0) - use_tmp_file = TRUE; - else -#endif return 0; } @@ -6523,117 +6520,63 @@ vim_rename(from, to) if (mch_stat((char *)from, &st) < 0) return -1; -#ifdef UNIX - { - struct stat 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 - -#if defined(UNIX) || defined(CASE_INSENSITIVE_FILENAME) - 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; } -#endif - - /* - * 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); #endif + fd_in = mch_open((char *)from, O_RDONLY|O_EXTRA, 0); if (fd_in == -1) { #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) @@ -6655,29 +6598,73 @@ vim_rename(from, to) vim_free(buffer); close(fd_in); - if (close(fd_out) < 0) - errmsg = _("E209: Error closing \"%s\""); + +#ifndef UNIX /* for Unix mch_open() already set the permission */ + mch_setperm(totemp, perm); +#endif +#ifdef HAVE_ACL + mch_set_acl(totemp, acl); + mch_free_acl(acl); +#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 (n < 0) { errmsg = _("E210: Error reading \"%s\""); to = from; } -#ifndef UNIX /* for Unix mch_open() already set the permission */ - mch_setperm(to, perm); -#endif -#ifdef HAVE_ACL - mch_set_acl(to, acl); - mch_free_acl(acl); -#endif + + 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; /* --- vim73/src/vim.h.orig 2011-01-28 17:36:54.301729296 +0200 +++ vim73/src/vim.h 2011-01-28 20:46:26.187076408 +0200 @@ -1667,6 +1667,8 @@ int vim_memcmp __ARGS((void *, void *, s # 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). */