Resolve file system links in Windows Vista

Share your advanced PureBasic knowledge/code with the community.
freak
PureBasic Team
PureBasic Team
Posts: 5940
Joined: Fri Apr 25, 2003 5:21 pm
Location: Germany

Resolve file system links in Windows Vista

Post by freak »

This code can read the real directory behind a file system link such as the "Documents and Settings" directory.

The code works with 32bit and 64bit. It will compile/run on all NT based Windows versions, not just Vista. It will just return the original path on older systems.

Code: Select all

; WinIoCtl.h
;
#FILE_DEVICE_FILE_SYSTEM         = $00000009
#METHOD_BUFFERED                 = 0
#FILE_ANY_ACCESS                 = 0
#FILE_SPECIAL_ACCESS             = (#FILE_ANY_ACCESS)

Macro CTL_CODE( DeviceType, Function, Method, Access )
  (((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
EndMacro

#FSCTL_SET_REPARSE_POINT         = CTL_CODE(#FILE_DEVICE_FILE_SYSTEM, 41, #METHOD_BUFFERED, #FILE_SPECIAL_ACCESS)
#FSCTL_GET_REPARSE_POINT         = CTL_CODE(#FILE_DEVICE_FILE_SYSTEM, 42, #METHOD_BUFFERED, #FILE_ANY_ACCESS)
#FSCTL_DELETE_REPARSE_POINT      = CTL_CODE(#FILE_DEVICE_FILE_SYSTEM, 43, #METHOD_BUFFERED, #FILE_SPECIAL_ACCESS)

; Winbase.h
;
#FILE_FLAG_OPEN_REPARSE_POINT    = $00200000

; WinNT.h
;
#IO_REPARSE_TAG_MOUNT_POINT  = $A0000003       
#IO_REPARSE_TAG_HSM          = $C0000004       
#IO_REPARSE_TAG_HSM2         = $80000006       
#IO_REPARSE_TAG_SIS          = $80000007       
#IO_REPARSE_TAG_DFS          = $8000000A       
#IO_REPARSE_TAG_SYMLINK      = $A000000C       
#IO_REPARSE_TAG_DFSR         = $80000012


; From Windows Driver Kit.
; http://msdn.microsoft.com/en-us/library/ms791514.aspx
;
Structure SymbolicLinkReparseBuffer
  SubstituteNameOffset.w
  SubstituteNameLength.w
  PrintNameOffset.w
  PrintNameLength.w
  Flags.l
  PathBuffer.w[1]
EndStructure

Structure MountPointReparseBuffer
  SubstituteNameOffset.w
  SubstituteNameLength.w
  PrintNameOffset.w
  PrintNameLength.w
  PathBuffer.w[1]
EndStructure

Structure GenericReparseBuffer
  DataBuffer.b[1]
EndStructure

Structure REPARSE_DATA_BUFFER
  ReparseTag.l
  ReparseDataLength.w
  Reserved.w
  StructureUnion
    SymbolicLinkReparseBuffer.SymbolicLinkReparseBuffer
    MountPointReparseBuffer.MountPointReparseBuffer
    GenericReparseBuffer.GenericReparseBuffer
  EndStructureUnion
EndStructure




; Tries to follow a directory link on Windows Vista (should also work for files)
;
; - If the directory is no link, the result is the original directory
; - If the target cannot be read, the result is ""
;
Procedure.s GetDirectoryTarget(Directory$)
  Protected TokenHandle, BufferSize, hDirectory, BytesReturned.l
  Protected Privileges.TOKEN_PRIVILEGES
  Protected *Buffer.REPARSE_DATA_BUFFER
  Protected Result$ = ""

  ; Check if the directory is a reparse point (link or mount point)
  ;
  If GetFileAttributes_(@Directory$) & #FILE_ATTRIBUTE_REPARSE_POINT

    ; The backup privilege is required to open a directory for io queries
    ; So try to set it on our process token. (usually it should be set already)
    ;
    If OpenProcessToken_(GetCurrentProcess_(), #TOKEN_ADJUST_PRIVILEGES, @TokenHandle)
      Privileges\PrivilegeCount = 1
      Privileges\Privileges[0]\Attributes = #SE_PRIVILEGE_ENABLED
    
      If LookupPrivilegeValue_(#Null, @"SeBackupPrivilege", @Privileges\Privileges[0]\Luid)       
        AdjustTokenPrivileges_(TokenHandle, #False, @Privileges, SizeOf(TOKEN_PRIVILEGES), #Null, #Null)
      EndIf    
      CloseHandle_(TokenHandle)
    EndIf
    
    ; Open the directory
    ;   Have to pass 0 as access right (not #GENERIC_READ), as it fails otherwise
    ;   http://www.codeproject.com/KB/vista/Windows_Vista.aspx
    ;
    hDirectory = CreateFile_(@Directory$, 0, #FILE_SHARE_READ|#FILE_SHARE_WRITE, #Null, #OPEN_EXISTING, #FILE_FLAG_OPEN_REPARSE_POINT | #FILE_FLAG_BACKUP_SEMANTICS, #Null)
    If hDirectory <> #INVALID_HANDLE_VALUE
    
      ; Allocate a buffer for the io query. 1000 bytes should be enough for the real path (in unicode)
      ;
      BufferSize = SizeOf(REPARSE_DATA_BUFFER) + 1000
      *Buffer = AllocateMemory(BufferSize)
      
      If *Buffer
        
        ; Query the directory for reparse point information
        ;
        If DeviceIoControl_(hDirectory, #FSCTL_GET_REPARSE_POINT, #Null, 0, *Buffer, BufferSize, @BytesReturned, #Null) <> 0
        
          ; Check the kind of reparse point (device drivers can create their own tags, so this is important)
          ; The "& $FFFFFFFF" is for 64bit, as the tags are negative when interpreted as quads
          ;
          If *Buffer\ReparseTag & $FFFFFFFF = #IO_REPARSE_TAG_MOUNT_POINT

            ; Read the result. The offset and length are in bytes. PeekS needs length in characters
            ;
            Result$ = PeekS(@*Buffer\MountPointReparseBuffer\PathBuffer[0] + *Buffer\MountPointReparseBuffer\SubstituteNameOffset, *Buffer\MountPointReparseBuffer\SubstituteNameLength / 2, #PB_Unicode)

          ElseIf *Buffer\ReparseTag & $FFFFFFFF = #IO_REPARSE_TAG_SYMLINK
          
            Result$ = PeekS(@*Buffer\SymbolicLinkReparseBuffer\PathBuffer[0] + *Buffer\SymbolicLinkReparseBuffer\SubstituteNameOffset, *Buffer\SymbolicLinkReparseBuffer\SubstituteNameLength / 2, #PB_Unicode)
            
          EndIf
        
        EndIf     
      
        FreeMemory(*Buffer)
      EndIf
    
      CloseHandle_(hDirectory)
    EndIf
  
  Else
  
    ; It is not a reparse point, so return the original path
    ;
    Result$ = Directory$
    
  EndIf
  
  ; Since the result is a unicode directory name, it can have the "\??\" prefix which allows a length of 32767 characters.
  ;
  If Left(Result$, 4) = "\??\"
    Result$ = Right(Result$, Len(Result$)-4)
  EndIf

  ProcedureReturn Result$
EndProcedure



; ----------------------------------------------------------------------------


Directory$ = "C:\Documents and Settings\"

Debug "Testing: " + Directory$
Debug "Target: " + GetDirectoryTarget(Directory$)
quidquid Latine dictum sit altum videtur
User avatar
ts-soft
Always Here
Always Here
Posts: 5756
Joined: Thu Jun 24, 2004 2:44 pm
Location: Berlin - Germany

Post by ts-soft »

:D thanks, very usefull for me
PureBasic 5.73 | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Old bugs good, new bugs bad! Updates are evil: might fix old bugs and introduce no new ones.
Image
rsts
Addict
Addict
Posts: 2736
Joined: Wed Aug 24, 2005 8:39 am
Location: Southwest OH - USA

Post by rsts »

nice to know.

Thanks
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

Aye, could be very useful. Thanks Freak.
I may look like a mule, but I'm not a complete ass.
SFSxOI
Addict
Addict
Posts: 2970
Joined: Sat Dec 31, 2005 5:24 pm
Location: Where ya would never look.....

Post by SFSxOI »

Thanks Freak. Saved me a lot of time. I was just working on this very thing. :)
Mistrel
Addict
Addict
Posts: 3415
Joined: Sat Jun 30, 2007 8:04 pm

Post by Mistrel »

Very useful, thanks! I was looking at experimenting with symbolic and hard links in XP but I decided that without explorer support it was just too dangerous to delete files by mistake. This will definitely come in handy once once I move to Windows 7. :)
User avatar
SimpleMind
Enthusiast
Enthusiast
Posts: 112
Joined: Sun May 18, 2003 12:40 pm
Location: Netherlands

IOCTL

Post by SimpleMind »

Thanks, just needed it. :D
Give me books, fruit, french wine, fine weather and a little music.
John Keats
Little John
Addict
Addict
Posts: 4770
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: Resolve file system links in Windows Vista

Post by Little John »

This is very useful. Many thanks, freak!
According to my experience so far, the code still runs fine now in 2018 on Windows 10, e.g. with PB 5.62 and 5.70 beta 1.
freak wrote:

Code: Select all

  ; Since the result is a unicode directory name, it can have the "\??\" prefix which allows a length of 32767 characters.
  ;
  If Left(Result$, 4) = "\??\"
    Result$ = Right(Result$, Len(Result$)-4)
  EndIf
Are you talking about "Long UNC" here? Then the respective prefix is "\\?\", isn't it?
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5494
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Resolve file system links in Windows Vista

Post by Kwai chang caine »

Not see before :oops: , but never too late to a thanks for sharing 8)
Works on W7 x86
ImageThe happiness is a road...
Not a destination
Post Reply