@@ -172,3 +172,42 @@ fn stderr_pipe() {
172172
173173 assert_eq ! ( stdout, "hello world" ) ;
174174}
175+
176+ #[ test]
177+ fn stdout_foreign_pty ( ) {
178+ let env = Env ( [ SUDOERS_ALL_ALL_NOPASSWD , "Defaults use_pty" ] ) . build ( ) ;
179+
180+ // Everything is put in a single command with separators to keep the pts numbers predictable
181+ let output = Command :: new ( "sh" )
182+ . args ( [
183+ "-c" ,
184+ "socat - SYSTEM:'ls -l /proc/self/fd; echo @@@@; sudo ls -l /proc/self/fd',pty,setsid,ctty,stderr;
185+ echo ====;
186+ socat - SYSTEM:'ls -l /proc/self/fd; echo @@@@; sudo ls -l /proc/self/fd',pty,stderr" ,
187+ ] )
188+ . tty ( true )
189+ . output ( & env) ;
190+
191+ let stdout = output. stdout ( ) ;
192+ let ( own_term, foreign_term) = stdout. split_once ( "====" ) . unwrap ( ) ;
193+
194+ let ( own_term_in, own_term_sudo) = own_term. split_once ( "@@@@" ) . unwrap ( ) ;
195+ assert_contains ! ( own_term_in, " 0 -> /dev/pts/1" ) ;
196+ assert_contains ! ( own_term_in, " 1 -> /dev/pts/1" ) ;
197+ assert_contains ! ( own_term_in, " 2 -> /dev/pts/0" ) ;
198+ // pts/1 is our controlling tty, so it gets proxied.
199+ // pts/0 is a foreign pty, so it gets inherited
200+ assert_contains ! ( own_term_sudo, " 0 -> /dev/pts/2" ) ;
201+ assert_contains ! ( own_term_sudo, " 1 -> /dev/pts/2" ) ;
202+ assert_contains ! ( own_term_sudo, " 2 -> /dev/pts/0" ) ;
203+
204+ let ( foreign_term_in, foreign_term_sudo) = foreign_term. split_once ( "@@@@" ) . unwrap ( ) ;
205+ assert_contains ! ( foreign_term_in, " 0 -> /dev/pts/1" ) ;
206+ assert_contains ! ( foreign_term_in, " 1 -> /dev/pts/1" ) ;
207+ assert_contains ! ( foreign_term_in, " 2 -> /dev/pts/0" ) ;
208+ // pts/1 is not our controlling tty, so it gets inherited.
209+ // pts/0 is our controlling tty, so it gets proxied
210+ assert_contains ! ( foreign_term_sudo, " 0 -> /dev/pts/1" ) ;
211+ assert_contains ! ( foreign_term_sudo, " 1 -> /dev/pts/1" ) ;
212+ assert_contains ! ( foreign_term_sudo, " 2 -> /dev/pts/2" ) ;
213+ }
0 commit comments