changeset 16752:e2d8d83e6721 v8.1.1378

patch 8.1.1378: delete() can not handle a file name that looks like a pattern commit https://github.com/vim/vim/commit/701ff0a3e53d253d7300c385e582659bbff7860d Author: Bram Moolenaar <Bram@vim.org> Date: Fri May 24 14:14:14 2019 +0200 patch 8.1.1378: delete() can not handle a file name that looks like a pattern 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 #4424, closes #696)
author Bram Moolenaar <Bram@vim.org>
date Fri, 24 May 2019 14:15:04 +0200
parents 29ae90e9f16a
children d0d3759cbd4d
files src/evalfunc.c src/fileio.c src/proto/fileio.pro src/testdir/test_functions.vim src/version.c
diffstat 5 files changed, 192 insertions(+), 140 deletions(-) [+]
line wrap: on
line diff
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -9349,17 +9349,21 @@ f_range(typval_T *argvars, typval_T *ret
 }
 
 /*
- * Evaluate "expr" for readdir().
+ * Evaluate "expr" (= "context") for readdir().
  */
     static int
-readdir_checkitem(typval_T *expr, char_u *name)
-{
+readdir_checkitem(void *context, char_u *name)
+{
+    typval_T	*expr = (typval_T *)context;
     typval_T	save_val;
     typval_T	rettv;
     typval_T	argv[2];
     int		retval = 0;
     int		error = FALSE;
 
+    if (expr->v_type == VAR_UNKNOWN)
+	return 1;
+
     prepare_vimvar(VV_VAL, &save_val);
     set_vim_var_string(VV_VAL, name, -1);
     argv[0].v_type = VAR_STRING;
@@ -9386,136 +9390,20 @@ theend:
 f_readdir(typval_T *argvars, typval_T *rettv)
 {
     typval_T	*expr;
-    int		failed = FALSE;
+    int		ret;
     char_u	*path;
+    char_u	*p;
     garray_T	ga;
     int		i;
-#ifdef MSWIN
-    char_u		*buf, *p;
-    int			ok;
-    HANDLE		hFind = INVALID_HANDLE_VALUE;
-    WIN32_FIND_DATAW    wfb;
-    WCHAR		*wn = NULL;	// UCS-2 name, NULL when not used.
-#endif
 
     if (rettv_list_alloc(rettv) == FAIL)
 	return;
     path = tv_get_string(&argvars[0]);
     expr = &argvars[1];
-    ga_init2(&ga, (int)sizeof(char *), 20);
-
-#ifdef MSWIN
-    buf = alloc((int)MAXPATHL);
-    if (buf == NULL)
-	return;
-    STRNCPY(buf, path, MAXPATHL-5);
-    p = vim_strpbrk(path, (char_u *)"\\/");
-    if (p != NULL)
-	*p = NUL;
-    STRCAT(buf, "\\*");
-
-    wn = enc_to_utf16(buf, NULL);
-    if (wn != NULL)
-	hFind = FindFirstFileW(wn, &wfb);
-    ok = (hFind != INVALID_HANDLE_VALUE);
-    if (!ok)
-	smsg(_(e_notopen), path);
-    else
-    {
-	while (ok)
-	{
-	    int	ignore;
-
-	    p = utf16_to_enc(wfb.cFileName, NULL);   // p is allocated here
-	    if (p == NULL)
-		break;  // out of memory
-
-	    ignore = p[0] == '.' && (p[1] == NUL
-					      || (p[1] == '.' && p[2] == NUL));
-	    if (!ignore && expr->v_type != VAR_UNKNOWN)
-	    {
-		int r = readdir_checkitem(expr, p);
-
-		if (r < 0)
-		{
-		    vim_free(p);
-		    break;
-		}
-		if (r == 0)
-		    ignore = TRUE;
-	    }
-
-	    if (!ignore)
-	    {
-		if (ga_grow(&ga, 1) == OK)
-		    ((char_u**)ga.ga_data)[ga.ga_len++] = vim_strsave(p);
-		else
-		{
-		    failed = TRUE;
-		    vim_free(p);
-		    break;
-		}
-	    }
-
-	    vim_free(p);
-	    ok = FindNextFileW(hFind, &wfb);
-	}
-	FindClose(hFind);
-    }
-
-    vim_free(buf);
-    vim_free(wn);
-#else
-    DIR		*dirp;
-    struct dirent *dp;
-    char_u	*p;
-
-    dirp = opendir((char *)path);
-    if (dirp == NULL)
-	smsg(_(e_notopen), path);
-    else
-    {
-	for (;;)
-	{
-	    int	ignore;
-
-	    dp = readdir(dirp);
-	    if (dp == NULL)
-		break;
-	    p = (char_u *)dp->d_name;
-
-	    ignore = p[0] == '.' &&
-		    (p[1] == NUL ||
-		     (p[1] == '.' && p[2] == NUL));
-	    if (!ignore && expr->v_type != VAR_UNKNOWN)
-	    {
-		int r = readdir_checkitem(expr, p);
-
-		if (r < 0)
-		    break;
-		if (r == 0)
-		    ignore = TRUE;
-	    }
-
-	    if (!ignore)
-	    {
-		if (ga_grow(&ga, 1) == OK)
-		    ((char_u**)ga.ga_data)[ga.ga_len++] = vim_strsave(p);
-		else
-		{
-		    failed = TRUE;
-		    break;
-		}
-	    }
-	}
-
-	closedir(dirp);
-    }
-#endif
-
-    if (!failed && rettv->vval.v_list != NULL && ga.ga_len > 0)
-    {
-	sort_strings((char_u **)ga.ga_data, ga.ga_len);
+
+    ret = readdir_core(&ga, path, (void *)expr, readdir_checkitem);
+    if (ret == OK && rettv->vval.v_list != NULL && ga.ga_len > 0)
+    {
 	for (i = 0; i < ga.ga_len; i++)
 	{
 	    p = ((char_u **)ga.ga_data)[i];
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -7172,20 +7172,164 @@ write_lnum_adjust(linenr_T offset)
 
 #if defined(TEMPDIRNAMES) || defined(FEAT_EVAL) || defined(PROTO)
 /*
+ * Core part of "readdir()" function.
+ * Retrieve the list of files/directories of "path" into "gap".
+ * Return OK for success, FAIL for failure.
+ */
+    int
+readdir_core(
+    garray_T	*gap,
+    char_u	*path,
+    void	*context,
+    int		(*checkitem)(void *context, char_u *name))
+{
+    int			failed = FALSE;
+#ifdef MSWIN
+    char_u		*buf, *p;
+    int			ok;
+    HANDLE		hFind = INVALID_HANDLE_VALUE;
+    WIN32_FIND_DATAW    wfb;
+    WCHAR		*wn = NULL;	// UTF-16 name, NULL when not used.
+#endif
+
+    ga_init2(gap, (int)sizeof(char *), 20);
+
+#ifdef MSWIN
+    buf = alloc((int)MAXPATHL);
+    if (buf == NULL)
+	return FAIL;
+    STRNCPY(buf, path, MAXPATHL-5);
+    p = buf + strlen(buf);
+    MB_PTR_BACK(buf, p);
+    if (*p == '\\' || *p == '/')
+	*p = NUL;
+    STRCAT(buf, "\\*");
+
+    wn = enc_to_utf16(buf, NULL);
+    if (wn != NULL)
+	hFind = FindFirstFileW(wn, &wfb);
+    ok = (hFind != INVALID_HANDLE_VALUE);
+    if (!ok)
+    {
+	failed = TRUE;
+	smsg(_(e_notopen), path);
+    }
+    else
+    {
+	while (ok)
+	{
+	    int	ignore;
+
+	    p = utf16_to_enc(wfb.cFileName, NULL);   // p is allocated here
+	    if (p == NULL)
+		break;  // out of memory
+
+	    ignore = p[0] == '.' && (p[1] == NUL
+					      || (p[1] == '.' && p[2] == NUL));
+	    if (!ignore && checkitem != NULL)
+	    {
+		int r = checkitem(context, p);
+
+		if (r < 0)
+		{
+		    vim_free(p);
+		    break;
+		}
+		if (r == 0)
+		    ignore = TRUE;
+	    }
+
+	    if (!ignore)
+	    {
+		if (ga_grow(gap, 1) == OK)
+		    ((char_u**)gap->ga_data)[gap->ga_len++] = vim_strsave(p);
+		else
+		{
+		    failed = TRUE;
+		    vim_free(p);
+		    break;
+		}
+	    }
+
+	    vim_free(p);
+	    ok = FindNextFileW(hFind, &wfb);
+	}
+	FindClose(hFind);
+    }
+
+    vim_free(buf);
+    vim_free(wn);
+#else
+    DIR		*dirp;
+    struct dirent *dp;
+    char_u	*p;
+
+    dirp = opendir((char *)path);
+    if (dirp == NULL)
+    {
+	failed = TRUE;
+	smsg(_(e_notopen), path);
+    }
+    else
+    {
+	for (;;)
+	{
+	    int	ignore;
+
+	    dp = readdir(dirp);
+	    if (dp == NULL)
+		break;
+	    p = (char_u *)dp->d_name;
+
+	    ignore = p[0] == '.' &&
+		    (p[1] == NUL ||
+		     (p[1] == '.' && p[2] == NUL));
+	    if (!ignore && checkitem != NULL)
+	    {
+		int r = checkitem(context, p);
+
+		if (r < 0)
+		    break;
+		if (r == 0)
+		    ignore = TRUE;
+	    }
+
+	    if (!ignore)
+	    {
+		if (ga_grow(gap, 1) == OK)
+		    ((char_u**)gap->ga_data)[gap->ga_len++] = vim_strsave(p);
+		else
+		{
+		    failed = TRUE;
+		    break;
+		}
+	    }
+	}
+
+	closedir(dirp);
+    }
+#endif
+
+    if (!failed && gap->ga_len > 0)
+	sort_strings((char_u **)gap->ga_data, gap->ga_len);
+
+    return failed ? FAIL : OK;
+}
+
+/*
  * Delete "name" and everything in it, recursively.
- * return 0 for succes, -1 if some file was not deleted.
+ * return 0 for success, -1 if some file was not deleted.
  */
     int
 delete_recursive(char_u *name)
 {
     int result = 0;
-    char_u	**files;
-    int		file_count;
     int		i;
     char_u	*exp;
-
-    /* A symbolic link to a directory itself is deleted, not the directory it
-     * points to. */
+    garray_T	ga;
+
+    // A symbolic link to a directory itself is deleted, not the directory it
+    // points to.
     if (
 # if defined(UNIX) || defined(MSWIN)
 	 mch_isrealdir(name)
@@ -7194,22 +7338,24 @@ delete_recursive(char_u *name)
 # endif
 	    )
     {
-	vim_snprintf((char *)NameBuff, MAXPATHL, "%s/*", name);
-	exp = vim_strsave(NameBuff);
+	exp = vim_strsave(name);
 	if (exp == NULL)
 	    return -1;
-	if (gen_expand_wildcards(1, &exp, &file_count, &files,
-	      EW_DIR|EW_FILE|EW_SILENT|EW_ALLLINKS|EW_DODOT|EW_EMPTYOK) == OK)
-	{
-	    for (i = 0; i < file_count; ++i)
-		if (delete_recursive(files[i]) != 0)
+	if (readdir_core(&ga, exp, NULL, NULL) == OK)
+	{
+	    for (i = 0; i < ga.ga_len; ++i)
+	    {
+		vim_snprintf((char *)NameBuff, MAXPATHL, "%s/%s", exp,
+					    ((char_u **)ga.ga_data)[i]);
+		if (delete_recursive(NameBuff) != 0)
 		    result = -1;
-	    FreeWild(file_count, files);
+	    }
+	    ga_clear_strings(&ga);
 	}
 	else
 	    result = -1;
+	(void)mch_rmdir(exp);
 	vim_free(exp);
-	(void)mch_rmdir(name);
     }
     else
 	result = mch_remove(name) == 0 ? 0 : -1;
--- a/src/proto/fileio.pro
+++ b/src/proto/fileio.pro
@@ -24,6 +24,7 @@ int buf_check_timestamp(buf_T *buf, int 
 void buf_reload(buf_T *buf, int orig_mode);
 void buf_store_time(buf_T *buf, stat_T *st, char_u *fname);
 void write_lnum_adjust(linenr_T offset);
+int readdir_core(garray_T *gap, char_u *path, void *context, int (*checkitem)(void *context, char_u *name));
 int delete_recursive(char_u *name);
 void vim_deltempdir(void);
 char_u *vim_tempname(int extra_char, int keep);
--- a/src/testdir/test_functions.vim
+++ b/src/testdir/test_functions.vim
@@ -1436,6 +1436,21 @@ func Test_readdir()
   call delete('Xdir', 'rf')
 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
+
 func Test_call()
   call assert_equal(3, call('len', [123]))
   call assert_fails("call call('len', 123)", 'E714:')
--- a/src/version.c
+++ b/src/version.c
@@ -768,6 +768,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1378,
+/**/
     1377,
 /**/
     1376,