From c19149e78ea43a22bb48fc1ebdaac1a0c5a35011 Mon Sep 17 00:00:00 2001 From: Fixer Date: Sun, 19 Jul 2026 13:03:04 +0800 Subject: [PATCH 1/2] Fix empty directory enumeration bug When a directory contains only '.' and '..' entries (an effectively empty directory), Ext2QueryDirectory skipped those entries and wrote nothing to the output buffer (fc.efc_start == 0). It then returned STATUS_NO_SUCH_FILE on the first query, which Windows translates to ERROR_FILE_NOT_FOUND -- causing FindFirstFileW / NtQueryDirectoryFile to report that the directory itself doesn't exist. Fix: return STATUS_NO_MORE_FILES instead, telling callers the enumeration completed with no matching entries. Co-Authored-By: Claude --- Ext4Fsd/dirctl.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Ext4Fsd/dirctl.c b/Ext4Fsd/dirctl.c index e624cb8..b6909c2 100644 --- a/Ext4Fsd/dirctl.c +++ b/Ext4Fsd/dirctl.c @@ -977,11 +977,14 @@ Ext2QueryDirectory (IN PEXT2_IRP_CONTEXT IrpContext) /* just return fc.efc_start/EntrySize bytes that we filled */ } else if (!fc.efc_start) { if (NT_SUCCESS(Status)) { - if (FirstQuery) { - Status = STATUS_NO_SUCH_FILE; - } else { - Status = STATUS_NO_MORE_FILES; - } + /* + * Empty directory: . and .. are skipped above, so no real + * entries were written. Return STATUS_NO_MORE_FILES instead + * of STATUS_NO_SUCH_FILE so callers (FindFirstFileW, + * NtQueryDirectoryFile) know enumeration completed with + * no entries, rather than thinking the dir doesn't exist. + */ + Status = STATUS_NO_MORE_FILES; } } else { Status = STATUS_SUCCESS; From 7efc3c39514345c5eb8fdf62f4a401939dbde427 Mon Sep 17 00:00:00 2001 From: Fixer Date: Sun, 19 Jul 2026 22:53:29 +0800 Subject: [PATCH 2/2] fix: empty ext4 directories return STATUS_NO_SUCH_FILE Ext4Fsd 0.71 skips '.' and '..' entries in both the linear while-loop and HTREE callback paths. For empty directories this leaves fc.efc_start == 0, causing the errorout path to return STATUS_NO_SUCH_FILE on the first query. FindFirstFileW maps this to ERROR_FILE_NOT_FOUND, and Java NIO's walkFileTree throws NoSuchFileException. Fix: - Remove explicit . and .. skip in Ext2FillEntry (HTREE path) - Remove explicit . and .. skip in while loop (linear path) - Change errorout to always return STATUS_NO_MORE_FILES instead of conditionally returning STATUS_NO_SUCH_FILE Windows filters . and .. itself, so returning them is safe. Tested: Minecraft saves on ext4 can now be re-opened after creation on Windows 11 x64. --- Ext4Fsd/dirctl.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/Ext4Fsd/dirctl.c b/Ext4Fsd/dirctl.c index b6909c2..e7081cd 100644 --- a/Ext4Fsd/dirctl.c +++ b/Ext4Fsd/dirctl.c @@ -444,11 +444,13 @@ static int Ext2FillEntry(void *context, const char *name, int namlen, Oem.Length = namlen & 0xFF; Oem.MaximumLength = Oem.Length; - /* skip . and .. */ - if ((Oem.Length == 1 && name[0] == '.') || (Oem.Length == 2 && - name[0] == '.' && name[1] == '.' )) { - goto errorout; - } + /* + * Do NOT skip . and .. entries. On empty directories these are + * the only entries in the dir block. Skipping them causes + * fc.efc_start to stay 0, which triggers STATUS_NO_SUCH_FILE + * in errorout -- FindFirstFileW then fails with + * ERROR_FILE_NOT_FOUND. Windows strips . and .. itself. + */ if (Ext2IsWearingCloak(Vcb, &Oem)) { goto errorout; @@ -850,11 +852,13 @@ Ext2QueryDirectory (IN PEXT2_IRP_CONTEXT IrpContext) goto ProcessNextEntry; } - /* skip . and .. */ - if ((pDir->name_len == 1 && pDir->name[0] == '.') || - (pDir->name_len == 2 && pDir->name[0] == '.' && pDir->name[1] == '.' )) { - goto ProcessNextEntry; - } + /* + * Do NOT skip . and .. entries. On empty directories these + * are the only entries in the dir block. Skipping them causes + * fc.efc_start to stay 0, which triggers STATUS_NO_SUCH_FILE + * in errorout -- FindFirstFileW then fails with + * ERROR_FILE_NOT_FOUND. Windows strips . and .. itself. + */ Oem.Buffer = pDir->name; Oem.Length = (pDir->name_len & 0xff); @@ -978,11 +982,11 @@ Ext2QueryDirectory (IN PEXT2_IRP_CONTEXT IrpContext) } else if (!fc.efc_start) { if (NT_SUCCESS(Status)) { /* - * Empty directory: . and .. are skipped above, so no real - * entries were written. Return STATUS_NO_MORE_FILES instead - * of STATUS_NO_SUCH_FILE so callers (FindFirstFileW, - * NtQueryDirectoryFile) know enumeration completed with - * no entries, rather than thinking the dir doesn't exist. + * Empty directory: . and .. are now passed through above, + * so on empty dirs they are the only entries and set + * efc_start > 0. This path is a safety net: return + * STATUS_NO_MORE_FILES instead of STATUS_NO_SUCH_FILE, + * so FindFirstFileW doesn't map it to ERROR_FILE_NOT_FOUND. */ Status = STATUS_NO_MORE_FILES; }