Skip to content

Commit a31fa3a

Browse files
committed
Changes to sort the results to readdir from the PAL layer on POSIX
The recent update of Mono changed the behavior of classlib file listing apis such as Directory.GetFiles. In the past the results were sorted alphabetically. The implementation was moved to the pal/mono-native library and makes use of readdir which does not guarantee any order. We are trading some memory and speed to bring back the sorted behavior. However the number are an improvement over the old glib implementation we were using. The downside is that programs that use enumerators.first or .any do not perform as well as we are paying the price to sort the whole list.
1 parent 4771afb commit a31fa3a

File tree

2 files changed

+104
-13
lines changed

2 files changed

+104
-13
lines changed

external/corefx-bugfix/src/Native/Unix/System.Native/pal_io.c

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -363,14 +363,14 @@ static void ConvertDirent(const struct dirent* entry, struct DirectoryEntry* out
363363
#endif
364364
}
365365

366-
#if HAVE_READDIR_R
366+
#if IL2CPP_HAVE_READDIR_R_DEPRECATED_DO_NOT_USE
367367
// struct dirent typically contains 64-bit numbers (e.g. d_ino), so we align it at 8-byte.
368368
static const size_t dirent_alignment = 8;
369369
#endif
370370

371371
int32_t SystemNative_GetReadDirRBufferSize(void)
372372
{
373-
#if HAVE_READDIR_R
373+
#if IL2CPP_HAVE_READDIR_R_DEPRECATED_DO_NOT_USE
374374
// dirent should be under 2k in size
375375
assert(sizeof(struct dirent) < 2048);
376376
// add some extra space so we can align the buffer to dirent.
@@ -380,17 +380,25 @@ int32_t SystemNative_GetReadDirRBufferSize(void)
380380
#endif
381381
}
382382

383+
#if READDIR_SORT
384+
static int cmpstring(const void *p1, const void *p2)
385+
{
386+
return strcmp(((struct dirent*) p1)->d_name, ((struct dirent*) p2)->d_name);
387+
}
388+
#endif
389+
383390
// To reduce the number of string copies, the caller of this function is responsible to ensure the memory
384391
// referenced by outputEntry remains valid until it is read.
385392
// If the platform supports readdir_r, the caller provides a buffer into which the data is read.
386393
// If the platform uses readdir, the caller must ensure no calls are made to readdir/closedir since those will invalidate
387394
// the current dirent. We assume the platform supports concurrent readdir calls to different DIRs.
388-
int32_t SystemNative_ReadDirR(DIR* dir, uint8_t* buffer, int32_t bufferSize, struct DirectoryEntry* outputEntry)
395+
int32_t SystemNative_ReadDirR(struct DIRWrapper* dirWrapper, uint8_t* buffer, int32_t bufferSize, struct DirectoryEntry* outputEntry)
389396
{
390-
assert(dir != NULL);
397+
assert(dirWrapper != NULL);
398+
assert(dirWrapper->dir != NULL);
391399
assert(outputEntry != NULL);
392400

393-
#if HAVE_READDIR_R
401+
#if IL2CPP_HAVE_READDIR_R_DEPRECATED_DO_NOT_USE
394402
assert(buffer != NULL);
395403

396404
// align to dirent
@@ -445,7 +453,48 @@ int32_t SystemNative_ReadDirR(DIR* dir, uint8_t* buffer, int32_t bufferSize, str
445453
(void)buffer; // unused
446454
(void)bufferSize; // unused
447455
errno = 0;
448-
struct dirent* entry = readdir(dir);
456+
457+
#if READDIR_SORT
458+
struct dirent* entry;
459+
460+
if (!dirWrapper->result)
461+
{
462+
size_t numEntries = 0;
463+
while ((entry = readdir(dirWrapper->dir))) numEntries++;
464+
if (numEntries)
465+
{
466+
dirWrapper->result = malloc(numEntries * sizeof(struct dirent));
467+
dirWrapper->curIndex = 0;
468+
dirWrapper->numEntries = numEntries;
469+
#if HAVE_REWINDDIR
470+
rewinddir (dirWrapper->dir);
471+
#else
472+
closedir(dirWrapper->dir);
473+
dirWrapper->dir = opendir(dirWrapper->dirPath);
474+
#endif
475+
size_t index = 0;
476+
while ((entry = readdir(dirWrapper->dir)))
477+
{
478+
memcpy(&((struct dirent*)dirWrapper->result)[index], entry, sizeof(struct dirent));
479+
index++;
480+
}
481+
482+
qsort(dirWrapper->result, numEntries, sizeof(struct dirent), cmpstring);
483+
}
484+
}
485+
486+
if (dirWrapper->curIndex < dirWrapper->numEntries)
487+
{
488+
entry = &((struct dirent*)dirWrapper->result)[dirWrapper->curIndex];
489+
dirWrapper->curIndex++;
490+
}
491+
492+
else
493+
entry = NULL;
494+
495+
#else
496+
struct dirent* entry = readdir(dirWrapper->dir);
497+
#endif
449498

450499
// 0 returned with null result -> end-of-stream
451500
if (entry == NULL)
@@ -465,14 +514,41 @@ int32_t SystemNative_ReadDirR(DIR* dir, uint8_t* buffer, int32_t bufferSize, str
465514
return 0;
466515
}
467516

468-
DIR* SystemNative_OpenDir(const char* path)
517+
struct DIRWrapper* SystemNative_OpenDir(const char* path)
469518
{
470-
return opendir(path);
519+
DIR* dir = opendir(path);
520+
521+
if (dir == NULL)
522+
return NULL;
523+
524+
struct DIRWrapper* ret = (struct DIRWrapper*)malloc(sizeof(struct DIRWrapper));
525+
ret->dir = dir;
526+
#if READDIR_SORT
527+
ret->result = NULL;
528+
ret->curIndex = 0;
529+
ret->numEntries = 0;
530+
#if !HAVE_REWINDDIR
531+
ret->dirPath = strdup(path);
532+
#endif
533+
#endif
534+
return ret;
471535
}
472536

473-
int32_t SystemNative_CloseDir(DIR* dir)
537+
int32_t SystemNative_CloseDir(struct DIRWrapper* dirWrapper)
474538
{
475-
return closedir(dir);
539+
assert(dirWrapper != NULL);
540+
int32_t ret = closedir(dirWrapper->dir);
541+
#if READDIR_SORT
542+
if (dirWrapper->result)
543+
free (dirWrapper->result);
544+
dirWrapper->result = NULL;
545+
#if !HAVE_REWINDDIR
546+
if (dirWrapper->dirPath)
547+
free(dirWrapper->dirPath);
548+
#endif
549+
free(dirWrapper);
550+
#endif
551+
return ret;
476552
}
477553

478554
int32_t SystemNative_Pipe(int32_t pipeFds[2], int32_t flags)

external/corefx-bugfix/src/Native/Unix/System.Native/pal_io.h

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,21 @@ enum NotifyEvents
338338
PAL_IN_ISDIR = 0x40000000,
339339
};
340340

341+
#define READDIR_SORT 1
342+
343+
struct DIRWrapper
344+
{
345+
DIR* dir;
346+
#if READDIR_SORT
347+
void* result;
348+
size_t curIndex;
349+
size_t numEntries;
350+
#if !HAVE_REWINDDIR
351+
char* dirPath;
352+
#endif
353+
#endif
354+
};
355+
341356

342357
int32_t SystemNative_Stat2(const char* path, struct FileStatus* output);
343358
int32_t SystemNative_FStat2(intptr_t fd, struct FileStatus* output);
@@ -416,17 +431,17 @@ DLLEXPORT int32_t SystemNative_GetReadDirRBufferSize(void);
416431
*
417432
* Returns 0 when data is retrieved; returns -1 when end-of-stream is reached; returns an error code on failure
418433
*/
419-
DLLEXPORT int32_t SystemNative_ReadDirR(DIR* dir, uint8_t* buffer, int32_t bufferSize, struct DirectoryEntry* outputEntry);
434+
DLLEXPORT int32_t SystemNative_ReadDirR(struct DIRWrapper* dirWrapper, uint8_t* buffer, int32_t bufferSize, struct DirectoryEntry* outputEntry);
420435

421436
/**
422437
* Returns a DIR struct containing info about the current path or NULL on failure; sets errno on fail.
423438
*/
424-
DLLEXPORT DIR* SystemNative_OpenDir(const char* path);
439+
DLLEXPORT struct DIRWrapper* SystemNative_OpenDir(const char* path);
425440

426441
/**
427442
* Closes the directory stream opened by opendir and returns 0 on success. On fail, -1 is returned and errno is set
428443
*/
429-
DLLEXPORT int32_t SystemNative_CloseDir(DIR* dir);
444+
DLLEXPORT int32_t SystemNative_CloseDir(struct DIRWrapper* dirWrapper);
430445

431446
/**
432447
* Creates a pipe. Implemented as shim to pipe(2) or pipe2(2) if available.

0 commit comments

Comments
 (0)