Win32-junction-point 0.2 → 0.2.1
raw patch · 4 files changed
+412/−408 lines, 4 filesdep ~text
Dependency ranges changed: text
Files
- ChangeLog +16/−12
- LICENSE +1/−1
- System/Win32/JunctionPoint.hs +336/−336
- Win32-junction-point.cabal +59/−59
ChangeLog view
@@ -1,12 +1,16 @@-## Changes in 0.2 - -* Depend on Win32-errors for error handling. -* More strict package dependencies - -## Changes in 0.1 - -* 64-bit GHC is now supported. -* Update cabal file to build on recent versions of GHC. -* An internal foreign import is now being called with the "safe" option. -* bug fixes. The deleteJunctionPoint function was failing to delete junction - points in many cases. +## Changes in 0.2.1++* Update constraints on 'text' library to support version 1.2.x++## Changes in 0.2++* Depend on Win32-errors for error handling.+* More strict package dependencies++## Changes in 0.1++* 64-bit GHC is now supported.+* Update cabal file to build on recent versions of GHC.+* An internal foreign import is now being called with the "safe" option.+* bug fixes. The deleteJunctionPoint function was failing to delete junction+ points in many cases.
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2011-2013, Michael Steele+Copyright (c) 2011-2015, Michael Steele Copyright (c) 2000, Mikael Nordell All rights reserved.
System/Win32/JunctionPoint.hs view
@@ -1,336 +1,336 @@-{-# LANGUAGE CPP #-} -{-# LANGUAGE OverloadedStrings #-} - -module System.Win32.JunctionPoint - ( createJunctionPoint - , deleteJunctionPoint - , getJunctionPointInfo - ) where - -import Control.Exception (bracket) -import Data.Bits -import Data.Char (chr) -import Data.Text (Text) -import qualified Data.Text as T -import Data.Text.Foreign -import Foreign -import Foreign.C -import System.Win32 hiding (createFile, failIf, failIfFalse_) -import System.Win32.Error -import System.Win32.Error.Foreign - - -#include "windows_cconv.h" - --- Macro taken from winioctl.h --- #define CTL_CODE( DeviceType, Function, Method, Access) ( \ --- (DWORD) ((DeviceType) << 16 | ((Access) << 14) | ((Function) << 2) \ --- | (Method)) -cTL_CODE :: DWORD -> DWORD -> DWORD -> DWORD -> DWORD -cTL_CODE d f m a = d `shift` 16 .|. a `shift` 14 .|. f `shift` 2 .|. m - --- taken from winioctl.h -fILE_DEVICE_FILE_SYSTEM :: DWORD -fILE_DEVICE_FILE_SYSTEM = 0x00000009 - --- taken from winioctl.h -mETHOD_BUFFERED :: DWORD -mETHOD_BUFFERED = 0 - --- taken from winioctl.h -fILE_ANY_ACCESS, fILE_SPECIAL_ACCESS :: DWORD -fILE_ANY_ACCESS = 0 -fILE_SPECIAL_ACCESS = 0 - --- taken from winioctl.h -fSCTL_SET_REPARSE_POINT :: DWORD -fSCTL_SET_REPARSE_POINT = cTL_CODE fILE_DEVICE_FILE_SYSTEM 41 - mETHOD_BUFFERED fILE_SPECIAL_ACCESS -fSCTL_GET_REPARSE_POINT :: DWORD -fSCTL_GET_REPARSE_POINT = cTL_CODE fILE_DEVICE_FILE_SYSTEM 42 - mETHOD_BUFFERED fILE_ANY_ACCESS -fSCTL_DELETE_REPARSE_POINT :: DWORD -fSCTL_DELETE_REPARSE_POINT = cTL_CODE fILE_DEVICE_FILE_SYSTEM 43 - mETHOD_BUFFERED fILE_SPECIAL_ACCESS - -iO_REPARSE_TAG_MOUNT_POINT :: DWORD -iO_REPARSE_TAG_MOUNT_POINT = 0xA0000003 - ---taken from MSDN's OpenFileById documentation -fILE_FLAG_OPEN_REPARSE_POINT :: DWORD -fILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 - -mAXIMUM_REPARSE_DATA_BUFFER_SIZE :: Int -mAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024 - --- The first 3 fields of TMN_REPARSE_DATA_BUFFER are considered the --- header. This is used in setReparsePoint. -tMN_REPARSE_DATA_BUFFER_HEADER_SIZE :: DWORD -tMN_REPARSE_DATA_BUFFER_HEADER_SIZE = 8 - --- I've seen this defined as FIELD_OFFSET(REPARSE_GUID_DATA_BUFFER, GenericReparseBuffer) --- All the struct members up to the genericreparsebuffer add up to 24. I'm guessing that --- the size-1 byte array does not add to this count. -rEPARSE_GUID_DATA_BUFFER_HEADER_SIZE :: DWORD -rEPARSE_GUID_DATA_BUFFER_HEADER_SIZE = 24 - --- C structure. This is not documented in current versions of the --- Windows SDK. - --- struct TMN_REPARSE_DATA_BUFFER --- { --- DWORD ReparseTag; --- WORD ReparseDataLength; --- WORD Reserved; --- WORD SubstituteNameOffset; --- WORD SubstituteNameLength; --- WORD PrintNameOffset; --- WORD PrintNameLength; --- WCHAR PathBuffer[1]; --- }; - -data TMN_REPARSE_DATA_BUFFER = TMN_REPARSE_DATA_BUFFER - { _reparseTag :: !DWORD - -- Bytes in addition to first 8. This will be 12 + length of _pathBuffer - , _reparseDataLength :: !WORD - , _reserved :: !WORD - , _substituteNameOffset :: !WORD - , _substituteNameLength :: !WORD - , _printNameOffset :: !WORD - , _printNameLength :: !WORD - -- The C struct indicates a single-element array. - -- Actually, we need to allocate enough memory to - -- hold a string here. - , _pathBuffer :: !(Ptr CWchar) - } - -instance Storable TMN_REPARSE_DATA_BUFFER where - -- Storable does not allow the pokes of different sizes, so we're just - -- allocating the maximum size Microsoft documents. - sizeOf _ = mAXIMUM_REPARSE_DATA_BUFFER_SIZE - alignment _ = 1 -- no alignment? I don't know what this means. - peek ptr = do - reparseTag <- peek . castPtr $ ptr - reparseDataLength <- castPtr ptr `peekByteOff` 4 - reserved <- castPtr ptr `peekByteOff` 6 - substituteNameOffset <- castPtr ptr `peekByteOff` 8 - substituteNameLength <- castPtr ptr `peekByteOff` 10 - printNameOffset <- castPtr ptr `peekByteOff` 12 - printNameLength <- castPtr ptr `peekByteOff` 14 - let pathBuffer = castPtr ptr `plusPtr` 16 - return $ TMN_REPARSE_DATA_BUFFER reparseTag reparseDataLength reserved - substituteNameOffset substituteNameLength printNameOffset - printNameLength pathBuffer - poke ptr rdb = do - castPtr ptr `poke` _reparseTag rdb - castPtr ptr `pokeByteOff` 4 $ _reparseDataLength rdb - castPtr ptr `pokeByteOff` 6 $ _reserved rdb - castPtr ptr `pokeByteOff` 8 $ _substituteNameOffset rdb - castPtr ptr `pokeByteOff` 10 $ _substituteNameLength rdb - castPtr ptr `pokeByteOff` 12 $ _printNameOffset rdb - castPtr ptr `pokeByteOff` 14 $ _printNameLength rdb - strLen <- lengthArray0 0 $ _pathBuffer rdb - copyArray (ptr `plusPtr` 16) (_pathBuffer rdb) (strLen + 1) - --- This data structure is almost identical to TMN_REPARSE_DATA_BUFFER, but the --- meaning of fields differ. Microsoft documents this structure. It is --- used for querying existing reparse points. -data REPARSE_GUID_DATA_BUFFER = REPARSE_GUID_DATA_BUFFER - { _rgdb_ReparseTag :: DWORD - -- MS specifies an odd structure, but a GUID is really just a 128-bit - -- value. - , _rgdb_GUID1 :: !DWORD - , _rgdb_GUID2 :: !DWORD - , _rgdb_GUID3 :: !DWORD - , _rgdb_GUID4 :: !DWORD - -- The C struct indicates a single-element array. - -- Actually, we need to allocate enough memory to - -- hold a string here. - , _rgdb_DataBuffer :: [BYTE] - } - -peekREPARSE_GUID_DATA_BUFFER :: Ptr REPARSE_GUID_DATA_BUFFER -> IO REPARSE_GUID_DATA_BUFFER -peekREPARSE_GUID_DATA_BUFFER ptr = do - _rgdb_ReparseTag <- peek . castPtr $ ptr - dataLength <- (castPtr ptr :: Ptr WORD) `peekByteOff` 4 - -- reserved WORD at byte 6 - _rgdb_GUID1 <- castPtr ptr `peekByteOff` 8 - _rgdb_GUID2 <- castPtr ptr `peekByteOff` 12 - _rgdb_GUID3 <- castPtr ptr `peekByteOff` 16 - _rgdb_GUID4 <- castPtr ptr `peekByteOff` 20 - dataBuffer <- peekArray dataLength (castPtr ptr `plusPtr` 24) - return $ REPARSE_GUID_DATA_BUFFER _rgdb_ReparseTag - _rgdb_GUID1 _rgdb_GUID2 _rgdb_GUID3 _rgdb_GUID4 dataBuffer - -pokeREPARSE_GUID_DATA_BUFFER :: Ptr REPARSE_GUID_DATA_BUFFER -> REPARSE_GUID_DATA_BUFFER -> IO () -pokeREPARSE_GUID_DATA_BUFFER ptr rdb = do - castPtr ptr `poke` _rgdb_ReparseTag rdb - castPtr ptr `pokeByteOff` 4 $ (fromIntegral dataLength :: WORD) - castPtr ptr `pokeByteOff` 8 $ _rgdb_GUID1 rdb - castPtr ptr `pokeByteOff` 12 $ _rgdb_GUID2 rdb - castPtr ptr `pokeByteOff` 16 $ _rgdb_GUID3 rdb - castPtr ptr `pokeByteOff` 20 $ _rgdb_GUID4 rdb - pokeArray (castPtr ptr `plusPtr` 24) $ _rgdb_DataBuffer rdb - where - dataLength = length $ _rgdb_DataBuffer rdb - -withTMN_REPARSE_DATA_BUFFER :: Text - -> (Ptr TMN_REPARSE_DATA_BUFFER -> IO a) -> IO a -withTMN_REPARSE_DATA_BUFFER dst f = - useAsPtr0 dst $ \c_dst -> - with (TMN_REPARSE_DATA_BUFFER - { _reparseTag = iO_REPARSE_TAG_MOUNT_POINT - , _reparseDataLength = dstLen + 12 - , _reserved = 0 - , _substituteNameOffset = 0 - , _substituteNameLength = dstLen - , _printNameOffset = dstLen + 2 - , _printNameLength = 0 - , _pathBuffer = c_dst - }) f - where - dstLen = fromIntegral (T.length dst) * 2 - -withREPARSE_GUID_DATA_BUFFER :: [BYTE] - -> (Ptr REPARSE_GUID_DATA_BUFFER -> IO a) -> IO a -withREPARSE_GUID_DATA_BUFFER bx f = - allocaBytes (fromIntegral rEPARSE_GUID_DATA_BUFFER_HEADER_SIZE + length bx) $ \prgdb -> do - pokeREPARSE_GUID_DATA_BUFFER prgdb - $ REPARSE_GUID_DATA_BUFFER - { _rgdb_ReparseTag = iO_REPARSE_TAG_MOUNT_POINT - , _rgdb_GUID1 = 0 - , _rgdb_GUID2 = 0 - , _rgdb_GUID3 = 0 - , _rgdb_GUID4 = 0 - -- must be empty when deleting a junction point - , _rgdb_DataBuffer = bx - } - f prgdb - --- | Create a junction point between two folders on the same filesystem. --- --- 'mountDir' should be the full file path to an empty folder. This --- folder should be on a local filesystem. --- --- 'destDir' should be the full file path to the junction point's target. The --- target must be a folder on the same filesystem as its source. The path --- should also be preceeded by \"\\??\\\". This indicates to Windows not to try --- validating the path before using it. --- --- >>> createJunctionPoint "c:\\Windows\\System32" "\\??\\c:\\System32" -createJunctionPoint :: Text -- ^ mountDir - -> Text -- ^ destDir - -> IO () -createJunctionPoint mountDir destDir = - withTMN_REPARSE_DATA_BUFFER destDir $ \rdb -> - bracket (openReparseHandle mountDir) closeHandle $ \handle -> - setReparsePoint handle rdb - --- | This "Deletes" the junction point at the supplied path. An --- empty folder will be left in its place on success. A 'Win32Exception' --- exception will be thrown in the event of an error condition. -deleteJunctionPoint :: Text -> IO () -deleteJunctionPoint dir = - bracket (openReparseHandle dir) closeHandle $ \handle -> do - deleteReparsePoint handle - --- | Discover the target of a junction point at the supplied path. A --- 'Win32Exception' exception will be thrown if the target is either invalid --- or not a junction point. The returned path will be prefixed by \"\\??\\\". -getJunctionPointInfo :: Text -> IO Text -getJunctionPointInfo dir = - bracket (openReparseHandle dir) closeHandle $ \handle -> - with (0 :: DWORD) $ \bytesReturned -> - withTMN_REPARSE_DATA_BUFFER (T.pack "") $ \pRdb -> do - deviceIoControl handle fSCTL_GET_REPARSE_POINT Nothing 0 - (Just $ castPtr pRdb) - (fromIntegral mAXIMUM_REPARSE_DATA_BUFFER_SIZE) - (Just bytesReturned) Nothing - --not sure how to call IsReparseTagValid. - rdb <- peek pRdb - fromPtr0 $ (_pathBuffer rdb) - --- NTFS junction points are implemented as reparse points. Reparse points are --- extra tagged information attached to filesystem objects. It is up to --- application software such as the Windows Shell to detect the presence of --- reparse points and act approprietly. --- --- There are many types of reparse points. For the internal purposes of --- this library I am pretending that junction points are the only kind of --- reparse point. -setReparsePoint :: HANDLE -> Ptr TMN_REPARSE_DATA_BUFFER -> IO () -setReparsePoint handle pRdb = - -- we don't care about bytesReturned - with (0 :: DWORD) $ \bytesReturned -> do - rdb <- peek pRdb - deviceIoControl handle fSCTL_SET_REPARSE_POINT - (Just $ castPtr pRdb) - (tMN_REPARSE_DATA_BUFFER_HEADER_SIZE + - (fromIntegral $ _reparseDataLength rdb)) - Nothing 0 (Just bytesReturned) Nothing - --- 'deleteReparsePoint' will remove a reparse point pointed to by the --- argument. An exception will be raised if the argument does not point to an --- open reparse point, or possibly if the user does not have write attribute --- permissions to the object. -deleteReparsePoint :: HANDLE -> IO () -deleteReparsePoint handle = - -- When deleting reparse points the destination does not matter. - -- msdn docs refer to REPARSE_GUID_DATA_BUFFER for this instead. - withREPARSE_GUID_DATA_BUFFER [] $ \pRgdb -> - with (0 :: DWORD) $ \bytesReturned -> do - deviceIoControl handle fSCTL_DELETE_REPARSE_POINT - (Just $ castPtr pRgdb) - rEPARSE_GUID_DATA_BUFFER_HEADER_SIZE - Nothing 0 (Just bytesReturned) Nothing - --- Open a reparse point attached to the supplied folder. A `Win32Error` exception --- will be thrown if the target does not exist, the user does not have read --- permissions to it, or the target does not have a reparse point attached. -openReparseHandle :: Text -> IO HANDLE -openReparseHandle path = createFile path (gENERIC_READ .|. gENERIC_WRITE) - fILE_SHARE_NONE Nothing oPEN_EXISTING - (fILE_FLAG_BACKUP_SEMANTICS .|. fILE_FLAG_OPEN_REPARSE_POINT) - Nothing - --- Low-level filesystem manipulation function. Check documentation in the MSDN --- library. -deviceIoControl :: HANDLE -> DWORD -> Maybe LPVOID -> DWORD -> Maybe LPVOID - -> DWORD -> Maybe LPDWORD -> Maybe LPOVERLAPPED -> IO () -deviceIoControl hDevice dwIoControlCode lpInBuffer nInBufferSize - lpOutBuffer nOutBufferSize lpBytesReturned lpOverlapped = - failIfFalse_ "DeviceIoControl" $ - c_DeviceIoControl hDevice dwIoControlCode - (maybe nullPtr id lpInBuffer) nInBufferSize - (maybe nullPtr id lpOutBuffer) nOutBufferSize - (maybe nullPtr id lpBytesReturned) - (maybe nullPtr id lpOverlapped) - -foreign import WINDOWS_CCONV "windows.h DeviceIoControl" - c_DeviceIoControl :: HANDLE -> DWORD -> LPVOID -> DWORD -> LPVOID - -> DWORD -> LPDWORD -> LPOVERLAPPED -> IO Bool - --- | Check MSDN documentation for what this action does. -createFile :: Text -> AccessMode -> ShareMode -> Maybe LPSECURITY_ATTRIBUTES - -> CreateMode -> FileAttributeOrFlag -> Maybe HANDLE -> IO HANDLE -createFile name access share mb_attr mode flag mb_h = - -- simply converting Text to a name does not add a null character - useAsPtr0 name $ \ c_name -> - failIf (== iNVALID_HANDLE_VALUE) "CreateFile" $ - c_CreateFile c_name access share (maybePtr mb_attr) mode flag (maybePtr mb_h) - --- | useAsPtr returns a length and byte buffer, but all the win32 functions --- rely on null termination. -useAsPtr0 :: Text -> (Ptr CWchar -> IO a) -> IO a -useAsPtr0 t f = useAsPtr (T.snoc t (chr 0x0)) $ \ str _ -> f (castPtr str) - --- This traverses the string twice. Is there a faster way? -fromPtr0 :: Ptr CWchar -> IO Text -fromPtr0 ptr = do - -- length in 16-bit words. - len <- lengthArray0 0x0000 ptr' - -- no loss of precision here. I16 is a newtype wrapper around Int. - fromPtr ptr' $ fromIntegral len - where - ptr' :: Ptr Word16 - ptr' = castPtr ptr +{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}++module System.Win32.JunctionPoint+ ( createJunctionPoint+ , deleteJunctionPoint+ , getJunctionPointInfo+ ) where++import Control.Exception (bracket)+import Data.Bits+import Data.Char (chr)+import Data.Text (Text)+import qualified Data.Text as T+import Data.Text.Foreign+import Foreign+import Foreign.C+import System.Win32 hiding (createFile, failIf, failIfFalse_)+import System.Win32.Error+import System.Win32.Error.Foreign+++#include "windows_cconv.h"++-- Macro taken from winioctl.h+-- #define CTL_CODE( DeviceType, Function, Method, Access) ( \+-- (DWORD) ((DeviceType) << 16 | ((Access) << 14) | ((Function) << 2) \+-- | (Method))+cTL_CODE :: DWORD -> DWORD -> DWORD -> DWORD -> DWORD+cTL_CODE d f m a = d `shift` 16 .|. a `shift` 14 .|. f `shift` 2 .|. m++-- taken from winioctl.h+fILE_DEVICE_FILE_SYSTEM :: DWORD+fILE_DEVICE_FILE_SYSTEM = 0x00000009++-- taken from winioctl.h+mETHOD_BUFFERED :: DWORD+mETHOD_BUFFERED = 0++-- taken from winioctl.h+fILE_ANY_ACCESS, fILE_SPECIAL_ACCESS :: DWORD+fILE_ANY_ACCESS = 0+fILE_SPECIAL_ACCESS = 0++-- taken from winioctl.h+fSCTL_SET_REPARSE_POINT :: DWORD+fSCTL_SET_REPARSE_POINT = cTL_CODE fILE_DEVICE_FILE_SYSTEM 41+ mETHOD_BUFFERED fILE_SPECIAL_ACCESS+fSCTL_GET_REPARSE_POINT :: DWORD+fSCTL_GET_REPARSE_POINT = cTL_CODE fILE_DEVICE_FILE_SYSTEM 42+ mETHOD_BUFFERED fILE_ANY_ACCESS+fSCTL_DELETE_REPARSE_POINT :: DWORD+fSCTL_DELETE_REPARSE_POINT = cTL_CODE fILE_DEVICE_FILE_SYSTEM 43+ mETHOD_BUFFERED fILE_SPECIAL_ACCESS++iO_REPARSE_TAG_MOUNT_POINT :: DWORD+iO_REPARSE_TAG_MOUNT_POINT = 0xA0000003++--taken from MSDN's OpenFileById documentation+fILE_FLAG_OPEN_REPARSE_POINT :: DWORD+fILE_FLAG_OPEN_REPARSE_POINT = 0x00200000++mAXIMUM_REPARSE_DATA_BUFFER_SIZE :: Int+mAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024++-- The first 3 fields of TMN_REPARSE_DATA_BUFFER are considered the+-- header. This is used in setReparsePoint.+tMN_REPARSE_DATA_BUFFER_HEADER_SIZE :: DWORD+tMN_REPARSE_DATA_BUFFER_HEADER_SIZE = 8++-- I've seen this defined as FIELD_OFFSET(REPARSE_GUID_DATA_BUFFER, GenericReparseBuffer)+-- All the struct members up to the genericreparsebuffer add up to 24. I'm guessing that+-- the size-1 byte array does not add to this count.+rEPARSE_GUID_DATA_BUFFER_HEADER_SIZE :: DWORD+rEPARSE_GUID_DATA_BUFFER_HEADER_SIZE = 24++-- C structure. This is not documented in current versions of the+-- Windows SDK.++-- struct TMN_REPARSE_DATA_BUFFER+-- {+-- DWORD ReparseTag;+-- WORD ReparseDataLength;+-- WORD Reserved;+-- WORD SubstituteNameOffset;+-- WORD SubstituteNameLength;+-- WORD PrintNameOffset;+-- WORD PrintNameLength;+-- WCHAR PathBuffer[1];+-- };++data TMN_REPARSE_DATA_BUFFER = TMN_REPARSE_DATA_BUFFER+ { _reparseTag :: !DWORD+ -- Bytes in addition to first 8. This will be 12 + length of _pathBuffer+ , _reparseDataLength :: !WORD+ , _reserved :: !WORD+ , _substituteNameOffset :: !WORD+ , _substituteNameLength :: !WORD+ , _printNameOffset :: !WORD+ , _printNameLength :: !WORD+ -- The C struct indicates a single-element array.+ -- Actually, we need to allocate enough memory to+ -- hold a string here.+ , _pathBuffer :: !(Ptr CWchar)+ }++instance Storable TMN_REPARSE_DATA_BUFFER where+ -- Storable does not allow the pokes of different sizes, so we're just+ -- allocating the maximum size Microsoft documents.+ sizeOf _ = mAXIMUM_REPARSE_DATA_BUFFER_SIZE+ alignment _ = 1 -- no alignment? I don't know what this means.+ peek ptr = do+ reparseTag <- peek . castPtr $ ptr+ reparseDataLength <- castPtr ptr `peekByteOff` 4+ reserved <- castPtr ptr `peekByteOff` 6+ substituteNameOffset <- castPtr ptr `peekByteOff` 8+ substituteNameLength <- castPtr ptr `peekByteOff` 10+ printNameOffset <- castPtr ptr `peekByteOff` 12+ printNameLength <- castPtr ptr `peekByteOff` 14+ let pathBuffer = castPtr ptr `plusPtr` 16+ return $ TMN_REPARSE_DATA_BUFFER reparseTag reparseDataLength reserved+ substituteNameOffset substituteNameLength printNameOffset+ printNameLength pathBuffer+ poke ptr rdb = do+ castPtr ptr `poke` _reparseTag rdb+ castPtr ptr `pokeByteOff` 4 $ _reparseDataLength rdb+ castPtr ptr `pokeByteOff` 6 $ _reserved rdb+ castPtr ptr `pokeByteOff` 8 $ _substituteNameOffset rdb+ castPtr ptr `pokeByteOff` 10 $ _substituteNameLength rdb+ castPtr ptr `pokeByteOff` 12 $ _printNameOffset rdb+ castPtr ptr `pokeByteOff` 14 $ _printNameLength rdb+ strLen <- lengthArray0 0 $ _pathBuffer rdb+ copyArray (ptr `plusPtr` 16) (_pathBuffer rdb) (strLen + 1)++-- This data structure is almost identical to TMN_REPARSE_DATA_BUFFER, but the+-- meaning of fields differ. Microsoft documents this structure. It is+-- used for querying existing reparse points.+data REPARSE_GUID_DATA_BUFFER = REPARSE_GUID_DATA_BUFFER+ { _rgdb_ReparseTag :: DWORD+ -- MS specifies an odd structure, but a GUID is really just a 128-bit+ -- value.+ , _rgdb_GUID1 :: !DWORD+ , _rgdb_GUID2 :: !DWORD+ , _rgdb_GUID3 :: !DWORD+ , _rgdb_GUID4 :: !DWORD+ -- The C struct indicates a single-element array.+ -- Actually, we need to allocate enough memory to+ -- hold a string here.+ , _rgdb_DataBuffer :: [BYTE]+ }++peekREPARSE_GUID_DATA_BUFFER :: Ptr REPARSE_GUID_DATA_BUFFER -> IO REPARSE_GUID_DATA_BUFFER+peekREPARSE_GUID_DATA_BUFFER ptr = do+ _rgdb_ReparseTag <- peek . castPtr $ ptr+ dataLength <- (castPtr ptr :: Ptr WORD) `peekByteOff` 4+ -- reserved WORD at byte 6+ _rgdb_GUID1 <- castPtr ptr `peekByteOff` 8+ _rgdb_GUID2 <- castPtr ptr `peekByteOff` 12+ _rgdb_GUID3 <- castPtr ptr `peekByteOff` 16+ _rgdb_GUID4 <- castPtr ptr `peekByteOff` 20+ dataBuffer <- peekArray dataLength (castPtr ptr `plusPtr` 24)+ return $ REPARSE_GUID_DATA_BUFFER _rgdb_ReparseTag+ _rgdb_GUID1 _rgdb_GUID2 _rgdb_GUID3 _rgdb_GUID4 dataBuffer++pokeREPARSE_GUID_DATA_BUFFER :: Ptr REPARSE_GUID_DATA_BUFFER -> REPARSE_GUID_DATA_BUFFER -> IO ()+pokeREPARSE_GUID_DATA_BUFFER ptr rdb = do+ castPtr ptr `poke` _rgdb_ReparseTag rdb+ castPtr ptr `pokeByteOff` 4 $ (fromIntegral dataLength :: WORD)+ castPtr ptr `pokeByteOff` 8 $ _rgdb_GUID1 rdb+ castPtr ptr `pokeByteOff` 12 $ _rgdb_GUID2 rdb+ castPtr ptr `pokeByteOff` 16 $ _rgdb_GUID3 rdb+ castPtr ptr `pokeByteOff` 20 $ _rgdb_GUID4 rdb+ pokeArray (castPtr ptr `plusPtr` 24) $ _rgdb_DataBuffer rdb+ where+ dataLength = length $ _rgdb_DataBuffer rdb++withTMN_REPARSE_DATA_BUFFER :: Text+ -> (Ptr TMN_REPARSE_DATA_BUFFER -> IO a) -> IO a+withTMN_REPARSE_DATA_BUFFER dst f =+ useAsPtr0 dst $ \c_dst ->+ with (TMN_REPARSE_DATA_BUFFER+ { _reparseTag = iO_REPARSE_TAG_MOUNT_POINT+ , _reparseDataLength = dstLen + 12+ , _reserved = 0+ , _substituteNameOffset = 0+ , _substituteNameLength = dstLen+ , _printNameOffset = dstLen + 2+ , _printNameLength = 0+ , _pathBuffer = c_dst+ }) f+ where+ dstLen = fromIntegral (T.length dst) * 2++withREPARSE_GUID_DATA_BUFFER :: [BYTE]+ -> (Ptr REPARSE_GUID_DATA_BUFFER -> IO a) -> IO a+withREPARSE_GUID_DATA_BUFFER bx f =+ allocaBytes (fromIntegral rEPARSE_GUID_DATA_BUFFER_HEADER_SIZE + length bx) $ \prgdb -> do+ pokeREPARSE_GUID_DATA_BUFFER prgdb+ $ REPARSE_GUID_DATA_BUFFER+ { _rgdb_ReparseTag = iO_REPARSE_TAG_MOUNT_POINT+ , _rgdb_GUID1 = 0+ , _rgdb_GUID2 = 0+ , _rgdb_GUID3 = 0+ , _rgdb_GUID4 = 0+ -- must be empty when deleting a junction point+ , _rgdb_DataBuffer = bx+ }+ f prgdb++-- | Create a junction point between two folders on the same filesystem.+--+-- 'mountDir' should be the full file path to an empty folder. This+-- folder should be on a local filesystem.+--+-- 'destDir' should be the full file path to the junction point's target. The+-- target must be a folder on the same filesystem as its source. The path+-- should also be preceeded by \"\\??\\\". This indicates to Windows not to try+-- validating the path before using it.+--+-- >>> createJunctionPoint "c:\\Windows\\System32" "\\??\\c:\\System32"+createJunctionPoint :: Text -- ^ mountDir+ -> Text -- ^ destDir+ -> IO ()+createJunctionPoint mountDir destDir =+ withTMN_REPARSE_DATA_BUFFER destDir $ \rdb ->+ bracket (openReparseHandle mountDir) closeHandle $ \handle ->+ setReparsePoint handle rdb++-- | This "Deletes" the junction point at the supplied path. An+-- empty folder will be left in its place on success. A 'Win32Exception'+-- exception will be thrown in the event of an error condition.+deleteJunctionPoint :: Text -> IO ()+deleteJunctionPoint dir =+ bracket (openReparseHandle dir) closeHandle $ \handle -> do+ deleteReparsePoint handle++-- | Discover the target of a junction point at the supplied path. A+-- 'Win32Exception' exception will be thrown if the target is either invalid+-- or not a junction point. The returned path will be prefixed by \"\\??\\\".+getJunctionPointInfo :: Text -> IO Text+getJunctionPointInfo dir =+ bracket (openReparseHandle dir) closeHandle $ \handle ->+ with (0 :: DWORD) $ \bytesReturned ->+ withTMN_REPARSE_DATA_BUFFER (T.pack "") $ \pRdb -> do+ deviceIoControl handle fSCTL_GET_REPARSE_POINT Nothing 0+ (Just $ castPtr pRdb)+ (fromIntegral mAXIMUM_REPARSE_DATA_BUFFER_SIZE)+ (Just bytesReturned) Nothing+ --not sure how to call IsReparseTagValid.+ rdb <- peek pRdb+ fromPtr0 $ (_pathBuffer rdb)++-- NTFS junction points are implemented as reparse points. Reparse points are+-- extra tagged information attached to filesystem objects. It is up to+-- application software such as the Windows Shell to detect the presence of+-- reparse points and act approprietly.+-- +-- There are many types of reparse points. For the internal purposes of+-- this library I am pretending that junction points are the only kind of+-- reparse point.+setReparsePoint :: HANDLE -> Ptr TMN_REPARSE_DATA_BUFFER -> IO ()+setReparsePoint handle pRdb =+ -- we don't care about bytesReturned+ with (0 :: DWORD) $ \bytesReturned -> do+ rdb <- peek pRdb+ deviceIoControl handle fSCTL_SET_REPARSE_POINT+ (Just $ castPtr pRdb)+ (tMN_REPARSE_DATA_BUFFER_HEADER_SIZE ++ (fromIntegral $ _reparseDataLength rdb))+ Nothing 0 (Just bytesReturned) Nothing++-- 'deleteReparsePoint' will remove a reparse point pointed to by the+-- argument. An exception will be raised if the argument does not point to an+-- open reparse point, or possibly if the user does not have write attribute+-- permissions to the object.+deleteReparsePoint :: HANDLE -> IO ()+deleteReparsePoint handle =+ -- When deleting reparse points the destination does not matter.+ -- msdn docs refer to REPARSE_GUID_DATA_BUFFER for this instead.+ withREPARSE_GUID_DATA_BUFFER [] $ \pRgdb ->+ with (0 :: DWORD) $ \bytesReturned -> do+ deviceIoControl handle fSCTL_DELETE_REPARSE_POINT+ (Just $ castPtr pRgdb)+ rEPARSE_GUID_DATA_BUFFER_HEADER_SIZE+ Nothing 0 (Just bytesReturned) Nothing++-- Open a reparse point attached to the supplied folder. A `Win32Error` exception+-- will be thrown if the target does not exist, the user does not have read+-- permissions to it, or the target does not have a reparse point attached.+openReparseHandle :: Text -> IO HANDLE+openReparseHandle path = createFile path (gENERIC_READ .|. gENERIC_WRITE)+ fILE_SHARE_NONE Nothing oPEN_EXISTING+ (fILE_FLAG_BACKUP_SEMANTICS .|. fILE_FLAG_OPEN_REPARSE_POINT)+ Nothing++-- Low-level filesystem manipulation function. Check documentation in the MSDN+-- library.+deviceIoControl :: HANDLE -> DWORD -> Maybe LPVOID -> DWORD -> Maybe LPVOID+ -> DWORD -> Maybe LPDWORD -> Maybe LPOVERLAPPED -> IO ()+deviceIoControl hDevice dwIoControlCode lpInBuffer nInBufferSize+ lpOutBuffer nOutBufferSize lpBytesReturned lpOverlapped =+ failIfFalse_ "DeviceIoControl" $+ c_DeviceIoControl hDevice dwIoControlCode+ (maybe nullPtr id lpInBuffer) nInBufferSize+ (maybe nullPtr id lpOutBuffer) nOutBufferSize+ (maybe nullPtr id lpBytesReturned)+ (maybe nullPtr id lpOverlapped)++foreign import WINDOWS_CCONV "windows.h DeviceIoControl"+ c_DeviceIoControl :: HANDLE -> DWORD -> LPVOID -> DWORD -> LPVOID+ -> DWORD -> LPDWORD -> LPOVERLAPPED -> IO Bool++-- | Check MSDN documentation for what this action does.+createFile :: Text -> AccessMode -> ShareMode -> Maybe LPSECURITY_ATTRIBUTES+ -> CreateMode -> FileAttributeOrFlag -> Maybe HANDLE -> IO HANDLE+createFile name access share mb_attr mode flag mb_h =+ -- simply converting Text to a name does not add a null character+ useAsPtr0 name $ \ c_name ->+ failIf (== iNVALID_HANDLE_VALUE) "CreateFile" $+ c_CreateFile c_name access share (maybePtr mb_attr) mode flag (maybePtr mb_h)++-- | useAsPtr returns a length and byte buffer, but all the win32 functions+-- rely on null termination.+useAsPtr0 :: Text -> (Ptr CWchar -> IO a) -> IO a+useAsPtr0 t f = useAsPtr (T.snoc t (chr 0x0)) $ \ str _ -> f (castPtr str)++-- This traverses the string twice. Is there a faster way?+fromPtr0 :: Ptr CWchar -> IO Text+fromPtr0 ptr = do+ -- length in 16-bit words.+ len <- lengthArray0 0x0000 ptr'+ -- no loss of precision here. I16 is a newtype wrapper around Int.+ fromPtr ptr' $ fromIntegral len+ where+ ptr' :: Ptr Word16+ ptr' = castPtr ptr
Win32-junction-point.cabal view
@@ -1,59 +1,59 @@-Name: Win32-junction-point -Version: 0.2 -Synopsis: Support for manipulating NTFS junction points. -Description: - This package provides the ability to manipulate NTFS junction points as - supported by Windows 2000 and above. Junction points, along with NTFS hard - links and NTFS symbolic links, are a type of symbolic link that can be made - between folders existing on the same filesystem. Please read Microsoft - KB205524 [1] for more information on junction points. - . - Junction points have always been left undocumented in the Win32 SDK. The - Windows 2000 Resource Kit came with a command-line utility named linkd.exe - to work with them. Later, Mark Russinovich of SysInternals distributed a - replacement utility named Junction [2] which accomplished the same thing. - . - This source code is based on an article [3] and C++ library [4] written by - Mike Nordell at codeproject.com. - . - It is against Microsoft's recommendation to make use of undocumented API - features. Use this library at your own risk. - . - (1) <http://support.microsoft.com/?kbid=205524> - . - (2) <http://technet.microsoft.com/en-us/sysinternals/bb896768> - . - (3) <http://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=194> - . - (4) <http://www.codeproject.com/KB/winsdk/junctionpoints.aspx> -License: BSD3 -License-file: LICENSE -Author: Michael Steele -Maintainer: mikesteele81@gmail.com -Copyright: Copyright (c) 2011-2014, Michael Steele. Copyright (c) 2000, Mikael Nordell -Category: System -Build-type: Simple -Cabal-version: >=1.16 -Homepage: http://github.com/mikesteele81/Win32-junction-point -bug-reports: http://github.com/mikesteele81/win32-junction-point/issues -Stability: experimental -tested-with: GHC==7.8.2 -extra-source-files: - include/windows_cconv.h - ChangeLog - -source-repository head - type: git - location: git://github.com/mikesteele81/win32-junction-point.git - -Library - default-language: Haskell2010 - ghc-options: -Wall -funbox-strict-fields - cc-options: -fno-strict-aliasing - Exposed-modules: System.Win32.JunctionPoint - - Build-depends: base >= 4.0 && < 4.8 - , text >= 0.8 && < 1.2 - , Win32 >= 2.0 && < 2.4 - , Win32-errors >= 0.2 && < 0.3 - include-dirs: include +Name: Win32-junction-point+Version: 0.2.1+Synopsis: Support for manipulating NTFS junction points.+Description:+ This package provides the ability to manipulate NTFS junction points as+ supported by Windows 2000 and above. Junction points, along with NTFS hard+ links and NTFS symbolic links, are a type of symbolic link that can be made+ between folders existing on the same filesystem. Please read Microsoft+ KB205524 [1] for more information on junction points.+ .+ Junction points have always been left undocumented in the Win32 SDK. The+ Windows 2000 Resource Kit came with a command-line utility named linkd.exe+ to work with them. Later, Mark Russinovich of SysInternals distributed a+ replacement utility named Junction [2] which accomplished the same thing.+ .+ This source code is based on an article [3] and C++ library [4] written by+ Mike Nordell at codeproject.com.+ .+ It is against Microsoft's recommendation to make use of undocumented API+ features. Use this library at your own risk.+ .+ (1) <http://support.microsoft.com/?kbid=205524>+ .+ (2) <http://technet.microsoft.com/en-us/sysinternals/bb896768>+ .+ (3) <http://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=194>+ .+ (4) <http://www.codeproject.com/KB/winsdk/junctionpoints.aspx>+License: BSD3+License-file: LICENSE+Author: Michael Steele+Maintainer: mikesteele81@gmail.com+Copyright: Copyright (c) 2011-2015, Michael Steele. Copyright (c) 2000, Mikael Nordell+Category: System+Build-type: Simple+Cabal-version: >=1.16+Homepage: http://github.com/mikesteele81/Win32-junction-point+bug-reports: http://github.com/mikesteele81/win32-junction-point/issues+Stability: experimental+tested-with: GHC==7.8.3+extra-source-files:+ include/windows_cconv.h+ ChangeLog++source-repository head+ type: git+ location: git://github.com/mikesteele81/win32-junction-point.git++Library+ default-language: Haskell2010+ ghc-options: -Wall -funbox-strict-fields+ cc-options: -fno-strict-aliasing+ Exposed-modules: System.Win32.JunctionPoint++ Build-depends: base >= 4.0 && < 4.8+ , text >= 0.8 && < 1.3+ , Win32 >= 2.0 && < 2.4+ , Win32-errors >= 0.2 && < 0.3+ include-dirs: include