Skip to content

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

Closed
wants to merge 1 commit into from

Conversation

compnerd
Copy link
Member

Adjust the API usage for the Windows environment.

Adjust the API usage for the Windows environment.
@compnerd
Copy link
Member Author

@swift-ci please test

@compnerd
Copy link
Member Author

@@ -12,7 +12,7 @@

#if DEPLOYMENT_RUNTIME_SWIFT

#if os(Linux)
#if os(Linux) || os(Windows)
Copy link
Contributor

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?

Copy link
Contributor

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.

Copy link
Member Author

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?

Copy link
Contributor

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 :)

@@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldnt this whole function just be replaced with:
return try _NSCreateTemporaryFile(dirPath._nsObject.appendingPathComponent("")
or something like that, they seem to have the same bodies of code expect for computing the initial template.

Copy link
Member Author

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about passing the template as the argument, eg:

internal func _NSCreateTemporaryFile(withTemplate template: String)

There only seems to be one caller of each method so generating the template at the call-site seems to solve the issue.

}
}
#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 {
Copy link
Contributor

Choose a reason for hiding this comment

The 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 FileHandle and eliminate all of the Windows/Unix differences here. It will requires some new internal FileHandle methods (eg a getFilePermissions, setFilePermissions, and a throwable version of synchronizeFile) but I think it should be possible.
makeTemporaryFile could return a FileHandle instead of an fd as well. What do you think?

The helper methods could probably be reused in FileManager later on any

Copy link
Member Author

Choose a reason for hiding this comment

The 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?

Copy link
Contributor

Choose a reason for hiding this comment

The 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 FileHandle.setFilePermissions(mode) method:

open func write(toFile path: String, options writeOptionsMask: WritingOptions = []) throws {
        let fm = FileManager.default

        let fh: FileHandle
        let useAuxiliaryFile = writeOptionsMask.contains(.atomic)
        var auxFilePath: String? = nil
        var mode: mode_t? = nil

        if useAuxiliaryFile {
            if let perms = try? fm.attributesOfItem(atPath: path)[.posixPermissions] as? NSNumber, let m = perms {
                mode = mode_t(m.uintValue)
            }

            let (newFD, path) = try self.makeTemporaryFile(inDirectory: path._nsObject.deletingLastPathComponent)
            fh = FileHandle(fileDescriptor: newFD, closeOnDealloc: true)
            auxFilePath = path
        } else {
            var flags = O_WRONLY | O_CREAT | O_TRUNC
            if writeOptionsMask.contains(.withoutOverwriting) {
                flags |= O_EXCL
            }

            guard let newfh = FileHandle(path: path, flags: flags, createMode: 0o666) else {
                throw _NSErrorWithErrno(errno, reading: false, path: path)
            }
            fh = newfh
        }

        try self.enumerateByteRangesUsingBlockRethrows { (buf, range, stop) in
            if range.length > 0 {
                do {
                    try NSData.write(toFileDescriptor: fh.fileDescriptor, path: path, buf: buf, length: range.length)
                    fh.synchronizeFile()
                } catch {
                    if let auxFilePath = auxFilePath {
                        try? fm.removeItem(atPath: auxFilePath)
                    }
                    throw error
                }
                fh.setFilePermissions(mode) // TODO: Add this as an internal Filehandle function 
            }
        }

        if let auxFilePath = auxFilePath {
            try fm._fileSystemRepresentation(withPath: path, { pathFsRep in
                try fm._fileSystemRepresentation(withPath: auxFilePath, { auxFilePathFsRep in
                    if rename(auxFilePathFsRep, pathFsRep) != 0 {
                        try? FileManager.default.removeItem(atPath: auxFilePath)
                        throw _NSErrorWithErrno(errno, reading: false, path: path)
                    }
                    if let mode = mode {
                        chmod(pathFsRep, mode)
                    }
                })
            })
        }
    }

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can rewrite this in terms of FileHandle, but note that there will still be Windows specific paths here as FileHandle.fileDescriptor is not available on Windows, since it works in terms of HANDLEs. Additionally, a few of the operations that this is performing would need to be moved around (e.g. chmod). The makeTemporaryFile will still require Windows specific handling (FileHandle wants a HANDLE, not a file descriptor).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about moving the two internal NSData.write() functions to be internal methods of FileHandle (its the only caller of them anyway, and it would match _readDataOfLength()) then you should be able to eliminate the fh.fileDescriptor.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, that is far more accessible for developers working on FileHandle. I remember looking at this and getting confused why it was split. @parkera @phausler - wanna chime in?

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ive done a refactor of the FileHandle write paths in #1876

@compnerd
Copy link
Member Author

I believe that all the needed changes here have been consumed. I'm going to go ahead and close this for now, and if we hit anything related to this, we should be able to pull that in then.

@compnerd compnerd closed this Feb 12, 2019
@compnerd compnerd deleted the data-is-breached branch February 12, 2019 23:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants