Merge PR #1212 'os_scandir/scandir_next/closedir()'

This commit is contained in:
Thiago de Arruda 2015-03-31 11:50:22 -03:00
commit ad7a317c42
15 changed files with 234 additions and 187 deletions

View File

@ -27,6 +27,9 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
if(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_INCLUDE_SYSTEM_FLAG_CXX "-isystem ")
endif()
# Enable fixing case-insensitive filenames for Mac.
set(USE_FNAME_CASE TRUE)
endif()
# Set available build types for CMake GUIs.

View File

@ -15,7 +15,6 @@
#cmakedefine HAVE__NSGETENVIRON
#cmakedefine HAVE_CRT_EXTERNS_H
#cmakedefine HAVE_DIRENT_H
#cmakedefine HAVE_FCNTL_H
#cmakedefine HAVE_FD_CLOEXEC
#cmakedefine HAVE_FSEEKO
@ -60,6 +59,7 @@
#define SIGRETURN return
#define TIME_WITH_SYS_TIME 1
#cmakedefine UNIX
#cmakedefine USE_FNAME_CASE
#define USEMAN_S 1
#define FEAT_BROWSE

View File

@ -2253,7 +2253,7 @@ setfname (
}
sfname = vim_strsave(sfname);
#ifdef USE_FNAME_CASE
fname_case(sfname, 0); /* set correct case for short file name */
path_fix_case(sfname); /* set correct case for short file name */
#endif
free(buf->b_ffname);
free(buf->b_sfname);

View File

@ -24,6 +24,7 @@
#include "nvim/move.h"
#include "nvim/os_unix.h"
#include "nvim/strings.h"
#include "nvim/path.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@ -871,7 +872,7 @@ int vim_isfilec(int c)
/// return TRUE if 'c' is a valid file-name character or a wildcard character
/// Assume characters above 0x100 are valid (multi-byte).
/// Explicitly interpret ']' as a wildcard character as mch_has_wildcard("]")
/// Explicitly interpret ']' as a wildcard character as path_has_wildcard("]")
/// returns false.
///
/// @param c
@ -882,7 +883,7 @@ int vim_isfilec_or_wc(int c)
char_u buf[2];
buf[0] = (char_u)c;
buf[1] = NUL;
return vim_isfilec(c) || c == ']' || mch_has_wildcard(buf);
return vim_isfilec(c) || c == ']' || path_has_wildcard(buf);
}
/// return TRUE if 'c' is a printable character

View File

@ -9895,9 +9895,7 @@ static void f_has(typval_T *argvars, typval_T *rettv)
#if defined(WIN64) || defined(_WIN64)
"win64",
#endif
#ifndef CASE_INSENSITIVE_FILENAME
"fname_case",
#endif
#ifdef HAVE_ACL
"acl",
#endif

View File

@ -2563,7 +2563,7 @@ do_ecmd (
sfname = ffname;
#ifdef USE_FNAME_CASE
if (sfname != NULL)
fname_case(sfname, 0); /* set correct case for sfname */
path_fix_case(sfname); // set correct case for sfname
#endif
if ((flags & ECMD_ADDBUF) && (ffname == NULL || *ffname == NUL))

View File

@ -3432,7 +3432,7 @@ int expand_filename(exarg_T *eap, char_u **cmdlinep, char_u **errormsgp)
* the file name contains a wildcard it should not cause expanding.
* (it will be expanded anyway if there is a wildcard before replacing).
*/
has_wildcards = mch_has_wildcard(p);
has_wildcards = path_has_wildcard(p);
while (*p != NUL) {
/* Skip over `=expr`, wildcards in it are not expanded. */
if (p[0] == '`' && p[1] == '=') {
@ -3543,7 +3543,7 @@ int expand_filename(exarg_T *eap, char_u **cmdlinep, char_u **errormsgp)
|| vim_strchr(eap->arg, '~') != NULL) {
expand_env_esc(eap->arg, NameBuff, MAXPATHL,
TRUE, TRUE, NULL);
has_wildcards = mch_has_wildcard(NameBuff);
has_wildcards = path_has_wildcard(NameBuff);
p = NameBuff;
} else
p = NULL;

View File

@ -1290,8 +1290,8 @@ scripterror:
}
#ifdef USE_FNAME_CASE
/* Make the case of the file name match the actual file. */
fname_case(p, 0);
// Make the case of the file name match the actual file.
path_fix_case(p);
#endif
alist_add(&global_alist, p,

View File

@ -348,6 +348,39 @@ int os_rmdir(const char *path)
return result;
}
/// Opens a directory.
/// @param[out] dir The Directory object.
/// @param path Path to the directory.
/// @returns true if dir contains one or more items, false if not or an error
/// occurred.
bool os_scandir(Directory *dir, const char *path)
FUNC_ATTR_NONNULL_ALL
{
int r = uv_fs_scandir(uv_default_loop(), &dir->request, path, 0, NULL);
if (r <= 0) {
os_closedir(dir);
}
return r > 0;
}
/// Increments the directory pointer.
/// @param dir The Directory object.
/// @returns a pointer to the next path in `dir` or `NULL`.
const char *os_scandir_next(Directory *dir)
FUNC_ATTR_NONNULL_ALL
{
int err = uv_fs_scandir_next(&dir->request, &dir->ent);
return err != UV_EOF ? dir->ent.name : NULL;
}
/// Frees memory associated with `os_scandir()`.
/// @param dir The directory.
void os_closedir(Directory *dir)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_req_cleanup(&dir->request);
}
/// Remove a file.
///
/// @return `0` for success, non-zero for failure.

View File

@ -16,4 +16,9 @@ typedef struct {
#define FILE_ID_EMPTY (FileID) {.inode = 0, .device_id = 0}
typedef struct {
uv_fs_t request; ///< @private The request to uv for the directory.
uv_dirent_t ent; ///< @private The entry information.
} Directory;
#endif // NVIM_OS_FS_DEFS_H

View File

@ -69,62 +69,6 @@ static int selinux_enabled = -1;
# include "os_unix.c.generated.h"
#endif
#if defined(USE_FNAME_CASE)
/*
* Set the case of the file name, if it already exists. This will cause the
* file name to remain exactly the same.
* Only required for file systems where case is ignored and preserved.
*/
void fname_case(
char_u *name,
int len /* buffer size, only used when name gets longer */
)
{
char_u *slash, *tail;
DIR *dirp;
struct dirent *dp;
FileInfo file_info;
if (os_fileinfo_link((char *)name, &file_info)) {
/* Open the directory where the file is located. */
slash = vim_strrchr(name, '/');
if (slash == NULL) {
dirp = opendir(".");
tail = name;
} else {
*slash = NUL;
dirp = opendir((char *)name);
*slash = '/';
tail = slash + 1;
}
if (dirp != NULL) {
while ((dp = readdir(dirp)) != NULL) {
/* Only accept names that differ in case and are the same byte
* length. TODO: accept different length name. */
if (STRICMP(tail, dp->d_name) == 0
&& STRLEN(tail) == STRLEN(dp->d_name)) {
char_u newname[MAXPATHL + 1];
/* Verify the inode is equal. */
STRLCPY(newname, name, MAXPATHL + 1);
STRLCPY(newname + (tail - name), dp->d_name,
MAXPATHL - (tail - name) + 1);
FileInfo file_info_new;
if (os_fileinfo_link((char *)newname, &file_info_new)
&& os_fileinfo_id_equal(&file_info, &file_info_new)) {
STRCPY(tail, dp->d_name);
break;
}
}
}
closedir(dirp);
}
}
}
#endif
#if defined(HAVE_ACL)
# ifdef HAVE_SYS_ACL_H
# include <sys/acl.h>
@ -719,47 +663,12 @@ static void save_patterns(int num_pat, char_u **pat, int *num_file,
*num_file = num_pat;
}
/*
* Return TRUE if the string "p" contains a wildcard that mch_expandpath() can
* expand.
*/
int mch_has_exp_wildcard(char_u *p)
{
for (; *p; mb_ptr_adv(p)) {
if (*p == '\\' && p[1] != NUL)
++p;
else if (vim_strchr((char_u *)
"*?[{'"
, *p) != NULL)
return TRUE;
}
return FALSE;
}
/*
* Return TRUE if the string "p" contains a wildcard.
* Don't recognize '~' at the end as a wildcard.
*/
int mch_has_wildcard(char_u *p)
{
for (; *p; mb_ptr_adv(p)) {
if (*p == '\\' && p[1] != NUL)
++p;
else if (vim_strchr((char_u *)
"*?[{`'$"
, *p) != NULL
|| (*p == '~' && p[1] != NUL))
return TRUE;
}
return FALSE;
}
static int have_wildcard(int num, char_u **file)
{
int i;
for (i = 0; i < num; i++)
if (mch_has_wildcard(file[i]))
if (path_has_wildcard(file[i]))
return 1;
return 0;
}

View File

@ -44,25 +44,6 @@
# define SIGDUMMYARG
#endif
#ifdef HAVE_DIRENT_H
# include <dirent.h>
# ifndef NAMLEN
# define NAMLEN(dirent) strlen((dirent)->d_name)
# endif
#else
# define dirent direct
# define NAMLEN(dirent) (dirent)->d_namlen
# if HAVE_SYS_NDIR_H
# include <sys/ndir.h>
# endif
# if HAVE_SYS_DIR_H
# include <sys/dir.h>
# endif
# if HAVE_NDIR_H
# include <ndir.h>
# endif
#endif
#if !defined(HAVE_SYS_TIME_H) || defined(TIME_WITH_SYS_TIME)
# include <time.h> /* on some systems time.h should not be
included together with sys/time.h */

View File

@ -400,6 +400,32 @@ char_u *save_absolute_path(const char_u *name)
return vim_strsave((char_u *) name);
}
/// Checks if a path has a wildcard character including '~', unless at the end.
/// @param p The path to expand.
/// @returns Unix: True if it contains one of "?[{`'$".
/// @returns Windows: True if it contains one of "*?$[".
bool path_has_wildcard(const char_u *p)
FUNC_ATTR_NONNULL_ALL
{
for (; *p; mb_ptr_adv(p)) {
#if defined(UNIX)
if (p[0] == '\\' && p[1] != NUL) {
p++;
continue;
}
const char *wildcards = "*?[{`'$";
#else
// Windows:
const char *wildcards = "?*$[`";
#endif
if (vim_strchr((char_u *)wildcards, *p) != NULL
|| (p[0] == '~' && p[1] != NUL)) {
return true;
}
}
return false;
}
#if defined(UNIX)
/*
@ -410,39 +436,67 @@ static int pstrcmp(const void *a, const void *b)
{
return pathcmp(*(char **)a, *(char **)b, -1);
}
#endif
/*
* 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 */
)
/// Checks if a path has a character path_expand can expand.
/// @param p The path to expand.
/// @returns Unix: True if it contains one of *?[{.
/// @returns Windows: True if it contains one of *?[.
bool path_has_exp_wildcard(const char_u *p)
FUNC_ATTR_NONNULL_ALL
{
for (; *p != NUL; mb_ptr_adv(p)) {
#if defined(UNIX)
if (p[0] == '\\' && p[1] != NUL) {
p++;
continue;
}
const char *wildcards = "*?[{";
#else
const char *wildcards = "*?["; // Windows.
#endif
if (vim_strchr((char_u *) wildcards, *p) != NULL) {
return true;
}
}
return false;
}
/// Recursively expands one path component into all matching files and/or
/// directories. Handles "*", "?", "[a-z]", "**", etc.
/// @remark "**" in `path` requests recursive expansion.
///
/// @param[out] gap The matches found.
/// @param path The path to search.
/// @param flags Flags for regexp expansion.
/// - EW_ICASE: Ignore case.
/// - EW_NOERROR: Silence error messeges.
/// - EW_NOTWILD: Add matches literally.
/// @returns the number of matches found.
static size_t path_expand(garray_T *gap, const char_u *path, int flags)
FUNC_ATTR_NONNULL_ALL
{
return do_path_expand(gap, path, 0, flags, false);
}
/// Implementation of path_expand().
///
/// Chars before `path + wildoff` do not get expanded.
static size_t do_path_expand(garray_T *gap, const char_u *path,
size_t wildoff, int flags, bool didstar)
FUNC_ATTR_NONNULL_ALL
{
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) {
os_breakcheck();
@ -461,7 +515,7 @@ unix_expandpath (
p = buf;
s = buf;
e = NULL;
path_end = path;
const char_u *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. */
@ -510,11 +564,14 @@ unix_expandpath (
return 0;
}
/* compile the regexp into a program */
if (flags & EW_ICASE)
regmatch.rm_ic = TRUE; /* 'wildignorecase' set */
else
regmatch.rm_ic = p_fic; /* ignore case when 'fileignorecase' is set */
// compile the regexp into a program
regmatch_T regmatch;
#if defined(UNIX)
// Ignore case if given 'wildignorecase', else respect 'fileignorecase'.
regmatch.rm_ic = (flags & EW_ICASE) || p_fic;
#else
regmatch.rm_ic = true; // Always ignore case on Windows.
#endif
if (flags & (EW_NOERROR | EW_NOTWILD))
++emsg_silent;
regmatch.regprog = vim_regcomp(pat, RE_MAGIC);
@ -533,26 +590,22 @@ unix_expandpath (
&& *path_end == '/') {
STRCPY(s, path_end + 1);
++stardepth;
(void)unix_expandpath(gap, buf, (int)(s - buf), flags, TRUE);
(void)do_path_expand(gap, buf, (int)(s - buf), flags, TRUE);
--stardepth;
}
/* open the directory for scanning */
*s = NUL;
dirp = opendir(*buf == NUL ? "." : (char *)buf);
/* Find all matching entries */
if (dirp != NULL) {
for (;; ) {
dp = readdir(dirp);
if (dp == NULL)
break;
if ((dp->d_name[0] != '.' || starts_with_dot)
&& ((regmatch.regprog != NULL && vim_regexec(&regmatch,
(char_u *)dp->d_name, (colnr_T)0))
Directory dir;
// open the directory for scanning
if (os_scandir(&dir, *buf == NUL ? "." : (char *)buf)) {
// Find all matching entries.
char_u *name;
while((name = (char_u *) os_scandir_next(&dir))) {
if ((name[0] != '.' || starts_with_dot)
&& ((regmatch.regprog != NULL && vim_regexec(&regmatch, name, 0))
|| ((flags & EW_NOTWILD)
&& fnamencmp(path + (s - buf), dp->d_name, e - s) == 0))) {
STRCPY(s, dp->d_name);
&& fnamencmp(path + (s - buf), name, e - s) == 0))) {
STRCPY(s, name);
len = STRLEN(buf);
if (starstar && stardepth < 100) {
@ -561,15 +614,15 @@ unix_expandpath (
STRCPY(buf + len, "/**");
STRCPY(buf + len + 3, path_end);
++stardepth;
(void)unix_expandpath(gap, buf, len + 1, flags, TRUE);
(void)do_path_expand(gap, buf, len + 1, flags, true);
--stardepth;
}
STRCPY(buf + len, path_end);
if (mch_has_exp_wildcard(path_end)) { /* handle more wildcards */
if (path_has_exp_wildcard(path_end)) { /* handle more wildcards */
/* need to expand another component of the path */
/* remove backslashes for the remaining components only */
(void)unix_expandpath(gap, buf, len + 1, flags, FALSE);
(void)do_path_expand(gap, buf, len + 1, flags, false);
} else {
/* no more wildcards, check if there is a match */
/* remove backslashes for the remaining components only */
@ -581,8 +634,7 @@ unix_expandpath (
}
}
}
closedir(dirp);
os_closedir(&dir);
}
free(buf);
@ -594,7 +646,6 @@ unix_expandpath (
sizeof(char_u *), pstrcmp);
return matches;
}
#endif
/*
* Moves "*psep" back to the previous path separator in "path".
@ -1078,7 +1129,7 @@ gen_expand_wildcards (
* If there are no wildcards: Add the file name if it exists or
* when EW_NOTFOUND is given.
*/
if (mch_has_exp_wildcard(p)) {
if (path_has_exp_wildcard(p)) {
if ((flags & EW_PATH)
&& !path_is_absolute_path(p)
&& !(p[0] == '.'
@ -1092,7 +1143,7 @@ gen_expand_wildcards (
recursive = TRUE;
did_expand_in_path = TRUE;
} else
add_pat = mch_expandpath(&ga, p, flags);
add_pat = path_expand(&ga, p, flags);
}
}
@ -1566,13 +1617,68 @@ char_u *fix_fname(char_u *fname)
fname = vim_strsave(fname);
# ifdef USE_FNAME_CASE
fname_case(fname, 0); // set correct case for file name
path_fix_case(fname); // set correct case for file name
# endif
return fname;
#endif
}
/// Set the case of the file name, if it already exists. This will cause the
/// file name to remain exactly the same.
/// Only required for file systems where case is ignored and preserved.
// TODO(SplinterOfChaos): Could also be used when mounting case-insensitive
// file systems.
void path_fix_case(char_u *name)
FUNC_ATTR_NONNULL_ALL
{
FileInfo file_info;
if (!os_fileinfo_link((char *)name, &file_info)) {
return;
}
// Open the directory where the file is located.
char_u *slash = vim_strrchr(name, '/');
char_u *tail;
Directory dir;
bool ok;
if (slash == NULL) {
ok = os_scandir(&dir, ".");
tail = name;
} else {
*slash = NUL;
ok = os_scandir(&dir, (char *) name);
*slash = '/';
tail = slash + 1;
}
if (!ok) {
return;
}
char_u *entry;
while ((entry = (char_u *) os_scandir_next(&dir))) {
// Only accept names that differ in case and are the same byte
// length. TODO: accept different length name.
if (STRICMP(tail, entry) == 0 && STRLEN(tail) == STRLEN(entry)) {
char_u newname[MAXPATHL + 1];
// Verify the inode is equal.
STRLCPY(newname, name, MAXPATHL + 1);
STRLCPY(newname + (tail - name), entry,
MAXPATHL - (tail - name) + 1);
FileInfo file_info_new;
if (os_fileinfo_link((char *)newname, &file_info_new)
&& os_fileinfo_id_equal(&file_info, &file_info_new)) {
STRCPY(tail, entry);
break;
}
}
}
os_closedir(&dir);
}
/*
* Return TRUE if "p" points to just after a path separator.
* Takes care of multi-byte characters.
@ -1670,19 +1776,6 @@ int pathcmp(const char *p, const char *q, int maxlen)
return 1;
}
/*
* 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.
*
* Uses EW_* flags
*/
int mch_expandpath(garray_T *gap, char_u *path, int flags)
{
return unix_expandpath(gap, path, 0, flags, FALSE);
}
/// Try to find a shortname by comparing the fullname with the current
/// directory.
///

View File

@ -2587,7 +2587,7 @@ static char_u *expand_tag_fname(char_u *fname, char_u *tag_fname, int expand)
/*
* Expand file name (for environment variables) when needed.
*/
if (expand && mch_has_wildcard(fname)) {
if (expand && path_has_wildcard(fname)) {
ExpandInit(&xpc);
xpc.xp_context = EXPAND_FILES;
expanded_fname = ExpandOne(&xpc, fname, NULL,

View File

@ -418,6 +418,30 @@ describe('more path function', function()
end)
end)
describe('path_fix_case', function()
function fix_case(file)
c_file = to_cstr(file)
path.path_fix_case(c_file)
return ffi.string(c_file)
end
if ffi.os == 'Windows' or ffi.os == 'OSX' then
it('Corrects the case of file names in Mac and Windows', function()
lfs.mkdir('CamelCase')
eq('CamelCase', fix_case('camelcase'))
eq('CamelCase', fix_case('cAMELcASE'))
lfs.rmdir('CamelCase')
end)
else
it('does nothing on Linux', function()
lfs.mkdir('CamelCase')
eq('camelcase', fix_case('camelcase'))
eq('cAMELcASE', fix_case('cAMELcASE'))
lfs.mkdir('CamelCase')
end)
end
end)
describe('append_path', function()
it('joins given paths with a slash', function()
local path1 = cstr(100, 'path1')