@@ -48,15 +48,97 @@ struct FileHandle: ~Copyable, Sendable {
4848 Self ( unsafeCFILEHandle: swt_stderr ( ) , closeWhenDone: false )
4949 }
5050
51+ /// Options to apply when opening a file.
52+ ///
53+ /// An instance of this type does _not_ represent a value of type `mode_t`.
54+ /// Use its ``stringValue`` property to get a string you can pass to `fopen()`
55+ /// and related functions.
56+ struct OpenOptions : RawRepresentable , OptionSet {
57+ var rawValue : Int
58+
59+ /// The file should be opened for reading.
60+ ///
61+ /// This option is equivalent to the `"r"` character or `O_RDONLY`. If you
62+ /// also specify ``writeAccess``, the two options combined are equivalent to
63+ /// the `"w+"` characters or `O_RDWR`.
64+ static var readAccess : Self { . init( rawValue: 1 << 0 ) }
65+
66+ /// The file should be opened for writing.
67+ ///
68+ /// This option is equivalent to the `"w"` character or `O_WRONLY`. If you
69+ /// also specify ``readAccess``, the two options combined are equivalent to
70+ /// the `"w+"` characters or `O_RDWR`.
71+ static var writeAccess : Self { . init( rawValue: 1 << 1 ) }
72+
73+ /// The underlying file descriptor or handle should be inherited by any
74+ /// child processes created by the current process.
75+ ///
76+ /// By default, the resulting file handle is not inherited by any child
77+ /// processes (that is, `FD_CLOEXEC` is set on POSIX-like systems and
78+ /// `HANDLE_FLAG_INHERIT` is cleared on Windows.).
79+ ///
80+ /// This option is equivalent to the `"e"` character (`"N"` on Windows) or
81+ /// `O_CLOEXEC` (`_O_NOINHERIT` on Windows).
82+ static var inheritedByChild : Self { . init( rawValue: 1 << 2 ) }
83+
84+ /// The file must be created when opened.
85+ ///
86+ /// When you use this option and a file already exists at the given path
87+ /// when you try to open it, the testing library throws an error equivalent
88+ /// to `EEXIST`.
89+ ///
90+ /// This option is equivalent to the `"x"` character or `O_CREAT | O_EXCL`.
91+ static var creationRequired : Self { . init( rawValue: 1 << 3 ) }
92+
93+ /// The string representation of this instance, suitable for passing to
94+ /// `fopen()` and related functions.
95+ var stringValue : String {
96+ var result = " "
97+ result. reserveCapacity ( 8 )
98+
99+ if contains ( . writeAccess) {
100+ result. append ( " w " )
101+ } else if contains ( . readAccess) {
102+ result. append ( " r " )
103+ }
104+ // Always open files in binary mode, not text mode.
105+ result. append ( " b " )
106+
107+ if contains ( . readAccess) && contains ( . writeAccess) {
108+ result. append ( " + " )
109+ }
110+
111+ if contains ( . creationRequired) {
112+ result. append ( " x " )
113+ }
114+
115+ if !contains( . inheritedByChild) {
116+ #if os(Windows)
117+ // On Windows, "N" is used rather than "e" to signify that a file
118+ // handle is not inherited.
119+ result. append ( " N " )
120+ #else
121+ result. append ( " e " )
122+ #endif
123+ }
124+
125+ #if DEBUG
126+ assert ( result. isContiguousUTF8, " Constructed a file mode string ' \( result) ' that isn't UTF-8. " )
127+ #endif
128+
129+ return result
130+ }
131+ }
132+
51133 /// Initialize an instance of this type by opening a file at the given path
52- /// with the given mode .
134+ /// with the given options .
53135 ///
54136 /// - Parameters:
55137 /// - path: The path to open.
56- /// - mode : The mode to open `path` with, such as `"wb"` .
138+ /// - options : The options to open `path` with.
57139 ///
58140 /// - Throws: Any error preventing the stream from being opened.
59- init ( atPath path: String , mode : String ) throws {
141+ init ( atPath path: String , options : OpenOptions ) throws {
60142#if os(Windows)
61143 // Special-case CONOUT$ to map to stdout. This way, if somebody specifies
62144 // CONOUT$ as the target path for XML or JSON output from `swift test`,
@@ -68,21 +150,14 @@ struct FileHandle: ~Copyable, Sendable {
68150 // To our knowledge, this sort of special-casing is not required on
69151 // POSIX-like platforms (i.e. when opening "/dev/stdout"), but it can be
70152 // adapted for use there if some POSIX-like platform does need it.
71- if path == " CONOUT$ " && mode . contains ( " w " ) {
153+ if path == " CONOUT$ " && options . contains ( . writeAccess ) {
72154 self = . stdout
73155 return
74156 }
75157
76- // On Windows, "N" is used rather than "e" to signify that a file handle is
77- // not inherited.
78- var mode = mode
79- if let eIndex = mode. firstIndex ( of: " e " ) {
80- mode. replaceSubrange ( eIndex ... eIndex, with: " N " )
81- }
82-
83158 // Windows deprecates fopen() as insecure, so call _wfopen_s() instead.
84159 let fileHandle = try path. withCString ( encodedAs: UTF16 . self) { path in
85- try mode . withCString ( encodedAs: UTF16 . self) { mode in
160+ try options . stringValue . withCString ( encodedAs: UTF16 . self) { mode in
86161 var file : SWT_FILEHandle ?
87162 let result = _wfopen_s ( & file, path, mode)
88163 guard let file, result == 0 else {
@@ -92,7 +167,7 @@ struct FileHandle: ~Copyable, Sendable {
92167 }
93168 }
94169#else
95- guard let fileHandle = fopen ( path, mode ) else {
170+ guard let fileHandle = fopen ( path, options . stringValue ) else {
96171 throw CError ( rawValue: swt_errno ( ) )
97172 }
98173#endif
@@ -103,28 +178,32 @@ struct FileHandle: ~Copyable, Sendable {
103178 ///
104179 /// - Parameters:
105180 /// - path: The path to read from.
181+ /// - options: The options to open the file in. ``OpenOptions/readAccess``
182+ /// is added to this value automatically.
106183 ///
107184 /// - Throws: Any error preventing the stream from being opened.
108185 ///
109186 /// By default, the resulting file handle is not inherited by any child
110187 /// processes (that is, `FD_CLOEXEC` is set on POSIX-like systems and
111188 /// `HANDLE_FLAG_INHERIT` is cleared on Windows.).
112- init ( forReadingAtPath path: String ) throws {
113- try self . init ( atPath: path, mode : " reb " )
189+ init ( forReadingAtPath path: String , options : OpenOptions = [ ] ) throws {
190+ try self . init ( atPath: path, options : options . union ( . readAccess ) )
114191 }
115192
116193 /// Initialize an instance of this type to write to the given path.
117194 ///
118195 /// - Parameters:
119196 /// - path: The path to write to.
197+ /// - options: The options to open the file in. ``OpenOptions/writeAccess``
198+ /// is added to this value automatically.
120199 ///
121200 /// - Throws: Any error preventing the stream from being opened.
122201 ///
123202 /// By default, the resulting file handle is not inherited by any child
124203 /// processes (that is, `FD_CLOEXEC` is set on POSIX-like systems and
125204 /// `HANDLE_FLAG_INHERIT` is cleared on Windows.).
126- init ( forWritingAtPath path: String ) throws {
127- try self . init ( atPath: path, mode : " web " )
205+ init ( forWritingAtPath path: String , options : OpenOptions = [ ] ) throws {
206+ try self . init ( atPath: path, options : options . union ( . writeAccess ) )
128207 }
129208
130209 /// Initialize an instance of this type with an existing C file handle.
@@ -148,17 +227,17 @@ struct FileHandle: ~Copyable, Sendable {
148227 /// - fd: The POSIX file descriptor to wrap. The caller is responsible for
149228 /// ensuring that this file handle is open in the expected mode and that
150229 /// another part of the system won't close it.
151- /// - mode : The mode `fd` was opened with, such as `"wb"` .
230+ /// - options : The options `fd` was opened with.
152231 ///
153232 /// - Throws: Any error preventing the stream from being opened.
154233 ///
155234 /// The resulting file handle takes ownership of `fd` and closes it when it is
156235 /// deinitialized or if an error is thrown from this initializer.
157- init ( unsafePOSIXFileDescriptor fd: CInt , mode : String ) throws {
236+ init ( unsafePOSIXFileDescriptor fd: CInt , options : OpenOptions ) throws {
158237#if os(Windows)
159- let fileHandle = _fdopen ( fd, mode )
238+ let fileHandle = _fdopen ( fd, options . stringValue )
160239#else
161- let fileHandle = fdopen ( fd, mode )
240+ let fileHandle = fdopen ( fd, options . stringValue )
162241#endif
163242 guard let fileHandle else {
164243 let errorCode = swt_errno ( )
@@ -566,11 +645,11 @@ extension FileHandle {
566645 defer {
567646 fdReadEnd = - 1
568647 }
569- try readEnd = FileHandle ( unsafePOSIXFileDescriptor: fdReadEnd, mode : " rb " )
648+ try readEnd = FileHandle ( unsafePOSIXFileDescriptor: fdReadEnd, options : . readAccess )
570649 defer {
571650 fdWriteEnd = - 1
572651 }
573- try writeEnd = FileHandle ( unsafePOSIXFileDescriptor: fdWriteEnd, mode : " wb " )
652+ try writeEnd = FileHandle ( unsafePOSIXFileDescriptor: fdWriteEnd, options : . writeAccess )
574653 } catch {
575654 // Don't leak file handles! Ensure we've cleared both pointers before
576655 // returning so the state is consistent in the caller.
0 commit comments