Skip to content

Commit

Permalink
mingw: make the dirent implementation pluggable
Browse files Browse the repository at this point in the history
Emulating the POSIX `dirent` API on Windows via
`FindFirstFile()`/`FindNextFile()` is pretty staightforward, however,
most of the information provided in the `WIN32_FIND_DATA` structure is
thrown away in the process. A more sophisticated implementation may
cache this data, e.g. for later reuse in calls to `lstat()`.

Make the `dirent` implementation pluggable so that it can be switched at
runtime, e.g. based on a config option.

Define a base DIR structure with pointers to `readdir()`/`closedir()`
that match the `opendir()` implementation (similar to vtable pointers in
Object-Oriented Programming). Define `readdir()`/`closedir()` so that
they call the function pointers in the `DIR` structure. This allows to
choose the `opendir()` implementation on a call-by-call basis.

Make the fixed-size `dirent.d_name` buffer a flex array, as `d_name` may
be implementation specific (e.g. a caching implementation may allocate a
`struct dirent` with _just_ the size needed to hold the `d_name` in
question).

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
  • Loading branch information
kblees authored and dscho committed Sep 24, 2024
1 parent 58f9dfe commit 071ce86
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 18 deletions.
30 changes: 19 additions & 11 deletions compat/win32/dirent.c
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
#include "../../git-compat-util.h"

struct DIR {
struct dirent dd_dir; /* includes d_type */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
typedef struct dirent_DIR {
struct DIR base_dir; /* extend base struct DIR */
HANDLE dd_handle; /* FindFirstFile handle */
int dd_stat; /* 0-based index */
};
struct dirent dd_dir; /* includes d_type */
} dirent_DIR;
#pragma GCC diagnostic pop

DIR *(*opendir)(const char *dirname) = dirent_opendir;

static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata)
{
/* convert UTF-16 name to UTF-8 */
xwcstoutf(ent->d_name, fdata->cFileName, sizeof(ent->d_name));
/* convert UTF-16 name to UTF-8 (d_name points to dirent_DIR.dd_name) */
xwcstoutf(ent->d_name, fdata->cFileName, MAX_PATH * 3);

/* Set file type, based on WIN32_FIND_DATA */
if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
Expand All @@ -18,7 +24,7 @@ static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata)
ent->d_type = DT_REG;
}

struct dirent *readdir(DIR *dir)
static struct dirent *dirent_readdir(dirent_DIR *dir)
{
if (!dir) {
errno = EBADF; /* No set_errno for mingw */
Expand All @@ -45,7 +51,7 @@ struct dirent *readdir(DIR *dir)
return &dir->dd_dir;
}

int closedir(DIR *dir)
static int dirent_closedir(dirent_DIR *dir)
{
if (!dir) {
errno = EBADF;
Expand All @@ -57,13 +63,13 @@ int closedir(DIR *dir)
return 0;
}

DIR *opendir(const char *name)
DIR *dirent_opendir(const char *name)
{
wchar_t pattern[MAX_PATH + 2]; /* + 2 for '/' '*' */
WIN32_FIND_DATAW fdata;
HANDLE h;
int len;
DIR *dir;
dirent_DIR *dir;

/* convert name to UTF-16 and check length < MAX_PATH */
if ((len = xutftowcs_path(pattern, name)) < 0)
Expand All @@ -84,9 +90,11 @@ DIR *opendir(const char *name)
}

/* initialize DIR structure and copy first dir entry */
dir = xmalloc(sizeof(DIR));
dir = xmalloc(sizeof(dirent_DIR) + MAX_PATH);
dir->base_dir.preaddir = (struct dirent *(*)(DIR *dir)) dirent_readdir;
dir->base_dir.pclosedir = (int (*)(DIR *dir)) dirent_closedir;
dir->dd_handle = h;
dir->dd_stat = 0;
finddata2dirent(&dir->dd_dir, &fdata);
return dir;
return (DIR*) dir;
}
26 changes: 19 additions & 7 deletions compat/win32/dirent.h
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
#ifndef DIRENT_H
#define DIRENT_H

typedef struct DIR DIR;

#define DT_UNKNOWN 0
#define DT_DIR 1
#define DT_REG 2
#define DT_LNK 3

struct dirent {
unsigned char d_type; /* file type to prevent lstat after readdir */
char d_name[MAX_PATH * 3]; /* file name (* 3 for UTF-8 conversion) */
unsigned char d_type; /* file type to prevent lstat after readdir */
char d_name[FLEX_ARRAY]; /* file name */
};

DIR *opendir(const char *dirname);
struct dirent *readdir(DIR *dir);
int closedir(DIR *dir);
/*
* Base DIR structure, contains pointers to readdir/closedir implementations so
* that opendir may choose a concrete implementation on a call-by-call basis.
*/
typedef struct DIR {
struct dirent *(*preaddir)(struct DIR *dir);
int (*pclosedir)(struct DIR *dir);
} DIR;

/* default dirent implementation */
extern DIR *dirent_opendir(const char *dirname);

/* current dirent implementation */
extern DIR *(*opendir)(const char *dirname);

#define readdir(dir) (dir->preaddir(dir))
#define closedir(dir) (dir->pclosedir(dir))

#endif /* DIRENT_H */

0 comments on commit 071ce86

Please sign in to comment.