vim-patch:8.1.1371: cannot recover from a swap file #11081

Problem:    Cannot recover from a swap file.
Solution:   Do not expand environment variables in the swap file name.
            Do not check the extension when we already know a file is a swap
            file.  (Ken Takata, closes 4415, closes vim/vim#4369)
99499b1c05
This commit is contained in:
Jurica Bradarić 2019-10-06 05:35:48 +02:00 committed by Justin M. Keyes
parent 1396cc9abb
commit fe074611cd
14 changed files with 143 additions and 42 deletions

View File

@ -929,7 +929,7 @@ void handle_swap_exists(bufref_T *old_curbuf)
// User selected Recover at ATTENTION prompt.
msg_scroll = true;
ml_recover();
ml_recover(false);
MSG_PUTS("\n"); // don't overwrite the last message
cmdline_row = msg_row;
do_modelines(0);
@ -4629,7 +4629,8 @@ do_arg_all(
if (i < alist->al_ga.ga_len
&& (AARGLIST(alist)[i].ae_fnum == buf->b_fnum
|| path_full_compare(alist_name(&AARGLIST(alist)[i]),
buf->b_ffname, true) & kEqualFiles)) {
buf->b_ffname,
true, true) & kEqualFiles)) {
int weight = 1;
if (old_curtab == curtab) {

View File

@ -5023,7 +5023,7 @@ void fix_help_buffer(void)
copy_option_part(&p, NameBuff, MAXPATHL, ",");
char_u *const rt = (char_u *)vim_getenv("VIMRUNTIME");
if (rt != NULL
&& path_full_compare(rt, NameBuff, false) != kEqualFiles) {
&& path_full_compare(rt, NameBuff, false, true) != kEqualFiles) {
int fcount;
char_u **fnames;
char_u *s;
@ -5233,7 +5233,7 @@ static void helptags_one(char_u *const dir, const char_u *const ext,
ga_init(&ga, (int)sizeof(char_u *), 100);
if (add_help_tags
|| path_full_compare((char_u *)"$VIMRUNTIME/doc",
dir, false) == kEqualFiles) {
dir, false, true) == kEqualFiles) {
s = xmalloc(18 + STRLEN(tagfname));
sprintf((char *)s, "help-tags\t%s\t1\n", tagfname);
GA_APPEND(char_u *, &ga, s);

View File

@ -1743,7 +1743,7 @@ static bool editing_arg_idx(win_T *win)
&& (win->w_buffer->b_ffname == NULL
|| !(path_full_compare(
alist_name(&WARGLIST(win)[win->w_arg_idx]),
win->w_buffer->b_ffname, true) & kEqualFiles))));
win->w_buffer->b_ffname, true, true) & kEqualFiles))));
}
/// Check if window "win" is editing the w_arg_idx file in its argument list.
@ -1761,7 +1761,7 @@ void check_arg_idx(win_T *win)
&& (win->w_buffer->b_fnum == GARGLIST[GARGCOUNT - 1].ae_fnum
|| (win->w_buffer->b_ffname != NULL
&& (path_full_compare(alist_name(&GARGLIST[GARGCOUNT - 1]),
win->w_buffer->b_ffname, true)
win->w_buffer->b_ffname, true, true)
& kEqualFiles)))) {
arg_had_last = true;
}

View File

@ -6708,17 +6708,18 @@ static void ex_preserve(exarg_T *eap)
/// ":recover".
static void ex_recover(exarg_T *eap)
{
/* Set recoverymode right away to avoid the ATTENTION prompt. */
recoverymode = TRUE;
// Set recoverymode right away to avoid the ATTENTION prompt.
recoverymode = true;
if (!check_changed(curbuf, (p_awa ? CCGD_AW : 0)
| CCGD_MULTWIN
| (eap->forceit ? CCGD_FORCEIT : 0)
| CCGD_EXCMD)
&& (*eap->arg == NUL
|| setfname(curbuf, eap->arg, NULL, TRUE) == OK))
ml_recover();
recoverymode = FALSE;
|| setfname(curbuf, eap->arg, NULL, true) == OK)) {
ml_recover(true);
}
recoverymode = false;
}
/*

View File

@ -1460,12 +1460,13 @@ static void create_windows(mparm_T *parmp)
} else
parmp->window_count = 1;
if (recoverymode) { /* do recover */
msg_scroll = TRUE; /* scroll message up */
ml_recover();
if (curbuf->b_ml.ml_mfp == NULL) /* failed */
if (recoverymode) { // do recover
msg_scroll = true; // scroll message up
ml_recover(true);
if (curbuf->b_ml.ml_mfp == NULL) { // failed
getout(1);
do_modelines(0); /* do modelines */
}
do_modelines(0); // do modelines
} else {
// Open a buffer for windows that don't have one yet.
// Commands in the vimrc might have loaded a file or split the window.
@ -1778,7 +1779,8 @@ static bool do_user_initialization(void)
if (do_source(user_vimrc, true, DOSO_VIMRC) != FAIL) {
do_exrc = p_exrc;
if (do_exrc) {
do_exrc = (path_full_compare((char_u *)VIMRC_FILE, user_vimrc, false)
do_exrc = (path_full_compare((char_u *)VIMRC_FILE, user_vimrc,
false, true)
!= kEqualFiles);
}
xfree(user_vimrc);
@ -1805,7 +1807,7 @@ static bool do_user_initialization(void)
do_exrc = p_exrc;
if (do_exrc) {
do_exrc = (path_full_compare((char_u *)VIMRC_FILE, (char_u *)vimrc,
false) != kEqualFiles);
false, true) != kEqualFiles);
}
xfree(vimrc);
xfree(config_dirs);

View File

@ -738,10 +738,10 @@ static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf)
}
/*
* Try to recover curbuf from the .swp file.
*/
void ml_recover(void)
/// Try to recover curbuf from the .swp file.
/// @param checkext If true, check the extension and detect whether it is a
/// swap file.
void ml_recover(bool checkext)
{
buf_T *buf = NULL;
memfile_T *mfp = NULL;
@ -785,7 +785,7 @@ void ml_recover(void)
if (fname == NULL) /* When there is no file name */
fname = (char_u *)"";
len = (int)STRLEN(fname);
if (len >= 4
if (checkext && len >= 4
&& STRNICMP(fname + len - 4, ".s", 2) == 0
&& vim_strchr((char_u *)"abcdefghijklmnopqrstuvw",
TOLOWER_ASC(fname[len - 2])) != NULL
@ -1375,7 +1375,9 @@ recover_names (
if (curbuf->b_ml.ml_mfp != NULL
&& (p = curbuf->b_ml.ml_mfp->mf_fname) != NULL) {
for (int i = 0; i < num_files; i++) {
if (path_full_compare(p, files[i], true) & kEqualFiles) {
// Do not expand wildcards, on Windows would try to expand
// "%tmp%" in "%tmp%file"
if (path_full_compare(p, files[i], true, false) & kEqualFiles) {
// Remove the name from files[i]. Move further entries
// down. When the array becomes empty free it here, since
// FreeWild() won't be called below.

View File

@ -51,9 +51,10 @@
/// expanded.
/// @param s2 Second file name.
/// @param checkname When both files don't exist, only compare their names.
/// @param expandenv Whether to expand environment variables in file names.
/// @return Enum of type FileComparison. @see FileComparison.
FileComparison path_full_compare(char_u *const s1, char_u *const s2,
const bool checkname)
const bool checkname, const bool expandenv)
{
assert(s1 && s2);
char_u exp1[MAXPATHL];
@ -61,7 +62,11 @@ FileComparison path_full_compare(char_u *const s1, char_u *const s2,
char_u full2[MAXPATHL];
FileID file_id_1, file_id_2;
if (expandenv) {
expand_env(s1, exp1, MAXPATHL);
} else {
xstrlcpy((char *)exp1, (const char *)s1, MAXPATHL - 1);
}
bool id_ok_1 = os_fileid((char *)exp1, &file_id_1);
bool id_ok_2 = os_fileid((char *)s2, &file_id_2);
if (!id_ok_1 && !id_ok_2) {
@ -1203,7 +1208,7 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file,
}
} else {
// First expand environment variables, "~/" and "~user/".
if (has_env_var(p) || *p == '~') {
if ((has_env_var(p) && !(flags & EW_NOTENV)) || *p == '~') {
p = expand_env_save_opt(p, true);
if (p == NULL)
p = pat[i];

View File

@ -25,6 +25,7 @@
// if executable is in $PATH
#define EW_DODOT 0x4000 // also files starting with a dot
#define EW_EMPTYOK 0x8000 // no matches is not an error
#define EW_NOTENV 0x10000 // do not expand environment variables
/// Return value for the comparison of two files. Also @see path_full_compare.
typedef enum file_comparison {

View File

@ -4458,7 +4458,8 @@ find_pattern_in_path(
if (i == max_path_depth) {
break;
}
if (path_full_compare(new_fname, files[i].name, true) & kEqualFiles) {
if (path_full_compare(new_fname, files[i].name,
true, true) & kEqualFiles) {
if (type != CHECK_PATH
&& action == ACTION_SHOW_ALL && files[i].matched) {
msg_putchar('\n'); // cursor below last one */

View File

@ -2031,7 +2031,8 @@ char_u *did_set_spelllang(win_T *wp)
// Check if we loaded this language before.
for (slang = first_lang; slang != NULL; slang = slang->sl_next) {
if (path_full_compare(lang, slang->sl_fname, false) == kEqualFiles) {
if (path_full_compare(lang, slang->sl_fname, false, true)
== kEqualFiles) {
break;
}
}
@ -2076,7 +2077,7 @@ char_u *did_set_spelllang(win_T *wp)
// Loop over the languages, there can be several files for "lang".
for (slang = first_lang; slang != NULL; slang = slang->sl_next) {
if (filename
? path_full_compare(lang, slang->sl_fname, false) == kEqualFiles
? path_full_compare(lang, slang->sl_fname, false, true) == kEqualFiles
: STRICMP(lang, slang->sl_name) == 0) {
region_mask = REGION_ALL;
if (!filename && region != NULL) {
@ -2129,7 +2130,7 @@ char_u *did_set_spelllang(win_T *wp)
for (c = 0; c < ga.ga_len; ++c) {
p = LANGP_ENTRY(ga, c)->lp_slang->sl_fname;
if (p != NULL
&& path_full_compare(spf_name, p, false) == kEqualFiles) {
&& path_full_compare(spf_name, p, false, true) == kEqualFiles) {
break;
}
}
@ -2139,7 +2140,8 @@ char_u *did_set_spelllang(win_T *wp)
// Check if it was loaded already.
for (slang = first_lang; slang != NULL; slang = slang->sl_next) {
if (path_full_compare(spf_name, slang->sl_fname, false) == kEqualFiles) {
if (path_full_compare(spf_name, slang->sl_fname, false, true)
== kEqualFiles) {
break;
}
}

View File

@ -1787,7 +1787,7 @@ spell_reload_one (
bool didit = false;
for (slang = first_lang; slang != NULL; slang = slang->sl_next) {
if (path_full_compare(fname, slang->sl_fname, false) == kEqualFiles) {
if (path_full_compare(fname, slang->sl_fname, false, true) == kEqualFiles) {
slang_clear(slang);
if (spell_load_file(fname, NULL, slang, false) == NULL)
// reloading failed, clear the language
@ -4719,7 +4719,8 @@ static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname)
// of the code for the soundfolding stuff.
// It might have been done already by spell_reload_one().
for (slang = first_lang; slang != NULL; slang = slang->sl_next) {
if (path_full_compare(wfname, slang->sl_fname, false) == kEqualFiles) {
if (path_full_compare(wfname, slang->sl_fname, false, true)
== kEqualFiles) {
break;
}
}

View File

@ -2666,7 +2666,8 @@ static int test_for_current(char_u *fname, char_u *fname_end, char_u *tag_fname,
*fname_end = NUL;
}
fullname = expand_tag_fname(fname, tag_fname, true);
retval = (path_full_compare(fullname, buf_ffname, true) & kEqualFiles);
retval = (path_full_compare(fullname, buf_ffname, true, true)
& kEqualFiles);
xfree(fullname);
*fname_end = c;
}

View File

@ -221,3 +221,87 @@ func Test_swapfile_delete()
augroup END
augroup! test_swapfile_delete
endfunc
func Test_swap_recover()
autocmd! SwapExists
augroup test_swap_recover
autocmd!
autocmd SwapExists * let v:swapchoice = 'r'
augroup END
call mkdir('Xswap')
let $Xswap = 'foo' " Check for issue #4369.
set dir=Xswap//
" Create a valid swapfile by editing a file.
split Xswap/text
call setline(1, ['one', 'two', 'three'])
write " file is written, not modified
" read the swapfile as a Blob
let swapfile_name = swapname('%')
let swapfile_bytes = readfile(swapfile_name, 'B')
" Close the file and recreate the swap file.
quit
call writefile(swapfile_bytes, swapfile_name)
" Edit the file again. This triggers recovery.
try
split Xswap/text
catch
" E308 should be caught, not E305.
call assert_exception('E308:') " Original file may have been changed
endtry
" The file should be recovered.
call assert_equal(['one', 'two', 'three'], getline(1, 3))
quit!
call delete('Xswap/text')
call delete(swapfile_name)
call delete('Xswap', 'd')
unlet $Xswap
set dir&
augroup test_swap_recover
autocmd!
augroup END
augroup! test_swap_recover
endfunc
func Test_swap_recover_ext()
autocmd! SwapExists
augroup test_swap_recover_ext
autocmd!
autocmd SwapExists * let v:swapchoice = 'r'
augroup END
" Create a valid swapfile by editing a file with a special extension.
split Xtest.scr
call setline(1, ['one', 'two', 'three'])
write " file is written, not modified
write " write again to make sure the swapfile is created
" read the swapfile as a Blob
let swapfile_name = swapname('%')
let swapfile_bytes = readfile(swapfile_name, 'B')
" Close and delete the file and recreate the swap file.
quit
call delete('Xtest.scr')
call writefile(swapfile_bytes, swapfile_name)
" Edit the file again. This triggers recovery.
try
split Xtest.scr
catch
" E308 should be caught, not E306.
call assert_exception('E308:') " Original file may have been changed
endtry
" The file should be recovered.
call assert_equal(['one', 'two', 'three'], getline(1, 3))
quit!
call delete('Xtest.scr')
call delete(swapfile_name)
augroup test_swap_recover_ext
autocmd!
augroup END
augroup! test_swap_recover_ext
endfunc

View File

@ -66,10 +66,10 @@ describe('path.c', function()
end)
describe('path_full_compare', function()
local function path_full_compare(s1, s2, cn)
local function path_full_compare(s1, s2, cn, ee)
s1 = to_cstr(s1)
s2 = to_cstr(s2)
return cimp.path_full_compare(s1, s2, cn or 0)
return cimp.path_full_compare(s1, s2, cn or 0, ee or 1)
end
local f1 = 'f1.o'