@@ -9,8 +9,7 @@ use clap::Parser;
9
9
use dialoguer:: { Confirm , Select } ;
10
10
11
11
use std:: {
12
- fs:: OpenOptions ,
13
- io:: { BufRead , BufReader , Seek , Write } ,
12
+ io:: BufReader ,
14
13
path:: PathBuf ,
15
14
process:: { Command , Stdio } ,
16
15
} ;
@@ -239,67 +238,54 @@ impl Install {
239
238
} ) ?;
240
239
241
240
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 )
243
242
. with_context ( || "Failed to update `php.ini`" ) ?;
244
243
}
245
244
246
245
Ok ( ( ) )
247
246
}
248
247
}
249
248
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) ?;
267
255
let mut ext_line = format ! ( "extension={ext_name}" ) ;
268
256
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 {
272
259
if line. contains ( & ext_line) {
273
260
bail ! ( "Extension already enabled." ) ;
274
261
}
275
-
276
- new_lines. push ( line) ;
277
262
}
278
263
279
264
// Comment out extension if user specifies disable flag
280
265
if disable {
281
266
ext_line. insert ( 0 , ';' ) ;
282
267
}
283
268
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) ?;
288
271
Ok ( ( ) )
289
272
}
290
273
291
- // Copy extension, if fails, try with sudo again .
274
+ // Copy extension, if fails, try with sudo cp .
292
275
//
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
295
278
// https://doc.rust-lang.org/std/fs/struct.Permissions.html#method.readonly
296
279
fn copy_extension ( ext_path : & Utf8PathBuf , ext_dir : & PathBuf ) -> anyhow:: Result < ( ) > {
297
280
if let Err ( _e) = std:: fs:: copy ( ext_path, ext_dir) {
298
281
#[ cfg( unix) ]
299
282
{
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 ( ) ?;
301
288
}
302
- std:: fs:: copy ( ext_path, ext_dir) ?;
303
289
}
304
290
Ok ( ( ) )
305
291
}
@@ -395,28 +381,28 @@ impl Remove {
395
381
bail ! ( "Installation cancelled." ) ;
396
382
}
397
383
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 ( ) ?;
415
392
}
393
+ }
394
+ anyhow:: ensure!( !ext_path. is_file( ) , "Failed to remove {ext_path:?}" ) ;
416
395
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)
420
406
. with_context ( || "Failed to update `php.ini`" ) ?;
421
407
}
422
408
@@ -612,3 +598,34 @@ fn build_ext(
612
598
613
599
bail ! ( "Failed to retrieve extension path from artifact" )
614
600
}
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