-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Foundation: port NSData to Windows #1868
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -435,6 +435,24 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { | |
|
||
internal func makeTemporaryFile(inDirectory dirPath: String) throws -> (Int32, String) { | ||
let template = dirPath._nsObject.appendingPathComponent("tmp.XXXXXX") | ||
#if os(Windows) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Couldnt this whole function just be replaced with: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I honestly don't know. I'm not sure why this is duplicated in the first place, as the same thing happens in the Linux case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, looking at this, no, it wouldn't work, since that would alter the template and give you a different result. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about passing the
There only seems to be one caller of each method so generating the template at the call-site seems to solve the issue. |
||
let maxLength = Int(MAX_PATH) + 1 | ||
var buf: [UInt16] = Array<UInt16>(repeating: 0, count: maxLength) | ||
let length = GetTempPathW(DWORD(MAX_PATH), &buf) | ||
precondition(length <= MAX_PATH - 14, "temp path too long") | ||
if "SCF".withCString(encodedAs: UTF16.self, { | ||
return GetTempFileNameW(buf, $0, 0, &buf) | ||
}) == FALSE { | ||
throw _NSErrorWithErrno(Int32(GetLastError()), reading: false, | ||
path: dirPath) | ||
} | ||
let pathResult = | ||
FileManager.default | ||
.string(withFileSystemRepresentation: String(decoding: buf, | ||
as: UTF16.self), | ||
length: wcslen(buf)) | ||
let fd = open(pathResult, _O_CREAT) | ||
#else | ||
let maxLength = Int(PATH_MAX) + 1 | ||
var buf = [Int8](repeating: 0, count: maxLength) | ||
let _ = template._nsObject.getFileSystemRepresentation(&buf, maxLength: maxLength) | ||
|
@@ -443,13 +461,14 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { | |
throw _NSErrorWithErrno(errno, reading: false, path: dirPath) | ||
} | ||
let pathResult = FileManager.default.string(withFileSystemRepresentation:buf, length: Int(strlen(buf))) | ||
#endif | ||
return (fd, pathResult) | ||
} | ||
|
||
internal class func write(toFileDescriptor fd: Int32, path: String? = nil, buf: UnsafeRawPointer, length: Int) throws { | ||
var bytesRemaining = length | ||
while bytesRemaining > 0 { | ||
var bytesWritten : Int | ||
var bytesWritten: Int = 0 | ||
repeat { | ||
#if os(macOS) || os(iOS) | ||
bytesWritten = Darwin.write(fd, buf.advanced(by: length - bytesRemaining), bytesRemaining) | ||
|
@@ -465,6 +484,22 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { | |
} | ||
} | ||
|
||
#if os(Windows) | ||
internal class func write(toHandle handle: HANDLE, path: String? = nil, buf: UnsafeRawPointer, length: Int) throws { | ||
var BytesToWrite = length | ||
while BytesToWrite > 0 { | ||
var BytesWritten: DWORD = 0 | ||
if WriteFile(handle, buf.advanced(by: length - BytesToWrite), DWORD(BytesToWrite), &BytesWritten, nil) == FALSE { | ||
throw _NSErrorWithErrno(Int32(GetLastError()), reading: false, path: path) | ||
} | ||
if BytesWritten == 0 { | ||
throw _NSErrorWithErrno(Int32(GetLastError()), reading: false, path: path) | ||
} | ||
BytesToWrite -= Int(BytesWritten) | ||
} | ||
} | ||
#endif | ||
|
||
/// Writes the data object's bytes to the file specified by a given path. | ||
open func write(toFile path: String, options writeOptionsMask: WritingOptions = []) throws { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it might be better to try and rewrite this whole method using The helper methods could probably be reused in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like that idea, if that is acceptable as per @parkera, @phausler, or @millenomi. The problem is that this is a public interface, no? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just meant change the function body. Would something like this work (untested, and you will need to add an internal
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can rewrite this in terms of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about moving the two There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the only reason why this was two separate implementations is because they are transliterations from the objc implementations. The implementations for NSData live in CoreFoundation and NSFileHandle lives in Foundation. Plus from a historical case the implementations for NSFileHandle use NSExceptions heavily (whereas NSData does not). Frankly these are all implementation details that probably dont need to be maintained across to SCLF. It makes sense to me that file handle things like writing etc should be handled by FileHandle. @millenomi might have some insight here on which way to favor. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ive done a refactor of the |
||
let fm = FileManager.default | ||
|
@@ -474,17 +509,37 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { | |
let useAuxiliaryFile = writeOptionsMask.contains(.atomic) | ||
var auxFilePath : String? = nil | ||
if useAuxiliaryFile { | ||
#if os(Windows) | ||
var info: ucrt._stat64 = ucrt._stat64() | ||
try! String(cString: pathFsRep).withCString(encodedAs: UTF16.self) { wpath in | ||
if ucrt._wstat64(wpath, &info) == 0 { | ||
let mode = mode_t(info.st_mode) | ||
} else if errno != ENOENT && errno != ENAMETOOLONG { | ||
throw _NSErrorWithErrno(errno, reading: false, path: path) | ||
} | ||
} | ||
#else | ||
// Preserve permissions. | ||
var info = stat() | ||
if lstat(pathFsRep, &info) == 0 { | ||
let mode = mode_t(info.st_mode) | ||
} else if errno != ENOENT && errno != ENAMETOOLONG { | ||
throw _NSErrorWithErrno(errno, reading: false, path: path) | ||
} | ||
#endif | ||
let (newFD, path) = try self.makeTemporaryFile(inDirectory: path._nsObject.deletingLastPathComponent) | ||
fd = newFD | ||
auxFilePath = path | ||
#if os(Windows) | ||
let handle: HANDLE = HANDLE(bitPattern: _get_osfhandle(fd))! | ||
var buf = [UInt16](repeating: 0, count: Int(MAX_PATH + 1)) | ||
if GetFinalPathNameByHandleW(handle, &buf, DWORD(MAX_PATH), DWORD(FILE_NAME_NORMALIZED)) == FALSE { | ||
fatalError("GetFinalPathNameByHandle() failed") | ||
} | ||
_wchmod(buf, 0o666) | ||
#else | ||
fchmod(fd, 0o666) | ||
#endif | ||
} else { | ||
var flags = O_WRONLY | O_CREAT | O_TRUNC | ||
if writeOptionsMask.contains(.withoutOverwriting) { | ||
|
@@ -503,9 +558,15 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { | |
if range.length > 0 { | ||
do { | ||
try NSData.write(toFileDescriptor: fd, path: path, buf: buf, length: range.length) | ||
#if os(Windows) | ||
if FlushFileBuffers(HANDLE(bitPattern: _get_osfhandle(fd))) == FALSE { | ||
throw _NSErrorWithErrno(Int32(GetLastError()), reading: false, path: path) | ||
} | ||
#else | ||
if fsync(fd) < 0 { | ||
throw _NSErrorWithErrno(errno, reading: false, path: path) | ||
} | ||
#endif | ||
} catch { | ||
if let auxFilePath = auxFilePath { | ||
try? FileManager.default.removeItem(atPath: auxFilePath) | ||
|
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.
Would this be better as a
#if !canImport(Darwin)
as I dont think it is supported on any other platforms apart from Apple's?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.
That is true. The only other malloc implementation that has this feature iirc is jemalloc.
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.
It would be interesting to be able to use jemalloc on other targets. I know that there are environments running off of jemalloc. I don't think that we have a good way to identify that yet. Perhaps having a macro from the build system
-DHAVE_MALLOC_GOOD_SIZE
?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 swift just needs a
#if hasImported(<some function>)
conditional :)