diff --git a/Public/Src/Engine/UnitTests/Processes/LinuxSandboxProcessTests.cs b/Public/Src/Engine/UnitTests/Processes/LinuxSandboxProcessTests.cs index d9a71a610..18467a87d 100644 --- a/Public/Src/Engine/UnitTests/Processes/LinuxSandboxProcessTests.cs +++ b/Public/Src/Engine/UnitTests/Processes/LinuxSandboxProcessTests.cs @@ -105,5 +105,42 @@ namespace Test.BuildXL.Processes AssertLogContains(GetRegex("readlink", "realDir/symlink.txt")); AssertLogNotContains(GetRegex("readlink", "symlinkDir/file.txt")); } + + [FactIfSupported(requiresSymlinkPermission: true)] + public void FileDescriptorAccessesFullyResolvesPath() + { + var tempFiles = new TempFileStorage(canGetFileNames : true); + var link = tempFiles.GetDirectory("symlinkDir", skipCreate: true); + var real = tempFiles.GetDirectory("realDir"); + var realFile = tempFiles.GetFileName(real, "file.txt"); + + File.WriteAllText(realFile, "chelivery"); + XAssert.IsTrue(File.Exists(realFile)); + + var createSymlink = FileUtilities.TryCreateSymbolicLink(link, real, isTargetFile: false); + if (!createSymlink.Succeeded) + { + XAssert.IsTrue(false, createSymlink.Failure.Describe()); + } + + var fileLink = tempFiles.GetFileName(real, "symlink.txt"); + createSymlink = FileUtilities.TryCreateSymbolicLink(fileLink, realFile, isTargetFile: true); + if (!createSymlink.Succeeded) + { + XAssert.IsTrue(false, createSymlink.Failure.Describe()); + } + + RunNativeTest("FileDescriptorAccessesFullyResolvesPath", workingDirectory: tempFiles); + + // The native side does: + // fd = open(symlinkDir/symlink.txt); + // __fxstat(fd) + // For the __fxstat report, we should associate the file descriptor to the real path, with the symlinks resolved + AssertLogContains(GetRegex("__fxstat", realFile)); + + // At some point (namely, on open) we also should get reports for the intermediate symlinks that got us to the file + AssertLogContains(GetRegex("_readlink", link)); + AssertLogContains(GetRegex("_readlink", fileLink)); + } } } diff --git a/Public/Src/Sandbox/Linux/UnitTests/TestProcesses/TestProcess/main.cpp b/Public/Src/Sandbox/Linux/UnitTests/TestProcesses/TestProcess/main.cpp index 4f4efbbe0..aeddc3d0d 100644 --- a/Public/Src/Sandbox/Linux/UnitTests/TestProcesses/TestProcess/main.cpp +++ b/Public/Src/Sandbox/Linux/UnitTests/TestProcesses/TestProcess/main.cpp @@ -54,6 +54,23 @@ int ReadlinkReportDoesNotResolveFinalComponent() return EXIT_SUCCESS; } +// The managed side creates: +// - a directory symlink realDir -> symlinkDir +// - a file symlink realDir/symlink.txt -> realDir/real.txt. +int FileDescriptorAccessesFullyResolvesPath() +{ + char buf[PATH_MAX] = { 0 }; + GET_CWD; + std::string testFile(cwd); + testFile.append("/realDir/symlink.txt"); + int fd = open("symlinkDir/symlink.txt", O_RDONLY); + struct stat sb; + + // Use __fxtat as representative for a "file descriptor event" + __fxstat(1, fd, &sb); + return EXIT_SUCCESS; +} + int main(int argc, char **argv) { @@ -191,6 +208,7 @@ int main(int argc, char **argv) IF_COMMAND(TestAnonymousFile); IF_COMMAND(FullPathResolutionOnReports); IF_COMMAND(ReadlinkReportDoesNotResolveFinalComponent); + IF_COMMAND(FileDescriptorAccessesFullyResolvesPath); // Invalid command exit(-1); diff --git a/Public/Src/Sandbox/Linux/bxl_observer.cpp b/Public/Src/Sandbox/Linux/bxl_observer.cpp index d3024994b..80eae0f32 100644 --- a/Public/Src/Sandbox/Linux/bxl_observer.cpp +++ b/Public/Src/Sandbox/Linux/bxl_observer.cpp @@ -290,7 +290,10 @@ bool BxlObserver::ResolveEventPaths(buildxl::linux::SandboxEvent& event) { if (event.GetDstFd() != -1) { FileDescriptorToPath(event.GetDstFd(), event.GetPid(), resolved_path_dst, PATH_MAX); } - ResolveEventPaths(event, resolved_path_src, resolved_path_dst); + + // FileDescriptorToPath returns a fully resolved path (by virtue of resolving /proc/self/fd/{fd}) + // so no need to call ResolveEventPaths here, we can just set them directly + event.SetResolvedPaths(resolved_path_src, resolved_path_dst); break; } case buildxl::linux::SandboxEventPathType::kRelativePaths: {