Skip to content

Commit 4d05835

Browse files
authored
Merge pull request swiftlang#1 from GunGraveKoga/FileManager-WideChar-API
Support Unicode paths on Windows OS
2 parents 723d02d + 6b073bc commit 4d05835

File tree

1 file changed

+178
-21
lines changed

1 file changed

+178
-21
lines changed

Foundation/FileManager.swift

Lines changed: 178 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,9 @@ open class FileManager : NSObject {
137137
let modeT = number.uint32Value
138138
#endif
139139
#if CAN_IMPORT_MINGWCRT
140-
if chmod(path, Int32(mode_t(modeT))) != 0 {
140+
if path.withCString(encodedAs:UTF16.self, {(pointer) -> Bool in
141+
return _wchmod(pointer, Int32(mode_t(modeT))) != 0
142+
}) {
141143
fatalError("errno \(errno)")
142144
}
143145
#else
@@ -164,7 +166,9 @@ open class FileManager : NSObject {
164166
try createDirectory(atPath: parent, withIntermediateDirectories: true, attributes: attributes)
165167
}
166168
#if CAN_IMPORT_MINGWCRT
167-
let mkdir_failed = _mkdir(path) != 0
169+
let mkdir_failed = path.withCString(encodedAs:UTF16.self, {pointer in
170+
return _wmkdir(pointer) != 0
171+
})
168172
#else
169173
let mkdir_failed = mkdir(pathh, S_IRWXU | S_IRWXG | S_IRWXO) != 0
170174
#endif
@@ -180,7 +184,9 @@ open class FileManager : NSObject {
180184
}
181185
} else {
182186
#if CAN_IMPORT_MINGWCRT
183-
let mkdir_failed = _mkdir(path) != 0
187+
let mkdir_failed = path.withCString(encodedAs:UTF16.self, {pointer in
188+
return _wmkdir(pointer) != 0
189+
})
184190
#else
185191
let mkdir_failed = mkdir(pathh, S_IRWXU | S_IRWXG | S_IRWXO) != 0
186192
#endif
@@ -207,14 +213,39 @@ open class FileManager : NSObject {
207213
*/
208214
open func contentsOfDirectory(atPath path: String) throws -> [String] {
209215
var contents : [String] = [String]()
210-
216+
#if CAN_IMPORT_MINGWCRT
217+
let dir = path.withCString(encodedAs:UTF16.self, {pointer in
218+
return _wopendir(pointer)
219+
})
220+
#else
211221
let dir = opendir(path)
212-
222+
#endif
223+
213224
if dir == nil {
214225
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadNoSuchFile.rawValue, userInfo: [NSFilePathErrorKey: path])
215226
}
216-
227+
228+
#if CAN_IMPORT_MINGWCRT
217229
defer {
230+
231+
_wclosedir(dir!)
232+
}
233+
234+
while let entry = _wreaddir(dir!) {
235+
let entryNameLen = withUnsafePointer(to: &entry.pointee.d_namlen) {
236+
UnsafeRawPointer($0).assumingMemoryBound(to: UInt16.self).pointee
237+
}
238+
let entryName = withUnsafePointer(to: &entry.pointee.d_name) {
239+
String(utf16CodeUnits:UnsafeRawPointer($0).assumingMemoryBound(to: UInt16.self), count:Int(entryNameLen))
240+
}
241+
242+
if entryName != "." && entryName != ".." {
243+
contents.append(entryName)
244+
}
245+
}
246+
#else
247+
defer {
248+
218249
closedir(dir!)
219250
}
220251

@@ -227,7 +258,7 @@ open class FileManager : NSObject {
227258
contents.append(entryName)
228259
}
229260
}
230-
261+
#endif
231262
return contents
232263
}
233264

@@ -245,31 +276,74 @@ open class FileManager : NSObject {
245276
- Returns: An array of NSString objects, each of which contains the path of an item in the directory specified by path. If path is a symbolic link, this method traverses the link. This method returns nil if it cannot retrieve the device of the linked-to file.
246277
*/
247278
open func subpathsOfDirectory(atPath path: String) throws -> [String] {
279+
var contents : [String] = [String]()
248280
#if CAN_IMPORT_MINGWCRT
249-
NSUnimplemented()
281+
282+
let dir = path.withCString(encodedAs:UTF16.self, {pointer in
283+
return _wopendir(pointer)
284+
})
250285
#else
251-
var contents : [String] = [String]()
252286

253287
let dir = opendir(path)
288+
#endif
254289

255290
if dir == nil {
256291
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadNoSuchFile.rawValue, userInfo: [NSFilePathErrorKey: path])
257292
}
258293

294+
295+
#if CAN_IMPORT_MINGWCRT
259296
defer {
297+
298+
_wclosedir(dir!)
299+
}
300+
301+
var entry = _wreaddir(dir!)
302+
#else
303+
defer {
304+
260305
closedir(dir!)
261306
}
262-
307+
263308
var entry = readdir(dir!)
309+
#endif
264310

265311
while entry != nil {
312+
#if CAN_IMPORT_MINGWCRT
313+
let entryNameLen = withUnsafePointer(to: &entry!.pointee.d_namlen) {
314+
UnsafeRawPointer($0).assumingMemoryBound(to: UInt16.self).pointee
315+
}
316+
let entryName = withUnsafePointer(to: &entry!.pointee.d_name) {
317+
String(utf16CodeUnits:UnsafeRawPointer($0).assumingMemoryBound(to: UInt16.self), count:Int(entryNameLen))
318+
}
319+
#else
266320
let entryName = withUnsafePointer(to: &entry!.pointee.d_name) {
267321
String(cString: UnsafeRawPointer($0).assumingMemoryBound(to: CChar.self))
268322
}
323+
#endif
269324
// TODO: `entryName` should be limited in length to `entry.memory.d_namlen`.
270325
if entryName != "." && entryName != ".." {
271326
contents.append(entryName)
272-
327+
328+
#if CAN_IMPORT_MINGWCRT
329+
do {
330+
331+
let subPath: String = path + "/" + entryName
332+
let isDir:Bool = subPath.withCString(encodedAs:UTF16.self, {(pointer) -> Bool in
333+
var s = _stat64()
334+
guard _wstat64(pointer, &s) >= 0 else {
335+
return false
336+
}
337+
338+
return (Int32(s.st_mode) & S_IFMT) == S_IFDIR
339+
})
340+
341+
if isDir {
342+
let entries = try subpathsOfDirectory(atPath: subPath)
343+
contents.append(contentsOf: entries.map({file in "\(entryName)/\(file)"}))
344+
}
345+
}
346+
#else
273347
let entryType = withUnsafePointer(to: &entry!.pointee.d_type) { (ptr) -> Int32 in
274348
return Int32(ptr.pointee)
275349
}
@@ -285,13 +359,16 @@ open class FileManager : NSObject {
285359
let entries = try subpathsOfDirectory(atPath: subPath)
286360
contents.append(contentsOf: entries.map({file in "\(entryName)/\(file)"}))
287361
}
362+
#endif
288363
}
289-
364+
#if CAN_IMPORT_MINGWCRT
365+
entry = _wreaddir(dir!)
366+
#else
290367
entry = readdir(dir!)
368+
#endif
291369
}
292370

293371
return contents
294-
#endif
295372
}
296373

297374
/* attributesOfItemAtPath:error: returns an NSDictionary of key/value pairs containing the attributes of the item (file, directory, symlink, etc.) at the path in question. If this method returns 'nil', an NSError will be returned by reference in the 'error' parameter. This method does not traverse a terminal symlink.
@@ -300,29 +377,46 @@ open class FileManager : NSObject {
300377
*/
301378
open func attributesOfItem(atPath path: String) throws -> [FileAttributeKey : Any] {
302379
#if CAN_IMPORT_MINGWCRT
303-
NSUnimplemented()
380+
var s = _stat64()
381+
382+
guard path.withCString(encodedAs:UTF16.self, {(pointer) -> Bool in
383+
return _wstat64(pointer, &s) == 0
384+
}) else {
385+
throw _NSErrorWithErrno(errno, reading: true, path: path)
386+
}
304387
#else
305388
var s = stat()
306389
guard lstat(path, &s) == 0 else {
307390
throw _NSErrorWithErrno(errno, reading: true, path: path)
308391
}
392+
#endif
309393
var result = [FileAttributeKey : Any]()
310394
result[.size] = NSNumber(value: UInt64(s.st_size))
311395

312396
#if os(OSX) || os(iOS)
313397
let ti = (TimeInterval(s.st_mtimespec.tv_sec) - kCFAbsoluteTimeIntervalSince1970) + (1.0e-9 * TimeInterval(s.st_mtimespec.tv_nsec))
314398
#elseif os(Android)
315399
let ti = (TimeInterval(s.st_mtime) - kCFAbsoluteTimeIntervalSince1970) + (1.0e-9 * TimeInterval(s.st_mtime_nsec))
400+
#elseif CAN_IMPORT_MINGWCRT
401+
let ti = (TimeInterval(s.st_mtime) - kCFAbsoluteTimeIntervalSince1970)
316402
#else
317-
let ti = (TimeInterval(s.st_mtim.tv_sec) - kCFAbsoluteTimeIntervalSince1970) + (1.0e-9 * TimeInterval(s.st_mtim.tv_nsec))
403+
let ti = (TimeInterval(s.st_mtime.tv_sec) - kCFAbsoluteTimeIntervalSince1970) + (1.0e-9 * TimeInterval(s.st_mtim.tv_nsec))
318404
#endif
319405
result[.modificationDate] = Date(timeIntervalSinceReferenceDate: ti)
320406

321407
result[.posixPermissions] = NSNumber(value: UInt64(s.st_mode & 0o7777))
322408
result[.referenceCount] = NSNumber(value: UInt64(s.st_nlink))
323409
result[.systemNumber] = NSNumber(value: UInt64(s.st_dev))
324410
result[.systemFileNumber] = NSNumber(value: UInt64(s.st_ino))
325-
411+
#if CAN_IMPORT_MINGWCRT
412+
var type : FileAttributeType
413+
switch Int32(s.st_mode) & S_IFMT {
414+
case S_IFCHR: type = .typeCharacterSpecial
415+
case S_IFDIR: type = .typeDirectory
416+
case S_IFREG: type = .typeRegular
417+
default: type = .typeUnknown
418+
}
419+
#else
326420
let pwd = getpwuid(s.st_uid)
327421
if pwd != nil && pwd!.pointee.pw_name != nil {
328422
let name = String(cString: pwd!.pointee.pw_name)
@@ -345,6 +439,7 @@ open class FileManager : NSObject {
345439
case S_IFSOCK: type = .typeSocket
346440
default: type = .typeUnknown
347441
}
442+
#endif
348443
result[.type] = type
349444

350445
if type == .typeBlockSpecial || type == .typeCharacterSpecial {
@@ -363,7 +458,7 @@ open class FileManager : NSObject {
363458
result[.groupOwnerAccountID] = NSNumber(value: UInt64(s.st_gid))
364459

365460
return result
366-
#endif
461+
367462
}
368463

369464
/* attributesOfFileSystemForPath:error: returns an NSDictionary of key/value pairs containing the attributes of the filesystem containing the provided path. If this method returns 'nil', an NSError will be returned by reference in the 'error' parameter. This method does not traverse a terminal symlink.
@@ -431,6 +526,16 @@ open class FileManager : NSObject {
431526
guard !self.fileExists(atPath: dstPath) else {
432527
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileWriteFileExists.rawValue, userInfo: [NSFilePathErrorKey : NSString(dstPath)])
433528
}
529+
#if CAN_IMPORT_MINGWCRT
530+
531+
if !srcPath.withCString(encodedAs:UTF16.self, {(srcPointer) -> Bool in
532+
return dstPath.withCString(encodedAs:UTF16.self, {(dstPointer) -> Bool in
533+
return _wrename(srcPointer, dstPointer) != 0
534+
})
535+
}) {
536+
throw _NSErrorWithErrno(errno, reading: false, path: srcPath)
537+
}
538+
#else
434539
if rename(srcPath, dstPath) != 0 {
435540
if errno == EXDEV {
436541
// TODO: Copy and delete.
@@ -439,6 +544,7 @@ open class FileManager : NSObject {
439544
throw _NSErrorWithErrno(errno, reading: false, path: srcPath)
440545
}
441546
}
547+
#endif
442548
}
443549

444550
open func linkItem(atPath srcPath: String, toPath dstPath: String) throws {
@@ -462,7 +568,30 @@ open class FileManager : NSObject {
462568

463569
open func removeItem(atPath path: String) throws {
464570
#if CAN_IMPORT_MINGWCRT
465-
NSUnimplemented()
571+
var s = _stat64()
572+
573+
guard try path.withCString(encodedAs:UTF16.self, {(pointer) -> Bool in
574+
guard _wstat64(pointer, &s) >= 0 else {
575+
return false
576+
}
577+
578+
if (Int32(s.st_mode) & S_IFMT) == S_IFDIR {
579+
let subpaths = try subpathsOfDirectory(atPath: path)
580+
for subpath in subpaths {
581+
try removeItem(atPath: path + "/" + subpath)
582+
}
583+
584+
return _wrmdir(pointer) == 0
585+
586+
} else {
587+
588+
return _wunlink(pointer) == 0
589+
}
590+
591+
}) else {
592+
593+
throw _NSErrorWithErrno(errno, reading: true, path: path)
594+
}
466595
#else
467596
if rmdir(path) == 0 {
468597
return
@@ -554,19 +683,33 @@ open class FileManager : NSObject {
554683
open var currentDirectoryPath: String {
555684
#if CAN_IMPORT_MINGWCRT
556685
let length = Int32(_MAX_PATH) + 1
557-
var buf = [Int8](repeating: 0, count: Int(length))
686+
var buf = [UInt16](repeating: 0, count: Int(length))
687+
688+
_wgetcwd(&buf, length)
689+
690+
let result = String(utf16CodeUnits:buf, count:wcslen(buf))
558691
#else
559692
let length = Int(PATH_MAX) + 1
560693
var buf = [Int8](repeating: 0, count: length)
561-
#endif
694+
562695
getcwd(&buf, length)
696+
563697
let result = self.string(withFileSystemRepresentation: buf, length: Int(strlen(buf)))
698+
#endif
699+
700+
564701
return result
565702
}
566703

567704
@discardableResult
568705
open func changeCurrentDirectoryPath(_ path: String) -> Bool {
706+
#if CAN_IMPORT_MINGWCRT
707+
return path.withCString(encodedAs:UTF16.self, {(pointer) -> Bool in
708+
return _wchdir(pointer) == 0
709+
})
710+
#else
569711
return chdir(path) == 0
712+
#endif
570713
}
571714

572715
/* The following methods are of limited utility. Attempting to predicate behavior based on the current state of the filesystem or a particular file on the filesystem is encouraging odd behavior in the face of filesystem race conditions. It's far better to attempt an operation (like loading a file or creating a directory) and handle the error gracefully than it is to try to figure out ahead of time whether the operation will succeed.
@@ -577,7 +720,21 @@ open class FileManager : NSObject {
577720

578721
open func fileExists(atPath path: String, isDirectory: UnsafeMutablePointer<ObjCBool>?) -> Bool {
579722
#if CAN_IMPORT_MINGWCRT
580-
NSUnimplemented()
723+
var s = _stat64()
724+
if path.withCString(encodedAs:UTF16.self, {(pointer) -> Bool in
725+
return _wstat64(pointer, &s) >= 0
726+
}) {
727+
728+
if let isDirectory = isDirectory {
729+
isDirectory.pointee = (Int32(s.st_mode) & S_IFMT) == S_IFDIR
730+
}
731+
732+
} else {
733+
734+
return false
735+
}
736+
737+
return true
581738
#else
582739
var s = stat()
583740
if lstat(path, &s) >= 0 {

0 commit comments

Comments
 (0)