@@ -104,11 +104,84 @@ public extension FileSystemError {
104104 case TSCLibc . ENOTDIR:
105105 self . init ( . notDirectory, path)
106106 case TSCLibc . EEXIST:
107- self . init ( . alreadyExistsAtDestination , path)
107+ self . init ( . noEntry , path)
108108 default :
109109 self . init ( . ioError( code: errno) , path)
110110 }
111111 }
112+
113+ init ( error: POSIXError , _ path: AbsolutePath ) {
114+ switch error. code {
115+ case . ENOENT:
116+ self . init ( . noEntry, path)
117+ case . EACCES:
118+ self . init ( . invalidAccess, path)
119+ case . EISDIR:
120+ self . init ( . isDirectory, path)
121+ case . ENOTDIR:
122+ self . init ( . notDirectory, path)
123+ case . EEXIST:
124+ self . init ( . alreadyExistsAtDestination, path)
125+ default :
126+ self . init ( . ioError( code: error. code. rawValue) , path)
127+ }
128+ }
129+ }
130+
131+ // MARK: - NSError to FileSystemError Mapping
132+ private extension FileSystemError {
133+ /// Maps NSError codes to appropriate FileSystemError kinds
134+ /// This centralizes error mapping logic and ensures consistency across file operations
135+ ///
136+ /// - Parameters:
137+ /// - error: The NSError to map
138+ /// - path: The file path associated with the error
139+ /// - Returns: A FileSystemError with appropriate semantic mapping
140+ static func from( nsError error: NSError , path: AbsolutePath ) -> FileSystemError {
141+ // First, check for POSIX errors in the underlying error chain
142+ // POSIX errors provide more precise semantic information
143+ if let posixError = error. userInfo [ NSUnderlyingErrorKey] as? POSIXError {
144+ return FileSystemError ( error: posixError, path)
145+ }
146+
147+ // Handle Cocoa domain errors with proper semantic mapping
148+ guard error. domain == NSCocoaErrorDomain else {
149+ // For non-Cocoa errors, preserve the original error information
150+ return FileSystemError ( . ioError( code: Int32 ( error. code) ) , path)
151+ }
152+
153+ // Map common Cocoa error codes to semantic FileSystemError kinds
154+ switch error. code {
155+ // File not found errors
156+ case NSFileReadNoSuchFileError, NSFileNoSuchFileError:
157+ return FileSystemError ( . noEntry, path)
158+
159+ // Permission denied errors
160+ case NSFileReadNoPermissionError, NSFileWriteNoPermissionError:
161+ return FileSystemError ( . invalidAccess, path)
162+
163+ // File already exists errors
164+ case NSFileWriteFileExistsError:
165+ return FileSystemError ( . alreadyExistsAtDestination, path)
166+
167+ // Read-only volume errors
168+ case NSFileWriteVolumeReadOnlyError:
169+ return FileSystemError ( . invalidAccess, path)
170+
171+ // File corruption or invalid format errors
172+ case NSFileReadCorruptFileError:
173+ return FileSystemError ( . ioError( code: Int32 ( error. code) ) , path)
174+
175+ // Directory-related errors
176+ case NSFileReadInvalidFileNameError:
177+ return FileSystemError ( . notDirectory, path)
178+
179+ default :
180+ // For any other Cocoa error, wrap it as an IO error preserving the original code
181+ // This ensures we don't lose diagnostic information
182+ return FileSystemError ( . ioError( code: Int32 ( error. code) ) , path)
183+ }
184+ }
112185}
113186
114187/// Defines the file modes.
@@ -513,64 +586,45 @@ private struct LocalFileSystem: FileSystem {
513586 }
514587
515588 func readFileContents( _ path: AbsolutePath ) throws -> ByteString {
516- // Open the file.
517- guard let fp = fopen ( path. pathString, " rb " ) else {
518- throw FileSystemError ( errno: errno, path)
519- }
520- defer { fclose ( fp) }
521-
522- // Read the data one block at a time.
523- let data = BufferedOutputByteStream ( )
524- var tmpBuffer = [ UInt8] ( repeating: 0 , count: 1 << 12 )
525- while true {
526- let n = fread ( & tmpBuffer, 1 , tmpBuffer. count, fp)
527- if n < 0 {
528- if errno == EINTR { continue }
529- throw FileSystemError ( . ioError( code: errno) , path)
530- }
531- if n == 0 {
532- let errno = ferror ( fp)
533- if errno != 0 {
534- throw FileSystemError ( . ioError( code: errno) , path)
535- }
536- break
589+ do {
590+ let dataContent = try Data ( contentsOf: URL ( fileURLWithPath: path. pathString) )
591+ return dataContent. withUnsafeBytes { bytes in
592+ ByteString ( Array ( bytes. bindMemory ( to: UInt8 . self) ) )
537593 }
538- data. send ( tmpBuffer [ 0 ..< n] )
594+ } catch let error as NSError {
595+ throw FileSystemError . from ( nsError: error, path: path)
596+ } catch {
597+ // Handle any other error types (e.g., Swift errors)
598+ throw FileSystemError ( . unknownOSError, path)
539599 }
540-
541- return data. bytes
542600 }
543601
544602 func writeFileContents( _ path: AbsolutePath , bytes: ByteString ) throws {
545- // Open the file.
546- guard let fp = fopen ( path. pathString, " wb " ) else {
547- throw FileSystemError ( errno: errno, path)
548- }
549- defer { fclose ( fp) }
550-
551- // Write the data in one chunk.
552- var contents = bytes. contents
553- while true {
554- let n = fwrite ( & contents, 1 , contents. count, fp)
555- if n < 0 {
556- if errno == EINTR { continue }
557- throw FileSystemError ( . ioError( code: errno) , path)
558- }
559- if n != contents. count {
560- throw FileSystemError ( . mismatchedByteCount( expected: contents. count, actual: n) , path)
603+ do {
604+ try bytes. withData {
605+ try $0. write ( to: URL ( fileURLWithPath: path. pathString) )
561606 }
562- break
607+ } catch let error as NSError {
608+ throw FileSystemError . from ( nsError: error, path: path)
609+ } catch {
610+ // Handle any other error types (e.g., Swift errors)
611+ throw FileSystemError ( . unknownOSError, path)
563612 }
564613 }
565614
566615 func writeFileContents( _ path: AbsolutePath , bytes: ByteString , atomically: Bool ) throws {
567- // Perform non-atomic writes using the fast path.
568616 if !atomically {
569617 return try writeFileContents ( path, bytes: bytes)
570618 }
571-
572- try bytes. withData {
573- try $0. write ( to: URL ( fileURLWithPath: path. pathString) , options: . atomic)
619+ do {
620+ try bytes. withData {
621+ try $0. write ( to: URL ( fileURLWithPath: path. pathString) , options: . atomic)
622+ }
623+ } catch let error as NSError {
624+ throw FileSystemError . from ( nsError: error, path: path)
625+ } catch {
626+ // Handle any other error types (e.g., Swift errors)
627+ throw FileSystemError ( . unknownOSError, path)
574628 }
575629 }
576630
0 commit comments