vim-patch:8.1.1378: delete() can not handle a file name that looks like a pattern (#12784)

Problem:    Delete() can not handle a file name that looks like a pattern.
Solution:   Use readdir() instead of appending "/*" and expanding wildcards.
            (Ken Takata, closes vim/vim#4424, closes vim/vim#696)
701ff0a3e5
This commit is contained in:
Shougo 2021-05-07 21:07:13 +09:00 committed by GitHub
parent 17434b88b4
commit 4be0e92db0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 134 additions and 168 deletions

View File

@ -6628,37 +6628,6 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr)
} }
} }
// Evaluate "expr" for readdir().
static varnumber_T readdir_checkitem(typval_T *expr, const char *name)
{
typval_T save_val;
typval_T rettv;
typval_T argv[2];
varnumber_T retval = 0;
bool error = false;
prepare_vimvar(VV_VAL, &save_val);
set_vim_var_string(VV_VAL, name, -1);
argv[0].v_type = VAR_STRING;
argv[0].vval.v_string = (char_u *)name;
if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) {
goto theend;
}
retval = tv_get_number_chk(&rettv, &error);
if (error) {
retval = -1;
}
tv_clear(&rettv);
theend:
set_vim_var_string(VV_VAL, NULL, 0);
restore_vimvar(VV_VAL, &save_val);
return retval;
}
// "readdir()" function // "readdir()" function
static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{ {
@ -6670,43 +6639,14 @@ static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
tv_list_alloc_ret(rettv, kListLenUnknown); tv_list_alloc_ret(rettv, kListLenUnknown);
path = tv_get_string(&argvars[0]); path = tv_get_string(&argvars[0]);
expr = &argvars[1]; expr = &argvars[1];
ga_init(&ga, (int)sizeof(char *), 20);
if (!os_scandir(&dir, path)) { if (!os_scandir(&dir, path)) {
smsg(_(e_notopen), path); smsg(_(e_notopen), path);
} else { } else {
for (;;) { readdir_core(&ga, &dir, expr, true);
bool ignore;
path = os_scandir_next(&dir);
if (path == NULL) {
break;
}
ignore = (path[0] == '.'
&& (path[1] == NUL || (path[1] == '.' && path[2] == NUL)));
if (!ignore && expr->v_type != VAR_UNKNOWN) {
varnumber_T r = readdir_checkitem(expr, path);
if (r < 0) {
break;
}
if (r == 0) {
ignore = true;
}
}
if (!ignore) {
ga_grow(&ga, 1);
((char **)ga.ga_data)[ga.ga_len++] = xstrdup(path);
}
}
os_closedir(&dir);
} }
if (rettv->vval.v_list != NULL && ga.ga_len > 0) { if (rettv->vval.v_list != NULL && ga.ga_len > 0) {
sort_strings((char_u **)ga.ga_data, ga.ga_len);
for (int i = 0; i < ga.ga_len; i++) { for (int i = 0; i < ga.ga_len; i++) {
path = ((const char **)ga.ga_data)[i]; path = ((const char **)ga.ga_data)[i];
tv_list_append_string(rettv->vval.v_list, path, -1); tv_list_append_string(rettv->vval.v_list, path, -1);

View File

@ -5204,38 +5204,113 @@ static void vim_maketempdir(void)
(void)umask(umask_save); (void)umask(umask_save);
} }
// Evaluate "expr" for readdir().
static varnumber_T readdir_checkitem(typval_T *expr, const char *name)
{
typval_T save_val;
typval_T rettv;
typval_T argv[2];
varnumber_T retval = 0;
bool error = false;
prepare_vimvar(VV_VAL, &save_val);
set_vim_var_string(VV_VAL, name, -1);
argv[0].v_type = VAR_STRING;
argv[0].vval.v_string = (char_u *)name;
if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) {
goto theend;
}
retval = tv_get_number_chk(&rettv, &error);
if (error) {
retval = -1;
}
tv_clear(&rettv);
theend:
set_vim_var_string(VV_VAL, NULL, 0);
restore_vimvar(VV_VAL, &save_val);
return retval;
}
/// Core part of "readdir()" function.
/// Retrieve the list of files/directories of "dirp" into "gap".
void readdir_core(
garray_T *gap,
Directory *dirp,
typval_T *expr,
bool is_checkitem)
{
ga_init(gap, (int)sizeof(char *), 20);
for (;;) {
bool ignore;
const char *path = os_scandir_next(dirp);
if (path == NULL) {
break;
}
ignore = (path[0] == '.'
&& (path[1] == NUL || (path[1] == '.' && path[2] == NUL)));
if (!ignore && expr != NULL && expr->v_type != VAR_UNKNOWN
&& is_checkitem) {
varnumber_T r = readdir_checkitem(expr, path);
if (r < 0) {
break;
}
if (r == 0) {
ignore = true;
}
}
if (!ignore) {
ga_grow(gap, 1);
((char **)gap->ga_data)[gap->ga_len++] = xstrdup(path);
}
}
if (gap->ga_len > 0) {
sort_strings((char_u **)gap->ga_data, gap->ga_len);
}
os_closedir(dirp);
}
/// Delete "name" and everything in it, recursively. /// Delete "name" and everything in it, recursively.
/// @param name The path which should be deleted. /// @param name The path which should be deleted.
/// @return 0 for success, -1 if some file was not deleted. /// @return 0 for success, -1 if some file was not deleted.
int delete_recursive(const char *name) int delete_recursive(const char *name)
{ {
int result = 0; int result = 0;
char *exp = (char *)vim_strsave((char_u *)name);
Directory dir;
if (os_isrealdir(name)) { if (os_isrealdir(name) && os_scandir(&dir, exp)) {
snprintf((char *)NameBuff, MAXPATHL, "%s/*", name); // NOLINT garray_T ga;
char_u **files; readdir_core(&ga, &dir, NULL, false);
int file_count;
char_u *exp = vim_strsave(NameBuff); for (int i = 0; i < ga.ga_len; i++) {
if (gen_expand_wildcards(1, &exp, &file_count, &files, vim_snprintf((char *)NameBuff, MAXPATHL, "%s/%s", exp,
EW_DIR | EW_FILE | EW_SILENT | EW_ALLLINKS ((char_u **)ga.ga_data)[i]);
| EW_DODOT | EW_EMPTYOK) == OK) { if (delete_recursive((const char *)NameBuff) != 0) {
for (int i = 0; i < file_count; i++) { result = -1;
if (delete_recursive((const char *)files[i]) != 0) {
result = -1;
}
} }
FreeWild(file_count, files);
} else {
result = -1;
} }
xfree(exp); ga_clear_strings(&ga);
os_rmdir(name); os_rmdir(name);
} else { } else {
result = os_remove(name) == 0 ? 0 : -1; result = os_remove(name) == 0 ? 0 : -1;
} }
xfree(exp);
return result; return result;
} }

View File

@ -26,6 +26,8 @@ func Test_argidx()
endfunc endfunc
func Test_argadd() func Test_argadd()
call Reset_arglist()
%argdelete %argdelete
argadd a b c argadd a b c
call assert_equal(0, argidx()) call assert_equal(0, argidx())
@ -103,6 +105,11 @@ func Init_abc()
next next
endfunc endfunc
func Reset_arglist()
cd
args a | %argd
endfunc
func Assert_argc(l) func Assert_argc(l)
call assert_equal(len(a:l), argc()) call assert_equal(len(a:l), argc())
let i = 0 let i = 0
@ -115,8 +122,7 @@ endfunc
" Test for [count]argument and [count]argdelete commands " Test for [count]argument and [count]argdelete commands
" Ported from the test_argument_count.in test script " Ported from the test_argument_count.in test script
func Test_argument() func Test_argument()
" Clean the argument list call Reset_arglist()
arga a | %argd
let save_hidden = &hidden let save_hidden = &hidden
set hidden set hidden
@ -244,8 +250,7 @@ endfunc
" Test for 0argadd and 0argedit " Test for 0argadd and 0argedit
" Ported from the test_argument_0count.in test script " Ported from the test_argument_0count.in test script
func Test_zero_argadd() func Test_zero_argadd()
" Clean the argument list call Reset_arglist()
arga a | %argd
arga a b c d arga a b c d
2argu 2argu
@ -272,10 +277,6 @@ func Test_zero_argadd()
call assert_equal('file with spaces', expand('%')) call assert_equal('file with spaces', expand('%'))
endfunc endfunc
func Reset_arglist()
args a | %argd
endfunc
" Test for argc() " Test for argc()
func Test_argc() func Test_argc()
call Reset_arglist() call Reset_arglist()
@ -408,6 +409,7 @@ endfunc
" Test for the :argdelete command " Test for the :argdelete command
func Test_argdelete() func Test_argdelete()
call Reset_arglist() call Reset_arglist()
args aa a aaa b bb args aa a aaa b bb
argdelete a* argdelete a*
call assert_equal(['b', 'bb'], argv()) call assert_equal(['b', 'bb'], argv())

View File

@ -103,6 +103,8 @@ endfunc
func Test_command_count_2() func Test_command_count_2()
silent! %argd silent! %argd
cd
arga a b c d arga a b c d
call assert_fails('5argu', 'E16:') call assert_fails('5argu', 'E16:')

View File

@ -1402,6 +1402,10 @@ func Test_bufadd_bufload()
endfunc endfunc
func Test_readdir() func Test_readdir()
if isdirectory('Xdir')
call delete('Xdir', 'rf')
endif
call mkdir('Xdir') call mkdir('Xdir')
call writefile([], 'Xdir/foo.txt') call writefile([], 'Xdir/foo.txt')
call writefile([], 'Xdir/bar.txt') call writefile([], 'Xdir/bar.txt')
@ -1456,4 +1460,19 @@ func Test_default_arg_value()
call assert_equal('msg', HasDefault()) call assert_equal('msg', HasDefault())
endfunc endfunc
func Test_delete_rf()
call mkdir('Xdir')
call writefile([], 'Xdir/foo.txt')
call writefile([], 'Xdir/bar.txt')
call mkdir('Xdir/[a-1]') " issue #696
call writefile([], 'Xdir/[a-1]/foo.txt')
call writefile([], 'Xdir/[a-1]/bar.txt')
call assert_true(filereadable('Xdir/foo.txt'))
call assert_true(filereadable('Xdir/[a-1]/foo.txt'))
call assert_equal(0, delete('Xdir', 'rf'))
call assert_false(filereadable('Xdir/foo.txt'))
call assert_false(filereadable('Xdir/[a-1]/foo.txt'))
endfunc
" vim: shiftwidth=2 sts=2 expandtab " vim: shiftwidth=2 sts=2 expandtab

View File

@ -1011,10 +1011,9 @@ endfunc
" Tests for %D and %X errorformat options " Tests for %D and %X errorformat options
func Test_efm_dirstack() func Test_efm_dirstack()
" Create the directory stack and files " Create the directory stack and files
call mkdir('dir1') call mkdir('dir1/a', 'p')
call mkdir('dir1/a') call mkdir('dir1/a/b', 'p')
call mkdir('dir1/a/b') call mkdir('dir1/c', 'p')
call mkdir('dir1/c')
call mkdir('dir2') call mkdir('dir2')
let lines =<< trim [DATA] let lines =<< trim [DATA]
@ -3485,6 +3484,9 @@ func Xqftick_tests(cchar)
\ {'filename' : 'F7', 'lnum' : 11, 'text' : 'L11'}], 'r') \ {'filename' : 'F7', 'lnum' : 11, 'text' : 'L11'}], 'r')
call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick) call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
if isdirectory("Xone")
call delete("Xone", 'rf')
endif
call writefile(["F8:80:L80", "F8:81:L81"], "Xone") call writefile(["F8:80:L80", "F8:81:L81"], "Xone")
Xfile Xone Xfile Xone
call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick) call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)

View File

@ -43,6 +43,9 @@ func Test_yank_shows_register()
endfunc endfunc
func Test_display_registers() func Test_display_registers()
" Disable clipboard
let g:clipboard = {}
e file1 e file1
e file2 e file2
call setline(1, ['foo', 'bar']) call setline(1, ['foo', 'bar'])

View File

@ -70,7 +70,7 @@ describe('file reading, writing and bufnew and filter autocommands', function()
feed_command('let $GZIP = ""') feed_command('let $GZIP = ""')
--execute('au FileChangedShell * echo "caught FileChangedShell"') --execute('au FileChangedShell * echo "caught FileChangedShell"')
feed_command('set bin') feed_command('set bin')
feed_command("au FileReadPost *.gz '[,']!gzip -d") feed_command("au FileReadPost *.gz '[,']!GZIP= gzip -d")
-- Read and decompress the testfile. -- Read and decompress the testfile.
feed_command('$r Xtestfile.gz') feed_command('$r Xtestfile.gz')
expect('\n'..text1) expect('\n'..text1)

View File

@ -17,33 +17,6 @@ describe('Test for delete()', function()
eq(-1, eval("delete('Xfile')")) eq(-1, eval("delete('Xfile')"))
end) end)
it('directory delete', function()
command("call mkdir('Xdir1')")
eq(1, eval("isdirectory('Xdir1')"))
eq(0, eval("delete('Xdir1', 'd')"))
eq(0, eval("isdirectory('Xdir1')"))
eq(-1, eval("delete('Xdir1', 'd')"))
end)
it('recursive delete', function()
command("call mkdir('Xdir1')")
command("call mkdir('Xdir1/subdir')")
command("call mkdir('Xdir1/empty')")
command('split Xdir1/Xfile')
command("call setline(1, ['a', 'b'])")
command('w')
command('w Xdir1/subdir/Xfile')
command('close')
eq(1, eval("isdirectory('Xdir1')"))
eq(eval("['a', 'b']"), eval("readfile('Xdir1/Xfile')"))
eq(1, eval("isdirectory('Xdir1/subdir')"))
eq(eval("['a', 'b']"), eval("readfile('Xdir1/subdir/Xfile')"))
eq(1, eval("isdirectory('Xdir1/empty')"))
eq(0, eval("delete('Xdir1', 'rf')"))
eq(0, eval("isdirectory('Xdir1')"))
eq(-1, eval("delete('Xdir1', 'd')"))
end)
it('symlink delete', function() it('symlink delete', function()
source([[ source([[
split Xfile split Xfile
@ -63,55 +36,4 @@ describe('Test for delete()', function()
eq(-1, eval("delete('Xlink')")) eq(-1, eval("delete('Xlink')"))
eq(0, eval("delete('Xfile')")) eq(0, eval("delete('Xfile')"))
end) end)
it('symlink directory delete', function()
command("call mkdir('Xdir1')")
if helpers.iswin() then
command("silent !mklink /j Xlink Xdir1")
else
command("silent !ln -s Xdir1 Xlink")
end
eq(1, eval("isdirectory('Xdir1')"))
eq(1, eval("isdirectory('Xlink')"))
-- Delete the link, not the directory
eq(0, eval("delete('Xlink')"))
eq(-1, eval("delete('Xlink')"))
eq(0, eval("delete('Xdir1', 'd')"))
end)
it('symlink recursive delete', function()
source([[
call mkdir('Xdir3')
call mkdir('Xdir3/subdir')
call mkdir('Xdir4')
split Xdir3/Xfile
call setline(1, ['a', 'b'])
w
w Xdir3/subdir/Xfile
w Xdir4/Xfile
close
if has('win32')
silent !mklink /j Xdir3\Xlink Xdir4
else
silent !ln -s ../Xdir4 Xdir3/Xlink
endif
]])
eq(1, eval("isdirectory('Xdir3')"))
eq(eval("['a', 'b']"), eval("readfile('Xdir3/Xfile')"))
eq(1, eval("isdirectory('Xdir3/subdir')"))
eq(eval("['a', 'b']"), eval("readfile('Xdir3/subdir/Xfile')"))
eq(1, eval("isdirectory('Xdir4')"))
eq(1, eval("isdirectory('Xdir3/Xlink')"))
eq(eval("['a', 'b']"), eval("readfile('Xdir4/Xfile')"))
eq(0, eval("delete('Xdir3', 'rf')"))
eq(0, eval("isdirectory('Xdir3')"))
eq(-1, eval("delete('Xdir3', 'd')"))
-- symlink is deleted, not the directory it points to
eq(1, eval("isdirectory('Xdir4')"))
eq(eval("['a', 'b']"), eval("readfile('Xdir4/Xfile')"))
eq(0, eval("delete('Xdir4/Xfile')"))
eq(0, eval("delete('Xdir4', 'd')"))
end)
end) end)

View File

@ -20,6 +20,7 @@ describe('packadd', function()
func SetUp() func SetUp()
let s:topdir = expand(getcwd() . '/Xdir') let s:topdir = expand(getcwd() . '/Xdir')
call delete(s:topdir, 'rf')
exe 'set packpath=' . s:topdir exe 'set packpath=' . s:topdir
let s:plugdir = expand(s:topdir . '/pack/mine/opt/mytest') let s:plugdir = expand(s:topdir . '/pack/mine/opt/mytest')
endfunc endfunc