RFC: Reparse Point/NT Symlink Support [3]

Erich E. Hoover erich.e.hoover at gmail.com
Wed Aug 18 13:41:37 CDT 2021


I would love any and all feedback on updated versions of my "Reparse
Point" patches, which are now available in staging:
https://github.com/wine-staging/wine-staging/tree/master/patches/ntdll-Junction_Points

Major changes since the last RFC version are:
1) Deletion of symlinks is now atomic (where supported).
2) Regular Unix symlinks are now reported as WSL Unix symlinks.
3) FILE_OPEN_REPARSE_POINT is now properly supported for applications
that wish to access reparse points directly (symlink operations by
file descriptor instead of by path).
4) Wine's cmd "dir" command now shows reparse point types and targets.
5) Wine's cmd "mklink" command now supports creating junction points
and NT symlinks.
6) Absolute reparse points that are outside of the prefix no longer
contain the path to drive Z (or appropriate drive letter).
7) Absolute reparse points now contain a string identifying the end of
the Wine prefix that is used to modify the target path on file access
if a prefix has been relocated (if the target is within the prefix).
8) Many, many bug fixes.

Thank you in advance to those who look these patches over, and
previous iterations, your feedback is greatly appreciated!  I know
that these patches have been a long time coming, but I believe that
we're finally getting to a place where we've figured out all the
gotchas.  Hopefully other folks agree ;)

===

Context:
For those of you that wonder "why do these patches exist?", I have
been putting these together over the past few years for several
reasons:
1) More modern Windows programs use NT symlinks to avoid duplicating
files and taking up extra disk space (particularly .NET).
2) Wine currently has no mechanism to make symlinks on the "PE" side
of the dividing line, these patches allow this for Windows file paths
and a small additional patch (creating WSL Unix symlinks) allows the
same behavior for Unix paths.
3) Wine currently takes up a lot of space with system DLLs that are
duplicates of the system-wide files, a logical extension to these
patches is a Wine-specific reparse tag that allows these system DLLs
to be "copy-on-write" so that Wine prefixes don't take up unnecessary
disk space.  Please note that this feature is not a focus of the
patchset (not included), as I would like to get the infrastructure in
place before tackling that problem.

Not currently supported:
1) Reparse points of type other than junction points, NT symlinks, and
WSL Unix symlinks.
2) Creating WSL Unix symlinks.
3) Changing ownership permissions (lrwxrwxrwx) on the symlink instead
of the target (currently a Linux limitation).

Implementation overview:
• Reparse points are implemented as Unix symlinks where relative
symlinks always start with "./" and absolute symlinks start with "/".
• Following the relative/absolute flag the 32-bit reparse point tag
(junction point or NT symlink) is encoded as a relative path where
each bit is encoded as "/" (0) or "./" (1).  When creating WSL Unix
symlinks (future feature) this tag encoding will be skipped.
• For NT symlinks this is followed by a file/directory flag ("./" for
directories, "/" for files) so that dangling symlinks can still be
deleted properly with RemoveFile or RemoveDirectory (as appropriate).
• After the reparse tag (and file/directory flag) the Unix path is
then appended, relative paths are included unmodified and absolute
paths have some minor modifications.
• If the path is an absolute path and points outside the prefix then
the portion of the path to drive Z (or appropriate drive letter) is
removed.
• If the path is an absolute path that points inside the prefix then
after the prefix path an 8-bit 'P' is encoded the same way that the
reparse point tag is ("/"=0, "./"=1).
• If an absolute symlink is opened by the wineserver then the prefix
path is checked (if appropriate) and if it doesn't match the path of
the current Wine prefix then the symlink is modified to replace the
old prefix path with the current prefix path.
• Because reparse points involve a modification to an existing empty
file/directory, symlink creation and "removal" is implemented by
creating the symlink at a temporary location and then replacing the
original with an atomic renameat2 call (on supported systems),
equivalent call (BSD/Mac), or by unlinking the original and moving the
symlink into place (for legacy systems that do not support atomic
replacement).

Best,
Erich



More information about the wine-devel mailing list