pythonwindowswinapifilesystemssymlink-traversal

Are there any problems with this symlink traversal code for Windows?


In my efforts to resolve Python issue 1578269, I've been working on trying to resolve the target of a symlink in a robust way. I started by using GetFinalPathNameByHandle as recommended here on stackoverflow and by Microsoft, but it turns out that technique fails when the target is in use (such as with pagefile.sys).

So, I've written a new routine to accomplish this using CreateFile and DeviceIoControl (as it appears this is what Explorer does). The relevant code from jaraco.windows.filesystem is included below.

The question is, is there a better technique for reliably resolving symlinks in Windows? Can you identify any issues with this implementation?

 def relpath(path, start=os.path.curdir):
  """
  Like os.path.relpath, but actually honors the start path
  if supplied. See http://bugs.python.org/issue7195
  """
  return os.path.normpath(os.path.join(start, path))

 def trace_symlink_target(link):
  """
  Given a file that is known to be a symlink, trace it to its ultimate
  target.

  Raises TargetNotPresent when the target cannot be determined.
  Raises ValueError when the specified link is not a symlink.
  """

  if not is_symlink(link):
   raise ValueError("link must point to a symlink on the system")
  while is_symlink(link):
   orig = os.path.dirname(link)
   link = _trace_symlink_immediate_target(link)
   link = relpath(link, orig)
  return link

 def _trace_symlink_immediate_target(link):
  handle = CreateFile(
   link,
   0,
   FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
   None,
   OPEN_EXISTING,
   FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS,
   None,
   )

  res = DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, None, 10240)

  bytes = create_string_buffer(res)
  p_rdb = cast(bytes, POINTER(REPARSE_DATA_BUFFER))
  rdb = p_rdb.contents
  if not rdb.tag == IO_REPARSE_TAG_SYMLINK:
   raise RuntimeError("Expected IO_REPARSE_TAG_SYMLINK, but got %d" % rdb.tag)
  return rdb.get_print_name()

Solution

  • Unfortunately I can't test with Vista until next week, but GetFinalPathNameByHandle should work, even for files in use - what's the problem you noticed? In your code above, you forget to close the file handle.