diff options
-rw-r--r-- | src/eval.c | 13 | ||||
-rw-r--r-- | src/ex_cmds.c | 2 | ||||
-rw-r--r-- | src/fileio.c | 175 | ||||
-rw-r--r-- | src/fileio.h | 1 | ||||
-rw-r--r-- | src/memline.c | 2 |
5 files changed, 178 insertions, 15 deletions
diff --git a/src/eval.c b/src/eval.c index 75245fc148..94eca72b4e 100644 --- a/src/eval.c +++ b/src/eval.c @@ -12329,14 +12329,13 @@ static void f_remove(typval_T *argvars, typval_T *rettv) */ static void f_rename(typval_T *argvars, typval_T *rettv) { - if (check_restricted() || check_secure()) { + char_u buf[NUMBUFLEN]; + + if (check_restricted() || check_secure()) rettv->vval.v_number = -1; - } else { - char_u buf[NUMBUFLEN]; - char_u *from = get_tv_string(&argvars[0]); - char_u *to = get_tv_string_buf(&argvars[1], buf); - rettv->vval.v_number = os_rename(from, to) == OK ? 0 : -1; - } + else + rettv->vval.v_number = vim_rename(get_tv_string(&argvars[0]), + get_tv_string_buf(&argvars[1], buf)); } /* diff --git a/src/ex_cmds.c b/src/ex_cmds.c index 0c5ac107bf..d8839d213c 100644 --- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -1711,7 +1711,7 @@ void write_viminfo(char_u *file, int forceit) * In case of an error keep the original viminfo file. * Otherwise rename the newly written file. */ - if (viminfo_errcnt || os_rename(tempname, fname) == FAIL) + if (viminfo_errcnt || vim_rename(tempname, fname) == -1) mch_remove(tempname); } diff --git a/src/fileio.c b/src/fileio.c index c32291b42a..9ab8090f51 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -3314,7 +3314,7 @@ nobackup: * If the renaming of the original file to the backup file * works, quit here. */ - if (os_rename(fname, backup) == OK) + if (vim_rename(fname, backup) == 0) break; vim_free(backup); /* don't do the rename below */ @@ -3520,18 +3520,18 @@ restore_backup: /* * There is a small chance that we removed the original, * try to move the copy in its place. - * This may not work if the os_rename() fails. + * 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) - os_rename(backup, fname); + 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 */ - os_rename(backup, fname); + vim_rename(backup, fname); } } @@ -3827,7 +3827,7 @@ restore_backup: close(fd); /* ignore errors for closing read file */ } } else { - if (os_rename(backup, fname) == OK) + if (vim_rename(backup, fname) == 0) end = 1; } } @@ -3934,7 +3934,7 @@ restore_backup: if (org == NULL) EMSG(_("E205: Patchmode: can't save original file")); else if (mch_stat(org, &st) < 0) { - os_rename(backup, (char_u *)org); + vim_rename(backup, (char_u *)org); vim_free(backup); /* don't delete the file */ backup = NULL; #ifdef UNIX @@ -4969,6 +4969,169 @@ int tag_fgets(char_u *buf, int size, FILE *fp) } #endif +/* + * os_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(char_u *from, char_u *to) +{ + int fd_in; + int fd_out; + int n; + char *errmsg = NULL; + char *buffer; + struct stat st; + long perm; +#ifdef HAVE_ACL + vim_acl_T acl; /* ACL from original file */ +#endif + int use_tmp_file = FALSE; + + /* + * When the names are identical, there is nothing to do. When they refer + * to the same file (ignoring case and slash/backslash differences) but + * the file name differs we need to go through a temp file. + */ + if (fnamecmp(from, to) == 0) { + if (p_fic && STRCMP(path_tail(from), path_tail(to)) != 0) + use_tmp_file = TRUE; + else + return 0; + } + + /* + * Fail if the "from" file doesn't exist. Avoids that "to" is deleted. + */ + if (mch_stat((char *)from, &st) < 0) + return -1; + +#ifdef UNIX + { + struct stat 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 + + if (use_tmp_file) { + char_u 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 *)path_tail(tempname), "%d", n); + if (os_file_exists(tempname) == FALSE) { + if (os_rename(from, tempname) == OK) { + if (os_rename(tempname, to) == OK) + return 0; + /* Strange, the second step failed. Try moving the + * file back and return failure. */ + os_rename(tempname, 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; + } + + /* + * Delete the "to" file, this is required on some systems to make the + * os_rename() work, on other systems it makes sure that we don't have + * two files when the os_rename() fails. + */ + + mch_remove(to); + + /* + * First try a normal rename, return if it works. + */ + if (os_rename(from, to) == OK) + return 0; + + /* + * Rename() failed, try copying the file. + */ + perm = os_getperm(from); +#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 + 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); + if (fd_out == -1) { + close(fd_in); +#ifdef HAVE_ACL + mch_free_acl(acl); +#endif + return -1; + } + + buffer = (char *)alloc(BUFSIZE); + if (buffer == NULL) { + close(fd_out); + close(fd_in); +#ifdef HAVE_ACL + mch_free_acl(acl); +#endif + return -1; + } + + while ((n = read_eintr(fd_in, buffer, BUFSIZE)) > 0) + if (write_eintr(fd_out, buffer, n) != n) { + errmsg = _("E208: Error writing to \"%s\""); + break; + } + + 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 */ + os_setperm(to, perm); +#endif +#ifdef HAVE_ACL + mch_set_acl(to, acl); + mch_free_acl(acl); +#endif +#ifdef HAVE_SELINUX + mch_copy_sec(from, to); +#endif + if (errmsg != NULL) { + EMSG2(errmsg, to); + return -1; + } + mch_remove(from); + return 0; +} + static int already_warned = FALSE; /* diff --git a/src/fileio.h b/src/fileio.h index ed9119dc7b..ce04b53e1e 100644 --- a/src/fileio.h +++ b/src/fileio.h @@ -39,6 +39,7 @@ char_u *buf_modname(int shortname, char_u *fname, char_u *ext, int prepend_dot); int vim_fgets(char_u *buf, int size, FILE *fp); int tag_fgets(char_u *buf, int size, FILE *fp); +int vim_rename(char_u *from, char_u *to); int check_timestamps(int focus); int buf_check_timestamp(buf_T *buf, int focus); void buf_reload(buf_T *buf, int orig_mode); diff --git a/src/memline.c b/src/memline.c index b1bd7e056f..fb5da647d8 100644 --- a/src/memline.c +++ b/src/memline.c @@ -598,7 +598,7 @@ void ml_setname(buf_T *buf) } /* try to rename the swap file */ - if (os_rename(mfp->mf_fname, fname) == OK) { + if (vim_rename(mfp->mf_fname, fname) == 0) { success = TRUE; vim_free(mfp->mf_fname); mfp->mf_fname = fname; |