char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
f4a0f5b
main
1use std:: ffi:: CString ; 2use std:: io; 3use std:: os:: fd::{ AsRawFd , FromRawFd , OwnedFd , RawFd }; 4use std:: os:: unix:: ffi:: OsStrExt ; 5use std:: os:: unix:: fs:: PermissionsExt ; 6use std:: os:: unix:: process:: ExitStatusExt ; 7use std:: path:: PathBuf ; 8use std:: pin:: Pin ; 9use std:: process:: ExitStatus ; 10use std:: task::{ Context , Poll }; 11 12use crate :: Launch ; 13use tokio:: io:: unix:: AsyncFd ; 14use tokio:: io::{ AsyncRead , AsyncWrite , ReadBuf }; 15 16# [ cfg ( target_os = "linux" )] 17# [ path = "linux.rs" ] 18mod os; 19# [ cfg ( target_os = "macos" )] 20# [ path = "macos.rs" ] 21mod os; 22 23/// A descriptor handed to the child at its declared number. 24pub type Endpoint =OwnedFd ; 25/// The terminal master, kept for resizing. 26pub type Window =OwnedFd ; 27/// Leaves room above every requested child descriptor for the spawn 28/// handshake's own descriptors. 29pub const MAX_DESCRIPTOR : u32 =( i32:: MAX -32 ) as u32 ; 30 31use crate :: signals:: signal_number; 32pub use crate :: signals::{ exit_signal, known_signal, validate_signal}; 33 34fn reserve ( fd : RawFd , minimum : RawFd ) -> io:: Result < OwnedFd > { 35let result =unsafe { libc:: fcntl ( fd, libc:: F_DUPFD_CLOEXEC , minimum) }; 36if result <0 { 37Err ( io:: Error :: last_os_error ()) 38} else { 39Ok ( unsafe { OwnedFd :: from_raw_fd ( result) }) 40} 41} 42 43/// Returns the parent's end and the child's end of a new pipe. 44pub fn pipe ( parent_writes : bool ) -> io:: Result <( Pipe , Endpoint )> { 45let ( read, write) = os:: pipe () ?; 46Ok ( if parent_writes{ 47( Pipe :: new ( write) ?, read) 48} else { 49( Pipe :: new ( read) ?, write) 50}) 51} 52 53pub fn null ( read : bool ) -> io:: Result < Endpoint > { 54Ok ( std:: fs:: OpenOptions :: new () 55. read ( read) 56. write ( !read) 57. open ( "/dev/null" ) ? 58. into ()) 59} 60 61pub fn inherit ( fd : u32 ) -> io:: Result < Endpoint > { 62reserve ( fdas RawFd , 3 ) 63} 64 65pub fn duplicate ( endpoint : & Endpoint ) -> io:: Result < Endpoint > { 66reserve ( endpoint. as_raw_fd (), 3 ) 67} 68 69pub struct Pty { 70pub reader : Pipe , 71pub writer : Pipe , 72pub endpoints : [ Endpoint ; 3 ], 73pub window : Window , 74} 75 76pub fn pty ( columns : u16 , rows : u16 ) -> io:: Result < Pty > { 77let ( master, slave) = os:: pty () ?; 78resize ( & master, columns, rows) ?; 79Ok ( Pty { 80reader : Pipe :: new ( reserve ( master. as_raw_fd (), 3 ) ?) ?, 81writer : Pipe :: new ( reserve ( master. as_raw_fd (), 3 ) ?) ?, 82endpoints : [ 83reserve ( slave. as_raw_fd (), 3 ) ?, 84reserve ( slave. as_raw_fd (), 3 ) ?, 85 slave, 86], 87window : master, 88}) 89} 90 91pub fn resize ( fd : & Window , columns : u16 , rows : u16 ) -> io:: Result <()> { 92let size = libc:: winsize { 93ws_row : rows, 94ws_col : columns, 95ws_xpixel : 0 , 96ws_ypixel : 0 , 97}; 98if unsafe { libc:: ioctl ( fd. as_raw_fd (), libc:: TIOCSWINSZ , & size) } <0 { 99Err ( io:: Error :: last_os_error ()) 100} else { 101Ok (()) 102} 103} 104 105pub struct Pipe ( AsyncFd < OwnedFd >); 106impl Pipe { 107fn new ( fd : OwnedFd ) -> io:: Result < Self > { 108let flags =unsafe { libc:: fcntl ( fd. as_raw_fd (), libc:: F_GETFL ) }; 109if flags <0 110 ||unsafe { libc:: fcntl ( fd. as_raw_fd (), libc:: F_SETFL , flags | libc:: O_NONBLOCK ) } <0 111{ 112return Err ( io:: Error :: last_os_error ()); 113} 114Ok ( Self ( AsyncFd :: new ( fd) ?)) 115} 116} 117impl AsyncRead for Pipe { 118fn poll_read ( 119self : Pin < & mut Self >, 120cx : & mut Context < ' _ >, 121buffer : & mut ReadBuf < ' _ >, 122) ->Poll < io:: Result <()>> { 123loop { 124let mut ready = std:: task:: ready!( self . 0 . poll_read_ready ( cx)) ?; 125match ready. try_io ( |inner|{ 126let bytes = buffer. initialize_unfilled (); 127let count =unsafe { 128 libc:: read ( 129 inner. get_ref (). as_raw_fd (), 130 bytes. as_mut_ptr (). cast (), 131 bytes. len (), 132) 133}; 134if count <0 { 135let error = io:: Error :: last_os_error (); 136// A PTY reports EIO after its last slave closes. 137if error. raw_os_error () ==Some ( libc:: EIO ) { 138return Ok ( 0 ); 139} 140Err ( error) 141} else { 142Ok ( countas usize ) 143} 144}) { 145Ok ( Ok ( count)) =>{ 146 buffer. advance ( count); 147return Poll :: Ready ( Ok (())); 148} 149Ok ( Err ( error)) if error. kind () == io:: ErrorKind :: Interrupted =>continue , 150Ok ( Err ( error)) =>return Poll :: Ready ( Err ( error)), 151Err ( _) =>continue , 152} 153} 154} 155} 156impl AsyncWrite for Pipe { 157fn poll_write ( 158self : Pin < & mut Self >, 159cx : & mut Context < ' _ >, 160bytes : & [ u8 ], 161) ->Poll < io:: Result < usize >> { 162loop { 163let mut ready = std:: task:: ready!( self . 0 . poll_write_ready ( cx)) ?; 164match ready. try_io ( |inner|{ 165let count =unsafe { 166 libc:: write ( 167 inner. get_ref (). as_raw_fd (), 168 bytes. as_ptr (). cast (), 169 bytes. len (), 170) 171}; 172if count <0 { 173Err ( io:: Error :: last_os_error ()) 174} else { 175Ok ( countas usize ) 176} 177}) { 178Ok ( Err ( error)) if error. kind () == io:: ErrorKind :: Interrupted =>continue , 179Ok ( result) =>return Poll :: Ready ( result), 180Err ( _) =>continue , 181} 182} 183} 184fn poll_flush ( self : Pin < & mut Self >, _: & mut Context < ' _ >) ->Poll < io:: Result <()>> { 185Poll :: Ready ( Ok (())) 186} 187fn poll_shutdown ( self : Pin < & mut Self >, _: & mut Context < ' _ >) ->Poll < io:: Result <()>> { 188Poll :: Ready ( Ok (())) 189} 190} 191 192pub fn is_executable ( metadata : & std:: fs:: Metadata ) ->bool { 193 metadata. is_file () && metadata. permissions (). mode () & 0o111 !=0 194} 195 196/// Unix runs exactly the named entry. 197pub fn expand ( path : PathBuf ) ->Vec < PathBuf > { 198vec! [ path] 199} 200 201pub use os:: Executable ; 202 203/// Whether this process can use a terminal: it has a controlling terminal 204/// and leads its foreground group. 205pub fn controlling_foreground () ->bool { 206let path =b"/dev/tty\0" ; 207let fd =unsafe { libc:: open ( path. as_ptr (). cast (), libc:: O_RDONLY | libc:: O_CLOEXEC ) }; 208if fd <0 { 209return false ; 210} 211let foreground =unsafe { libc:: tcgetpgrp ( fd) }; 212unsafe { 213 libc:: close ( fd); 214} 215 foreground >=0 && foreground ==unsafe { libc:: getpgrp () } 216} 217 218struct Exec { 219executable : Executable , 220/// argv[0] is the authorized path. 221_strings : Vec < CString >, 222argv : Vec < usize >, 223env : Vec < usize >, 224} 225impl Exec { 226fn execute ( & self ) -> io:: Error { 227self . executable 228. execute ( self . argv . as_ptr (). cast (), self . env . as_ptr (). cast ()) 229} 230} 231 232pub struct Child { 233pid : u32 , 234group : bool , 235reaped : bool , 236} 237impl Child { 238pub fn id ( & self ) ->u32 { 239self . pid 240} 241pub fn wait ( & mut self ) -> io:: Result < ExitStatus > { 242loop { 243let mut status =0 ; 244if unsafe { libc:: waitpid ( self . pid as i32 , & mut status, 0 ) } >=0 { 245self . reaped =true ; 246return Ok ( ExitStatus :: from_raw ( status)); 247} 248let error = io:: Error :: last_os_error (); 249if error. kind () != io:: ErrorKind :: Interrupted { 250return Err ( error); 251} 252} 253} 254/// Observes exit without reaping, so the PID (and with it the process 255/// group) cannot be reused before group cleanup. 256pub fn observe_exit ( & self ) -> io:: Result < Option < ExitStatus >> { 257let mut info = std:: mem:: MaybeUninit ::< libc:: siginfo_t >:: zeroed (); 258let result =unsafe { 259 libc:: waitid ( 260 libc:: P_PID , 261self . pid as libc:: id_t , 262 info. as_mut_ptr (), 263 libc:: WEXITED | libc:: WNOHANG | libc:: WNOWAIT , 264) 265}; 266if result <0 { 267return Err ( io:: Error :: last_os_error ()); 268} 269let info =unsafe { info. assume_init () }; 270if unsafe { info. si_pid () } ==0 { 271return Ok ( None ); 272} 273let status =unsafe { info. si_status () }; 274// Darwin's waitid has reported stopped children as waitable even 275// without WSTOPPED (golang/go#19314). 276Ok ( match info. si_code { 277 libc:: CLD_EXITED =>Some ( ExitStatus :: from_raw ( status <<8 )), 278 libc:: CLD_KILLED =>Some ( ExitStatus :: from_raw ( status)), 279 libc:: CLD_DUMPED =>Some ( ExitStatus :: from_raw ( status |0x80 )), 280 _ =>None , 281}) 282} 283pub fn signal ( & self , group : bool , name : & str ) -> io:: Result < bool > { 284self . send ( group, signal_number ( name) ?) 285} 286fn send ( & self , group : bool , number : libc:: c_int ) -> io:: Result < bool > { 287if self . reaped { 288return Ok ( false ); 289} 290crate :: signals:: send ( self . pid , group, number) 291} 292} 293impl Drop for Child { 294fn drop ( & mut self ) { 295// Only reachable when the spawn handshake failed; the normal path reaps 296// in supervision. Reap blocking so a failed spawn never leaves a zombie. 297if !self . reaped { 298let _ =self . send ( false , libc:: SIGKILL ); 299if self . group { 300let _ =self . send ( true , libc:: SIGKILL ); 301} 302let mut status =0 ; 303loop { 304if unsafe { libc:: waitpid ( self . pid as i32 , & mut status, 0 ) } >=0 { 305break ; 306} 307if io:: Error :: last_os_error (). kind () != io:: ErrorKind :: Interrupted { 308break ; 309} 310} 311} 312} 313} 314 315pub ( crate ) fn spawn ( launch : Launch ) -> io:: Result < Child > { 316use std:: io:: Read ; 317let Launch { 318 executable, 319 program, 320 args, 321 env, 322 cwd, 323 group, 324 join, 325 terminal, 326 endpoints, 327} = launch; 328let pgid = join. map ( |group| group. pgid ()). transpose () ?. unwrap_or ( 0 ); 329// Every descriptor the handshake holds sits above all requested targets. 330let minimum = endpoints 331. iter () 332. map ( |( fd, _) |* fdas RawFd +16 ) 333. max () 334. unwrap_or ( 0 ) 335. max ( 64 ); 336let pointers = |strings : & [ CString ] |{ 337 strings 338. iter () 339. map ( |value| value. as_ptr () as usize ) 340. chain ([ 0 ]) 341. collect () 342}; 343let argv =[ CString :: new ( program. as_os_str (). as_bytes ())] 344. into_iter () 345. chain ( args. iter (). map ( |arg|CString :: new ( arg. as_bytes ()))) 346. collect ::< Result < Vec < _ >, _ >>() ?; 347let env = env 348. iter () 349. map ( |( name, value) |CString :: new ([ name. as_bytes (), b"=" , value. as_bytes ()]. concat ())) 350. collect ::< Result < Vec < _ >, _ >>() ?; 351let exec =Exec { 352executable : executable. reserve ( minimum) ?, 353argv : pointers ( & argv), 354env : pointers ( & env), 355_strings : argv. into_iter (). chain ( env). collect (), 356}; 357let entries = endpoints 358. into_iter () 359. map ( |( target, fd) |Ok (( reserve ( fd. as_raw_fd (), minimum) ?, targetas RawFd ))) 360. collect ::< io:: Result < Vec < _ >>>() ?; 361let cwd =CString :: new ( cwd. as_os_str (). as_bytes ()) ?; 362let ( read_end, original_writer) = os:: pipe () ?; 363// The exec-error pipe must not collide with a requested child descriptor. 364let write_end =reserve ( original_writer. as_raw_fd (), minimum) ?; 365drop ( original_writer); 366let mut descriptors = os:: Descriptors :: new () ?; 367let owner = os:: Owner :: new (); 368let mut action: libc:: sigaction =unsafe { std:: mem:: zeroed () }; 369 action. sa_sigaction = libc:: SIG_DFL ; 370let mut mask: libc:: sigset_t =unsafe { std:: mem:: zeroed () }; 371unsafe { 372 libc:: sigemptyset ( & mut mask); 373} 374let pid =unsafe { libc:: fork () }; 375if pid <0 { 376return Err ( io:: Error :: last_os_error ()); 377} 378if pid ==0 { 379// All allocation and CString pointer construction precedes fork. 380let result =( || -> io:: Result <()> { 381unsafe { 382 libc:: close ( read_end. as_raw_fd ()); 383if libc:: sigaction ( libc:: SIGPIPE , & action, std:: ptr:: null_mut ()) <0 384 || libc:: sigprocmask ( libc:: SIG_SETMASK , & mask, std:: ptr:: null_mut ()) <0 385 || libc:: chdir ( cwd. as_ptr ()) <0 386{ 387return Err ( io:: Error :: last_os_error ()); 388} 389// Caught handlers reset on exec but ignores persist: 390// re-apply the startup ignores the supervisor or listener 391// feeds may have taken over, so children keep the fate 392// they were launched with. Reads the snapshot only, so no 393// allocation or locking happens after fork. 394for numberin [ 395 libc:: SIGINT , 396 libc:: SIGTERM , 397 libc:: SIGHUP , 398 libc:: SIGUSR1 , 399 libc:: SIGUSR2 , 400 libc:: SIGWINCH , 401] { 402if crate :: signals:: originally_ignored ( number) 403 && libc:: signal ( number, libc:: SIG_IGN ) == libc:: SIG_ERR 404{ 405return Err ( io:: Error :: last_os_error ()); 406} 407} 408if terminal{ 409if libc:: setsid () <0 { 410return Err ( io:: Error :: last_os_error ()); 411} 412} else if ( group || join. is_some ()) && libc:: setpgid ( 0 , pgidas i32 ) <0 { 413return Err ( io:: Error :: last_os_error ()); 414} 415 owner. bind () ?; 416for ( source, target) in & entries{ 417if libc:: dup2 ( source. as_raw_fd (), * target) <0 { 418return Err ( io:: Error :: last_os_error ()); 419} 420} 421 descriptors. restrict () ?; 422for ( _, target) in & entries{ 423if * target >=3 && libc:: fcntl ( * target, libc:: F_SETFD , 0 ) <0 { 424return Err ( io:: Error :: last_os_error ()); 425} 426} 427if terminal && libc:: ioctl ( 0 , libc:: TIOCSCTTY as _ , 0 ) <0 { 428return Err ( io:: Error :: last_os_error ()); 429} 430} 431Err ( exec. execute ()) 432})(); 433let error = result. unwrap_err (). raw_os_error (). unwrap_or ( libc:: EIO ); 434unsafe { 435 libc:: write ( 436 write_end. as_raw_fd (), 437( & erroras * const i32 ). cast (), 438 std:: mem:: size_of ::< i32 >(), 439); 440 libc:: _exit ( 127 ); 441} 442} 443drop ( write_end); 444let mut child =Child { 445pid : pidas u32 , 446 group, 447reaped : false , 448}; 449let mut read_end = std:: fs:: File :: from ( read_end); 450let mut error =[ 0 ; 4 ]; 451match read_end. read ( & mut error) ?{ 4520 =>Ok ( child), 4534 =>{ 454 child. wait () ?; 455Err ( io:: Error :: from_raw_os_error ( i32:: from_ne_bytes ( error))) 456} 457 _ =>Err ( io:: Error :: other ( "Invalid subprocess exec handshake" )), 458} 459}