Skip to content

Commit 1f20e12

Browse files
RyanGlScottMistuke
authored andcommitted
Implement isMinTTY (#63)
* Implement isMinTTY * Address some of Mistuke's comments * Make FILE_NAME_INFO an explicit data structure * Use concrete struct for UNICODE_STRING * No need to hide void * Null-terminate wide strings properly * Suggestions from review * Use size of OBJECT_NAME_INFORMATION directly * Minor spacing fixes * Use return instead of pure * Old 32-bit Windows compatibility
1 parent bf54fa7 commit 1f20e12

File tree

6 files changed

+283
-4
lines changed

6 files changed

+283
-4
lines changed

System/Win32.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ module System.Win32
2323
, module System.Win32.FileMapping
2424
, module System.Win32.Info
2525
, module System.Win32.Mem
26+
, module System.Win32.MinTTY
2627
, module System.Win32.NLS
2728
, module System.Win32.Process
2829
, module System.Win32.Registry
@@ -38,6 +39,7 @@ import System.Win32.File
3839
import System.Win32.FileMapping
3940
import System.Win32.Info
4041
import System.Win32.Mem
42+
import System.Win32.MinTTY
4143
import System.Win32.NLS hiding ( LCID, LANGID, SortID, SubLANGID
4244
, PrimaryLANGID, mAKELCID, lANGIDFROMLCID
4345
, sORTIDFROMLCID, mAKELANGID, pRIMARYLANGID

System/Win32/MinTTY.hsc

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
{-# LANGUAGE ScopedTypeVariables #-}
2+
3+
#if __GLASGOW_HASKELL__ >= 709
4+
{-# LANGUAGE Safe #-}
5+
#elif __GLASGOW_HASKELL__ >= 701
6+
{-# LANGUAGE Trustworthy #-}
7+
#endif
8+
-----------------------------------------------------------------------------
9+
-- |
10+
-- Module : System.Win32.MinTTY
11+
-- Copyright : (c) University of Glasgow 2006
12+
-- License : BSD-style (see the file LICENSE)
13+
--
14+
-- Maintainer : Esa Ilari Vuokko <[email protected]>
15+
-- Stability : provisional
16+
-- Portability : portable
17+
--
18+
-- A function to check if the current terminal uses MinTTY.
19+
-- Much of this code was originally authored by Phil Ruffwind and the
20+
-- git-for-windows project.
21+
--
22+
-----------------------------------------------------------------------------
23+
24+
module System.Win32.MinTTY (isMinTTY, isMinTTYHandle) where
25+
26+
import Graphics.Win32.Misc
27+
import System.Win32.DLL
28+
import System.Win32.File
29+
import System.Win32.Types
30+
31+
#if MIN_VERSION_base(4,6,0)
32+
import Control.Exception (catch)
33+
#endif
34+
import Data.List (isPrefixOf, isInfixOf, isSuffixOf)
35+
import Foreign
36+
import Foreign.C.Types
37+
import System.FilePath (takeFileName)
38+
39+
#if __GLASGOW_HASKELL__ < 711
40+
#let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__); }, y__)
41+
#endif
42+
43+
-- The headers that are shipped with GHC's copy of MinGW-w64 assume Windows XP.
44+
-- Since we need some structs that are only available with Vista or later,
45+
-- we must manually set WINVER/_WIN32_WINNT accordingly.
46+
#undef WINVER
47+
#define WINVER 0x0600
48+
#undef _WIN32_WINNT
49+
#define _WIN32_WINNT 0x0600
50+
##include "windows_cconv.h"
51+
#include <windows.h>
52+
#include "winternl_compat.h"
53+
54+
-- | Returns 'True' if the current process's standard error is attached to a
55+
-- MinTTY console (e.g., Cygwin or MSYS). Returns 'False' otherwise.
56+
isMinTTY :: IO Bool
57+
isMinTTY = do
58+
h <- getStdHandle sTD_ERROR_HANDLE
59+
`catch` \(_ :: IOError) ->
60+
return nullHANDLE
61+
if h == nullHANDLE
62+
then return False
63+
else isMinTTYHandle h
64+
65+
-- | Returns 'True' is the given handle is attached to a MinTTY console
66+
-- (e.g., Cygwin or MSYS). Returns 'False' otherwise.
67+
isMinTTYHandle :: HANDLE -> IO Bool
68+
isMinTTYHandle h = do
69+
fileType <- getFileType h
70+
if fileType /= fILE_TYPE_PIPE
71+
then return False
72+
else isMinTTYVista h `catch` \(_ :: IOError) -> isMinTTYCompat h
73+
-- GetFileNameByHandleEx is only available on Vista and later (hence
74+
-- the name isMinTTYVista). If we're on an older version of Windows,
75+
-- getProcAddress will throw an IOException when it fails to find
76+
-- GetFileNameByHandleEx, and thus we will default to using
77+
-- NtQueryObject (isMinTTYCompat).
78+
79+
isMinTTYVista :: HANDLE -> IO Bool
80+
isMinTTYVista h = do
81+
fn <- getFileNameByHandle h
82+
return $ cygwinMSYSCheck fn
83+
`catch` \(_ :: IOError) ->
84+
return False
85+
86+
isMinTTYCompat :: HANDLE -> IO Bool
87+
isMinTTYCompat h = do
88+
fn <- ntQueryObjectNameInformation h
89+
return $ cygwinMSYSCheck fn
90+
`catch` \(_ :: IOError) ->
91+
return False
92+
93+
cygwinMSYSCheck :: String -> Bool
94+
cygwinMSYSCheck fn = ("cygwin-" `isPrefixOf` fn' || "msys-" `isPrefixOf` fn') &&
95+
"-pty" `isInfixOf` fn' &&
96+
"-master" `isSuffixOf` fn'
97+
where
98+
fn' = takeFileName fn
99+
-- Note that GetFileInformationByHandleEx might return a filepath like:
100+
--
101+
-- \msys-dd50a72ab4668b33-pty1-to-master
102+
--
103+
-- But NtQueryObject might return something like:
104+
--
105+
-- \Device\NamedPipe\msys-dd50a72ab4668b33-pty1-to-master
106+
--
107+
-- This means we can't rely on "\cygwin-" or "\msys-" being at the very start
108+
-- of the filepath. Therefore, we must take care to first call takeFileName
109+
-- before checking for "cygwin" or "msys" at the start using `isPrefixOf`.
110+
111+
getFileNameByHandle :: HANDLE -> IO String
112+
getFileNameByHandle h = do
113+
let sizeOfDWORD = sizeOf (undefined :: DWORD)
114+
-- note: implicitly assuming that DWORD has stronger alignment than wchar_t
115+
bufSize = sizeOfDWORD + mAX_PATH * sizeOfTCHAR
116+
allocaBytes bufSize $ \buf -> do
117+
getFileInformationByHandleEx h fileNameInfo buf (fromIntegral bufSize)
118+
fni <- peek buf
119+
return $ fniFileName fni
120+
121+
getFileInformationByHandleEx
122+
:: HANDLE -> CInt -> Ptr FILE_NAME_INFO -> DWORD -> IO ()
123+
getFileInformationByHandleEx h cls buf bufSize = do
124+
lib <- getModuleHandle (Just "kernel32.dll")
125+
ptr <- getProcAddress lib "GetFileInformationByHandleEx"
126+
let c_GetFileInformationByHandleEx =
127+
mk_GetFileInformationByHandleEx (castPtrToFunPtr ptr)
128+
failIfFalse_ "getFileInformationByHandleEx"
129+
(c_GetFileInformationByHandleEx h cls buf bufSize)
130+
131+
ntQueryObjectNameInformation :: HANDLE -> IO String
132+
ntQueryObjectNameInformation h = do
133+
let sizeOfONI = sizeOf (undefined :: OBJECT_NAME_INFORMATION)
134+
bufSize = sizeOfONI + mAX_PATH * sizeOfTCHAR
135+
allocaBytes bufSize $ \buf ->
136+
alloca $ \p_len -> do
137+
_ <- failIfNeg "NtQueryObject" $ c_NtQueryObject
138+
h objectNameInformation buf (fromIntegral bufSize) p_len
139+
oni <- peek buf
140+
return $ usBuffer $ oniName oni
141+
142+
fileNameInfo :: CInt
143+
fileNameInfo = #const FileNameInfo
144+
145+
mAX_PATH :: Num a => a
146+
mAX_PATH = #const MAX_PATH
147+
148+
objectNameInformation :: CInt
149+
objectNameInformation = #const ObjectNameInformation
150+
151+
type F_GetFileInformationByHandleEx =
152+
HANDLE -> CInt -> Ptr FILE_NAME_INFO -> DWORD -> IO BOOL
153+
154+
foreign import WINDOWS_CCONV "dynamic"
155+
mk_GetFileInformationByHandleEx
156+
:: FunPtr F_GetFileInformationByHandleEx -> F_GetFileInformationByHandleEx
157+
158+
data FILE_NAME_INFO = FILE_NAME_INFO
159+
{ fniFileNameLength :: DWORD
160+
, fniFileName :: String
161+
} deriving Show
162+
163+
instance Storable FILE_NAME_INFO where
164+
sizeOf _ = #size FILE_NAME_INFO
165+
alignment _ = #alignment FILE_NAME_INFO
166+
poke buf fni = withTStringLen (fniFileName fni) $ \(str, len) -> do
167+
let len' = (min mAX_PATH len) * sizeOfTCHAR
168+
start = advancePtr (castPtr buf) (#offset FILE_NAME_INFO, FileName)
169+
end = advancePtr start len'
170+
(#poke FILE_NAME_INFO, FileNameLength) buf len'
171+
copyArray start (castPtr str :: Ptr Word8) len'
172+
poke (castPtr end) (0 :: TCHAR)
173+
peek buf = do
174+
vfniFileNameLength <- (#peek FILE_NAME_INFO, FileNameLength) buf
175+
let len = fromIntegral vfniFileNameLength `div` sizeOfTCHAR
176+
vfniFileName <- peekTStringLen (plusPtr buf (#offset FILE_NAME_INFO, FileName), len)
177+
return $ FILE_NAME_INFO
178+
{ fniFileNameLength = vfniFileNameLength
179+
, fniFileName = vfniFileName
180+
}
181+
182+
foreign import WINDOWS_CCONV "winternl.h NtQueryObject"
183+
c_NtQueryObject :: HANDLE -> CInt -> Ptr OBJECT_NAME_INFORMATION
184+
-> ULONG -> Ptr ULONG -> IO NTSTATUS
185+
186+
type NTSTATUS = #type NTSTATUS
187+
188+
newtype OBJECT_NAME_INFORMATION = OBJECT_NAME_INFORMATION
189+
{ oniName :: UNICODE_STRING
190+
} deriving Show
191+
192+
instance Storable OBJECT_NAME_INFORMATION where
193+
sizeOf _ = #size OBJECT_NAME_INFORMATION
194+
alignment _ = #alignment OBJECT_NAME_INFORMATION
195+
poke buf oni = (#poke OBJECT_NAME_INFORMATION, Name) buf (oniName oni)
196+
peek buf = fmap OBJECT_NAME_INFORMATION $ (#peek OBJECT_NAME_INFORMATION, Name) buf
197+
198+
data UNICODE_STRING = UNICODE_STRING
199+
{ usLength :: USHORT
200+
, usMaximumLength :: USHORT
201+
, usBuffer :: String
202+
} deriving Show
203+
204+
instance Storable UNICODE_STRING where
205+
sizeOf _ = #size UNICODE_STRING
206+
alignment _ = #alignment UNICODE_STRING
207+
poke buf us = withTStringLen (usBuffer us) $ \(str, len) -> do
208+
let len' = (min mAX_PATH len) * sizeOfTCHAR
209+
start = advancePtr (castPtr buf) (#size UNICODE_STRING)
210+
end = advancePtr start len'
211+
(#poke UNICODE_STRING, Length) buf len'
212+
(#poke UNICODE_STRING, MaximumLength) buf (len' + sizeOfTCHAR)
213+
(#poke UNICODE_STRING, Buffer) buf start
214+
copyArray start (castPtr str :: Ptr Word8) len'
215+
poke (castPtr end) (0 :: TCHAR)
216+
peek buf = do
217+
vusLength <- (#peek UNICODE_STRING, Length) buf
218+
vusMaximumLength <- (#peek UNICODE_STRING, MaximumLength) buf
219+
vusBufferPtr <- (#peek UNICODE_STRING, Buffer) buf
220+
let len = fromIntegral vusLength `div` sizeOfTCHAR
221+
vusBuffer <- peekTStringLen (vusBufferPtr, len)
222+
return $ UNICODE_STRING
223+
{ usLength = vusLength
224+
, usMaximumLength = vusMaximumLength
225+
, usBuffer = vusBuffer
226+
}
227+
228+
sizeOfTCHAR :: Int
229+
sizeOfTCHAR = sizeOf (undefined :: TCHAR)

System/Win32/Types.hs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ type DWORD = Word32
7171
type LONG = Int32
7272
type FLOAT = Float
7373
type LARGE_INTEGER = Int64
74+
type ULONG = Word32
7475

7576
type UINT_PTR = Word
7677
type LONG_PTR = CIntPtr
@@ -207,6 +208,9 @@ failIf_ p wh act = do
207208
v <- act
208209
if p v then errorWin wh else return ()
209210

211+
failIfNeg :: (Num a, Ord a) => String -> IO a -> IO a
212+
failIfNeg = failIf (< 0)
213+
210214
failIfNull :: String -> IO (Ptr a) -> IO (Ptr a)
211215
failIfNull = failIf (== nullPtr)
212216

@@ -279,7 +283,7 @@ try loc f n = do
279283
case e of
280284
Left n -> try loc f n
281285
Right str -> return str
282-
286+
283287
----------------------------------------------------------------
284288
-- Primitives
285289
----------------------------------------------------------------

Win32.cabal

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ extra-source-files:
1818
changelog.md
1919

2020
Library
21-
build-depends: base >= 4.5 && < 5, bytestring
21+
build-depends: base >= 4.5 && < 5, bytestring, filepath
2222
ghc-options: -Wall -fno-warn-name-shadowing
2323
cc-options: -fno-strict-aliasing
2424
exposed-modules:
@@ -52,6 +52,7 @@ Library
5252
System.Win32.Info
5353
System.Win32.Path
5454
System.Win32.Mem
55+
System.Win32.MinTTY
5556
System.Win32.NLS
5657
System.Win32.Process
5758
System.Win32.Registry
@@ -65,10 +66,10 @@ Library
6566
if impl(ghc >= 7.1)
6667
extensions: NondecreasingIndentation
6768
extra-libraries:
68-
"user32", "gdi32", "winmm", "advapi32", "shell32", "shfolder", "shlwapi"
69+
"user32", "gdi32", "winmm", "advapi32", "shell32", "shfolder", "shlwapi", "ntdll"
6970
include-dirs: include
7071
includes: "HsWin32.h", "HsGDI.h", "WndProc.h"
71-
install-includes: "HsWin32.h", "HsGDI.h", "WndProc.h", "windows_cconv.h"
72+
install-includes: "HsWin32.h", "HsGDI.h", "WndProc.h", "windows_cconv.h", "winternl_compat.h"
7273
c-sources:
7374
cbits/HsGDI.c
7475
cbits/HsWin32.c

changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
* `failWith` (and the API calls that use it) now throw `IOError`s with proper
66
`IOErrorType`s.
7+
* Add `System.Win32.MinTTY` module for detecting the presence of MinTTY.
8+
* Add `ULONG` type to `System.Win32.Types`.
9+
* Add function `failIfNeg` to `System.Win32.Types`, which fails if a negative
10+
number is returned. This simulates the behavior of the `NT_SUCCESS` macro.
711

812
## 2.4.0.0 *Nov 2016*
913

include/winternl_compat.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#ifndef WINTERNL_COMPAT_H
2+
#define WINTERNL_COMPAT_H
3+
4+
/*
5+
* winternl.h is not included in MinGW, which was shipped with the 32-bit
6+
* Windows version of GHC prior to the 7.10.3 release.
7+
*/
8+
#if defined(x86_64_HOST_ARCH) || \
9+
__GLASGOW_HASKELL__ >= 711 || \
10+
(__GLASGOW_HASKELL__ == 710 && \
11+
defined(__GLASGOW_HASKELL_PATCHLEVEL1__) && \
12+
__GLASGOW_HASKELL_PATCHLEVEL1__ >= 2)
13+
# include <winternl.h>
14+
#else
15+
// Some declarations from winternl.h that we need in Win32
16+
# include <windows.h>
17+
18+
typedef enum _OBJECT_INFORMATION_CLASS {
19+
ObjectBasicInformation,
20+
ObjectNameInformation,
21+
ObjectTypeInformation,
22+
ObjectAllInformation,
23+
ObjectDataInformation
24+
} OBJECT_INFORMATION_CLASS, *POBJECT_INFORMATION_CLASS;
25+
26+
typedef LONG NTSTATUS, *PNTSTATUS;
27+
28+
typedef struct _UNICODE_STRING {
29+
USHORT Length;
30+
USHORT MaximumLength;
31+
PWSTR Buffer;
32+
} UNICODE_STRING, *PUNICODE_STRING;
33+
34+
typedef struct _OBJECT_NAME_INFORMATION {
35+
UNICODE_STRING Name;
36+
} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;
37+
#endif
38+
39+
#endif /* WINTERNL_COMPAT_H */

0 commit comments

Comments
 (0)