55use crate :: path_util:: normalize_path;
66use crate :: { Availability , LinuxBwrapError , Preset } ;
77use parking_lot:: RwLock ;
8+ use std:: collections:: HashSet ;
89use std:: env;
9- use std:: ffi:: OsString ;
10+ use std:: ffi:: { OsStr , OsString } ;
1011use std:: path:: { Path , PathBuf } ;
1112use std:: process:: { Command , Output , Stdio } ;
1213use std:: sync:: Arc ;
@@ -91,10 +92,10 @@ fn profile_name(preset: Option<Preset>) -> &'static str {
9192 }
9293}
9394
94- /// Searches `PATH` directories for a file named `name` and returns the first match.
95- pub ( crate ) fn find_binary_on_path ( name : & str ) -> Option < Box < Path > > {
96- let path = env :: var_os ( "PATH" ) ?;
97- for dir in env:: split_paths ( & path) {
95+ # [ inline ]
96+ fn find_binary_on_path_in ( name : & str , path : Option < & OsStr > ) -> Option < Box < Path > > {
97+ let path = path ?;
98+ for dir in env:: split_paths ( path) {
9899 if !dir. is_absolute ( ) || dir. as_os_str ( ) . is_empty ( ) {
99100 continue ;
100101 }
@@ -115,29 +116,59 @@ pub(crate) fn first_shell_candidate_with<F, R>(mut classify: F) -> Option<(Box<P
115116where
116117 F : FnMut ( & Path ) -> Option < R > ,
117118{
119+ first_shell_candidate_with_in ( env:: var_os ( "PATH" ) . as_deref ( ) , & mut classify)
120+ }
121+
122+ fn first_shell_candidate_with_in < F , R > (
123+ env_path : Option < & OsStr > ,
124+ classify : & mut F ,
125+ ) -> Option < ( Box < Path > , R ) >
126+ where
127+ F : FnMut ( & Path ) -> Option < R > ,
128+ {
129+ let mut seen = HashSet :: with_capacity ( 10 ) ;
130+
118131 for name in [ "bash" , "sh" ] {
119- if let Some ( path) = find_binary_on_path ( name) {
120- let path = normalize_path ( path. as_ref ( ) ) ;
121- if let Some ( r) = classify ( path. as_ref ( ) ) {
122- return Some ( ( path, r) ) ;
132+ if let Some ( shell_path) = find_binary_on_path_in ( name, env_path) {
133+ if let Some ( result) = classify_shell_candidate ( classify, & mut seen, shell_path) {
134+ return Some ( result) ;
123135 }
124136 }
125137 }
138+
126139 for candidate in SHELL_CANDIDATES {
127- let path = PathBuf :: from ( candidate) ;
128- if path. is_file ( ) {
129- let path = normalize_path ( & path) ;
130- if let Some ( r) = classify ( path. as_ref ( ) ) {
131- return Some ( ( path, r) ) ;
140+ let candidate_path = PathBuf :: from ( candidate) ;
141+ if candidate_path. is_file ( ) {
142+ if let Some ( result) =
143+ classify_shell_candidate ( classify, & mut seen, candidate_path. into_boxed_path ( ) )
144+ {
145+ return Some ( result) ;
132146 }
133147 }
134148 }
149+
135150 None
136151}
137152
138- /// Returns any available host shell (`bash` preferred, then `sh`).
139- pub ( crate ) fn resolve_host_shell ( ) -> Option < Box < Path > > {
140- first_shell_candidate_with ( |_| Some ( ( ) ) ) . map ( |( path, _) | path)
153+ #[ inline]
154+ fn classify_shell_candidate < F , R > (
155+ classify : & mut F ,
156+ seen : & mut HashSet < Box < Path > > ,
157+ path : Box < Path > ,
158+ ) -> Option < ( Box < Path > , R ) >
159+ where
160+ F : FnMut ( & Path ) -> Option < R > ,
161+ {
162+ let path = normalize_path ( path. as_ref ( ) ) ;
163+ if !seen. insert ( path. clone ( ) ) {
164+ return None ;
165+ }
166+ classify ( path. as_ref ( ) ) . map ( |result| ( path, result) )
167+ }
168+
169+ #[ inline]
170+ fn resolve_host_shell_in ( path : Option < & OsStr > ) -> Option < Box < Path > > {
171+ first_shell_candidate_with_in ( path, & mut |_| Some ( ( ) ) ) . map ( |( path, _) | path)
141172}
142173
143174fn probe_backend ( ) -> LinuxBwrapBackend {
@@ -157,7 +188,7 @@ fn probe_backend() -> LinuxBwrapBackend {
157188 }
158189 }
159190
160- let backend = probe_backend_uncached ( ) ;
191+ let backend = probe_backend_uncached ( path . as_deref ( ) ) ;
161192 * cache. write ( ) = Some ( ( path, backend. clone ( ) ) ) ;
162193 backend
163194}
@@ -166,31 +197,14 @@ fn probe_backend() -> LinuxBwrapBackend {
166197///
167198/// The probe binds the host root read-only and runs a tiny shell command. That
168199/// checks both namespace support and shell visibility on FHS and Nix systems.
169- fn probe_backend_uncached ( ) -> LinuxBwrapBackend {
170- let Some ( bwrap) = find_binary_on_path ( "bwrap" ) else {
200+ fn probe_backend_uncached ( path : Option < & OsStr > ) -> LinuxBwrapBackend {
201+ let Some ( bwrap) = find_binary_on_path_in ( "bwrap" , path ) else {
171202 return LinuxBwrapBackend :: MissingBinary {
172203 reason : Box :: from ( "`bwrap` was not found on PATH" ) ,
173204 } ;
174205 } ;
175206
176- let version = Command :: new ( bwrap. as_os_str ( ) )
177- . arg ( "--version" )
178- . stdin ( Stdio :: null ( ) )
179- . stdout ( Stdio :: null ( ) )
180- . stderr ( Stdio :: piped ( ) )
181- . output ( ) ;
182- let Ok ( version) = version else {
183- return LinuxBwrapBackend :: MissingBinary {
184- reason : format ! ( "failed to execute {}" , bwrap. display( ) ) . into_boxed_str ( ) ,
185- } ;
186- } ;
187- if !version. status . success ( ) {
188- return LinuxBwrapBackend :: Unusable {
189- reason : probe_failure_reason ( & version, "`bwrap --version` failed" ) ,
190- } ;
191- }
192-
193- let Some ( shell) = resolve_host_shell ( ) else {
207+ let Some ( shell) = resolve_host_shell_in ( path) else {
194208 return LinuxBwrapBackend :: Unusable {
195209 reason : Box :: from ( "no usable host shell (`bash` or `sh`) was found" ) ,
196210 } ;
@@ -257,6 +271,11 @@ mod tests {
257271 use serial_test:: serial;
258272 use tempfile:: TempDir ;
259273
274+ /// Searches `PATH` directories for a file named `name` and returns the first match.
275+ fn find_binary_on_path ( name : & str ) -> Option < Box < Path > > {
276+ find_binary_on_path_in ( name, env:: var_os ( "PATH" ) . as_deref ( ) )
277+ }
278+
260279 // These tests swap PATH and exercise a process-wide availability cache, so
261280 // cases that probe `bwrap` run serially to avoid cross-test contamination.
262281
@@ -298,14 +317,6 @@ mod tests {
298317 // Make `bwrap` look installed, then fail the "can it sandbox?" probe.
299318 let script = format ! (
300319 r#"#!/bin/sh
301- # Handle --version probe
302- for arg in "$@"; do
303- if [ "$arg" = "--version" ]; then
304- echo "bubblewrap 0.8.0"
305- exit 0
306- fi
307- done
308- # Fail the "can it sandbox?" probe
309320echo "{}" >&2
310321exit 1
311322"# ,
0 commit comments