11use std:: process:: { Command , Stdio } ;
2- use std:: io:: { Write , Read } ;
2+ use std:: io:: Read ;
33use std:: fs;
44use std:: os:: unix:: fs:: PermissionsExt ;
5- use std:: path:: Path ;
5+ use std:: path:: { Path , PathBuf } ;
66use std:: time:: { Duration , Instant } ;
7+ use std:: sync:: OnceLock ;
8+
9+ /// Common locations to search for anylinuxfs
10+ const SEARCH_PATHS : & [ & str ] = & [
11+ "/opt/homebrew/bin/anylinuxfs" ,
12+ "/usr/local/bin/anylinuxfs" ,
13+ "/usr/bin/anylinuxfs" ,
14+ ] ;
15+
16+ /// Cached path to anylinuxfs binary
17+ static ANYLINUXFS_PATH : OnceLock < Option < PathBuf > > = OnceLock :: new ( ) ;
18+
19+ /// Find anylinuxfs in PATH or common locations
20+ fn find_anylinuxfs ( ) -> Option < PathBuf > {
21+ // First check ANYLINUXFS_PATH environment variable
22+ if let Ok ( env_path) = std:: env:: var ( "ANYLINUXFS_PATH" ) {
23+ let path = PathBuf :: from ( & env_path) ;
24+ if path. exists ( ) {
25+ return Some ( path) ;
26+ }
27+ }
728
8- const ANYLINUXFS_PATH : & str = "/opt/homebrew/bin/anylinuxfs" ;
29+ // Search in PATH using `which`
30+ if let Ok ( output) = Command :: new ( "which" ) . arg ( "anylinuxfs" ) . output ( ) {
31+ if output. status . success ( ) {
32+ let path_str = String :: from_utf8_lossy ( & output. stdout ) . trim ( ) . to_string ( ) ;
33+ if !path_str. is_empty ( ) {
34+ let path = PathBuf :: from ( & path_str) ;
35+ if path. exists ( ) {
36+ return Some ( path) ;
37+ }
38+ }
39+ }
40+ }
41+
42+ // Fall back to common locations
43+ for search_path in SEARCH_PATHS {
44+ let path = Path :: new ( search_path) ;
45+ if path. exists ( ) {
46+ return Some ( path. to_path_buf ( ) ) ;
47+ }
48+ }
49+
50+ None
51+ }
52+
53+ /// Get the cached path to anylinuxfs, finding it if needed
54+ fn get_anylinuxfs_path ( ) -> Option < & ' static PathBuf > {
55+ ANYLINUXFS_PATH . get_or_init ( find_anylinuxfs) . as_ref ( )
56+ }
957
1058/// Check if the anylinuxfs CLI is available
1159pub fn is_available ( ) -> bool {
12- Path :: new ( ANYLINUXFS_PATH ) . exists ( )
60+ get_anylinuxfs_path ( ) . is_some ( )
1361}
1462
1563/// Get the path to the anylinuxfs CLI
16- pub fn get_path ( ) -> & ' static str {
17- ANYLINUXFS_PATH
64+ pub fn get_path ( ) -> Option < & ' static Path > {
65+ get_anylinuxfs_path ( ) . map ( |p| p . as_path ( ) )
1866}
1967
2068/// Execute an anylinuxfs command with optional sudo elevation
@@ -27,7 +75,10 @@ pub fn execute_command(args: &[&str], needs_sudo: bool, passphrase: Option<&str>
2775}
2876
2977fn execute_direct ( args : & [ & str ] , passphrase : Option < & str > ) -> Result < String , String > {
30- let mut cmd = Command :: new ( ANYLINUXFS_PATH ) ;
78+ let cli_path = get_anylinuxfs_path ( )
79+ . ok_or_else ( || "anylinuxfs CLI not found in PATH or standard locations" . to_string ( ) ) ?;
80+
81+ let mut cmd = Command :: new ( cli_path) ;
3182 cmd. args ( args) ;
3283 cmd. stdin ( Stdio :: null ( ) ) ;
3384
@@ -47,12 +98,16 @@ fn execute_direct(args: &[&str], passphrase: Option<&str>) -> Result<String, Str
4798}
4899
49100fn execute_with_sudo ( args : & [ & str ] , passphrase : Option < & str > ) -> Result < String , String > {
101+ let cli_path = get_anylinuxfs_path ( )
102+ . ok_or_else ( || "anylinuxfs CLI not found in PATH or standard locations" . to_string ( ) ) ?;
103+
50104 // Create a temporary askpass script that uses osascript
51105 // This way the password never passes through our code
52106 let askpass_script = create_askpass_script ( ) ?;
53107
54108 // Build the command arguments for sudo with SUDO_ASKPASS
55- let mut sudo_args = vec ! [ "-A" , "--" , ANYLINUXFS_PATH ] ;
109+ let cli_path_str = cli_path. to_string_lossy ( ) ;
110+ let mut sudo_args: Vec < & str > = vec ! [ "-A" , "--" , & cli_path_str] ;
56111 sudo_args. extend ( args. iter ( ) . copied ( ) ) ;
57112
58113 let mut cmd = Command :: new ( "sudo" ) ;
0 commit comments