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.
-- v2: ntdll: Pass attr and nt_name down to lookup_unix_name(). server: Implement FSCTL_GET_REPARSE_POINT. server: Implement FSCTL_DELETE_REPARSE_POINT. server: Implement FSCTL_SET_REPARSE_POINT. ntdll/tests: Fix reparse test failures.