From: John Marriott Date: Fri, 13 Dec 2024 12:58:53 +0000 (+0100) Subject: patch 9.1.0923: too many strlen() calls in filepath.c X-Git-Tag: v9.1.0923^0 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e29c8bafa78847414419522baecd008e287389db;p=thirdparty%2Fvim.git 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 Signed-off-by: Christian Brabandt --- diff --git a/src/filepath.c b/src/filepath.c index 3dd71bc407..7298842527 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 @@ get_short_pathname(char_u **fnamep, char_u **bufp, int *fnamelen) 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 @@ shortpath_for_invalid_fname( 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 @@ shortpath_for_invalid_fname( // 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 @@ shortpath_for_invalid_fname( * 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 @@ shortpath_for_partial( 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 @@ shortpath_for_partial( // 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 @@ repeat: 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 @@ repeat: 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 @@ f_mkdir(typval_T *argvars, typval_T *rettv) 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 @@ f_resolve(typval_T *argvars, typval_T *rettv) 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 @@ f_resolve(typval_T *argvars, typval_T *rettv) #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 @@ f_resolve(typval_T *argvars, typval_T *rettv) 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 @@ f_resolve(typval_T *argvars, typval_T *rettv) || 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 @@ f_resolve(typval_T *argvars, typval_T *rettv) 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 @@ f_resolve(typval_T *argvars, typval_T *rettv) #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 @@ getnextcomp(char_u *fname) { while (*fname && !vim_ispathsep(*fname)) MB_PTR_ADV(fname); + if (*fname) ++fname; return fname; @@ -3116,16 +3194,18 @@ vim_fnamencmp(char_u *x, char_u *y, size_t len) 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 @@ concat_fnames(char_u *fname1, char_u *fname2, int sep) 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 @@ expand_backtick( } #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 @@ pstrcmp(const void *a, const void *b) * "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,15 +3581,16 @@ dos_expandpath( 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. + * 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; @@ -3513,18 +3602,25 @@ dos_expandpath( // 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 == '/') + 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) +#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; @@ -3535,270 +3631,38 @@ dos_expandpath( e = p; *e = NUL; - // now we have one wildcard component between s and e + // 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) + p = buf + wildoff; + if (p < s) { - vim_free(buf); - return 0; - } + size_t psize = STRLEN(p) + 1; - // 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(®match, p, (colnr_T)0) - || (p_alt != NULL - && vim_regexec(®match, p_alt, (colnr_T)0)))) - || ((flags & EW_NOTWILD) - && fnamencmp(path + (s - buf), p, e - s) == 0))) + do { - 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); - } + if (!rem_backslash(p)) + ++p; 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); + mch_memmove(p, p + 1, psize); + --e; + --s; } - } - - vim_free(p_alt); - vim_free(p); - ok = FindNextFileW(hFind, &wfb); + --psize; + } while (p < s); } - 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. - */ - 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 == '/') - { - 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))))) - 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; - } + 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 @@ unix_expandpath( } // 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 @@ unix_expandpath( // 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(®match, - (char_u *)dp->d_name, (colnr_T)0)) + d_name = (char_u *)dp->d_name; +#endif + + // 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(®match, (char_u *)d_name, (colnr_T)0)) +#ifdef MSWIN + || (d_name_alt != NULL + && vim_regexec(®match, d_name_alt, (colnr_T)0)) +#endif || ((flags & EW_NOTWILD) - && fnamencmp(path + (s - buf), dp->d_name, e - s) == 0))) + && fnamencmp(path + basepathlen, d_name, wildcardlen) == 0)) + ) { - vim_strncpy(s, (char_u *)dp->d_name, buflen - (s - buf) - 1); - len = STRLEN(buf); + int len = (int)basepathlen + vim_snprintf((char *)s, (size_t)(MAXPATHL - (basepathlen + 1)), "%s", d_name); - if (starstar && stardepth < 100) + 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 @@ unix_expandpath( 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 @@ unix_expandpath( } } } + +#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 @@ unix_expandpath( 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 /* diff --git a/src/os_unix.c b/src/os_unix.c index dc518fc676..ac78733d34 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -6796,21 +6796,6 @@ select_eintr: return result; } -/* - * Expand a path into all matching files and/or directories. Handles "*", - * "?", "[a-z]", "**", etc. - * "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); -} - /* * mch_expand_wildcards() - this code does wild-card pattern matching using * the shell diff --git a/src/proto/filepath.pro b/src/proto/filepath.pro index 46f51cb36f..1665089e6c 100644 --- a/src/proto/filepath.pro +++ b/src/proto/filepath.pro @@ -56,6 +56,7 @@ int expand_wildcards_eval(char_u **pat, int *num_file, char_u ***file, int flags int expand_wildcards(int num_pat, char_u **pat, int *num_files, char_u ***files, int flags); int match_suffix(char_u *fname); int unix_expandpath(garray_T *gap, char_u *path, int wildoff, int flags, int didstar); +int mch_expandpath(garray_T *gap, char_u *path, int flags); int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, char_u ***file, int flags); void addfile(garray_T *gap, char_u *f, int flags); void FreeWild(int count, char_u **files); diff --git a/src/version.c b/src/version.c index 47ff826398..4feba92608 100644 --- a/src/version.c +++ b/src/version.c @@ -704,6 +704,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 923, /**/ 922, /**/