-
Notifications
You must be signed in to change notification settings - Fork 109
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
Initial port to Windows #6
Conversation
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.
Just some initial comments and questions. Glad to see you working on this!
public var system_errno: CInt { | ||
get { | ||
var value: CInt = 0 | ||
// TODO(compnerd) handle the error? |
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 is there to handle?
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.
_get_errno
returns an errno_t
. It can fail in case of a PEB/TEB corruption or the parameter being invalid.
@@ -36,47 +50,129 @@ public var system_errno: CInt { | |||
#endif | |||
|
|||
public func system_open(_ path: UnsafePointer<CChar>, _ oflag: Int32) -> CInt { | |||
#if os(Windows) | |||
var fh: CInt = -1 | |||
_ = _sopen_s(&fh, path, oflag, _SH_DENYNO, _S_IREAD | _S_IWRITE) |
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.
Is this the rough equivalent of POSIX open? E.g., what is the difference between a Windows file handle and a POSIX file descriptor?
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.
Yes, this is roughly the equivalent of the POSIX open
.
I don't think that its valuable to try to enumerate every single difference. What type of differences are you really looking for?
But for a taste, there are a large number of differences between a file handle and a POSIX file. The POSIX file is a user mode only, process specific construct. It cannot be shared across processes. It is a mapping from the kernel side representation (HANDLE
) and an application specific view. It is maintained by means of a lookup table in the application's address space by the C library. Additionally, it has a comparatively restricted set of operations available to it.
#if os(Windows) | ||
public func system_open( | ||
_ path: UnsafePointer<CChar>, _ oflag: Int32, _ mode: CInt | ||
) -> CInt { |
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 wonder if we should try to unify the interface around trivial type casts
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 was considering that myself, but wasn't sure if you would be okay with choosing the minimal type across the interfaces.
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.
If we want to do that, I think that doing a follow up might be the best way forward.
// TODO(compnerd) map windows error to errno | ||
return Int(-1) | ||
} | ||
return Int(nNumberOfBytesRead) |
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.
Could you explain more what is going on here? Does Windows not support pread/pwrite? We will have to decide on a case-by-case basis what should be handled by availability and what should be handled by implementing the functionality ourselves (but this doesn't look too bad). This is definitely worthy of being extracted into a helper function and given a good name (and comments!) since it's non-obvious to a non-Windows contributor what's going on.
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.
Yes, there is no pread
or pwrite
function that is available in the C library. However, the operation itself is supported, but you need to use the Win32 APIs to do that. Basically, I have to convert the file descriptor to the kernel/win32 representation (HANDLE
) and then I can perform the operation via the Win32 API (ReadFile
or WriteFile
) as I don't see the value in going directly to the syscall interface (NtReadFile
or NtWriteFile
) as they are more complicated to use (it would directly map to the kernel call ZwReadFile
or ZwWriteFile
).
@compnerd How do you plan to handle file paths in SwiftSystem? You probably know what I'm referring to, but to spell it out for the record: 8-bit file paths on Windows are not UTF-8 like on POSIX (except on Windows 10 and even then only in certain cases). Should SwiftSystem go through the Win32 APIs instead, in order to ensure proper Unicode support? |
@jakepetroules thats something that I did bring up with @milseman and the plan is to have a representation which is platform dependent AIUI. So that would enable the paths to handle the proper conversion at the right time. |
Right, so on nix you'd have an initializer taking a collection of bytes in some platform encoding (e.g. UTF-8) and on Windows you'd have an init taking (presumably) a collection of UInt16s. Both platforms can form paths from strings and both platforms can (with potential encoding error correction) go to a string. The storage representation of |
return _lseek(fd, off, whence) | ||
#else | ||
return lseek(fd, off, whence) | ||
#endif |
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.
To reduce the number of #if/#else
inside of these functions, should Windows have a internal let lseek = _lseek
, etc?
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.
There is a lseek
as well, so I don't think that we should be shadowing that.
This LGTM after I merge mocking at #8. I apologize in advance that it will cause you some merge conflicts around the |
@compnerd sorry for the conflict, it's worth moving these sys call wrappers into their own file. The good news is that I was able to structure the mocking-related code such that there is less boilerplate and it all appears at the very top of the function, so your code shouldn't need much modification. |
That change introduced more than just conflicts :/ The threading model doesn't work for Windows - there is no |
For Windows, would you just use |
If you can show me the Windows calls you'd like use and an example of how to use them, I can refactor the mocking code to accommodate Windows better. |
@milseman, @kylemacomber - this part is rebased, I'll do a follow up for the threading |
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 LGTM, but I would like to do some cleanup. If there is something blocked by this, that can happen on a subsequent PR. If not, then it might be cleaner to do it here.
-
Can Windows just define
open
,pwrite
,lseek
, etc? That would keep thesystem_foo
bodies clean and straight forward. It's an unfortunate amount of boilerplate we have to have in System while we wait on compiler features (or we do codegen ourselves), so ideally the bodies would be simple and uniform. -
We'd like to unify the types for
system_write
andsystem_read
interfaces as much as possible. I'm worried It might be too easy to break Windows when modifying the System module in what seems to be trivial changes. We can unify on the wider type.
return -1 | ||
case .counted(let e, let count): | ||
assert(count >= 1) | ||
#if os(Windows) | ||
_ = ucrt._set_errno(e) | ||
#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.
I'm definitely in favor of making a helper function to set errno if it means we can avoid scattering #if/#else
throughout function bodies. But that can be a subsequent PR.
|
One approach might be to add a #If os(Windows)
internal let lseek = _lseek
internal func open(...) {
var handle: ...
_open(&handle, ...)
...
return handle
}
...
#endif The advantage of this is it eliminates the |
For "widening", what I mean is effectively moving the extending/truncating code from // FileOperations.swift
extension FileDescriptor {
func read(...) {
Int(system_read(self.rawValue, buffer.baseAddress, numericCast(buffer.count)))
}
} We'd have: // WindowsSyscalls.swift
#if os(Windows)
func read(_ fd: Int32, _ buf: UnsafeMutableRawPointer!, _ nbyte: Int) -> Int {
Int(_read(fd, buf, numericCast(nbyte)))
}
#endif being semantically equivalent and removing the cognitive burden from anyone touching other parts of System. |
I could take a stab at this if you like. I wouldn't be able to test it (or even compile it), but it might be a good proof of concept. |
var fh: CInt = -1 | ||
_ = _sopen_s(&fh, path, oflag, _SH_DENYNO, _S_IREAD | _S_IWRITE) | ||
return fh | ||
} |
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.
mode
is not used, so why do we care to type it as a CInt
? For Windows, would we not have a permissions parameter for FileDescriptor.open
?.
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.
There is no mode_t
type, and I don't think that you can use an internal typealias on a public func.
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.
Should we make the FilePermissions
OptionSet empty on Windows? I'm a little uneasy about ignoring information.
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.
no, there is the concept of file permissions - its just that the permissions are different, there are no group permissions, and there are no execute permissions - only read/write.
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.
Ah, then we should we probably have group/execute unavailable on Windows, and the implementation of open here should do the read/write permission setting.
#11 is a draft that shows what I mean, let me know what you think. |
Hmm, as long as we can ensure that the function gets flattened, I think that might not be a bad idea. I'll play around with that. |
Do you mean inlined? We can slap an |
Yeah, an |
Alright, I think #11 is ready to review. It adds the basic organization and mocking support. If you want attribution for the code, feel free to steal the commits and use them for this branch :-). Up to you. Let me know if I can help with anything else to move this PR forwards. |
IIUC, the way to resolve the conflict is to remove all changes to |
@@ -96,10 +98,10 @@ public func system_close(_ fd: Int32) -> Int32 { | |||
|
|||
// read | |||
public func system_read( | |||
_ fd: Int32, _ buf: UnsafeMutableRawPointer!, _ nbyte: Int | |||
) -> Int { |
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 think we want the wider types here. Does Windows have a 64-bit offset API for read for 64-bit systems? For whatever reason, size_t
is imported into Swift as Int
_ fd: Int32, _ buf: UnsafeRawPointer!, _ nbyte: Int | ||
) -> Int { | ||
_ fd: Int32, _ buf: UnsafeRawPointer!, _ nbyte: CUnsignedInt | ||
) -> CInt { |
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.
Similar to above.
Sources/System/FileOperations.swift
Outdated
@@ -168,7 +168,7 @@ extension FileDescriptor { | |||
retryOnInterrupt: Bool = true | |||
) throws -> Result<Int, Errno> { | |||
valueOrErrno(retryOnInterrupt: retryOnInterrupt) { | |||
system_read(self.rawValue, buffer.baseAddress, buffer.count) | |||
Int(system_read(self.rawValue, buffer.baseAddress, numericCast(buffer.count))) |
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.
Ideally, this casting would be done in the body of read/write
inside the Windows syscall adapters. Can we avoid this at all?
This adds enough shims and tweaks the types sufficiently to allow building System on Windows.
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.
LGTM! Thanks for all the cleanup!
This adds enough shims and tweaks the types sufficiently to allow
building System on Windows.