patch 9.1.0923: too many strlen() calls in filepath.c

Problem:  too many strlen() calls in filepath.c
Solution: refactor filepath.c and remove calls to STRLEN(),
          unify dos_expandpath() and unix_expandpath() into
          a single function

closes: #16160

Signed-off-by: John Marriott <basilisk@internode.on.net>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/filepath.c b/src/filepath.c
index 3dd71bc..7298842 100644
--- a/src/filepath.c
+++ b/src/filepath.c
@@ -29,12 +29,11 @@
     static int
 get_short_pathname(char_u **fnamep, char_u **bufp, int *fnamelen)
 {
-    int		l, len;
+    int		l;
     WCHAR	*newbuf;
     WCHAR	*wfname;
 
-    len = MAXPATHL;
-    newbuf = malloc(len * sizeof(*newbuf));
+    newbuf = alloc(MAXPATHL * sizeof(*newbuf));
     if (newbuf == NULL)
 	return FAIL;
 
@@ -45,8 +44,8 @@
 	return FAIL;
     }
 
-    l = GetShortPathNameW(wfname, newbuf, len);
-    if (l > len - 1)
+    l = GetShortPathNameW(wfname, newbuf, MAXPATHL);
+    if (l > MAXPATHL - 1)
     {
 	// If that doesn't work (not enough space), then save the string
 	// and try again with a new buffer big enough.
@@ -105,7 +104,7 @@
     char_u	**bufp,
     int		*fnamelen)
 {
-    char_u	*short_fname, *save_fname, *pbuf_unused;
+    char_u	*short_fname = NULL, *save_fname = NULL, *pbuf_unused = NULL;
     char_u	*endp, *save_endp;
     char_u	ch;
     int		old_len, len;
@@ -115,6 +114,11 @@
     // Make a copy
     old_len = *fnamelen;
     save_fname = vim_strnsave(*fname, old_len);
+    if (save_fname == NULL)
+    {
+	retval = FAIL;
+	goto theend;
+    }
     pbuf_unused = NULL;
     short_fname = NULL;
 
@@ -139,9 +143,9 @@
 	 * resulting path.
 	 */
 	ch = *endp;
-	*endp = 0;
+	*endp = NUL;
 	short_fname = save_fname;
-	len = (int)STRLEN(short_fname) + 1;
+	len = (int)(endp - save_fname) + 1;
 	if (get_short_pathname(&short_fname, &pbuf_unused, &len) == FAIL)
 	{
 	    retval = FAIL;
@@ -225,7 +229,7 @@
 	if (vim_ispathsep(*p))
 	    ++sepcount;
 
-    // Need full path first (use expand_env() to remove a "~/")
+    // Need full path first (use expand_env_save() to remove a "~/")
     hasTilde = (**fnamep == '~');
     if (hasTilde)
 	pbuf = tfname = expand_env_save(*fnamep);
@@ -273,7 +277,7 @@
 
     // Copy in the string - p indexes into tfname - allocated at pbuf
     vim_free(*bufp);
-    *fnamelen = (int)STRLEN(p);
+    *fnamelen = (int)((tfname + len) - p);
     *bufp = pbuf;
     *fnamep = p;
 
@@ -414,7 +418,7 @@
 	    continue;
 	}
 	pbuf = NULL;
-	// Need full path first (use expand_env() to remove a "~/")
+	// Need full path first (use expand_env_save() to remove a "~/")
 	if (!has_fullname && !has_homerelative)
 	{
 	    if (**fnamep == '~')
@@ -505,7 +509,7 @@
 	if (*fnamelen == 0)
 	{
 	    // Result is empty.  Turn it into "." to make ":cd %:h" work.
-	    p = vim_strsave((char_u *)".");
+	    p = vim_strnsave((char_u *)".", 1);
 	    if (p == NULL)
 		return -1;
 	    vim_free(*bufp);
@@ -1544,8 +1548,10 @@
 	tv[0].vval.v_string = created;
 	tv[1].v_type = VAR_STRING;
 	tv[1].v_lock = 0;
-	tv[1].vval.v_string = vim_strsave(
-				       (char_u *)(defer_recurse ? "rf" : "d"));
+	if (defer_recurse)
+	    tv[1].vval.v_string = vim_strnsave((char_u *)"rf", 2);
+	else
+	    tv[1].vval.v_string = vim_strnsave((char_u *)"d", 1);
 	if (tv[0].vval.v_string == NULL || tv[1].vval.v_string == NULL
 		|| add_defer((char_u *)"delete", 2, tv) == FAIL)
 	{
@@ -2058,6 +2064,8 @@
     char_u	*p;
 #ifdef HAVE_READLINK
     char_u	*buf = NULL;
+    char_u	*remain = NULL;
+    int		p_was_allocated = FALSE;
 #endif
 
     if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
@@ -2077,110 +2085,166 @@
 #else
 # ifdef HAVE_READLINK
     {
+	size_t	plen;
+	size_t	buflen;
 	char_u	*cpy;
-	int	len;
-	char_u	*remain = NULL;
+	size_t	cpysize;
+	char_u	*r = NULL;			// points to current position in "remain"
+	size_t	rlen = 0;			// length of r (excluding the NUL)
 	char_u	*q;
 	int	is_relative_to_current = FALSE;
 	int	has_trailing_pathsep = FALSE;
 	int	limit = 100;
+	size_t	len;
 
-	p = vim_strsave(p);
+	rettv->vval.v_string = NULL;
+
+	plen = STRLEN(p);
+	p = vim_strnsave(p, plen);
 	if (p == NULL)
 	    goto fail;
+
+	p_was_allocated = TRUE;
 	if (p[0] == '.' && (vim_ispathsep(p[1])
 				   || (p[1] == '.' && (vim_ispathsep(p[2])))))
 	    is_relative_to_current = TRUE;
 
-	len = STRLEN(p);
-	if (len > 1 && after_pathsep(p, p + len))
+	if (plen > 1 && after_pathsep(p, p + plen))
 	{
 	    has_trailing_pathsep = TRUE;
-	    p[len - 1] = NUL; // the trailing slash breaks readlink()
+	    p[--plen] = NUL;			// the trailing slash breaks readlink()
 	}
 
 	q = getnextcomp(p);
 	if (*q != NUL)
 	{
+	    char_u  *q_prev = q - 1;
+
+	    // getnextcomp() finds the first path separator.
+	    // if there is a run of >1 path separators, set all
+	    // but the last in the run to NUL.
+	    while (*q != NUL && vim_ispathsep(*q))
+	    {
+		*q_prev = NUL;
+		q_prev = q;
+		MB_PTR_ADV(q);
+	    }
+	    q = q_prev;
+
 	    // Separate the first path component in "p", and keep the
 	    // remainder (beginning with the path separator).
-	    remain = vim_strsave(q - 1);
-	    q[-1] = NUL;
+	    rlen = (size_t)(plen - (q - p));
+	    r = remain = vim_strnsave(q, rlen);
+	    if (remain == NULL)
+		rlen = 0;
+	    *q = NUL;
+	    plen -= rlen;
 	}
 
 	buf = alloc(MAXPATHL + 1);
 	if (buf == NULL)
-	{
-	    vim_free(p);
-	    vim_free(remain);
 	    goto fail;
-	}
 
 	for (;;)
 	{
 	    for (;;)
 	    {
-		len = readlink((char *)p, (char *)buf, MAXPATHL);
-		if (len <= 0)
+		ssize_t rv = readlink((char *)p, (char *)buf, MAXPATHL);
+		if (rv <= 0)
 		    break;
-		buf[len] = NUL;
 
 		if (limit-- == 0)
 		{
-		    vim_free(p);
-		    vim_free(remain);
 		    emsg(_(e_too_many_symbolic_links_cycle));
-		    rettv->vval.v_string = NULL;
 		    goto fail;
 		}
 
+		buflen = (size_t)rv;
+		buf[buflen] = NUL;
+
 		// Ensure that the result will have a trailing path separator
 		// if the argument has one.
-		if (remain == NULL && has_trailing_pathsep)
-		    add_pathsep(buf);
+		if (remain == NULL && has_trailing_pathsep && !after_pathsep(buf, buf + buflen))
+		{
+		    STRCPY(buf + buflen, PATHSEPSTR);
+		    ++buflen;
+		}
 
 		// Separate the first path component in the link value and
 		// concatenate the remainders.
 		q = getnextcomp(vim_ispathsep(*buf) ? buf + 1 : buf);
 		if (*q != NUL)
 		{
+		    char_u  *q_prev = q - 1;
+
+		    // getnextcomp() finds the first path separator.
+		    // if there is a run of >1 path separators, set all
+		    // but the last in the run to NUL.
+		    while (*q != NUL && vim_ispathsep(*q))
+		    {
+			*q_prev = NUL;
+			q_prev = q;
+			MB_PTR_ADV(q);
+		    }
+		    q = q_prev;
+
 		    if (remain == NULL)
-			remain = vim_strsave(q - 1);
+		    {
+			rlen = (size_t)(buflen - (q - buf));
+			r = remain = vim_strnsave(q, rlen);
+			if (remain == NULL)
+			    rlen = 0;
+		    }
 		    else
 		    {
-			cpy = concat_str(q - 1, remain);
-			if (cpy != NULL)
-			{
-			    vim_free(remain);
-			    remain = cpy;
-			}
+			len = (size_t)(buflen - (q - buf));
+			cpysize = (size_t)(len + rlen + 1);		// +1 for NUL
+			cpy = alloc(plen + buflen + 1);
+			if (cpy == NULL)
+			    goto fail;
+
+			rlen = (size_t)vim_snprintf((char *)cpy, cpysize, "%.*s%s", (int)len, q, r);
+			vim_free(remain);
+			r = remain = cpy;
 		    }
-		    q[-1] = NUL;
+		    *q = NUL;
+		    buflen = (size_t)(q - buf);
 		}
 
 		q = gettail(p);
 		if (q > p && *q == NUL)
 		{
 		    // Ignore trailing path separator.
-		    p[q - p - 1] = NUL;
+		    plen = (size_t)(q - p - 1);
+		    p[plen] = NUL;
 		    q = gettail(p);
 		}
 		if (q > p && !mch_isFullName(buf))
 		{
+		    char_u *tail;
+
 		    // symlink is relative to directory of argument
-		    cpy = alloc(STRLEN(p) + STRLEN(buf) + 1);
-		    if (cpy != NULL)
-		    {
-			STRCPY(cpy, p);
-			STRCPY(gettail(cpy), buf);
-			vim_free(p);
-			p = cpy;
-		    }
+		    cpy = alloc(plen + buflen + 1);
+		    if (cpy == NULL)
+			goto fail;
+
+		    STRCPY(cpy, p);
+		    tail = gettail(cpy);
+		    if (*tail != NUL)
+			plen -= (size_t)(plen - (tail - cpy));	// remove portion that will be replaced
+		    STRCPY(tail, buf);
+		    vim_free(p);
+		    p = cpy;
+		    plen += buflen;
 		}
 		else
 		{
 		    vim_free(p);
-		    p = vim_strsave(buf);
+		    p = vim_strnsave(buf, buflen);
+		    if (p == NULL)
+			goto fail;
+
+		    plen = buflen;
 		}
 	    }
 
@@ -2188,20 +2252,29 @@
 		break;
 
 	    // Append the first path component of "remain" to "p".
-	    q = getnextcomp(remain + 1);
-	    len = q - remain - (*q != NUL);
-	    cpy = vim_strnsave(p, STRLEN(p) + len);
-	    if (cpy != NULL)
-	    {
-		STRNCAT(cpy, remain, len);
-		vim_free(p);
-		p = cpy;
-	    }
+	    q = getnextcomp(r + 1);
+	    len = (size_t)(q - r);
+	    cpysize = (size_t)(plen + len + 1);			// +1 for NUL
+	    cpy = alloc(cpysize);
+	    if (cpy == NULL)
+		goto fail;
+
+	    plen = (size_t)vim_snprintf((char *)cpy, cpysize, "%s%.*s", p, (int)len, r);
+	    vim_free(p);
+	    p = cpy;
+
 	    // Shorten "remain".
 	    if (*q != NUL)
-		STRMOVE(remain, q - 1);
+	    {
+		r += len;
+		rlen -= len;
+	    }
 	    else
+	    {
 		VIM_CLEAR(remain);
+		r = NULL;
+		rlen = 0;
+	    }
 	}
 
 	// If the result is a relative path name, make it explicitly relative to
@@ -2218,12 +2291,14 @@
 				    || vim_ispathsep(p[2]))))))
 	    {
 		// Prepend "./".
-		cpy = concat_str((char_u *)"./", p);
-		if (cpy != NULL)
-		{
-		    vim_free(p);
-		    p = cpy;
-		}
+		cpysize = plen + 3;		    // +2 for "./" and +1 for NUL
+		cpy = alloc(cpysize);
+		if (cpy == NULL)
+		    goto fail;
+
+		plen = (size_t)vim_snprintf((char *)cpy, cpysize, "./%s", p);
+		vim_free(p);
+		p = cpy;
 	    }
 	    else if (!is_relative_to_current)
 	    {
@@ -2232,18 +2307,17 @@
 		while (q[0] == '.' && vim_ispathsep(q[1]))
 		    q += 2;
 		if (q > p)
-		    STRMOVE(p, p + 2);
+		{
+		    mch_memmove(p, p + 2, (plen - 2) + 1);
+		    plen -= 2;
+		}
 	    }
 	}
 
 	// Ensure that the result will have no trailing path separator
 	// if the argument had none.  But keep "/" or "//".
-	if (!has_trailing_pathsep)
-	{
-	    q = p + STRLEN(p);
-	    if (after_pathsep(p, q))
-		*gettail_sep(p) = NUL;
-	}
+	if (!has_trailing_pathsep && after_pathsep(p, p + plen))
+	    *gettail_sep(p) = NUL;
 
 	rettv->vval.v_string = p;
     }
@@ -2256,7 +2330,10 @@
 
 #ifdef HAVE_READLINK
 fail:
+    if (rettv->vval.v_string == NULL && p_was_allocated)
+	vim_free(p);
     vim_free(buf);
+    vim_free(remain);
 #endif
     rettv->v_type = VAR_STRING;
 }
@@ -2964,6 +3041,7 @@
 {
     while (*fname && !vim_ispathsep(*fname))
 	MB_PTR_ADV(fname);
+
     if (*fname)
 	++fname;
     return fname;
@@ -3116,16 +3194,18 @@
     char_u  *
 concat_fnames(char_u *fname1, char_u *fname2, int sep)
 {
+    size_t  fname1len = STRLEN(fname1);
+    size_t  destsize = fname1len + STRLEN(fname2) + 3;
     char_u  *dest;
 
-    dest = alloc(STRLEN(fname1) + STRLEN(fname2) + 3);
+    dest = alloc(destsize);
     if (dest == NULL)
 	return NULL;
 
-    STRCPY(dest, fname1);
-    if (sep)
-	add_pathsep(dest);
-    STRCAT(dest, fname2);
+    vim_snprintf((char *)dest, destsize, "%s%s%s",
+	    fname1,
+	    (sep && !after_pathsep(fname1, fname1 + fname1len)) ? PATHSEPSTR : "",
+	    fname2);
     return dest;
 }
 
@@ -3136,8 +3216,14 @@
     void
 add_pathsep(char_u *p)
 {
-    if (*p != NUL && !after_pathsep(p, p + STRLEN(p)))
-	STRCAT(p, PATHSEPSTR);
+    size_t  plen;
+
+    if (p == NULL || *p == NUL)
+	return;
+
+    plen = STRLEN(p);
+    if (!after_pathsep(p, p + plen))
+	STRCPY(p + plen, PATHSEPSTR);
 }
 
 /*
@@ -3435,14 +3521,14 @@
 }
 #endif // VIM_BACKTICK
 
-#if defined(MSWIN)
+#if defined(MSWIN) || (defined(UNIX) && !defined(VMS)) || defined(USE_UNIXFILENAME) || defined(PROTO)
 /*
- * File name expansion code for MS-DOS, Win16 and Win32.  It's here because
+ * File name expansion code for Unix, Mac, MS-DOS, Win16 and Win32.  It's here because
  * it's shared between these systems.
  */
 
 /*
- * comparison function for qsort in dos_expandpath()
+ * comparison function for qsort in unix_expandpath()
  */
     static int
 pstrcmp(const void *a, const void *b)
@@ -3457,33 +3543,35 @@
  * "path" has backslashes before chars that are not to be expanded, starting
  * at "path[wildoff]".
  * Return the number of matches found.
- * NOTE: much of this is identical to unix_expandpath(), keep in sync!
  */
-    static int
-dos_expandpath(
+    int
+unix_expandpath(
     garray_T	*gap,
-    char_u	*path,
-    int		wildoff,
-    int		flags,		// EW_* flags
-    int		didstar)	// expanded "**" once already
+    char_u  *path,
+    int	    wildoff,
+    int	    flags,	// EW_* flags
+    int	    didstar)	// expanded "**" once already
 {
-    char_u	*buf;
-    char_u	*path_end;
-    char_u	*p, *s, *e;
-    int		start_len = gap->ga_len;
-    char_u	*pat;
+    char_u  *buf;
+    char_u  *path_end;
+    size_t  basepathlen;	    // length of non-variable portion of the path
+    size_t  wildcardlen;	    // length of wildcard segment
+    char_u  *p, *s, *e;
+    int	    start_len = gap->ga_len;
+    char_u  *pat;
     regmatch_T	regmatch;
-    int		starts_with_dot;
-    int		matches;
-    int		len;
-    int		starstar = FALSE;
+    int	    starts_with_dot;
+    int	    matches;		    // number of matches found
+    int	    starstar = FALSE;
     static int	stardepth = 0;	    // depth for "**" expansion
-    HANDLE		hFind = INVALID_HANDLE_VALUE;
-    WIN32_FIND_DATAW    wfb;
-    WCHAR		*wn = NULL;	// UCS-2 name, NULL when not used.
-    char_u		*matchname;
-    int			ok;
-    char_u		*p_alt;
+#ifdef MSWIN
+    HANDLE  hFind = INVALID_HANDLE_VALUE;
+    WIN32_FIND_DATAW	wfb;
+    WCHAR   *wn = NULL;		    // UCS-2 name, NULL when not used.
+#else
+    DIR	    *dirp;
+#endif
+    int	    ok;
 
     // Expanding "**" may take a long time, check for CTRL-C.
     if (stardepth > 0)
@@ -3493,262 +3581,16 @@
 	    return 0;
     }
 
-    // Make room for file name.  When doing encoding conversion the actual
-    // length may be quite a bit longer, thus use the maximum possible length.
+    // Make room for file name. When doing encoding conversion the actual
+    // length may be quite a bit longer.
     buf = alloc(MAXPATHL);
     if (buf == NULL)
 	return 0;
 
     /*
      * Find the first part in the path name that contains a wildcard or a ~1.
-     * Copy it into buf, including the preceding characters.
-     */
-    p = buf;
-    s = buf;
-    e = NULL;
-    path_end = path;
-    while (*path_end != NUL)
-    {
-	// May ignore a wildcard that has a backslash before it; it will
-	// be removed by rem_backslash() or file_pat_to_reg_pat() below.
-	if (path_end >= path + wildoff && rem_backslash(path_end))
-	    *p++ = *path_end++;
-	else if (*path_end == '\\' || *path_end == ':' || *path_end == '/')
-	{
-	    if (e != NULL)
-		break;
-	    s = p + 1;
-	}
-	else if (path_end >= path + wildoff
-			 && vim_strchr((char_u *)"*?[~", *path_end) != NULL)
-	    e = p;
-	if (has_mbyte)
-	{
-	    len = (*mb_ptr2len)(path_end);
-	    STRNCPY(p, path_end, len);
-	    p += len;
-	    path_end += len;
-	}
-	else
-	    *p++ = *path_end++;
-    }
-    e = p;
-    *e = NUL;
-
-    // now we have one wildcard component between s and e
-    // Remove backslashes between "wildoff" and the start of the wildcard
-    // component.
-    for (p = buf + wildoff; p < s; ++p)
-	if (rem_backslash(p))
-	{
-	    STRMOVE(p, p + 1);
-	    --e;
-	    --s;
-	}
-
-    // Check for "**" between "s" and "e".
-    for (p = s; p < e; ++p)
-	if (p[0] == '*' && p[1] == '*')
-	    starstar = TRUE;
-
-    starts_with_dot = *s == '.';
-    pat = file_pat_to_reg_pat(s, e, NULL, FALSE);
-    if (pat == NULL)
-    {
-	vim_free(buf);
-	return 0;
-    }
-
-    // compile the regexp into a program
-    if (flags & (EW_NOERROR | EW_NOTWILD))
-	++emsg_silent;
-    regmatch.rm_ic = TRUE;		// Always ignore case
-    regmatch.regprog = vim_regcomp(pat, RE_MAGIC);
-    if (flags & (EW_NOERROR | EW_NOTWILD))
-	--emsg_silent;
-    vim_free(pat);
-
-    if (regmatch.regprog == NULL && (flags & EW_NOTWILD) == 0)
-    {
-	vim_free(buf);
-	return 0;
-    }
-
-    // remember the pattern or file name being looked for
-    matchname = vim_strsave(s);
-
-    // If "**" is by itself, this is the first time we encounter it and more
-    // is following then find matches without any directory.
-    if (!didstar && stardepth < 100 && starstar && e - s == 2
-							  && *path_end == '/')
-    {
-	STRCPY(s, path_end + 1);
-	++stardepth;
-	(void)dos_expandpath(gap, buf, (int)(s - buf), flags, TRUE);
-	--stardepth;
-    }
-
-    // Scan all files in the directory with "dir/ *.*"
-    STRCPY(s, "*.*");
-    wn = enc_to_utf16(buf, NULL);
-    if (wn != NULL)
-	hFind = FindFirstFileW(wn, &wfb);
-    ok = (hFind != INVALID_HANDLE_VALUE);
-
-    while (ok)
-    {
-	p = utf16_to_enc(wfb.cFileName, NULL);   // p is allocated here
-
-	if (p == NULL)
-	    break;  // out of memory
-
-	// Do not use the alternate filename when the file name ends in '~',
-	// because it picks up backup files: short name for "foo.vim~" is
-	// "foo~1.vim", which matches "*.vim".
-	if (*wfb.cAlternateFileName == NUL || p[STRLEN(p) - 1] == '~')
-	    p_alt = NULL;
-	else
-	    p_alt = utf16_to_enc(wfb.cAlternateFileName, NULL);
-
-	// Ignore entries starting with a dot, unless when asked for.  Accept
-	// all entries found with "matchname".
-	if ((p[0] != '.' || starts_with_dot
-			 || ((flags & EW_DODOT)
-			     && p[1] != NUL && (p[1] != '.' || p[2] != NUL)))
-		&& (matchname == NULL
-		  || (regmatch.regprog != NULL
-		      && (vim_regexec(&regmatch, p, (colnr_T)0)
-			 || (p_alt != NULL
-				&& vim_regexec(&regmatch, p_alt, (colnr_T)0))))
-		  || ((flags & EW_NOTWILD)
-		     && fnamencmp(path + (s - buf), p, e - s) == 0)))
-	{
-	    STRCPY(s, p);
-	    len = (int)STRLEN(buf);
-
-	    if (starstar && stardepth < 100
-			  && (wfb.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
-	    {
-		// For "**" in the pattern first go deeper in the tree to
-		// find matches.
-		STRCPY(buf + len, "/**");
-		STRCPY(buf + len + 3, path_end);
-		++stardepth;
-		(void)dos_expandpath(gap, buf, len + 1, flags, TRUE);
-		--stardepth;
-	    }
-
-	    STRCPY(buf + len, path_end);
-	    if (mch_has_exp_wildcard(path_end))
-	    {
-		// need to expand another component of the path
-		// remove backslashes for the remaining components only
-		(void)dos_expandpath(gap, buf, len + 1, flags, FALSE);
-	    }
-	    else
-	    {
-		stat_T  sb;
-
-		// no more wildcards, check if there is a match
-		// remove backslashes for the remaining components only
-		if (*path_end != 0)
-		    backslash_halve(buf + len + 1);
-		// add existing file
-		if ((flags & EW_ALLLINKS) ? mch_lstat((char *)buf, &sb) >= 0
-			: mch_getperm(buf) >= 0)
-		    addfile(gap, buf, flags);
-	    }
-	}
-
-	vim_free(p_alt);
-	vim_free(p);
-	ok = FindNextFileW(hFind, &wfb);
-    }
-
-    FindClose(hFind);
-    vim_free(wn);
-    vim_free(buf);
-    vim_regfree(regmatch.regprog);
-    vim_free(matchname);
-
-    matches = gap->ga_len - start_len;
-    if (matches > 0)
-	qsort(((char_u **)gap->ga_data) + start_len, (size_t)matches,
-						   sizeof(char_u *), pstrcmp);
-    return matches;
-}
-
-    int
-mch_expandpath(
-    garray_T	*gap,
-    char_u	*path,
-    int		flags)		// EW_* flags
-{
-    return dos_expandpath(gap, path, 0, flags, FALSE);
-}
-#endif // MSWIN
-
-#if (defined(UNIX) && !defined(VMS)) || defined(USE_UNIXFILENAME) \
-	|| defined(PROTO)
-/*
- * Unix style wildcard expansion code.
- * It's here because it's used both for Unix and Mac.
- */
-    static int
-pstrcmp(const void *a, const void *b)
-{
-    return (pathcmp(*(char **)a, *(char **)b, -1));
-}
-
-/*
- * Recursively expand one path component into all matching files and/or
- * directories.  Adds matches to "gap".  Handles "*", "?", "[a-z]", "**", etc.
- * "path" has backslashes before chars that are not to be expanded, starting
- * at "path + wildoff".
- * Return the number of matches found.
- * NOTE: much of this is identical to dos_expandpath(), keep in sync!
- */
-    int
-unix_expandpath(
-    garray_T	*gap,
-    char_u	*path,
-    int		wildoff,
-    int		flags,		// EW_* flags
-    int		didstar)	// expanded "**" once already
-{
-    char_u	*buf;
-    char_u	*path_end;
-    char_u	*p, *s, *e;
-    int		start_len = gap->ga_len;
-    char_u	*pat;
-    regmatch_T	regmatch;
-    int		starts_with_dot;
-    int		matches;
-    int		len;
-    int		starstar = FALSE;
-    static int	stardepth = 0;	    // depth for "**" expansion
-
-    DIR		*dirp;
-    struct dirent *dp;
-
-    // Expanding "**" may take a long time, check for CTRL-C.
-    if (stardepth > 0)
-    {
-	ui_breakcheck();
-	if (got_int)
-	    return 0;
-    }
-
-    // make room for file name (a bit too much to stay on the safe side)
-    size_t buflen = STRLEN(path) + MAXPATHL;
-    buf = alloc(buflen);
-    if (buf == NULL)
-	return 0;
-
-    /*
-     * Find the first part in the path name that contains a wildcard.
-     * When EW_ICASE is set every letter is considered to be a wildcard.
      * Copy it into "buf", including the preceding characters.
+     * Note: for unix, when EW_ICASE is set every letter is considered to be a wildcard.
      */
     p = buf;
     s = buf;
@@ -3760,20 +3602,25 @@
 	// be removed by rem_backslash() or file_pat_to_reg_pat() below.
 	if (path_end >= path + wildoff && rem_backslash(path_end))
 	    *p++ = *path_end++;
-	else if (*path_end == '/')
+	else if (vim_ispathsep(*path_end))
 	{
 	    if (e != NULL)
 		break;
 	    s = p + 1;
 	}
 	else if (path_end >= path + wildoff
-			 && (vim_strchr((char_u *)"*?[{~$", *path_end) != NULL
-			     || (!p_fic && (flags & EW_ICASE)
-					  && vim_isalpha(PTR2CHAR(path_end)))))
+#ifdef MSWIN
+		&& vim_strchr((char_u *)"*?[~", *path_end) != NULL
+#else
+		&& (vim_strchr((char_u *)"*?[{~$", *path_end) != NULL
+		     || (!p_fic && (flags & EW_ICASE)
+			  && vim_isalpha(PTR2CHAR(path_end))))
+#endif
+	)
 	    e = p;
 	if (has_mbyte)
 	{
-	    len = (*mb_ptr2len)(path_end);
+	    int	len = (*mb_ptr2len)(path_end);
 	    STRNCPY(p, path_end, len);
 	    p += len;
 	    path_end += len;
@@ -3787,18 +3634,35 @@
     // Now we have one wildcard component between "s" and "e".
     // Remove backslashes between "wildoff" and the start of the wildcard
     // component.
-    for (p = buf + wildoff; p < s; ++p)
-	if (rem_backslash(p))
+    p = buf + wildoff;
+    if (p < s)
+    {
+	size_t	psize = STRLEN(p) + 1;
+
+	do
 	{
-	    STRMOVE(p, p + 1);
-	    --e;
-	    --s;
-	}
+	    if (!rem_backslash(p))
+		++p;
+	    else
+	    {
+		mch_memmove(p, p + 1, psize);
+		--e;
+		--s;
+	    }
+	    --psize;
+	} while (p < s);
+    }
+
+    basepathlen = (size_t)(s - buf);
+    wildcardlen = (size_t)(e - s);
 
     // Check for "**" between "s" and "e".
     for (p = s; p < e; ++p)
 	if (p[0] == '*' && p[1] == '*')
+	{
 	    starstar = TRUE;
+	    break;
+	}
 
     // convert the file pattern to a regexp pattern
     starts_with_dot = *s == '.';
@@ -3810,10 +3674,14 @@
     }
 
     // compile the regexp into a program
+#ifdef MSWIN
+    regmatch.rm_ic = TRUE;	// Always ignore case
+#else
     if (flags & EW_ICASE)
-	regmatch.rm_ic = TRUE;		// 'wildignorecase' set
+	regmatch.rm_ic = TRUE;	// 'wildignorecase' set
     else
-	regmatch.rm_ic = p_fic;	// ignore case when 'fileignorecase' is set
+	regmatch.rm_ic = p_fic; // ignore case when 'fileignorecase' is set
+#endif
     if (flags & (EW_NOERROR | EW_NOTWILD))
 	++emsg_silent;
     regmatch.regprog = vim_regcomp(pat, RE_MAGIC);
@@ -3829,51 +3697,100 @@
 
     // If "**" is by itself, this is the first time we encounter it and more
     // is following then find matches without any directory.
-    if (!didstar && stardepth < 100 && starstar && e - s == 2
-							  && *path_end == '/')
+    if (!didstar && stardepth < 100 && starstar && wildcardlen == 2
+			      && *path_end == '/')
     {
 	STRCPY(s, path_end + 1);
 	++stardepth;
-	(void)unix_expandpath(gap, buf, (int)(s - buf), flags, TRUE);
+	(void)unix_expandpath(gap, buf, (int)basepathlen, flags, TRUE);
 	--stardepth;
     }
 
+#ifdef MSWIN
+    // open the directory for scanning
+    STRCPY(s, "*.*");
+    wn = enc_to_utf16(buf, NULL);
+    if (wn != NULL)
+	hFind = FindFirstFileW(wn, &wfb);
+    ok = (hFind != INVALID_HANDLE_VALUE);
+#else
     // open the directory for scanning
     *s = NUL;
     dirp = opendir(*buf == NUL ? "." : (char *)buf);
+    ok = (dirp != NULL);
+#endif
 
     // Find all matching entries
-    if (dirp != NULL)
+    if (ok)
     {
+	char_u	*d_name;
+#ifdef MSWIN
+	char_u  *d_name_alt;
+	// remember the pattern or file name being looked for
+	char_u	*matchname = vim_strnsave(s, basepathlen);
+#else
+	struct dirent	*dp;
+#endif
+
 	while (!got_int)
 	{
+#ifdef MSWIN
+	    d_name = utf16_to_enc(wfb.cFileName, NULL);   // p is allocated here
+	    if (d_name == NULL)
+		break;  // out of memory
+
+	    // Do not use the alternate filename when the file name ends in '~',
+	    // because it picks up backup files: short name for "foo.vim~" is
+	    // "foo~1.vim", which matches "*.vim".
+	    if (*wfb.cAlternateFileName == NUL || d_name[STRLEN(d_name) - 1] == '~')
+		d_name_alt = NULL;
+	    else
+		d_name_alt = utf16_to_enc(wfb.cAlternateFileName, NULL);
+#else
 	    dp = readdir(dirp);
 	    if (dp == NULL)
 		break;
-	    if ((dp->d_name[0] != '.' || starts_with_dot
-			|| ((flags & EW_DODOT)
-			    && dp->d_name[1] != NUL
-			    && (dp->d_name[1] != '.' || dp->d_name[2] != NUL)))
-		 && ((regmatch.regprog != NULL && vim_regexec(&regmatch,
-					     (char_u *)dp->d_name, (colnr_T)0))
-		   || ((flags & EW_NOTWILD)
-		     && fnamencmp(path + (s - buf), dp->d_name, e - s) == 0)))
-	    {
-		vim_strncpy(s, (char_u *)dp->d_name, buflen - (s - buf) - 1);
-		len = STRLEN(buf);
+	    d_name = (char_u *)dp->d_name;
+#endif
 
-		if (starstar && stardepth < 100)
+	    // Ignore entries starting with a dot, unless when asked for. For MSWIN accept
+	    // all entries found with "matchname".
+	    if (
+		(d_name[0] != '.' || starts_with_dot || (
+		    (flags & EW_DODOT) && d_name[1] != NUL &&
+		    (d_name[1] != '.' || d_name[2] != NUL)))
+	     && (
+#ifdef MSWIN
+		matchname == NULL ||
+#endif
+		(regmatch.regprog != NULL
+		    && vim_regexec(&regmatch, (char_u *)d_name, (colnr_T)0))
+#ifdef MSWIN
+		 || (d_name_alt != NULL
+		    && vim_regexec(&regmatch, d_name_alt, (colnr_T)0))
+#endif
+		   || ((flags & EW_NOTWILD)
+		     && fnamencmp(path + basepathlen, d_name, wildcardlen) == 0))
+	    )
+	    {
+		int len = (int)basepathlen + vim_snprintf((char *)s, (size_t)(MAXPATHL - (basepathlen + 1)), "%s", d_name);
+
+		if (starstar && stardepth < 100
+#ifdef MSWIN
+			    && (wfb.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+#endif
+		    )
 		{
 		    // For "**" in the pattern first go deeper in the tree to
 		    // find matches.
-		    vim_snprintf((char *)buf + len, buflen - len,
-							    "/**%s", path_end);
+		    vim_snprintf((char *)buf + len, (size_t)(MAXPATHL - len),
+					"/**%s", path_end);
 		    ++stardepth;
 		    (void)unix_expandpath(gap, buf, len + 1, flags, TRUE);
 		    --stardepth;
 		}
 
-		vim_snprintf((char *)buf + len, buflen - len, "%s", path_end);
+		vim_snprintf((char *)buf + len, (size_t)(MAXPATHL - len), "%s", path_end);
 		if (mch_has_exp_wildcard(path_end)) // handle more wildcards
 		{
 		    // need to expand another component of the path
@@ -3890,7 +3807,7 @@
 			backslash_halve(buf + len + 1);
 		    // add existing file or symbolic link
 		    if ((flags & EW_ALLLINKS) ? mch_lstat((char *)buf, &sb) >= 0
-						      : mch_getperm(buf) >= 0)
+				      : mch_getperm(buf) >= 0)
 		    {
 #ifdef MACOS_CONVERT
 			size_t precomp_len = STRLEN(buf)+1;
@@ -3907,11 +3824,26 @@
 		    }
 		}
 	    }
+
+#ifdef MSWIN
+	    vim_free(d_name);
+	    if (!FindNextFileW(hFind, &wfb))
+		break;
+#endif
 	}
 
+#ifdef MSWIN
+	FindClose(hFind);
+	vim_free(matchname);
+	vim_free(d_name_alt);
+#else
 	closedir(dirp);
+#endif
     }
 
+#ifdef MSWIN
+    vim_free(wn);
+#endif
     vim_free(buf);
     vim_regfree(regmatch.regprog);
 
@@ -3920,9 +3852,24 @@
     matches = gap->ga_len - start_len;
     if (matches > 0 && !got_int)
 	qsort(((char_u **)gap->ga_data) + start_len, matches,
-						   sizeof(char_u *), pstrcmp);
+			   sizeof(char_u *), pstrcmp);
     return matches;
 }
+
+/*
+ * Expand a path into all matching files and/or directories.  Handles "*",
+ * "?", "[a-z]", "**", etc where appropriate for the platform.
+ * "path" has backslashes before chars that are not to be expanded.
+ * Returns the number of matches found.
+ */
+    int
+mch_expandpath(
+    garray_T	*gap,
+    char_u	*path,
+    int		flags)		// EW_* flags
+{
+    return unix_expandpath(gap, path, 0, flags, FALSE);
+}
 #endif
 
 /*