From 6ad8f5b8f65f308338e01fbf122db08a1934d8af Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Thu, 10 Jan 2019 14:41:31 -0400 Subject: [PATCH] Fix ConvertDirent for AIX by always returning PAL_DT_UNKNOWN (dotnet/corefx#34088) * Fix ConvertDirent for AIX by always returning PAL_DT_UNKNOWN I commited the sin of untested code, as this function wasn't wired up to Mono's BCL until a few days ago, and I had triggered the situation where I needed this working by purging stale build artifacts. What happened here was me being too clever; I tried stat, but we only have the file name, and from the unmanaged call stack this would run in, we'd have no way to get the directory it's in (without butchering the function signature) to properly use stat for non-cwd directories. This meant that the check would get random garbage on the stack; sometimes the data for "..", sometimes another function would clobber it, and it'd read that. This caused the Mono gensources tool to act erratically, preventing the BCL from building. Fix this by always returning DT_UNKNOWN. This isn't ideal, but the BCL seems to handle this with grace and does a stat afterwards to properly fill in data. It's enough to get the Mono build from scratch working again. (The stat bit was verified by me running the syscall tracing tool, `truss` over it, plus Mono's JIT trace.) * Handle DT_UNKNOWN cases in FileSystemEntry.Initialize On some OSes like AIX, we return DT_UNKNOWN as a sentinel value, because they don't have a type field in dirent. If so, or the OS does return a type and we got an unknown, also set symlink status if so when making the managed equivalent structures. TimeZoneInfo.Unix also calls ReadDir and thus ConvertDirent, but it does this logic already. * Move symlink check to its own if-elif block Commit migrated from https://github.com/dotnet/corefx/commit/c34dd2fb843c45ad135ef2b0348c06224e15c736 --- src/libraries/Native/Unix/System.Native/pal_io.c | 39 +++------------------- .../System/IO/Enumeration/FileSystemEntry.Unix.cs | 17 +++++++++- 2 files changed, 20 insertions(+), 36 deletions(-) diff --git a/src/libraries/Native/Unix/System.Native/pal_io.c b/src/libraries/Native/Unix/System.Native/pal_io.c index 34ea79d..3c676a6 100644 --- a/src/libraries/Native/Unix/System.Native/pal_io.c +++ b/src/libraries/Native/Unix/System.Native/pal_io.c @@ -352,41 +352,10 @@ static void ConvertDirent(const struct dirent* entry, DirectoryEntry* outputEntr // location of the start of the string that exists in their own byte buffer. outputEntry->Name = entry->d_name; #if !defined(DT_UNKNOWN) - /* AIX has no d_type, make a substitute */ - struct stat s; - stat(entry->d_name, &s); - if (S_ISDIR(s.st_mode)) - { - outputEntry->InodeType = PAL_DT_DIR; - } - else if (S_ISFIFO(s.st_mode)) - { - outputEntry->InodeType = PAL_DT_FIFO; - } - else if (S_ISCHR(s.st_mode)) - { - outputEntry->InodeType = PAL_DT_CHR; - } - else if (S_ISBLK(s.st_mode)) - { - outputEntry->InodeType = PAL_DT_BLK; - } - else if (S_ISREG(s.st_mode)) - { - outputEntry->InodeType = PAL_DT_REG; - } - else if (S_ISLNK(s.st_mode)) - { - outputEntry->InodeType = PAL_DT_LNK; - } - else if (S_ISSOCK(s.st_mode)) - { - outputEntry->InodeType = PAL_DT_SOCK; - } - else - { - outputEntry->InodeType = PAL_DT_UNKNOWN; - } + // AIX has no d_type, and since we can't get the directory that goes with + // the filename from ReadDir, we can't stat the file. Return unknown and + // hope that managed code can properly stat the file. + outputEntry->InodeType = PAL_DT_UNKNOWN; #else outputEntry->InodeType = (int32_t)entry->d_type; #endif diff --git a/src/libraries/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEntry.Unix.cs b/src/libraries/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEntry.Unix.cs index fbcfcf5..283360d 100644 --- a/src/libraries/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEntry.Unix.cs +++ b/src/libraries/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEntry.Unix.cs @@ -38,11 +38,15 @@ namespace System.IO.Enumeration // IMPORTANT: Attribute logic must match the logic in FileStatus bool isDirectory = false; + bool isSymlink = false; if (directoryEntry.InodeType == Interop.Sys.NodeType.DT_DIR) { // We know it's a directory. isDirectory = true; } + // Some operating systems don't have the inode type in the dirent structure, + // so we use DT_UNKNOWN as a sentinel value. As such, check if the dirent is a + // directory. else if ((directoryEntry.InodeType == Interop.Sys.NodeType.DT_LNK || directoryEntry.InodeType == Interop.Sys.NodeType.DT_UNKNOWN) && Interop.Sys.Stat(entry.FullPath, out Interop.Sys.FileStatus targetStatus) >= 0) @@ -50,12 +54,23 @@ namespace System.IO.Enumeration // Symlink or unknown: Stat to it to see if we can resolve it to a directory. isDirectory = (targetStatus.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR; } + // Same idea as the directory check, just repeated for (and tweaked due to the + // nature of) symlinks. + if (directoryEntry.InodeType == Interop.Sys.NodeType.DT_LNK) + { + isSymlink = true; + } + else if ((directoryEntry.InodeType == Interop.Sys.NodeType.DT_UNKNOWN) + && (Interop.Sys.LStat(entry.FullPath, out Interop.Sys.FileStatus linkTargetStatus) >= 0)) + { + isSymlink = (linkTargetStatus.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFLNK; + } entry._status = default; FileStatus.Initialize(ref entry._status, isDirectory); FileAttributes attributes = default; - if (directoryEntry.InodeType == Interop.Sys.NodeType.DT_LNK) + if (isSymlink) attributes |= FileAttributes.ReparsePoint; if (isDirectory) attributes |= FileAttributes.Directory; -- 2.7.4