-
Notifications
You must be signed in to change notification settings - Fork 566
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Win32 stat() didn't handle AF_UNIX socket files #20271
Conversation
Just a small heads up: I'm planning on doing some testing with this and some reviewing; (so if possible: wait with merging until I had a chance to test/review) |
That's no problem. One test is failing, though I don't see why yet. |
It succeeds sometimes, but mostly fails. I haven't managed to get it to fail locally, and DrMemory (like valgrind) isn't showing anything relevant that I can see. |
Or it was, I can't get it to fail in CI at all now. |
FWIW, this 20204-stat-socket branch is fine for me when I build it on Windows 11 using gcc-12, where _WIN32_WINNT is 0x0a00 - though some of the TODO tests fail, and the only archtype I've tested is MSWin32-x64-multi-thread. With the same archtype and same compiler on Windows 7, t/win32/stat.t doesn't run to completion.
Something awry with the Of course, there's a couple of questionable practices being undertaken on Windows 7:
But this has not been a problem before with gcc-12 builds of perl on Windows 7 - so long as optimization is set to Cheers, |
Unfortunately both symbolic links and sockets can only be "statted" by opening with FILE_FLAG_OPEN_REPARSE_POINT which obviously doesn't follow symbolic links. So to find if a chain of symbolic links points to a socket, is a broken chain, or loops, we need to follow the chain ourselves.
While debugging socket stat()ing I noticed that sometimes the name returned by win32_readlink() was a full pathname rather than the name that the link was created as. Changing this to use the PrintName values changed win32_readlink() to return the create as name, which seems closer to the POSIX readlink.
The failing TODO tests are for bugs that had been reported against Win32 stat() before I re-worked it, unfortunately they seem to be Windows limits (or bugs, I guess). |
c735a93
to
33cac23
Compare
CreateFileA(path, FILE_READ_ATTRIBUTES, | ||
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, | ||
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); | ||
if (handle == INVALID_HANDLE_VALUE) { | ||
/* AF_UNIX sockets need to be opened as a reparse point, but | ||
that will also open symlinks rather than following them. | ||
|
||
There may be other reparse points that need similar | ||
treatment. | ||
*/ | ||
handle = S_follow_symlinks_to(aTHX_ path, &reparse_type); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part of the commit doesn't make sense to me...
Outdated comment
What I expect when path
is a symlink:
- if the
FILE_FLAG_OPEN_REPARSE_POINT
flag is not included then it points to the target of the symlink (i.e. to the destination, i.e. to the real file) - if the
FILE_FLAG_OPEN_REPARSE_POINT
flag is included then it points to th e symlink
In this code the FILE_FLAG_OPEN_REPARSE_POINT
flag is not included in the CreateFileA
call so I would expect handle
to refer to the target file (i.e. not the symlink) but then the comment and code reads: S_follow_symlinks_to
which doesn't make sense: if it was a symlink then handle
already points to the destination file.. (assuming I read the MS docs correct)~
Update: Ok, it does make a bit of sense. It's only a bit confusing.
It first calls CreateFileA
without FILE_FLAG_OPEN_REPARSE_POINT
.
That either returns a valid handle (to the target file) or return an invalid handle.
The invalid handle can mean three different things:
- there is no such file with that name
- there is a file with that name but it's a symlink and points to a non-existing file
- there is a file with that name but it's a unix socket
The code doesn't really check which case applies and (sort of) assumes it might be a symlink and calls S_follow_symlinks_to
which calls readlink
to follow the link (if it's a link I guess);
Eventually it calls CreateFileA
again with the FILE_FLAG_OPEN_REPARSE_POINT
flag set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I have/had initially in mind for this code was something like: (pseudo-code obviously):
handle =
CreateFileA(path, FILE_READ_ATTRIBUTES,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
if (handle != INVALID_HANDLE_VALUE) {
result = GetFileInformationByHandleEx(handle, FileBasicInfo, &basicInfo, ...)
if (baiscInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, ....)
....
if (reparse_type == IO_REÄRSE_TAG_SYMLINK) {
handle =
CreateFileA(path, FILE_READ_ATTRIBUTES,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
}
}
}
That is: first open with FILE_FLAG_OPEN_REPARSE_POINT
and if it's a symlink then call CreateFileA
again but this time without the FILE_FLAG_OPEN_REPARSE_POINT
flag so that it resolves the symlink.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative to the current code could also be to change S_follow_symlinks_to
to not follow the symlink chain and instead check the reparse_type
.
That is:
- if
CreateFileA(...., FILE_FLAG_BACKUP_SEMANTICS, ...)
fails - and if
CreateFileA(...., FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINTS, ...)
succeeds - then check if
reparse_type
is 'symlink'. If it is return ENOENT
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking some more.. I guess that won't do the right thing if it's a symlink to a unix socket..
(I'll do some testing tomorrow)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do the non-reparse-point open first because I expect it to be by far the more common case, and it does the right thing for win32_stat() for valid symlink chains.
I went direct for win32_readlink() mostly out of laziness - I didn't want to include the CreateFile/DeviceIOControl/WideCharToMultiByte() code here (though maybe a win32_readlink_handle() would help), and win32_readlink() does the job.
I don't really consider this code path to be performance critical.
And yes, a symlink chain to a socket fails the non-reparse CreateFile().
if (reparse_type) { | ||
/* Lie to get to the right place */ | ||
type = FILE_TYPE_DISK; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In what case was this needed?
When I test with a unix socket it seems GetFileType()
returns FILE_TYPE_DISK
.
Did you encounter a case in which it returned something else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part may be unnecessary, I ass-umed that it would fail, but didn't test.
Thanks for the review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This passes the eyeball test for me. I dont have a win32 box to play with it on however. I need to fix that.
Unfortunately both symbolic links and sockets can only be "statted" by opening with FILE_FLAG_OPEN_REPARSE_POINT which obviously doesn't follow symbolic links.
So to find if a chain of symbolic links points to a socket, is a broken chain, or loops, we need to follow the chain ourselves.
Fixes #20204