Skip to content

Commit

Permalink
fix: allow relinking on read-only files (#1231)
Browse files Browse the repository at this point in the history
  • Loading branch information
wolfv authored Dec 4, 2024
1 parent 0dbe0a6 commit 1a71f62
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 3 deletions.
3 changes: 3 additions & 0 deletions src/linux/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use std::path::{Path, PathBuf};
use crate::post_process::relink::{RelinkError, Relinker};
use crate::recipe::parser::GlobVec;
use crate::system_tools::{SystemTools, Tool};
use crate::unix::permission_guard::{PermissionGuard, READ_WRITE};
use crate::utils::to_lexical_absolute;

/// A linux shared object (ELF)
Expand Down Expand Up @@ -214,6 +215,8 @@ impl Relinker for SharedObject {
// keep only first unique item
final_rpaths = final_rpaths.into_iter().unique().collect();

let _permission_guard = PermissionGuard::new(&self.path, READ_WRITE)?;

// run builtin relink. if it fails, try patchelf
if builtin_relink(&self.path, &final_rpaths).is_err() {
call_patchelf(&self.path, &final_rpaths, system_tools)?;
Expand Down
10 changes: 7 additions & 3 deletions src/macos/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use std::path::{Path, PathBuf};
use crate::post_process::relink::{RelinkError, Relinker};
use crate::recipe::parser::GlobVec;
use crate::system_tools::{SystemTools, Tool};
use crate::unix::permission_guard::{PermissionGuard, READ_WRITE};
use crate::utils::to_lexical_absolute;

/// A macOS dylib (Mach-O)
Expand Down Expand Up @@ -257,8 +258,11 @@ impl Relinker for Dylib {
}

if modified {
// run builtin relink. if it fails, try install_name_tool
if relink(&self.path, &changes).is_err() {
let _permission_guard = PermissionGuard::new(&self.path, READ_WRITE)?;
// run builtin relink. If it fails, try install_name_tool
if let Err(e) = relink(&self.path, &changes) {
assert!(self.path.exists());
tracing::warn!("Builtin relink failed {:?}, trying install_name_tool", e);
install_name_tool(&self.path, &changes, system_tools)?;
}
codesign(&self.path, system_tools)?;
Expand Down Expand Up @@ -402,7 +406,7 @@ fn relink(dylib_path: &Path, changes: &DylibChanges) -> Result<(), RelinkError>
let new_path = new_path.as_bytes();

if new_path.len() > old_path.len() {
tracing::debug!(
tracing::info!(
"new path is longer than old path: {} > {}",
new_path.len(),
old_path.len()
Expand Down
1 change: 1 addition & 0 deletions src/unix/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod env;
pub mod permission_guard;
112 changes: 112 additions & 0 deletions src/unix/permission_guard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//! Implementation of the `PermissionGuard` struct.
/// User read/write permissions (0o600).
pub const READ_WRITE: u32 = 0o600;

#[cfg(unix)]
mod unix {
use std::fs::Permissions;
use std::io;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};

/// A guard that modifies the permissions of a file and restores them when dropped.
pub struct PermissionGuard {
/// The path to the file.
path: PathBuf,
/// The original permissions of the file.
original_permissions: Permissions,
}

impl PermissionGuard {
/// Create a new `PermissionGuard` for the given path with the given permissions.
pub fn new<P: AsRef<Path>>(path: P, permissions: u32) -> io::Result<Self> {
let path = path.as_ref().to_path_buf();
let metadata = std::fs::metadata(&path)?;
let original_permissions = metadata.permissions();

let new_permissions = Permissions::from_mode(original_permissions.mode() | permissions);

// Set new permissions
std::fs::set_permissions(&path, new_permissions)?;

Ok(Self {
path,
original_permissions,
})
}
}

impl Drop for PermissionGuard {
fn drop(&mut self) {
if self.path.exists() {
if let Err(e) =
std::fs::set_permissions(&self.path, self.original_permissions.clone())
{
eprintln!("Failed to restore file permissions: {}", e);
}
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use std::fs::{self, File};
use tempfile::tempdir;

#[test]
fn test_permission_guard_modifies_and_restores() -> io::Result<()> {
let dir = tempdir()?;
let test_file = dir.path().join("test-restore.txt");
File::create(&test_file)?;

// Set initial permissions to 0o002 so we can check if the guard modifies them
fs::set_permissions(&test_file, Permissions::from_mode(0o002))?;
let initial_mode = fs::metadata(&test_file)?.permissions().mode();

// Create scope for PermissionGuard
{
let _guard = PermissionGuard::new(&test_file, 0o200)?; // Write permission

// Check permissions were modified
let modified_mode = fs::metadata(&test_file)?.permissions().mode();
assert_ne!(initial_mode, modified_mode);
assert_eq!(modified_mode & 0o200, 0o200);
}

// Check permissions were restored after guard dropped
let final_mode = fs::metadata(&test_file)?.permissions().mode();
assert_eq!(initial_mode, final_mode);

Ok(())
}

#[test]
fn test_permission_guard_nonexistent_file() {
let result = PermissionGuard::new("nonexistent_file", 0o777);
assert!(result.is_err());
}
}
}

#[cfg(windows)]
mod windows {
use std::io;
use std::path::Path;

pub struct PermissionGuard;

impl PermissionGuard {
/// Create a new `PermissionGuard` for the given path with the given permissions. Does nothing on Windows.
pub fn new<P: AsRef<Path>>(_path: P, _permissions: u32) -> io::Result<Self> {
Ok(Self)
}
}
}

#[cfg(unix)]
pub use self::unix::PermissionGuard;

#[cfg(windows)]
pub use self::windows::PermissionGuard;

0 comments on commit 1a71f62

Please sign in to comment.