Skip to content

Commit b2910b4

Browse files
committed
SWBUtil: use FileManager for more operations
`unlink` is deprecated on Windows, and `RemoveFileW` should be preferred. However, that would limit the path to `MAX_PATH` (261) characters. Prefer to use `FileManager to remove the file to avoid the path limit. Adopt the fileManager path in more locations.
1 parent 96bb5fb commit b2910b4

File tree

2 files changed

+20
-174
lines changed

2 files changed

+20
-174
lines changed

Sources/SWBUtil/FSProxy.swift

Lines changed: 4 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,6 @@ class LocalFS: FSProxy, @unchecked Sendable {
362362
/// Check whether a given path is a symlink.
363363
/// - parameter destinationExists: If the path is a symlink, then this `inout` parameter will be set to `true` if the destination exists. Otherwise it will be set to `false`.
364364
func isSymlink(_ path: Path, _ destinationExists: inout Bool) -> Bool {
365-
#if os(Windows)
366365
do {
367366
let destination = try fileManager.destinationOfSymbolicLink(atPath: path.str)
368367
destinationExists = exists((path.isAbsolute ? path.dirname : Path.currentDirectory).join(destination))
@@ -371,22 +370,6 @@ class LocalFS: FSProxy, @unchecked Sendable {
371370
destinationExists = false
372371
return false
373372
}
374-
#else
375-
destinationExists = false
376-
var statBuf = stat()
377-
if lstat(path.str, &statBuf) < 0 {
378-
return false
379-
}
380-
guard createFileInfo(statBuf).isSymlink else {
381-
return false
382-
}
383-
statBuf = stat()
384-
if stat(path.str, &statBuf) < 0 {
385-
return true
386-
}
387-
destinationExists = true
388-
return true
389-
#endif
390373
}
391374

392375
func listdir(_ path: Path) throws -> [String] {
@@ -397,70 +380,11 @@ class LocalFS: FSProxy, @unchecked Sendable {
397380
/// - parameter recursive: If `false`, then the parent directory at `path` must already exist in order to create the directory. If it doesn't, then it will return without creating the directory (it will not throw an exception). If `true`, then the directory hierarchy of `path` will be created if possible.
398381
func createDirectory(_ path: Path, recursive: Bool) throws {
399382
// Try to create the directory.
400-
#if os(Windows)
401383
do {
402384
return try fileManager.createDirectory(atPath: path.str, withIntermediateDirectories: recursive)
403385
} catch {
404386
throw StubError.error("Could not create directory at path '\(path.str)': \(error)")
405387
}
406-
#else
407-
let result = mkdir(path.str, S_IRWXU | S_IRWXG | S_IRWXO)
408-
409-
// If it succeeded, we are done.
410-
if result == 0 {
411-
return
412-
}
413-
414-
// If the failure was because something exists at this path, then we examine it to see whether it means we're okay.
415-
if errno == EEXIST {
416-
var destinationExists = false
417-
if isDirectory(path) {
418-
// If the item at the path is a directory, then we're good. This includes if it's a symlink which points to a directory.
419-
return
420-
}
421-
else if isSymlink(path, &destinationExists) {
422-
// If the item at the path is a symlink, then we check whether it's a broken symlink or points to something that is not a directory.
423-
if destinationExists {
424-
// The destination does exist, so it's not a directory.
425-
throw StubError.error("File is a symbolic link which references a path which is not a directory: \(path.str)")
426-
}
427-
else {
428-
// The destination does not exist - throw an exception because we have a broken symlink.
429-
throw StubError.error("File is a broken symbolic link: \(path.str)")
430-
}
431-
}
432-
else {
433-
/// The path exists but is not a directory
434-
throw StubError.error("File exists but is not a directory: \(path.str)")
435-
}
436-
}
437-
438-
// If we are recursive and not the root path, then...
439-
if recursive && !path.isRoot {
440-
// If it failed due to ENOENT (e.g., a missing parent), then attempt to create the parent and retry.
441-
if errno == ENOENT {
442-
// Attempt to create the parent.
443-
guard path.isAbsolute else {
444-
throw StubError.error("Cannot recursively create directory at non-absolute path: \(path.str)")
445-
}
446-
try createDirectory(path.dirname, recursive: true)
447-
448-
// Re-attempt creation, non-recursively.
449-
try createDirectory(path)
450-
451-
// We are done.
452-
return
453-
}
454-
455-
// If our parent is not a directory, then report that.
456-
if !isDirectory(path.dirname) {
457-
throw StubError.error("File exists but is not a directory: \(path.dirname.str)")
458-
}
459-
}
460-
461-
// Otherwise, we failed due to some other error. Report it.
462-
throw POSIXError(errno, context: "mkdir", path.str, "S_IRWXU | S_IRWXG | S_IRWXO")
463-
#endif
464388
}
465389

466390
func createTemporaryDirectory(parent: Path) throws -> Path {
@@ -562,24 +486,12 @@ class LocalFS: FSProxy, @unchecked Sendable {
562486
}
563487

564488
func remove(_ path: Path) throws {
565-
guard unlink(path.str) == 0 else {
566-
throw POSIXError(errno, context: "unlink", path.str)
567-
}
489+
try fileManager.removeItem(atPath: path.str)
568490
}
569491

570492
func removeDirectory(_ path: Path) throws {
571493
if isDirectory(path) {
572-
#if os(Windows)
573494
try fileManager.removeItem(atPath: path.str)
574-
#else
575-
var paths = [path]
576-
try traverse(path) { paths.append($0) }
577-
for path in paths.reversed() {
578-
guard SWBLibc.remove(path.str) == 0 else {
579-
throw POSIXError(errno, context: "remove", path.str)
580-
}
581-
}
582-
#endif
583495
}
584496
}
585497

@@ -608,69 +520,11 @@ class LocalFS: FSProxy, @unchecked Sendable {
608520
}
609521

610522
func touch(_ path: Path) throws {
611-
#if os(Windows)
612-
let handle: HANDLE = path.withPlatformString {
613-
CreateFileW($0, DWORD(GENERIC_WRITE), DWORD(FILE_SHARE_READ), nil,
614-
DWORD(OPEN_EXISTING), DWORD(FILE_FLAG_BACKUP_SEMANTICS), nil)
615-
}
616-
if handle == INVALID_HANDLE_VALUE {
617-
throw StubError.error("Failed to update file time")
618-
}
619-
try handle.closeAfter {
620-
var ft = FILETIME()
621-
var st = SYSTEMTIME()
622-
GetSystemTime(&st)
623-
SystemTimeToFileTime(&st, &ft)
624-
if !SetFileTime(handle, nil, &ft, &ft) {
625-
throw StubError.error("Failed to update file time")
626-
}
627-
}
628-
#else
629-
try eintrLoop {
630-
guard utimensat(AT_FDCWD, path.str, nil, 0) == 0 else {
631-
throw POSIXError(errno, context: "utimensat", "AT_FDCWD", path.str)
632-
}
633-
}
634-
#endif
523+
try fileManager.setAttributes([.modificationDate:Date()], ofItemAtPath: path.str)
635524
}
636525

637526
func setFileTimestamp(_ path: Path, timestamp: Int) throws {
638-
#if os(Windows)
639-
let handle: HANDLE = path.withPlatformString {
640-
CreateFileW($0, DWORD(GENERIC_WRITE), DWORD(FILE_SHARE_READ), nil,
641-
DWORD(OPEN_EXISTING), DWORD(FILE_FLAG_BACKUP_SEMANTICS), nil)
642-
}
643-
if handle == INVALID_HANDLE_VALUE {
644-
throw StubError.error("Failed to update file time")
645-
}
646-
try handle.closeAfter {
647-
// Number of 100ns intervals between 1601 and 1970 epochs
648-
let delta = 116444736000000000
649-
650-
let ll = UInt64((timestamp * 10000000) + delta)
651-
652-
var timeInt = ULARGE_INTEGER()
653-
timeInt.QuadPart = ll
654-
655-
var ft = FILETIME()
656-
ft.dwLowDateTime = timeInt.LowPart
657-
ft.dwHighDateTime = timeInt.HighPart
658-
if !SetFileTime(handle, nil, &ft, &ft) {
659-
throw StubError.error("Failed to update file time")
660-
}
661-
}
662-
#else
663-
try eintrLoop {
664-
#if os(Linux) || os(Android)
665-
let UTIME_OMIT = 1073741822
666-
#endif
667-
let atime = timespec(tv_sec: 0, tv_nsec: Int(UTIME_OMIT))
668-
let mtime = timespec(tv_sec: timestamp, tv_nsec: 0)
669-
guard utimensat(AT_FDCWD, path.str, [atime, mtime], 0) == 0 else {
670-
throw POSIXError(errno, context: "utimensat", "AT_FDCWD", path.str, String(timestamp))
671-
}
672-
}
673-
#endif
527+
try fileManager.setAttributes([.modificationDate:Date(timeIntervalSince1970: Double(timestamp))], ofItemAtPath: path.str)
674528
}
675529

676530
func getFileInfo(_ path: Path) throws -> FileInfo {
@@ -711,7 +565,7 @@ class LocalFS: FSProxy, @unchecked Sendable {
711565
#if os(Windows)
712566
try eintrLoop {
713567
guard stat(path.str, &buf) == 0 else {
714-
throw POSIXError(errno, context: "lstat", path.str)
568+
throw POSIXError(errno, context: "stat", path.str)
715569
}
716570
}
717571

Tests/SWBUtilTests/FSProxyTests.swift

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,8 @@ import SWBTestSupport
9696
var didThrow = false
9797
do {
9898
try localFS.createDirectory(filePath)
99-
}
100-
catch {
99+
} catch {
101100
didThrow = true
102-
#expect(error.localizedDescription == "File exists but is not a directory: \(filePath.str)")
103101
}
104102
#expect(didThrow)
105103

@@ -108,10 +106,8 @@ import SWBTestSupport
108106
didThrow = false
109107
do {
110108
try localFS.createDirectory(dirPath, recursive: true)
111-
}
112-
catch {
109+
} catch {
113110
didThrow = true
114-
#expect(error.localizedDescription == "File exists but is not a directory: \(filePath.str)")
115111
}
116112
#expect(didThrow)
117113
}
@@ -127,10 +123,8 @@ import SWBTestSupport
127123
var didThrow = false
128124
do {
129125
try localFS.createDirectory(symlinkPath)
130-
}
131-
catch {
126+
} catch {
132127
didThrow = true
133-
#expect(error.localizedDescription == "File is a broken symbolic link: \(symlinkPath.str)")
134128
}
135129
#expect(didThrow)
136130

@@ -139,10 +133,8 @@ import SWBTestSupport
139133
didThrow = false
140134
do {
141135
try localFS.createDirectory(dirPath, recursive: true)
142-
}
143-
catch {
136+
} catch {
144137
didThrow = true
145-
#expect(error.localizedDescription == "File is a broken symbolic link: \(symlinkPath.str)")
146138
}
147139
#expect(didThrow)
148140
}
@@ -164,10 +156,8 @@ import SWBTestSupport
164156
var didThrow = false
165157
do {
166158
try localFS.createDirectory(symlinkPath)
167-
}
168-
catch {
159+
} catch {
169160
didThrow = true
170-
#expect(error.localizedDescription == "File is a symbolic link which references a path which is not a directory: \(symlinkPath.str)")
171161
}
172162
#expect(didThrow)
173163

@@ -176,32 +166,34 @@ import SWBTestSupport
176166
didThrow = false
177167
do {
178168
try localFS.createDirectory(dirPath, recursive: true)
179-
}
180-
catch {
169+
} catch {
181170
didThrow = true
182-
#expect(error.localizedDescription == "File exists but is not a directory: \(symlinkPath.str)")
183171
}
184172
#expect(didThrow)
185173
}
186174

187175
// Test that trying to recursively create a directory with an empty path fails.
188176
do {
189177
let filePath = Path("")
190-
#expect {
178+
var didThrow = false
179+
do {
191180
try localFS.createDirectory(filePath, recursive: true)
192-
} throws: { error in
193-
error.localizedDescription == "Cannot recursively create directory at non-absolute path: "
181+
} catch {
182+
didThrow = true
194183
}
184+
#expect(didThrow)
195185
}
196186

197187
// Test that trying to recursively create a directory with a relative path fails.
198188
do {
199189
let filePath = Path("foo/bar/baz")
200-
#expect {
190+
var didThrow = false
191+
do {
201192
try localFS.createDirectory(filePath, recursive: true)
202-
} throws: { error in
203-
error.localizedDescription == "Cannot recursively create directory at non-absolute path: foo/bar/baz"
193+
} catch {
194+
didThrow = true
204195
}
196+
#expect(didThrow)
205197
}
206198
}
207199
}

0 commit comments

Comments
 (0)