@@ -20,6 +20,8 @@ use crate::ffi::CStr;
20
20
)
21
21
) ) ]
22
22
use core:: mem:: MaybeUninit ;
23
+ #[ cfg( linux_kernel) ]
24
+ use core:: sync:: atomic:: { AtomicBool , Ordering } ;
23
25
#[ cfg( not( target_os = "wasi" ) ) ]
24
26
use { crate :: io, crate :: pid:: Pid } ;
25
27
#[ cfg( not( any( target_os = "espidf" , target_os = "wasi" ) ) ) ]
28
30
crate :: utils:: as_mut_ptr,
29
31
} ;
30
32
33
+ /// Is `TCGETS2` known to be available?
34
+ #[ cfg( linux_kernel) ]
35
+ static TCGETS2_KNOWN : AtomicBool = AtomicBool :: new ( false ) ;
36
+
31
37
#[ cfg( not( any( target_os = "espidf" , target_os = "wasi" ) ) ) ]
32
38
pub ( crate ) fn tcgetattr ( fd : BorrowedFd < ' _ > ) -> io:: Result < Termios > {
33
39
// If we have `TCGETS2`, use it, so that we fill in the `c_ispeed` and
@@ -38,7 +44,7 @@ pub(crate) fn tcgetattr(fd: BorrowedFd<'_>) -> io::Result<Termios> {
38
44
use crate :: utils:: default_array;
39
45
40
46
let termios2 = unsafe {
41
- let mut termios2 = MaybeUninit :: < c :: termios2 > :: uninit ( ) ;
47
+ let mut termios2 = MaybeUninit :: < termios2 > :: uninit ( ) ;
42
48
43
49
// QEMU's `TCGETS2` doesn't currently set `input_speed` or
44
50
// `output_speed` on PowerPC, so zero out the fields ourselves.
@@ -47,11 +53,26 @@ pub(crate) fn tcgetattr(fd: BorrowedFd<'_>) -> io::Result<Termios> {
47
53
termios2. write ( core:: mem:: zeroed ( ) ) ;
48
54
}
49
55
50
- ret ( c:: ioctl (
56
+ let res = ret ( c:: ioctl (
51
57
borrowed_fd ( fd) ,
52
58
c:: TCGETS2 as _ ,
53
59
termios2. as_mut_ptr ( ) ,
54
- ) ) ?;
60
+ ) ) ;
61
+ match res {
62
+ Ok ( ( ) ) => TCGETS2_KNOWN . store ( true , Ordering :: Relaxed ) ,
63
+ Err ( io:: Errno :: NOTTY ) => tcgetattr_fallback ( fd, & mut termios2) ?,
64
+ Err ( err) => {
65
+ TCGETS2_KNOWN . store ( true , Ordering :: Relaxed ) ;
66
+ return Err ( err) ;
67
+ }
68
+ }
69
+
70
+ // QEMU's `TCGETS2` doesn't currently set `input_speed` or
71
+ // `output_speed` on PowerPC, so set them manually if we can.
72
+ #[ cfg( any( target_arch = "powerpc" , target_arch = "powerpc64" ) ) ]
73
+ {
74
+ infer_input_output_speed ( termios2. assume_init_mut ( ) )
75
+ }
55
76
56
77
termios2. assume_init ( )
57
78
} ;
@@ -68,32 +89,6 @@ pub(crate) fn tcgetattr(fd: BorrowedFd<'_>) -> io::Result<Termios> {
68
89
output_speed : termios2. c_ospeed ,
69
90
} ;
70
91
71
- // QEMU's `TCGETS2` doesn't currently set `input_speed` or
72
- // `output_speed` on PowerPC, so set them manually if we can.
73
- #[ cfg( any( target_arch = "powerpc" , target_arch = "powerpc64" ) ) ]
74
- {
75
- use crate :: termios:: speed;
76
-
77
- if result. output_speed == 0 && ( termios2. c_cflag & c:: CBAUD ) != c:: BOTHER {
78
- if let Some ( output_speed) = speed:: decode ( termios2. c_cflag & c:: CBAUD ) {
79
- result. output_speed = output_speed;
80
- }
81
- }
82
- if result. input_speed == 0
83
- && ( ( termios2. c_cflag & c:: CIBAUD ) >> c:: IBSHIFT ) != c:: BOTHER
84
- {
85
- // For input speeds, `B0` is special-cased to mean the input
86
- // speed is the same as the output speed.
87
- if ( ( termios2. c_cflag & c:: CIBAUD ) >> c:: IBSHIFT ) == c:: B0 {
88
- result. input_speed = result. output_speed ;
89
- } else if let Some ( input_speed) =
90
- speed:: decode ( ( termios2. c_cflag & c:: CIBAUD ) >> c:: IBSHIFT )
91
- {
92
- result. input_speed = input_speed;
93
- }
94
- }
95
- }
96
-
97
92
result. special_codes . 0 [ ..termios2. c_cc . len ( ) ] . copy_from_slice ( & termios2. c_cc ) ;
98
93
99
94
Ok ( result)
@@ -111,6 +106,65 @@ pub(crate) fn tcgetattr(fd: BorrowedFd<'_>) -> io::Result<Termios> {
111
106
}
112
107
}
113
108
109
+ /// Workaround for WSL where `TCGETS2` is unavailable.
110
+ #[ cfg( linux_kernel) ]
111
+ #[ cold]
112
+ unsafe fn tcgetattr_fallback (
113
+ fd : BorrowedFd < ' _ > ,
114
+ result : & mut MaybeUninit < termios2 > ,
115
+ ) -> io:: Result < ( ) > {
116
+ // If we've already seen `TCGETS2` succeed or fail in a way other than
117
+ // `NOTTY`, then can trust a `NOTTY` error from it.
118
+ if TCGETS2_KNOWN . load ( Ordering :: Relaxed ) {
119
+ return Err ( io:: Errno :: NOTTY ) ;
120
+ }
121
+
122
+ // Ensure that the `c_ispeed` and `c_ospeed` fields are initialized,
123
+ // because `TCGETS` won't write to them.
124
+ result. write ( core:: mem:: zeroed ( ) ) ;
125
+
126
+ let res = ret ( c:: ioctl (
127
+ borrowed_fd ( fd) ,
128
+ c:: TCGETS as _ ,
129
+ result. as_mut_ptr ( ) ,
130
+ ) ) ;
131
+ match res {
132
+ Ok ( ( ) ) => {
133
+ infer_input_output_speed ( result. assume_init_mut ( ) ) ;
134
+ Ok ( ( ) )
135
+ }
136
+ Err ( io:: Errno :: NOTTY ) => Err ( io:: Errno :: NOTTY ) ,
137
+ Err ( err) => {
138
+ TCGETS2_KNOWN . store ( true , Ordering :: Relaxed ) ;
139
+ Err ( err)
140
+ }
141
+ }
142
+ }
143
+
144
+ /// Fill in the `input_speed` and `output_speed` fields of `Termios` using
145
+ /// information available in other fields.
146
+ #[ cfg( linux_kernel) ]
147
+ #[ cold]
148
+ fn infer_input_output_speed ( result : & mut termios2 ) {
149
+ use crate :: termios:: speed;
150
+
151
+ if result. c_ospeed == 0 && ( result. c_cflag & c:: CBAUD ) != c:: BOTHER {
152
+ if let Some ( output_speed) = speed:: decode ( result. c_cflag & c:: CBAUD ) {
153
+ result. c_ospeed = output_speed;
154
+ }
155
+ }
156
+ if result. c_ispeed == 0 && ( ( result. c_cflag & c:: CIBAUD ) >> c:: IBSHIFT ) != c:: BOTHER {
157
+ // For input speeds, `B0` is special-cased to mean the input
158
+ // speed is the same as the output speed.
159
+ if ( ( result. c_cflag & c:: CIBAUD ) >> c:: IBSHIFT ) == c:: B0 {
160
+ result. c_ispeed = result. c_ospeed ;
161
+ } else if let Some ( input_speed) = speed:: decode ( ( result. c_cflag & c:: CIBAUD ) >> c:: IBSHIFT )
162
+ {
163
+ result. c_ispeed = input_speed;
164
+ }
165
+ }
166
+ }
167
+
114
168
#[ cfg( not( target_os = "wasi" ) ) ]
115
169
pub ( crate ) fn tcgetpgrp ( fd : BorrowedFd < ' _ > ) -> io:: Result < Pid > {
116
170
unsafe {
@@ -133,6 +187,22 @@ pub(crate) fn tcsetpgrp(fd: BorrowedFd<'_>, pid: Pid) -> io::Result<()> {
133
187
unsafe { ret ( c:: tcsetpgrp ( borrowed_fd ( fd) , pid. as_raw_nonzero ( ) . get ( ) ) ) }
134
188
}
135
189
190
+ #[ cfg( linux_kernel) ]
191
+ use linux_raw_sys:: general:: termios2;
192
+
193
+ #[ cfg( linux_kernel) ]
194
+ #[ cfg( not( any( target_arch = "sparc" , target_arch = "sparc64" ) ) ) ]
195
+ use linux_raw_sys:: ioctl:: { TCSETS , TCSETS2 } ;
196
+
197
+ // linux-raw-sys' ioctl-generation script for sparc isn't working yet,
198
+ // so as a temporary workaround, declare these manually.
199
+ #[ cfg( linux_kernel) ]
200
+ #[ cfg( any( target_arch = "sparc" , target_arch = "sparc64" ) ) ]
201
+ const TCSETS : u32 = 0x8024_5409 ;
202
+ #[ cfg( linux_kernel) ]
203
+ #[ cfg( any( target_arch = "sparc" , target_arch = "sparc64" ) ) ]
204
+ const TCSETS2 : u32 = 0x802c_540d ;
205
+
136
206
#[ cfg( not( any( target_os = "espidf" , target_os = "wasi" ) ) ) ]
137
207
pub ( crate ) fn tcsetattr (
138
208
fd : BorrowedFd < ' _ > ,
@@ -145,17 +215,7 @@ pub(crate) fn tcsetattr(
145
215
{
146
216
use crate :: termios:: speed;
147
217
use crate :: utils:: default_array;
148
- use linux_raw_sys:: general:: { termios2, BOTHER , CBAUD , IBSHIFT } ;
149
-
150
- #[ cfg( not( any( target_arch = "sparc" , target_arch = "sparc64" ) ) ) ]
151
- use linux_raw_sys:: ioctl:: { TCSETS , TCSETS2 } ;
152
-
153
- // linux-raw-sys' ioctl-generation script for sparc isn't working yet,
154
- // so as a temporary workaround, declare these manually.
155
- #[ cfg( any( target_arch = "sparc" , target_arch = "sparc64" ) ) ]
156
- const TCSETS : u32 = 0x8024_5409 ;
157
- #[ cfg( any( target_arch = "sparc" , target_arch = "sparc64" ) ) ]
158
- const TCSETS2 : u32 = 0x802c_540d ;
218
+ use linux_raw_sys:: general:: { BOTHER , CBAUD , IBSHIFT } ;
159
219
160
220
// Translate from `optional_actions` into an ioctl request code. On
161
221
// MIPS, `optional_actions` already has `TCGETS` added to it.
@@ -194,7 +254,14 @@ pub(crate) fn tcsetattr(
194
254
. c_cc
195
255
. copy_from_slice ( & termios. special_codes . 0 [ ..nccs] ) ;
196
256
197
- unsafe { ret ( c:: ioctl ( borrowed_fd ( fd) , request as _ , & termios2) ) }
257
+ unsafe {
258
+ let res = ret ( c:: ioctl ( borrowed_fd ( fd) , request as _ , & termios2) ) ;
259
+ match res {
260
+ Ok ( ( ) ) => Ok ( ( ) ) ,
261
+ Err ( io:: Errno :: NOTTY ) => tcsetattr_fallback ( fd, optional_actions, & termios2) ,
262
+ Err ( err) => Err ( err) ,
263
+ }
264
+ }
198
265
}
199
266
200
267
#[ cfg( not( linux_kernel) ) ]
@@ -207,6 +274,30 @@ pub(crate) fn tcsetattr(
207
274
}
208
275
}
209
276
277
+ /// Workaround for WSL where `TCSETS2` is unavailable.
278
+ #[ cfg( linux_kernel) ]
279
+ #[ cold]
280
+ unsafe fn tcsetattr_fallback (
281
+ fd : BorrowedFd < ' _ > ,
282
+ optional_actions : OptionalActions ,
283
+ result : & termios2 ,
284
+ ) -> io:: Result < ( ) > {
285
+ // Translate from `optional_actions` into an ioctl request code. On
286
+ // MIPS, `optional_actions` already has `TCGETS` added to it.
287
+ let request = if cfg ! ( any(
288
+ target_arch = "mips" ,
289
+ target_arch = "mips32r6" ,
290
+ target_arch = "mips64" ,
291
+ target_arch = "mips64r6"
292
+ ) ) {
293
+ optional_actions as u32
294
+ } else {
295
+ optional_actions as u32 + TCSETS
296
+ } ;
297
+
298
+ ret ( c:: ioctl ( borrowed_fd ( fd) , request as _ , result) )
299
+ }
300
+
210
301
#[ cfg( not( target_os = "wasi" ) ) ]
211
302
pub ( crate ) fn tcsendbreak ( fd : BorrowedFd < ' _ > ) -> io:: Result < ( ) > {
212
303
unsafe { ret ( c:: tcsendbreak ( borrowed_fd ( fd) , 0 ) ) }
0 commit comments