@@ -9,8 +9,7 @@ use clap::Parser;
99use dialoguer:: { Confirm , Select } ;
1010
1111use std:: {
12- fs:: OpenOptions ,
13- io:: { BufRead , BufReader , Seek , Write } ,
12+ io:: BufReader ,
1413 path:: PathBuf ,
1514 process:: { Command , Stdio } ,
1615} ;
@@ -239,67 +238,54 @@ impl Install {
239238 } ) ?;
240239
241240 if let Some ( php_ini) = php_ini {
242- copy_ini_file ( & php_ini, ext_name, self . disable )
241+ update_ini_file ( & php_ini, ext_name, self . disable )
243242 . with_context ( || "Failed to update `php.ini`" ) ?;
244243 }
245244
246245 Ok ( ( ) )
247246 }
248247}
249248
250- // Copy ini file, if fails, try with sudo again.
251- fn copy_ini_file ( php_ini : & PathBuf , ext_name : & str , disable : bool ) -> anyhow:: Result < ( ) > {
252- let mut file = match OpenOptions :: new ( ) . read ( true ) . write ( true ) . open ( php_ini) {
253- Ok ( x) => x,
254- Err ( _e) => {
255- #[ cfg( unix) ]
256- {
257- elevate:: escalate_if_needed ( ) . expect ( "sudo failed" ) ;
258- }
259- OpenOptions :: new ( )
260- . read ( true )
261- . write ( true )
262- . open ( php_ini)
263- . with_context ( || "Failed to open `php.ini`" ) ?
264- }
265- } ;
266-
249+ // Update extension information in the ini file, if fails, try with sudo again.
250+ //
251+ // Write to a temp file then move it to given path. Use `sudo` on unix to move
252+ // file if needed.
253+ fn update_ini_file ( php_ini : & PathBuf , ext_name : & str , disable : bool ) -> anyhow:: Result < ( ) > {
254+ let current_ini_content = std:: fs:: read_to_string ( php_ini) ?;
267255 let mut ext_line = format ! ( "extension={ext_name}" ) ;
268256
269- let mut new_lines = vec ! [ ] ;
270- for line in BufReader :: new ( & file) . lines ( ) {
271- let line = line. with_context ( || "Failed to read line from `php.ini`" ) ?;
257+ let mut new_lines = current_ini_content. lines ( ) . collect :: < Vec < _ > > ( ) ;
258+ for line in & new_lines {
272259 if line. contains ( & ext_line) {
273260 bail ! ( "Extension already enabled." ) ;
274261 }
275-
276- new_lines. push ( line) ;
277262 }
278263
279264 // Comment out extension if user specifies disable flag
280265 if disable {
281266 ext_line. insert ( 0 , ';' ) ;
282267 }
283268
284- new_lines. push ( ext_line) ;
285- file. rewind ( ) ?;
286- file. set_len ( 0 ) ?;
287- let _ = file. write ( new_lines. join ( "\n " ) . as_bytes ( ) ) ?;
269+ new_lines. push ( & ext_line) ;
270+ write_to_file ( new_lines. join ( "\n " ) , php_ini) ?;
288271 Ok ( ( ) )
289272}
290273
291- // Copy extension, if fails, try with sudo again .
274+ // Copy extension, if fails, try with sudo cp .
292275//
293- // We can check if we have write permission for ext_dir but due to ACL, group
294- // list and and other nuances, it may not be reliable . See
276+ // Checking if we have write permission for ext_dir may fail due to ACL, group
277+ // list and and other nuances. See
295278// https://doc.rust-lang.org/std/fs/struct.Permissions.html#method.readonly
296279fn copy_extension ( ext_path : & Utf8PathBuf , ext_dir : & PathBuf ) -> anyhow:: Result < ( ) > {
297280 if let Err ( _e) = std:: fs:: copy ( ext_path, ext_dir) {
298281 #[ cfg( unix) ]
299282 {
300- elevate:: escalate_if_needed ( ) . expect ( "sudo failed" ) ;
283+ let _ = std:: process:: Command :: new ( "sudo" )
284+ . arg ( "cp" )
285+ . arg ( ext_path)
286+ . arg ( ext_dir)
287+ . status ( ) ?;
301288 }
302- std:: fs:: copy ( ext_path, ext_dir) ?;
303289 }
304290 Ok ( ( ) )
305291}
@@ -395,28 +381,28 @@ impl Remove {
395381 bail ! ( "Installation cancelled." ) ;
396382 }
397383
398- std:: fs:: remove_file ( ext_path) . with_context ( || "Failed to remove extension" ) ?;
399-
400- if let Some ( php_ini) = php_ini. filter ( |path| path. is_file ( ) ) {
401- let mut file = OpenOptions :: new ( )
402- . read ( true )
403- . write ( true )
404- . create ( true )
405- . truncate ( false )
406- . open ( php_ini)
407- . with_context ( || "Failed to open `php.ini`" ) ?;
408-
409- let mut new_lines = vec ! [ ] ;
410- for line in BufReader :: new ( & file) . lines ( ) {
411- let line = line. with_context ( || "Failed to read line from `php.ini`" ) ?;
412- if !line. contains ( & ext_file) {
413- new_lines. push ( line) ;
414- }
384+ if let Err ( _e) = std:: fs:: remove_file ( & ext_path) {
385+ #[ cfg( unix) ]
386+ {
387+ let _ = std:: process:: Command :: new ( "sudo" )
388+ . arg ( "rm" )
389+ . arg ( "-f" )
390+ . arg ( & ext_path)
391+ . status ( ) ?;
415392 }
393+ }
394+ anyhow:: ensure!( !ext_path. is_file( ) , "Failed to remove {ext_path:?}" ) ;
416395
417- file. rewind ( ) ?;
418- file. set_len ( 0 ) ?;
419- file. write ( new_lines. join ( "\n " ) . as_bytes ( ) )
396+ // modify the ini file
397+ if let Some ( php_ini) = php_ini. filter ( |path| path. is_file ( ) ) {
398+ let ini_file_content = std:: fs:: read_to_string ( & php_ini) ?;
399+
400+ let new_ini_content = ini_file_content
401+ . lines ( )
402+ . filter ( |x| x. contains ( & ext_file) )
403+ . collect :: < Vec < _ > > ( )
404+ . join ( "\n " ) ;
405+ write_to_file ( new_ini_content, & php_ini)
420406 . with_context ( || "Failed to update `php.ini`" ) ?;
421407 }
422408
@@ -612,3 +598,34 @@ fn build_ext(
612598
613599 bail ! ( "Failed to retrieve extension path from artifact" )
614600}
601+
602+ // Write given string to a given filepath.
603+ //
604+ // - Write to a temp file first.
605+ // - Copy the temp file to the given filepath.
606+ // - If it fails, move tempfile using `sudo mv`.
607+ //
608+ // TODO: Try with sudo when error is permission related.
609+ fn write_to_file ( content : String , filepath : & PathBuf ) -> anyhow:: Result < ( ) > {
610+ // write to a temp file
611+ let tempf = std:: env:: temp_dir ( ) . join ( "__tmp_cargo_php" ) ;
612+ std:: fs:: write ( & tempf, content) ?;
613+
614+ // Now move. `rename` will overwrite existing file.
615+ if std:: fs:: rename ( & tempf, filepath) . is_err ( ) {
616+ #[ cfg( unix) ]
617+ {
618+ // if not successful, try with sudo on unix.
619+ let _ = std:: process:: Command :: new ( "sudo" )
620+ . arg ( "mv" )
621+ . arg ( & tempf)
622+ . arg ( filepath)
623+ . status ( ) ?;
624+ }
625+
626+ #[ cfg( not( unix) ) ]
627+ anyhow:: bail!( "failed to write to {filepath:?}" ) ;
628+ }
629+
630+ Ok ( ( ) )
631+ }
0 commit comments