Fix ConvertDirent for AIX by always returning PAL_DT_UNKNOWN (dotnet/corefx#34088)
authorCalvin Buckley <calvin@cmpct.info>
Thu, 10 Jan 2019 18:41:31 +0000 (14:41 -0400)
committerStephen Toub <stoub@microsoft.com>
Thu, 10 Jan 2019 18:41:31 +0000 (13:41 -0500)
* 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
src/libraries/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEntry.Unix.cs

index 34ea79d..3c676a6 100644 (file)
@@ -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
index fbcfcf5..283360d 100644 (file)
@@ -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;