title: Having trouble implementing a readlink() function
body: <p>I've been trying to figure out a way to get some sort of ability to be able to return the true abspath of a symbolic link in Windows, under Python 2.7. (I cannot upgrade to 3.x, as most DCCs such as Maya/3ds max do not use that version of Python)</p>
  
  <p>I've looked at the sid0 ntfs utils (whose islink() function works, but readlink() function always returns an empty unicode string for me for some reason), and the juntalis ntfs libs (which unfortunately, I couldn't get to work), along with a helpful script someone posted:</p>
  
  <pre><code>import os, ctypes, struct
  from ctypes import windll, wintypes
  
  FSCTL_GET_REPARSE_POINT = 0x900a8
  
  FILE_ATTRIBUTE_READONLY      = 0x0001
  FILE_ATTRIBUTE_HIDDEN        = 0x0002
  FILE_ATTRIBUTE_DIRECTORY     = 0x0010
  FILE_ATTRIBUTE_NORMAL        = 0x0080
  FILE_ATTRIBUTE_REPARSE_POINT = 0x0400
  
  
  GENERIC_READ  = 0x80000000
  GENERIC_WRITE = 0x40000000
  OPEN_EXISTING = 3
  FILE_READ_ATTRIBUTES = 0x80
  FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
  INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value
  
  INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
  
  FILE_FLAG_OPEN_REPARSE_POINT = 2097152
  FILE_FLAG_BACKUP_SEMANTICS = 33554432
  # FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTI
  FILE_FLAG_REPARSE_BACKUP = 35651584
  
  
  GetFileAttributes = windll.kernel32.GetFileAttributesW
  _CreateFileW = windll.kernel32.CreateFileW
  _DevIoCtl = windll.kernel32.DeviceIoControl
  _DevIoCtl.argtypes = [
      wintypes.HANDLE, #HANDLE hDevice
      wintypes.DWORD, #DWORD dwIoControlCode
      wintypes.LPVOID, #LPVOID lpInBuffer
      wintypes.DWORD, #DWORD nInBufferSize
      wintypes.LPVOID, #LPVOID lpOutBuffer
      wintypes.DWORD, #DWORD nOutBufferSize
      ctypes.POINTER(wintypes.DWORD), #LPDWORD lpBytesReturned
      wintypes.LPVOID] #LPOVERLAPPED lpOverlapped
  _DevIoCtl.restype = wintypes.BOOL
  
  
  def islink(path):
      # assert os.path.isdir(path), path
      if GetFileAttributes(path) &amp; FILE_ATTRIBUTE_REPARSE_POINT:
          return True
      else:
          return False
  
  
  def DeviceIoControl(hDevice, ioControlCode, input, output):
      # DeviceIoControl Function
      # http://msdn.microsoft.com/en-us/library/aa363216(v=vs.85).aspx
      if input:
          input_size = len(input)
      else:
          input_size = 0
      if isinstance(output, int):
          output = ctypes.create_string_buffer(output)
      output_size = len(output)
      assert isinstance(output, ctypes.Array)
      bytesReturned = wintypes.DWORD()
      status = _DevIoCtl(hDevice, ioControlCode, input,
                         input_size, output, output_size, bytesReturned, None)
      print "status(%d)" % status
      if status != 0:
          return output[:bytesReturned.value]
      else:
          return None
  
  
  def CreateFile(path, access, sharemode, creation, flags):
      return _CreateFileW(path, access, sharemode, None, creation, flags, None)
  
  
  SymbolicLinkReparseFormat = "LHHHHHHL"
  SymbolicLinkReparseSize = struct.calcsize(SymbolicLinkReparseFormat);
  
  def readlink(path):
      """ Windows readlink implementation. """
      # This wouldn't return true if the file didn't exist, as far as I know.
      assert islink(path)
      # assert type(path) == unicode
  
      # Open the file correctly depending on the string type.
      hfile = CreateFile(path, GENERIC_READ, 0, OPEN_EXISTING,
                         FILE_FLAG_REPARSE_BACKUP)
      # MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16384 = (16*1024)
      buffer = DeviceIoControl(hfile, FSCTL_GET_REPARSE_POINT, None, 16384)
      windll.CloseHandle(hfile)
  
      # Minimum possible length (assuming length of the target is bigger than 0)
      if not buffer or len(buffer) &lt; 9:
          return None
  
      # Only handle SymbolicLinkReparseBuffer
      (tag, dataLength, reserver, SubstituteNameOffset, SubstituteNameLength,
       PrintNameOffset, PrintNameLength,
       Flags) = struct.unpack(SymbolicLinkReparseFormat,
                              buffer[:SymbolicLinkReparseSize])
      print tag, dataLength, reserver, SubstituteNameOffset, SubstituteNameLength
      start = SubstituteNameOffset + SymbolicLinkReparseSize
      actualPath = buffer[start : start + SubstituteNameLength].decode("utf-16")
      # This utf-16 string is null terminated
      index = actualPath.find(u"\0")
      assert index &gt; 0
      if index &gt; 0:
          actualPath = actualPath[:index]
      if actualPath.startswith(u"?\\"):
          return actualPath[2:]
      else:
          return actualPath
  </code></pre>
  
  <p>However, most of the time various solutions I've tried end up giving me:</p>
  
  <p>[Error 126] The specified module could not be found</p>
  
  <p>Even though I am able to import ctypes and do stuff such as importing cdll:</p>
  
  <pre><code>libc = cdll.msvcrt
  libc.printf
  &lt;_FuncPtr object at 0x0000000002A9F388&gt;
  </code></pre>
  
  <p>I'm pretty new to this part of Python and ctypes in general, so any pointers on dealing with symbolic links here would be very much appreciated!</p>