As prescribed some time ago, this attempts to implement correct reparse
behaviour simply without attempting at all to make reparse points resolvable
Unix symlinks.
A reparse point consists two files. For a file whose NT name is "foobar", the
contents of the file or directory (and note that a reparse point *can* have
arbitrary contents) is stored in a file or directory "foobar?". The contents of
the reparse point are stored in a file "foobar*". Both * and ? are valid in
POSIX filenames but not NT, so no collision should be possible.
The actual file "foobar" does not exist in this case. This is to allow the case
where there are no reparse points (which is most common) to work as it currently
does, without any extra overhead to check for a reparse point. [Simply guarding
this behind FILE_OPEN_REPARSE_POINT does not seem good enough to me; that flag
is used for example by many builtin functions like MoveFile().]
When failing to locate a path segment "foobar", we check for "foobar*" and
attempt to parse and resolve that reparse point.
Issues:
(1) Since we are manipulating two files, are there any race conditions?
I believe there are no meaningful races.
Read/write races:
- When setting a reparse point, we move the contents file out of the way last,
so attempts to open the file before that will see a normal file. Attempts to
read the reparse point might see a partial file, but we treat that as
failure.
- When removing a reparse point from a file, we move the contents file back
first, so attempts to open or read will see a normal file.
- When deleting a reparse point entirely, we delete the contents first, so
attempts to open the file will see neither a normal file nor the contents of
the symlink, and fail. Attempts to query attributes don't need to open the
contents, only the data, which will either be present or not.
Write/write races:
- Races involving file deletion don't matter, because deletion doesn't take
effect until all handles to an inode are closed.
- If we try to simultaneously set the reparse point from two different
threads, one will fail with no effect since we create the reparse data file
with O_EXCL.
- If we try to remove the reparse point while setting it, the contents won't
be in the right place and we'll fail.
- If we try to remove the reparse point from two different threads, one will
fail to move the contents back or delete them.
(2) How does this interact with hard links?
Creating a hard link to an existing reparse point is not too hard to do; we
can just hard link both the data file and the contents file.
However, turning an existing hardlinked file into a reparse point cannot work.
This is also a limitation of other approaches that have been proposed.
For this reason I've elected to forbid hard link interaction for now.
One alternative approach that would allow turning hardlinked files into
reparse points would be to store the reparse data in xattr. This would be less
portable though.
--
v7: ntdll: Resolve IO_REPARSE_TAG_MOUNT_POINT during path lookup.
ntdll: Pass attr and nt_name down to lookup_unix_name().
server: Implement FSCTL_GET_REPARSE_POINT.
https://gitlab.winehq.org/wine/wine/-/merge_requests/9293