@@ -891,7 +891,10 @@ fn cmd_exec(
891891 args : req. args ,
892892 env : req. env ,
893893 cwd : req. cwd ,
894- detach,
894+ // Always ask the supervisor to report the exec'd process's exit. The
895+ // caller's `--detach` (set by the containerd shim) is handled locally
896+ // by the exec proxy below, which still needs that exit status.
897+ detach : false ,
895898 } ;
896899 let payload = serde_json:: to_vec ( & cmd) . context ( "serialize exec command" ) ?;
897900
@@ -924,15 +927,80 @@ fn cmd_exec(
924927 other => bail ! ( "unexpected exec reply: {:?}" , other) ,
925928 } ;
926929
930+ if detach {
931+ // Detached exec (containerd: `exec --detach --pid-file`). The shim
932+ // expects this CLI to return promptly and reaps the pid-file pid for
933+ // exit. The exec'd process is sandlock-init's child, which the shim
934+ // cannot reap; so fork a proxy whose pid goes in the pid-file. When this
935+ // CLI returns, the proxy reparents to the shim (the subreaper), and the
936+ // proxy exits with the exec'd process's status, so the shim reaps a
937+ // matching wait-status. This is the exec-path analog of the create-path
938+ // supervisor that the shim already reaps.
939+ let mut rdy = [ 0i32 ; 2 ] ;
940+ if unsafe { libc:: pipe2 ( rdy. as_mut_ptr ( ) , libc:: O_CLOEXEC ) } != 0 {
941+ bail ! ( "pipe2 for exec proxy failed: {}" , std:: io:: Error :: last_os_error( ) ) ;
942+ }
943+ let ( rdy_r, rdy_w) = ( rdy[ 0 ] , rdy[ 1 ] ) ;
944+ let kid = unsafe { libc:: fork ( ) } ;
945+ if kid < 0 {
946+ bail ! ( "fork exec proxy failed: {}" , std:: io:: Error :: last_os_error( ) ) ;
947+ }
948+ if kid == 0 {
949+ // ===== EXEC PROXY =====
950+ unsafe { libc:: close ( rdy_r) } ;
951+ // Drop the shim's exec stdio so we do not pin those streams open;
952+ // the exec'd process holds its own dup'd copies (via SCM_RIGHTS), so
953+ // the shim still sees EOF when that process exits.
954+ unsafe {
955+ let n = libc:: open ( b"/dev/null\0 " . as_ptr ( ) as * const libc:: c_char , libc:: O_RDWR ) ;
956+ if n >= 0 {
957+ libc:: dup2 ( n, 0 ) ;
958+ libc:: dup2 ( n, 1 ) ;
959+ libc:: dup2 ( n, 2 ) ;
960+ if n > 2 {
961+ libc:: close ( n) ;
962+ }
963+ }
964+ }
965+ // The pid the shim will reap is THIS proxy.
966+ if let Some ( pf) = pid_file {
967+ let _ = std:: fs:: write ( pf, std:: process:: id ( ) . to_string ( ) ) ;
968+ }
969+ // Tell the parent the pid-file is written; it can now return.
970+ unsafe {
971+ libc:: write ( rdy_w, b"x" . as_ptr ( ) as * const libc:: c_void , 1 ) ;
972+ libc:: close ( rdy_w) ;
973+ }
974+ // Block until the exec'd process exits, then exit with its status.
975+ let mut line2 = String :: new ( ) ;
976+ if reader. read_line ( & mut line2) . is_ok ( ) {
977+ if let Ok ( supervisor:: SupervisorReply :: Exit { code, signal } ) =
978+ serde_json:: from_str :: < supervisor:: SupervisorReply > ( line2. trim ( ) )
979+ {
980+ supervisor_exit ( Some ( state:: ExitInfo { code, signal } ) ) ;
981+ }
982+ }
983+ // Lost the channel before an Exit arrived: report a generic failure
984+ // so the shim does not hang.
985+ unsafe { libc:: _exit ( 255 ) } ;
986+ }
987+ // ===== PARENT: wait until the proxy has written the pid-file, return =====
988+ unsafe { libc:: close ( rdy_w) } ;
989+ let mut b = [ 0u8 ; 1 ] ;
990+ unsafe {
991+ libc:: read ( rdy_r, b. as_mut_ptr ( ) as * mut libc:: c_void , 1 ) ;
992+ libc:: close ( rdy_r) ;
993+ }
994+ return Ok ( ( ) ) ;
995+ }
996+
997+ // Attached: this CLI is the handle the caller waits on. Record the exec'd
998+ // pid and block until it exits.
927999 if let Some ( pf) = pid_file {
9281000 std:: fs:: write ( pf, exec_pid. to_string ( ) )
9291001 . with_context ( || format ! ( "write pid file {:?}" , pf) ) ?;
9301002 }
9311003
932- if detach {
933- return Ok ( ( ) ) ;
934- }
935-
9361004 // Second reply: Exit. Exit this CLI with the same status.
9371005 let mut line2 = String :: new ( ) ;
9381006 reader. read_line ( & mut line2) . context ( "read exec exit reply" ) ?;
0 commit comments