Skip to content

Commit 8548205

Browse files
committed
Use sudo mv command when writing to file fails
1 parent a4c6d58 commit 8548205

File tree

1 file changed

+71
-54
lines changed

1 file changed

+71
-54
lines changed

crates/cli/src/lib.rs

Lines changed: 71 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ use clap::Parser;
99
use dialoguer::{Confirm, Select};
1010

1111
use 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
296279
fn 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

Comments
 (0)