blob: cea2efa9133eb2780c9c0b450c1fd78f93253b7d [file] [log] [blame]
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
import CoreFoundation
#if os(Windows)
internal func joinPath(prefix: String, suffix: String) -> String {
var pszPath: PWSTR?
_ = prefix.withCString(encodedAs: UTF16.self) { prefix in
_ = suffix.withCString(encodedAs: UTF16.self) { suffix in
PathAllocCombine(prefix, suffix, ULONG(PATHCCH_ALLOW_LONG_PATHS.rawValue), &pszPath)
}
}
let path: String = String(decodingCString: pszPath!, as: UTF16.self)
LocalFree(pszPath)
return path
}
extension FileManager {
internal func _mountedVolumeURLs(includingResourceValuesForKeys propertyKeys: [URLResourceKey]?, options: VolumeEnumerationOptions = []) -> [URL]? {
var urls: [URL] = []
var wszVolumeName: [WCHAR] = Array<WCHAR>(repeating: 0, count: Int(MAX_PATH))
var hVolumes: HANDLE = FindFirstVolumeW(&wszVolumeName, DWORD(wszVolumeName.count))
guard hVolumes != INVALID_HANDLE_VALUE else { return nil }
defer { FindVolumeClose(hVolumes) }
repeat {
var dwCChReturnLength: DWORD = 0
GetVolumePathNamesForVolumeNameW(&wszVolumeName, nil, 0, &dwCChReturnLength)
var wszPathNames: [WCHAR] = Array<WCHAR>(repeating: 0, count: Int(dwCChReturnLength + 1))
if !GetVolumePathNamesForVolumeNameW(&wszVolumeName, &wszPathNames, DWORD(wszPathNames.count), &dwCChReturnLength) {
// TODO(compnerd) handle error
continue
}
var pPath: DWORD = 0
repeat {
let path: String = String(decodingCString: &wszPathNames[Int(pPath)], as: UTF16.self)
if path.length == 0 {
break
}
urls.append(URL(fileURLWithPath: path, isDirectory: true))
pPath += DWORD(path.length + 1)
} while pPath < dwCChReturnLength
} while FindNextVolumeW(hVolumes, &wszVolumeName, DWORD(wszVolumeName.count))
return urls
}
internal func _urls(for directory: SearchPathDirectory, in domainMask: SearchPathDomainMask) -> [URL] {
let domains = _SearchPathDomain.allInSearchOrder(from: domainMask)
var urls: [URL] = []
for domain in domains {
urls.append(contentsOf: windowsURLs(for: directory, in: domain))
}
return urls
}
private class func url(for id: KNOWNFOLDERID) -> URL {
var pszPath: PWSTR?
let hResult: HRESULT = withUnsafePointer(to: id) { id in
SHGetKnownFolderPath(id, DWORD(KF_FLAG_DEFAULT.rawValue), nil, &pszPath)
}
precondition(hResult >= 0, "SHGetKnownFolderpath failed \(GetLastError())")
let url: URL = URL(fileURLWithPath: String(decodingCString: pszPath!, as: UTF16.self), isDirectory: true)
CoTaskMemFree(pszPath)
return url
}
private func windowsURLs(for directory: SearchPathDirectory, in domain: _SearchPathDomain) -> [URL] {
switch directory {
case .autosavedInformationDirectory:
// FIXME(compnerd) where should this go?
return []
case .desktopDirectory:
guard domain == .user else { return [] }
return [FileManager.url(for: FOLDERID_Desktop)]
case .documentDirectory:
guard domain == .user else { return [] }
return [FileManager.url(for: FOLDERID_Documents)]
case .cachesDirectory:
guard domain == .user else { return [] }
return [URL(fileURLWithPath: NSTemporaryDirectory())]
case .applicationSupportDirectory:
switch domain {
case .local:
return [FileManager.url(for: FOLDERID_ProgramData)]
case .user:
return [FileManager.url(for: FOLDERID_LocalAppData)]
default:
return []
}
case .downloadsDirectory:
guard domain == .user else { return [] }
return [FileManager.url(for: FOLDERID_Downloads)]
case .userDirectory:
guard domain == .user else { return [] }
return [FileManager.url(for: FOLDERID_UserProfiles)]
case .moviesDirectory:
guard domain == .user else { return [] }
return [FileManager.url(for: FOLDERID_Videos)]
case .musicDirectory:
guard domain == .user else { return [] }
return [FileManager.url(for: FOLDERID_Music)]
case .picturesDirectory:
guard domain == .user else { return [] }
return [FileManager.url(for: FOLDERID_PicturesLibrary)]
case .sharedPublicDirectory:
guard domain == .user else { return [] }
return [FileManager.url(for: FOLDERID_Public)]
case .trashDirectory:
guard domain == .user else { return [] }
return [FileManager.url(for: FOLDERID_RecycleBinFolder)]
// None of these are supported outside of Darwin:
case .applicationDirectory,
.demoApplicationDirectory,
.developerApplicationDirectory,
.adminApplicationDirectory,
.libraryDirectory,
.developerDirectory,
.documentationDirectory,
.coreServiceDirectory,
.inputMethodsDirectory,
.preferencePanesDirectory,
.applicationScriptsDirectory,
.allApplicationsDirectory,
.allLibrariesDirectory,
.printerDescriptionDirectory,
.itemReplacementDirectory:
return []
}
}
internal func _createDirectory(atPath path: String, withIntermediateDirectories createIntermediates: Bool, attributes: [FileAttributeKey : Any]? = [:]) throws {
if createIntermediates {
var isDir: ObjCBool = false
if fileExists(atPath: path, isDirectory: &isDir) {
guard isDir.boolValue else { throw _NSErrorWithErrno(EEXIST, reading: false, path: path) }
return
}
let parent = path._nsObject.deletingLastPathComponent
if !parent.isEmpty && !fileExists(atPath: parent, isDirectory: &isDir) {
try createDirectory(atPath: parent, withIntermediateDirectories: true, attributes: attributes)
}
}
var saAttributes: SECURITY_ATTRIBUTES =
SECURITY_ATTRIBUTES(nLength: DWORD(MemoryLayout<SECURITY_ATTRIBUTES>.size),
lpSecurityDescriptor: nil,
bInheritHandle: false)
let psaAttributes: UnsafeMutablePointer<SECURITY_ATTRIBUTES> =
UnsafeMutablePointer<SECURITY_ATTRIBUTES>(&saAttributes)
try path.withCString(encodedAs: UTF16.self) {
if !CreateDirectoryW($0, psaAttributes) {
// FIXME(compnerd) pass along path
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
}
}
if let attr = attributes {
try self.setAttributes(attr, ofItemAtPath: path)
}
}
internal func _contentsOfDir(atPath path: String, _ closure: (String, Int32) throws -> () ) throws {
guard path != "" else {
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadInvalidFileName.rawValue, userInfo: [NSFilePathErrorKey : NSString(path)])
}
try (path + "\\*").withCString(encodedAs: UTF16.self) {
var ffd: WIN32_FIND_DATAW = WIN32_FIND_DATAW()
let hDirectory: HANDLE = FindFirstFileW($0, &ffd)
if hDirectory == INVALID_HANDLE_VALUE {
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
}
defer { FindClose(hDirectory) }
repeat {
let path: String = withUnsafePointer(to: &ffd.cFileName) {
$0.withMemoryRebound(to: UInt16.self, capacity: MemoryLayout.size(ofValue: $0) / MemoryLayout<WCHAR>.size) {
String(decodingCString: $0, as: UTF16.self)
}
}
if path != "." && path != ".." {
try closure(path.standardizingPath, Int32(ffd.dwFileAttributes))
}
} while FindNextFileW(hDirectory, &ffd)
}
}
internal func _subpathsOfDirectory(atPath path: String) throws -> [String] {
var contents: [String] = []
try _contentsOfDir(atPath: path, { (entryName, entryType) throws in
contents.append(entryName)
if entryType & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY
&& entryType & FILE_ATTRIBUTE_REPARSE_POINT != FILE_ATTRIBUTE_REPARSE_POINT {
let subPath: String = joinPath(prefix: path, suffix: entryName)
let entries = try subpathsOfDirectory(atPath: subPath)
contents.append(contentsOf: entries.map { joinPath(prefix: entryName, suffix: $0).standardizingPath })
}
})
return contents
}
internal func windowsFileAttributes(atPath path: String) throws -> WIN32_FILE_ATTRIBUTE_DATA {
var faAttributes: WIN32_FILE_ATTRIBUTE_DATA = WIN32_FILE_ATTRIBUTE_DATA()
return try path.withCString(encodedAs: UTF16.self) {
if !GetFileAttributesExW($0, GetFileExInfoStandard, &faAttributes) {
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
}
return faAttributes
}
}
internal func _attributesOfFileSystemIncludingBlockSize(forPath path: String) throws -> (attributes: [FileAttributeKey : Any], blockSize: UInt64?) {
return (attributes: try _attributesOfFileSystem(forPath: path), blockSize: nil)
}
internal func _attributesOfFileSystem(forPath path: String) throws -> [FileAttributeKey : Any] {
var result: [FileAttributeKey:Any] = [:]
try path.withCString(encodedAs: UTF16.self) {
let dwLength: DWORD = GetFullPathNameW($0, 0, nil, nil)
guard dwLength != 0 else {
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
}
var szVolumePath: [WCHAR] = Array<WCHAR>(repeating: 0, count: Int(dwLength + 1))
guard GetVolumePathNameW($0, &szVolumePath, dwLength) else {
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
}
var liTotal: ULARGE_INTEGER = ULARGE_INTEGER()
var liFree: ULARGE_INTEGER = ULARGE_INTEGER()
guard GetDiskFreeSpaceExW(&szVolumePath, nil, &liTotal, &liFree) else {
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
}
var volumeSerialNumber: DWORD = 0
guard GetVolumeInformationW(&szVolumePath, nil, 0, &volumeSerialNumber, nil, nil, nil, 0) else {
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
}
result[.systemSize] = NSNumber(value: liTotal.QuadPart)
result[.systemFreeSize] = NSNumber(value: liFree.QuadPart)
result[.systemNumber] = NSNumber(value: volumeSerialNumber)
// FIXME(compnerd): what about .systemNodes, .systemFreeNodes?
}
return result
}
internal func _createSymbolicLink(atPath path: String, withDestinationPath destPath: String, isDirectory: Bool? = nil) throws {
var dwFlags = DWORD(SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)
// If destPath is relative, we should look for it relative to `path`, not our current working directory
switch isDirectory {
case .some(true):
dwFlags |= DWORD(SYMBOLIC_LINK_FLAG_DIRECTORY)
case .some(false):
break;
case .none:
let resolvedDest = destPath.isAbsolutePath
? destPath
: try joinPath(prefix: path.deletingLastPathComponent, suffix: destPath)
guard let faAttributes = try? windowsFileAttributes(atPath: resolvedDest) else {
// Note: windowsfileAttributes will throw if the destPath is not found.
// Since on Windows, you are required to know the type of the symlink
// target (file or directory) during creation, and assuming one or the
// other doesn't make a lot of sense, we allow it to throw, thus
// disallowing the creation of broken symlinks on Windows is the target
// is of unknown type.
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path, resolvedDest])
}
if faAttributes.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY) == DWORD(FILE_ATTRIBUTE_DIRECTORY) {
dwFlags |= DWORD(SYMBOLIC_LINK_FLAG_DIRECTORY)
}
}
try path.withCString(encodedAs: UTF16.self) { name in
try destPath.withCString(encodedAs: UTF16.self) { dest in
guard CreateSymbolicLinkW(name, dest, dwFlags) != 0 else {
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path, destPath])
}
}
}
}
internal func _destinationOfSymbolicLink(atPath path: String) throws -> String {
let faAttributes = try windowsFileAttributes(atPath: path)
guard faAttributes.dwFileAttributes & DWORD(FILE_ATTRIBUTE_REPARSE_POINT) == DWORD(FILE_ATTRIBUTE_REPARSE_POINT) else {
throw _NSErrorWithWindowsError(DWORD(ERROR_BAD_ARGUMENTS), reading: false)
}
let handle = path.withCString(encodedAs: UTF16.self) { symlink in
CreateFileW(symlink, GENERIC_READ, DWORD(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
nil, DWORD(OPEN_EXISTING), DWORD(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS),
nil)
}
guard handle != INVALID_HANDLE_VALUE else {
throw _NSErrorWithWindowsError(GetLastError(), reading: true)
}
defer { CloseHandle(handle) }
// Since REPARSE_DATA_BUFFER ends with an arbitrarily long buffer, we
// have to manually get the path buffer out of it since binding it to a
// type will truncate the path buffer.
//
// 20 is the sum of the offsets of:
// ULONG ReparseTag
// USHORT ReparseDataLength
// USHORT Reserved
// USHORT SubstituteNameOffset
// USHORT SubstituteNameLength
// USHORT PrintNameOffset
// USHORT PrintNameLength
// ULONG Flags (Symlink only)
let symLinkPathBufferOffset = 20 // 4 + 2 + 2 + 2 + 2 + 2 + 2 + 4
let mountPointPathBufferOffset = 16 // 4 + 2 + 2 + 2 + 2 + 2 + 2
let buff = UnsafeMutableRawBufferPointer.allocate(byteCount: Int(MAXIMUM_REPARSE_DATA_BUFFER_SIZE),
alignment: 8)
guard let buffBase = buff.baseAddress else {
throw _NSErrorWithWindowsError(DWORD(ERROR_INVALID_DATA), reading: false)
}
var bytesWritten: DWORD = 0
guard DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, nil, 0,
buffBase, DWORD(MAXIMUM_REPARSE_DATA_BUFFER_SIZE),
&bytesWritten, nil) else {
throw _NSErrorWithWindowsError(GetLastError(), reading: true)
}
guard bytesWritten >= MemoryLayout<REPARSE_DATA_BUFFER>.size else {
throw _NSErrorWithWindowsError(DWORD(ERROR_INVALID_DATA), reading: false)
}
let bound = buff.bindMemory(to: REPARSE_DATA_BUFFER.self)
guard let reparseDataBuffer = bound.first else {
throw _NSErrorWithWindowsError(DWORD(ERROR_INVALID_DATA), reading: false)
}
guard reparseDataBuffer.ReparseTag == IO_REPARSE_TAG_SYMLINK
|| reparseDataBuffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT else {
throw _NSErrorWithWindowsError(DWORD(ERROR_BAD_ARGUMENTS), reading: false)
}
let pathBufferPtr: UnsafeMutableRawPointer
let substituteNameBytes: Int
let substituteNameOffset: Int
switch reparseDataBuffer.ReparseTag {
case IO_REPARSE_TAG_SYMLINK:
pathBufferPtr = buffBase + symLinkPathBufferOffset
substituteNameBytes = Int(reparseDataBuffer.SymbolicLinkReparseBuffer.SubstituteNameLength)
substituteNameOffset = Int(reparseDataBuffer.SymbolicLinkReparseBuffer.SubstituteNameOffset)
case IO_REPARSE_TAG_MOUNT_POINT:
pathBufferPtr = buffBase + mountPointPathBufferOffset
substituteNameBytes = Int(reparseDataBuffer.MountPointReparseBuffer.SubstituteNameLength)
substituteNameOffset = Int(reparseDataBuffer.MountPointReparseBuffer.SubstituteNameOffset)
default:
throw _NSErrorWithWindowsError(DWORD(ERROR_BAD_ARGUMENTS), reading: false)
}
guard substituteNameBytes + substituteNameOffset <= bytesWritten else {
throw _NSErrorWithWindowsError(DWORD(ERROR_INVALID_DATA), reading: false)
}
let substituteNameBuff = Data(bytes: pathBufferPtr + substituteNameOffset, count: substituteNameBytes)
guard let substitutePath = String(data: substituteNameBuff, encoding: .utf16LittleEndian) else {
throw _NSErrorWithWindowsError(DWORD(ERROR_INVALID_DATA), reading: false)
}
return substitutePath
}
internal func _canonicalizedPath(toFileAtPath path: String) throws -> String {
var hFile: HANDLE = INVALID_HANDLE_VALUE
path.withCString(encodedAs: UTF16.self) { link in
// BACKUP_SEMANTICS are (confusingly) required in order to receive a
// handle to a directory
hFile = CreateFileW(link, 0, DWORD(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
nil, DWORD(OPEN_EXISTING), DWORD(FILE_FLAG_BACKUP_SEMANTICS),
nil)
}
guard hFile != INVALID_HANDLE_VALUE else {
return try path.withCString(encodedAs: UTF16.self) {
var dwLength = GetFullPathNameW($0, 0, nil, nil)
var szPath = Array<WCHAR>(repeating: 0, count: Int(dwLength + 1))
dwLength = GetFullPathNameW($0, DWORD(szPath.count), &szPath, nil)
guard dwLength > 0 && dwLength <= szPath.count else {
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
}
return String(decodingCString: szPath, as: UTF16.self)
}
}
defer { CloseHandle(hFile) }
let dwLength: DWORD = GetFinalPathNameByHandleW(hFile, nil, 0, DWORD(FILE_NAME_NORMALIZED))
var szPath: [WCHAR] = Array<WCHAR>(repeating: 0, count: Int(dwLength + 1))
GetFinalPathNameByHandleW(hFile, &szPath, dwLength, DWORD(FILE_NAME_NORMALIZED))
return String(decodingCString: &szPath, as: UTF16.self)
}
internal func _copyRegularFile(atPath srcPath: String, toPath dstPath: String, variant: String = "Copy") throws {
try srcPath.withCString(encodedAs: UTF16.self) { src in
try dstPath.withCString(encodedAs: UTF16.self) { dst in
if !CopyFileW(src, dst, false) {
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [srcPath, dstPath])
}
}
}
}
internal func _copySymlink(atPath srcPath: String, toPath dstPath: String, variant: String = "Copy") throws {
let faAttributes: WIN32_FILE_ATTRIBUTE_DATA = try windowsFileAttributes(atPath: srcPath)
guard faAttributes.dwFileAttributes & DWORD(FILE_ATTRIBUTE_REPARSE_POINT) == DWORD(FILE_ATTRIBUTE_REPARSE_POINT) else {
throw _NSErrorWithErrno(EINVAL, reading: true, path: srcPath, extraUserInfo: extraErrorInfo(srcPath: srcPath, dstPath: dstPath, userVariant: variant))
}
let destination = try destinationOfSymbolicLink(atPath: srcPath)
let isDir = try windowsFileAttributes(atPath: srcPath).dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY) == DWORD(FILE_ATTRIBUTE_DIRECTORY)
if fileExists(atPath: dstPath) {
try removeItem(atPath: dstPath)
}
try _createSymbolicLink(atPath: dstPath, withDestinationPath: destination, isDirectory: isDir)
}
internal func _copyOrLinkDirectoryHelper(atPath srcPath: String, toPath dstPath: String, variant: String = "Copy", _ body: (String, String, FileAttributeType) throws -> ()) throws {
let faAttributes = try windowsFileAttributes(atPath: srcPath)
var fileType = FileAttributeType(attributes: faAttributes, atPath: srcPath)
if fileType == .typeDirectory {
try createDirectory(atPath: dstPath, withIntermediateDirectories: false, attributes: nil)
guard let enumerator = enumerator(atPath: srcPath) else {
throw _NSErrorWithErrno(ENOENT, reading: true, path: srcPath)
}
while let item = enumerator.nextObject() as? String {
let src = joinPath(prefix: srcPath, suffix: item)
let dst = joinPath(prefix: dstPath, suffix: item)
let faAttributes = try windowsFileAttributes(atPath: src)
fileType = FileAttributeType(attributes: faAttributes, atPath: srcPath)
if fileType == .typeDirectory {
try createDirectory(atPath: dst, withIntermediateDirectories: false, attributes: nil)
} else {
try body(src, dst, fileType)
}
}
} else {
try body(srcPath, dstPath, fileType)
}
}
internal func _moveItem(atPath srcPath: String, toPath dstPath: String, isURL: Bool) throws {
guard shouldMoveItemAtPath(srcPath, toPath: dstPath, isURL: isURL) else {
return
}
guard !self.fileExists(atPath: dstPath) else {
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileWriteFileExists.rawValue, userInfo: [NSFilePathErrorKey : NSString(dstPath)])
}
try srcPath.withCString(encodedAs: UTF16.self) { src in
try dstPath.withCString(encodedAs: UTF16.self) { dst in
if !MoveFileExW(src, dst, DWORD(MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH)) {
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [srcPath, dstPath])
}
}
}
}
internal func _linkItem(atPath srcPath: String, toPath dstPath: String, isURL: Bool) throws {
try _copyOrLinkDirectoryHelper(atPath: srcPath, toPath: dstPath) { (srcPath, dstPath, fileType) in
guard shouldLinkItemAtPath(srcPath, toPath: dstPath, isURL: isURL) else {
return
}
do {
switch fileType {
case .typeRegular:
try srcPath.withCString(encodedAs: UTF16.self) { src in
try dstPath.withCString(encodedAs: UTF16.self) { dst in
if !CreateHardLinkW(dst, src, nil) {
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [srcPath, dstPath])
}
}
}
case .typeSymbolicLink:
try _copySymlink(atPath: srcPath, toPath: dstPath)
default:
break
}
} catch {
if !shouldProceedAfterError(error, linkingItemAtPath: srcPath, toPath: dstPath, isURL: isURL) {
throw error
}
}
}
}
internal func _removeItem(atPath path: String, isURL: Bool, alreadyConfirmed: Bool = false) throws {
guard alreadyConfirmed || shouldRemoveItemAtPath(path, isURL: isURL) else {
return
}
guard path != "" else {
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadInvalidFileName.rawValue, userInfo: [NSFilePathErrorKey : NSString(path)])
}
let url = URL(fileURLWithPath: path)
var fsrBuf: [WCHAR] = Array<WCHAR>(repeating: 0, count: Int(MAX_PATH))
_CFURLGetWideFileSystemRepresentation(url._cfObject, false, &fsrBuf, Int(MAX_PATH))
let length = wcsnlen_s(&fsrBuf, fsrBuf.count)
let fsrPath = String(utf16CodeUnits: &fsrBuf, count: length)
let faAttributes: WIN32_FILE_ATTRIBUTE_DATA
do {
faAttributes = try windowsFileAttributes(atPath: fsrPath)
} catch {
// removeItem on POSIX throws fileNoSuchFile rather than
// fileReadNoSuchFile that windowsFileAttributes will
// throw if it doesn't find the file.
if (error as NSError).code == CocoaError.fileReadNoSuchFile.rawValue {
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
} else {
throw error
}
}
if faAttributes.dwFileAttributes & DWORD(FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY {
let readableAttributes = faAttributes.dwFileAttributes & DWORD(bitPattern: ~FILE_ATTRIBUTE_READONLY)
guard fsrPath.withCString(encodedAs: UTF16.self, { SetFileAttributesW($0, readableAttributes) }) else {
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
}
}
if faAttributes.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY) == 0 {
if !fsrPath.withCString(encodedAs: UTF16.self, DeleteFileW) {
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
}
return
}
var dirStack = [fsrPath]
var itemPath = ""
while let currentDir = dirStack.popLast() {
do {
itemPath = currentDir
guard alreadyConfirmed || shouldRemoveItemAtPath(itemPath, isURL: isURL) else {
continue
}
guard !itemPath.withCString(encodedAs: UTF16.self, RemoveDirectoryW) else {
continue
}
guard GetLastError() == ERROR_DIR_NOT_EMPTY else {
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [itemPath])
}
dirStack.append(itemPath)
var ffd: WIN32_FIND_DATAW = WIN32_FIND_DATAW()
let h: HANDLE = (itemPath + "\\*").withCString(encodedAs: UTF16.self, {
FindFirstFileW($0, &ffd)
})
guard h != INVALID_HANDLE_VALUE else {
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [itemPath])
}
defer { FindClose(h) }
repeat {
let fileArr = Array<WCHAR>(
UnsafeBufferPointer(start: &ffd.cFileName.0,
count: MemoryLayout.size(ofValue: ffd.cFileName)))
let file = String(decodingCString: fileArr, as: UTF16.self)
itemPath = "\(currentDir)\\\(file)"
if ffd.dwFileAttributes & DWORD(FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY {
let readableAttributes = ffd.dwFileAttributes & DWORD(bitPattern: ~FILE_ATTRIBUTE_READONLY)
guard itemPath.withCString(encodedAs: UTF16.self, { SetFileAttributesW($0, readableAttributes) }) else {
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [file])
}
}
if (ffd.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY) != 0) {
if file != "." && file != ".." {
dirStack.append(itemPath)
}
} else {
guard alreadyConfirmed || shouldRemoveItemAtPath(itemPath, isURL: isURL) else {
continue
}
if !itemPath.withCString(encodedAs: UTF16.self, DeleteFileW) {
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [file])
}
}
} while FindNextFileW(h, &ffd)
} catch {
if !shouldProceedAfterError(error, removingItemAtPath: itemPath, isURL: isURL) {
throw error
}
}
}
}
internal func _currentDirectoryPath() -> String {
let dwLength: DWORD = GetCurrentDirectoryW(0, nil)
var szDirectory: [WCHAR] = Array<WCHAR>(repeating: 0, count: Int(dwLength + 1))
GetCurrentDirectoryW(dwLength, &szDirectory)
return String(decodingCString: &szDirectory, as: UTF16.self)
}
@discardableResult
internal func _changeCurrentDirectoryPath(_ path: String) -> Bool {
return path.withCString(encodedAs: UTF16.self) { SetCurrentDirectoryW($0) }
}
internal func _fileExists(atPath path: String, isDirectory: UnsafeMutablePointer<ObjCBool>?) -> Bool {
var faAttributes: WIN32_FILE_ATTRIBUTE_DATA = WIN32_FILE_ATTRIBUTE_DATA()
do { faAttributes = try windowsFileAttributes(atPath: path) } catch { return false }
if faAttributes.dwFileAttributes & DWORD(FILE_ATTRIBUTE_REPARSE_POINT) == DWORD(FILE_ATTRIBUTE_REPARSE_POINT) {
do {
let contents = try destinationOfSymbolicLink(atPath: path)
let resolvedPath = contents.isAbsolutePath
? contents
: joinPath(prefix: path.deletingLastPathComponent, suffix: contents)
try faAttributes = windowsFileAttributes(atPath: resolvedPath)
} catch {
return false
}
}
if let isDirectory = isDirectory {
isDirectory.pointee = ObjCBool(faAttributes.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY) == DWORD(FILE_ATTRIBUTE_DIRECTORY))
}
return true
}
internal func _isReadableFile(atPath path: String) -> Bool {
do { let _ = try windowsFileAttributes(atPath: path) } catch { return false }
return true
}
internal func _isWritableFile(atPath path: String) -> Bool {
guard let faAttributes: WIN32_FILE_ATTRIBUTE_DATA = try? windowsFileAttributes(atPath: path) else { return false }
return faAttributes.dwFileAttributes & DWORD(FILE_ATTRIBUTE_READONLY) != DWORD(FILE_ATTRIBUTE_READONLY)
}
internal func _isExecutableFile(atPath path: String) -> Bool {
var isDirectory: ObjCBool = false
guard fileExists(atPath: path, isDirectory: &isDirectory) else { return false }
return !isDirectory.boolValue && _isReadableFile(atPath: path)
}
internal func _isDeletableFile(atPath path: String) -> Bool {
guard path != "" else { return true }
// Get the parent directory of supplied path
let parent = path._nsObject.deletingLastPathComponent
var faAttributes: WIN32_FILE_ATTRIBUTE_DATA = WIN32_FILE_ATTRIBUTE_DATA()
do { faAttributes = try windowsFileAttributes(atPath: parent) } catch { return false }
if faAttributes.dwFileAttributes & DWORD(FILE_ATTRIBUTE_READONLY) == DWORD(FILE_ATTRIBUTE_READONLY) {
return false
}
do { faAttributes = try windowsFileAttributes(atPath: path) } catch { return false }
if faAttributes.dwFileAttributes & DWORD(FILE_ATTRIBUTE_READONLY) == DWORD(FILE_ATTRIBUTE_READONLY) {
return false
}
return true
}
internal func _lstatFile(atPath path: String, withFileSystemRepresentation fsRep: UnsafePointer<Int8>? = nil) throws -> stat {
let _fsRep: UnsafePointer<Int8>
if fsRep == nil {
_fsRep = try __fileSystemRepresentation(withPath: path)
} else {
_fsRep = fsRep!
}
defer {
if fsRep == nil { _fsRep.deallocate() }
}
var statInfo = stat()
let h = path.withCString(encodedAs: UTF16.self) {
CreateFileW(/*lpFileName=*/$0,
/*dwDesiredAccess=*/DWORD(0),
/*dwShareMode=*/DWORD(FILE_SHARE_READ),
/*lpSecurityAttributes=*/nil,
/*dwCreationDisposition=*/DWORD(OPEN_EXISTING),
/*dwFlagsAndAttributes=*/DWORD(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS),
/*hTemplateFile=*/nil)
}
if h == INVALID_HANDLE_VALUE {
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
}
var info: BY_HANDLE_FILE_INFORMATION = BY_HANDLE_FILE_INFORMATION()
GetFileInformationByHandle(h, &info)
// Group id is always 0 on Windows
statInfo.st_gid = 0
statInfo.st_atime = info.ftLastAccessTime.time_t
statInfo.st_ctime = info.ftCreationTime.time_t
statInfo.st_dev = info.dwVolumeSerialNumber
// inodes have meaning on FAT/HPFS/NTFS
statInfo.st_ino = 0
statInfo.st_rdev = info.dwVolumeSerialNumber
let isReparsePoint = info.dwFileAttributes & DWORD(FILE_ATTRIBUTE_REPARSE_POINT) != 0
let isDir = info.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY) != 0
let fileMode = isDir ? _S_IFDIR : _S_IFREG
// On a symlink to a directory, Windows sets both the REPARSE_POINT and
// DIRECTORY attributes. Since Windows doesn't provide S_IFLNK and we
// want unix style "symlinks to directories are not directories
// themselves, we say symlinks are regular files
statInfo.st_mode = UInt16(isReparsePoint ? _S_IFREG : fileMode)
let isReadOnly = info.dwFileAttributes & DWORD(FILE_ATTRIBUTE_READONLY) != 0
statInfo.st_mode |= UInt16(isReadOnly ? _S_IREAD : (_S_IREAD | _S_IWRITE))
statInfo.st_mode |= UInt16(_S_IEXEC)
statInfo.st_mtime = info.ftLastWriteTime.time_t
statInfo.st_nlink = Int16(info.nNumberOfLinks)
if info.nFileSizeHigh != 0 {
throw _NSErrorWithErrno(EOVERFLOW, reading: true, path: path)
}
statInfo.st_size = Int32(info.nFileSizeLow)
// Uid is always 0 on Windows systems
statInfo.st_uid = 0
CloseHandle(h)
return statInfo
}
internal func _contentsEqual(atPath path1: String, andPath path2: String) -> Bool {
guard let path1Handle = path1.withCString(encodedAs: UTF16.self, {
CreateFileW($0, GENERIC_READ, DWORD(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), nil,
DWORD(OPEN_EXISTING), DWORD(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS), nil)
}), path1Handle != INVALID_HANDLE_VALUE else {
return false
}
defer { CloseHandle(path1Handle) }
guard let path2Handle = path2.withCString(encodedAs: UTF16.self, {
CreateFileW($0, GENERIC_READ, DWORD(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), nil,
DWORD(OPEN_EXISTING), DWORD(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS), nil)
}), path2Handle != INVALID_HANDLE_VALUE else {
return false
}
defer { CloseHandle(path2Handle) }
let file1Type = GetFileType(path1Handle)
guard GetLastError() == NO_ERROR else {
return false
}
let file2Type = GetFileType(path2Handle)
guard GetLastError() == NO_ERROR else {
return false
}
guard file1Type == FILE_TYPE_DISK, file2Type == FILE_TYPE_DISK else {
return false
}
var path1FileInfo = BY_HANDLE_FILE_INFORMATION()
var path2FileInfo = BY_HANDLE_FILE_INFORMATION()
guard GetFileInformationByHandle(path1Handle, &path1FileInfo),
GetFileInformationByHandle(path2Handle, &path2FileInfo) else {
return false
}
// If both paths point to the same volume/filenumber or they are both zero length
// then they are considered equal
if path1FileInfo.nFileIndexHigh == path2FileInfo.nFileIndexHigh
&& path1FileInfo.nFileIndexLow == path2FileInfo.nFileIndexLow
&& path1FileInfo.dwVolumeSerialNumber == path2FileInfo.dwVolumeSerialNumber {
return true
}
let path1Attrs = path1FileInfo.dwFileAttributes
let path2Attrs = path2FileInfo.dwFileAttributes
if path1Attrs & DWORD(FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT
|| path2Attrs & DWORD(FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT {
guard path1Attrs & DWORD(FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT
&& path2Attrs & DWORD(FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT else {
return false
}
guard let pathDest1 = try? _destinationOfSymbolicLink(atPath: path1),
let pathDest2 = try? _destinationOfSymbolicLink(atPath: path2) else {
return false
}
return pathDest1 == pathDest2
} else if DWORD(FILE_ATTRIBUTE_DIRECTORY) & path1Attrs == DWORD(FILE_ATTRIBUTE_DIRECTORY)
|| DWORD(FILE_ATTRIBUTE_DIRECTORY) & path2Attrs == DWORD(FILE_ATTRIBUTE_DIRECTORY) {
guard DWORD(FILE_ATTRIBUTE_DIRECTORY) & path1Attrs == DWORD(FILE_ATTRIBUTE_DIRECTORY)
&& DWORD(FILE_ATTRIBUTE_DIRECTORY) & path2Attrs == FILE_ATTRIBUTE_DIRECTORY else {
return false
}
return _compareDirectories(atPath: path1, andPath: path2)
} else {
if path1FileInfo.nFileSizeHigh == 0 && path1FileInfo.nFileSizeLow == 0
&& path2FileInfo.nFileSizeHigh == 0 && path2FileInfo.nFileSizeLow == 0 {
return true
}
let path1Fsr = fileSystemRepresentation(withPath: path1)
defer { path1Fsr.deallocate() }
let path2Fsr = fileSystemRepresentation(withPath: path2)
defer { path2Fsr.deallocate() }
return _compareFiles(withFileSystemRepresentation: path1Fsr,
andFileSystemRepresentation: path2Fsr,
size: (Int64(path1FileInfo.nFileSizeHigh) << 32) | Int64(path1FileInfo.nFileSizeLow),
bufSize: 0x1000)
}
}
internal func _appendSymlinkDestination(_ dest: String, toPath: String) -> String {
var isAbsolutePath: Bool = false
dest.withCString(encodedAs: UTF16.self) {
isAbsolutePath = !PathIsRelativeW($0)
}
if isAbsolutePath {
return dest
}
let temp = toPath._bridgeToObjectiveC().deletingLastPathComponent
return temp._bridgeToObjectiveC().appendingPathComponent(dest)
}
internal func _updateTimes(atPath path: String,
withFileSystemRepresentation fsr: UnsafePointer<Int8>,
creationTime: Date? = nil,
accessTime: Date? = nil,
modificationTime: Date? = nil) throws {
let stat = try _lstatFile(atPath: path, withFileSystemRepresentation: fsr)
var atime: FILETIME =
FILETIME(from: time_t((accessTime ?? stat.lastAccessDate).timeIntervalSince1970))
var mtime: FILETIME =
FILETIME(from: time_t((modificationTime ?? stat.lastModificationDate).timeIntervalSince1970))
let hFile: HANDLE = String(utf8String: fsr)!.withCString(encodedAs: UTF16.self) {
CreateFileW($0, DWORD(GENERIC_WRITE), DWORD(FILE_SHARE_WRITE),
nil, DWORD(OPEN_EXISTING), 0, nil)
}
if hFile == INVALID_HANDLE_VALUE {
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
}
defer { CloseHandle(hFile) }
if !SetFileTime(hFile, nil, &atime, &mtime) {
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
}
}
internal class NSURLDirectoryEnumerator : DirectoryEnumerator {
var _options : FileManager.DirectoryEnumerationOptions
var _errorHandler : ((URL, Error) -> Bool)?
var _stack: [URL]
var _lastReturned: URL
var _rootDepth : Int
init(url: URL, options: FileManager.DirectoryEnumerationOptions, errorHandler: (/* @escaping */ (URL, Error) -> Bool)?) {
_options = options
_errorHandler = errorHandler
_stack = []
_rootDepth = url.pathComponents.count
_lastReturned = url
}
override func nextObject() -> Any? {
func firstValidItem() -> URL? {
while let url = _stack.popLast() {
if !FileManager.default.fileExists(atPath: url.path) {
guard let handler = _errorHandler,
handler(url, _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [url.path]))
else { return nil }
continue
}
_lastReturned = url
return _lastReturned
}
return nil
}
// If we most recently returned a directory, decend into it
guard let attrs = try? FileManager.default.windowsFileAttributes(atPath: _lastReturned.path) else {
guard let handler = _errorHandler,
handler(_lastReturned, _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [_lastReturned.path]))
else { return nil }
return firstValidItem()
}
let isDir = attrs.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY) == DWORD(FILE_ATTRIBUTE_DIRECTORY)
&& attrs.dwFileAttributes & DWORD(FILE_ATTRIBUTE_REPARSE_POINT) == 0
if isDir && (level == 0 || !_options.contains(.skipsSubdirectoryDescendants)) {
var ffd = WIN32_FIND_DATAW()
let dirPath = joinPath(prefix: _lastReturned.path, suffix: "*")
let handle = dirPath.withCString(encodedAs: UTF16.self) {
FindFirstFileW($0, &ffd)
}
guard handle != INVALID_HANDLE_VALUE else { return firstValidItem() }
defer { FindClose(handle) }
repeat {
let fileArr = Array<WCHAR>(
UnsafeBufferPointer(start: &ffd.cFileName.0,
count: MemoryLayout.size(ofValue: ffd.cFileName)))
let file = String(decodingCString: fileArr, as: UTF16.self)
if file != "." && file != ".."
&& (!_options.contains(.skipsHiddenFiles)
|| (ffd.dwFileAttributes & DWORD(FILE_ATTRIBUTE_HIDDEN) == 0)) {
let relative = URL(fileURLWithPath: file, relativeTo: _lastReturned)
_stack.append(relative)
}
} while FindNextFileW(handle, &ffd)
}
return firstValidItem()
}
override var level: Int {
return _lastReturned.pathComponents.count - _rootDepth
}
override func skipDescendants() {
_options.insert(.skipsSubdirectoryDescendants)
}
override var directoryAttributes : [FileAttributeKey : Any]? {
return nil
}
override var fileAttributes: [FileAttributeKey : Any]? {
return nil
}
}
}
extension FileManager.NSPathDirectoryEnumerator {
internal func _nextObject() -> Any? {
guard let url = innerEnumerator.nextObject() as? URL else { return nil }
var relativePath: [WCHAR] = Array<WCHAR>(repeating: 0, count: Int(MAX_PATH))
guard baseURL._withUnsafeWideFileSystemRepresentation({ baseUrlFsr in
url._withUnsafeWideFileSystemRepresentation { urlFsr in
let fromAttrs = GetFileAttributesW(baseUrlFsr)
let toAttrs = GetFileAttributesW(urlFsr)
guard fromAttrs != INVALID_FILE_ATTRIBUTES, toAttrs != INVALID_FILE_ATTRIBUTES else {
return false
}
return PathRelativePathToW(&relativePath, baseUrlFsr, fromAttrs, urlFsr, toAttrs)
}
}) else { return nil }
let path = String(decodingCString: &relativePath, as: UTF16.self)
// Drop the leading ".\" from the path
_currentItemPath = String(path.dropFirst(2))
return _currentItemPath
}
}
#endif