[commit: ghc] master: Follow symlinks in the Win32 code for System.Environment.getExecutablePath (6282366)
git at git.haskell.org
git at git.haskell.org
Mon Nov 27 15:21:39 UTC 2017
Repository : ssh://git@git.haskell.org/ghc
On branch : master
Link : http://ghc.haskell.org/trac/ghc/changeset/62823668c48e13290e2ffe0d593a9f6a95cf628b/ghc
>---------------------------------------------------------------
commit 62823668c48e13290e2ffe0d593a9f6a95cf628b
Author: Alp Mestanogullari <alp at well-typed.com>
Date: Mon Nov 27 09:45:52 2017 -0500
Follow symlinks in the Win32 code for System.Environment.getExecutablePath
This partially addresses #14483 by fixing the Windows implementation of
System.Environment.getExecutablePath. This is achieved by using
GetFinalPathNameByHandleW to resolve potential symlinks, while making
sure we do not get back a UNC path (see #14460).
Test Plan: Validate
Reviewers: Phyx, bgamari, angerman, hvr, goldfire
Reviewed By: Phyx, bgamari
GHC Trac Issues: #14483
Differential Revision: https://phabricator.haskell.org/D4227
>---------------------------------------------------------------
62823668c48e13290e2ffe0d593a9f6a95cf628b
.../base/System/Environment/ExecutablePath.hsc | 85 ++++++++++++++++++++--
libraries/base/changelog.md | 2 +
2 files changed, 82 insertions(+), 5 deletions(-)
diff --git a/libraries/base/System/Environment/ExecutablePath.hsc b/libraries/base/System/Environment/ExecutablePath.hsc
index 8b6c7b6..448cade 100644
--- a/libraries/base/System/Environment/ExecutablePath.hsc
+++ b/libraries/base/System/Environment/ExecutablePath.hsc
@@ -33,11 +33,13 @@ import Foreign.C
import Foreign.Marshal.Array
import System.Posix.Internals
#elif defined(mingw32_HOST_OS)
+import Control.Exception
+import Data.List
import Data.Word
import Foreign.C
import Foreign.Marshal.Array
import Foreign.Ptr
-import System.Posix.Internals
+#include <windows.h>
#else
import Foreign.C
import Foreign.Marshal.Alloc
@@ -54,6 +56,10 @@ import System.Posix.Internals
-- Note that for scripts and interactive sessions, this is the path to
-- the interpreter (e.g. ghci.)
--
+-- Since base 4.11.0.0, 'getExecutablePath' resolves symlinks on Windows.
+-- If an executable is launched through a symlink, 'getExecutablePath'
+-- returns the absolute path of the original executable.
+--
-- @since 4.6.0.0
getExecutablePath :: IO FilePath
@@ -137,18 +143,87 @@ getExecutablePath = readSymbolicLink $ "/proc/self/exe"
# error Unknown mingw32 arch
# endif
-foreign import WINDOWS_CCONV unsafe "windows.h GetModuleFileNameW"
- c_GetModuleFileName :: Ptr () -> CWString -> Word32 -> IO Word32
-
getExecutablePath = go 2048 -- plenty, PATH_MAX is 512 under Win32
where
go size = allocaArray (fromIntegral size) $ \ buf -> do
ret <- c_GetModuleFileName nullPtr buf size
case ret of
0 -> errorWithoutStackTrace "getExecutablePath: GetModuleFileNameW returned an error"
- _ | ret < size -> peekFilePath buf
+ _ | ret < size -> do
+ path <- peekCWString buf
+ real <- getFinalPath path
+ exists <- withCWString real c_pathFileExists
+ if exists
+ then return real
+ else fail path
| otherwise -> go (size * 2)
+-- | Returns the final path of the given path. If the given
+-- path is a symbolic link, the returned value is the
+-- path the (possibly chain of) symbolic link(s) points to.
+-- Otherwise, the original path is returned, even when the filepath
+-- is incorrect.
+--
+-- Adapted from:
+-- https://msdn.microsoft.com/en-us/library/windows/desktop/aa364962.aspx
+getFinalPath :: FilePath -> IO FilePath
+getFinalPath path = withCWString path $ \s ->
+ bracket (createFile s) c_closeHandle $ \h -> do
+ let invalid = h == wordPtrToPtr (#const INVALID_HANDLE_VALUE)
+ if invalid then pure path else go h bufSize
+
+ where go h sz = allocaArray (fromIntegral sz) $ \outPath -> do
+ ret <- c_getFinalPathHandle h outPath sz (#const FILE_NAME_OPENED)
+ if ret < sz
+ then sanitize . rejectUNCPath <$> peekCWString outPath
+ else go h (2 * sz)
+
+ sanitize s
+ | "\\\\?\\" `isPrefixOf` s = drop 4 s
+ | otherwise = s
+
+ -- see https://ghc.haskell.org/trac/ghc/ticket/14460
+ rejectUNCPath s
+ | "\\\\?\\UNC\\" `isPrefixOf` s = path
+ | otherwise = s
+
+ -- the initial size of the buffer in which we store the
+ -- final path; if this is not enough, we try with a buffer of
+ -- size 2^k * bufSize, for k = 1, 2, 3, ... until the buffer
+ -- is large enough.
+ bufSize = 1024
+
+foreign import WINDOWS_CCONV unsafe "windows.h GetModuleFileNameW"
+ c_GetModuleFileName :: Ptr () -> CWString -> Word32 -> IO Word32
+
+foreign import WINDOWS_CCONV unsafe "windows.h PathFileExistsW"
+ c_pathFileExists :: CWString -> IO Bool
+
+foreign import WINDOWS_CCONV unsafe "windows.h CreateFileW"
+ c_createFile :: CWString
+ -> Word32
+ -> Word32
+ -> Ptr ()
+ -> Word32
+ -> Word32
+ -> Ptr ()
+ -> IO (Ptr ())
+
+createFile :: CWString -> IO (Ptr ())
+createFile file =
+ c_createFile file (#const GENERIC_READ)
+ (#const FILE_SHARE_READ)
+ nullPtr
+ (#const OPEN_EXISTING)
+ (#const FILE_ATTRIBUTE_NORMAL)
+ nullPtr
+
+foreign import WINDOWS_CCONV unsafe "windows.h CloseHandle"
+ c_closeHandle :: Ptr () -> IO Bool
+
+foreign import WINDOWS_CCONV unsafe "windows.h GetFinalPathNameByHandleW"
+ c_getFinalPathHandle :: Ptr () -> CWString -> Word32 -> Word32 -> IO Word32
+
--------------------------------------------------------------------------------
-- Fallback to argv[0]
diff --git a/libraries/base/changelog.md b/libraries/base/changelog.md
index 34911a9..1e0a67d 100644
--- a/libraries/base/changelog.md
+++ b/libraries/base/changelog.md
@@ -69,6 +69,8 @@
* Add `generateStackTrace` to `MiscFlags` in `GHC.RTS.Flags` to determine if
stack traces will be generated on unhandled exceptions by the RTS.
+ * `getExecutablePath` now resolves symlinks on Windows (#14483)
+
## 4.10.0.0 *July 2017*
* Bundled with GHC 8.2.1
More information about the ghc-commits
mailing list