mirror of
https://github.com/rui314/mold.git
synced 2026-11-07 09:16:04 -10:00
Expand response files in mold-common for both linkers
The ELF and Mach-O linkers each had a copy of the same response file reader. They now share mold-common's expand_response_files. Its macho flag keeps the Mach-O linker's one difference: an argument starting with @rpath, @loader_path or @executable_path is a dylib path, not a response file. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
ad4102263d
commit
eb21f960fd
6 files changed
+125
-207
No files matched your search
@@ -22,6 +22,7 @@ pub mod path;
|
||||
pub mod perf;
|
||||
mod prefetch;
|
||||
pub mod record;
|
||||
pub mod response_file;
|
||||
pub mod siphash;
|
||||
pub mod subprocess;
|
||||
pub mod tar;
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
//! Response files. If a command line argument is in the form of
|
||||
//! `@path/to/some/file` (i.e. it starts with an at sign), the linker reads
|
||||
//! the given file and interprets its contents as a list of command line
|
||||
//! arguments. A file containing command line arguments is called a
|
||||
//! "response file".
|
||||
//!
|
||||
//! A response file is often used to pass a very large number of arguments
|
||||
//! to the linker without exceeding the kernel's command line length limit.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::Path;
|
||||
|
||||
use bstr::ByteVec;
|
||||
|
||||
use crate::bytes::{display, is_space, os_str};
|
||||
use crate::fatal;
|
||||
use crate::mapped_file::MappedFile;
|
||||
|
||||
/// Replaces each "@path/to/some/text/file" argument with the arguments
|
||||
/// the file holds. For the Mach-O linker (`macho`), an argument starting
|
||||
/// with "@rpath", "@loader_path" or "@executable_path" is a dylib path,
|
||||
/// not a response file, wherever it is, even an option's argument.
|
||||
pub fn expand_response_files(argv: Vec<OsString>, macho: bool) -> Vec<Cow<'static, OsStr>> {
|
||||
let mut args = Vec::new();
|
||||
for arg in argv {
|
||||
if let Some(path) = response_file(arg.as_encoded_bytes(), macho) {
|
||||
args.extend(read_response_file(path, 1, macho));
|
||||
} else {
|
||||
args.push(Cow::Owned(arg));
|
||||
}
|
||||
}
|
||||
args
|
||||
}
|
||||
|
||||
/// The response file an argument names, if it names one (see
|
||||
/// expand_response_files).
|
||||
fn response_file(arg: &[u8], macho: bool) -> Option<&Path> {
|
||||
const DYLIB_PATHS: [&[u8]; 3] = [b"@rpath", b"@loader_path", b"@executable_path"];
|
||||
if macho && DYLIB_PATHS.iter().any(|prefix| arg.starts_with(prefix)) {
|
||||
return None;
|
||||
}
|
||||
arg.strip_prefix(b"@").map(|path| Path::new(os_str(path)))
|
||||
}
|
||||
|
||||
/// Opens a response file, tokenizes its contents, and returns the tokens.
|
||||
fn read_response_file(path: &Path, depth: usize, macho: bool) -> Vec<Cow<'static, OsStr>> {
|
||||
if depth > 10 {
|
||||
fatal!("{}: response file nesting too deep", path.display());
|
||||
}
|
||||
|
||||
let mf = MappedFile::must_open(path);
|
||||
mf.set_dependency(false);
|
||||
let data = mf.data();
|
||||
|
||||
// Arguments are passed on as C strings, e.g. to the LTO plugin, so they
|
||||
// must not contain a NUL byte. Arguments given by the OS never do.
|
||||
if data.contains(&0) {
|
||||
fatal!("{}: response file contains a NUL byte", path.display());
|
||||
}
|
||||
|
||||
let mut expanded = Vec::new();
|
||||
let mut i = 0;
|
||||
|
||||
while i < data.len() {
|
||||
if is_space(data[i]) {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Plain tokens can borrow the mapping, which lives for the complete
|
||||
// link. Copy only when removing quotes or backslashes.
|
||||
let start = i;
|
||||
while i < data.len() && !is_space(data[i]) && !matches!(data[i], b'\\' | b'\'' | b'"') {
|
||||
i += 1;
|
||||
}
|
||||
let mut tok = Cow::Borrowed(&data[start..i]);
|
||||
let mut quote = None;
|
||||
while i < data.len() {
|
||||
let c = data[i];
|
||||
if c == b'\\' {
|
||||
if i + 1 == data.len() {
|
||||
fatal!("{}: premature end of input", path.display());
|
||||
}
|
||||
tok.to_mut().push(data[i + 1]);
|
||||
i += 2;
|
||||
} else if let Some(q) = quote {
|
||||
if c == q {
|
||||
quote = None;
|
||||
} else {
|
||||
tok.to_mut().push(c);
|
||||
}
|
||||
i += 1;
|
||||
} else if c == b'\'' || c == b'"' {
|
||||
quote = Some(c);
|
||||
i += 1;
|
||||
} else if is_space(c) {
|
||||
break;
|
||||
} else {
|
||||
tok.to_mut().push(c);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
if quote.is_some() {
|
||||
fatal!("{}: premature end of input", path.display());
|
||||
}
|
||||
if let Some(nested) = response_file(&tok, macho) {
|
||||
expanded.extend(read_response_file(nested, depth + 1, macho));
|
||||
} else {
|
||||
expanded.push(match tok {
|
||||
Cow::Borrowed(bytes) => Cow::Borrowed(os_str(bytes)),
|
||||
Cow::Owned(bytes) => Cow::Owned(
|
||||
bytes
|
||||
.into_os_string()
|
||||
.unwrap_or_else(|e| fatal!("invalid OS string: {}", display(e.as_bytes()))),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
expanded
|
||||
}
|
||||
+1
-100
@@ -7,7 +7,7 @@ use std::io::{IsTerminal, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use bstr::{ByteSlice, ByteVec};
|
||||
use bstr::ByteSlice;
|
||||
use mold_common::bits::align_down;
|
||||
use mold_common::bytes::{display, is_space, os_str};
|
||||
use mold_common::error::strerror;
|
||||
@@ -739,105 +739,6 @@ pub struct TargetTraits {
|
||||
pub page_size: u64,
|
||||
}
|
||||
|
||||
// If a command line argument is in the form of `@path/to/some/file` (i.e.
|
||||
// it starts with an at sign), the linker reads the given file and
|
||||
// interprets its contents as a list of command line arguments. A file
|
||||
// containing command line arguments is called a "response file".
|
||||
//
|
||||
// A response file is often used to pass a very large number of arguments
|
||||
// to the linker without exceeding the kernel's command line length limit.
|
||||
//
|
||||
// This function opens a given file, tokenizes its contents, and returns a
|
||||
// list of tokens.
|
||||
fn read_response_file(path: &Path, depth: usize) -> Vec<Cow<'static, OsStr>> {
|
||||
if depth > 10 {
|
||||
fatal!("{}: response file nesting too deep", path.display());
|
||||
}
|
||||
|
||||
let mf = MappedFile::must_open(path);
|
||||
mf.set_dependency(false);
|
||||
let data = mf.data();
|
||||
|
||||
// Arguments are passed on as C strings, e.g. to the LTO plugin, so they
|
||||
// must not contain a NUL byte. Arguments given by the OS never do.
|
||||
if data.contains(&0) {
|
||||
fatal!("{}: response file contains a NUL byte", path.display());
|
||||
}
|
||||
|
||||
let mut expanded = Vec::new();
|
||||
let mut i = 0;
|
||||
|
||||
while i < data.len() {
|
||||
if is_space(data[i]) {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Plain tokens can borrow the mapping, which lives for the complete
|
||||
// link. Copy only when removing quotes or backslashes.
|
||||
let start = i;
|
||||
while i < data.len() && !is_space(data[i]) && !matches!(data[i], b'\\' | b'\'' | b'"') {
|
||||
i += 1;
|
||||
}
|
||||
let mut tok = Cow::Borrowed(&data[start..i]);
|
||||
let mut quote = None;
|
||||
while i < data.len() {
|
||||
let c = data[i];
|
||||
if c == b'\\' {
|
||||
if i + 1 == data.len() {
|
||||
fatal!("{}: premature end of input", path.display());
|
||||
}
|
||||
tok.to_mut().push(data[i + 1]);
|
||||
i += 2;
|
||||
} else if let Some(q) = quote {
|
||||
if c == q {
|
||||
quote = None;
|
||||
} else {
|
||||
tok.to_mut().push(c);
|
||||
}
|
||||
i += 1;
|
||||
} else if c == b'\'' || c == b'"' {
|
||||
quote = Some(c);
|
||||
i += 1;
|
||||
} else if is_space(c) {
|
||||
break;
|
||||
} else {
|
||||
tok.to_mut().push(c);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
if quote.is_some() {
|
||||
fatal!("{}: premature end of input", path.display());
|
||||
}
|
||||
if let Some(nested) = tok.strip_prefix(b"@") {
|
||||
expanded.extend(read_response_file(Path::new(os_str(nested)), depth + 1));
|
||||
} else {
|
||||
expanded.push(match tok {
|
||||
Cow::Borrowed(bytes) => Cow::Borrowed(os_str(bytes)),
|
||||
Cow::Owned(bytes) => Cow::Owned(
|
||||
bytes
|
||||
.into_os_string()
|
||||
.unwrap_or_else(|e| fatal!("invalid OS string: {}", display(e.as_bytes()))),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
expanded
|
||||
}
|
||||
|
||||
// Replace "@path/to/some/text/file" with its file contents.
|
||||
pub fn expand_response_files(argv: Vec<OsString>) -> Vec<Cow<'static, OsStr>> {
|
||||
let mut args = Vec::new();
|
||||
for arg in argv {
|
||||
if let Some(path) = arg.as_encoded_bytes().strip_prefix(b"@") {
|
||||
args.extend(read_response_file(Path::new(os_str(path)), 1));
|
||||
} else {
|
||||
args.push(Cow::Owned(arg));
|
||||
}
|
||||
}
|
||||
args
|
||||
}
|
||||
|
||||
// This function matches a command line argument against an option
|
||||
// name and, on success, returns the remainder of the argument. For
|
||||
// example, matching "--foo=bar" against "foo" yields "=bar", and
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ pub fn main(
|
||||
let orig_cwd = std::env::current_dir().ok();
|
||||
|
||||
// Expand response files
|
||||
let cmdline: Arc<[_]> = cmdline::expand_response_files(argv).into();
|
||||
let cmdline: Arc<[_]> = mold_common::response_file::expand_response_files(argv, false).into();
|
||||
|
||||
// Parse with an enabled target's defaults; if the target turns out to
|
||||
// be different, start over with the right one.
|
||||
|
||||
@@ -19,7 +19,6 @@ use mold_common::{fatal, warn};
|
||||
use crate::arch::Target;
|
||||
use crate::context::Context;
|
||||
use crate::macho::*;
|
||||
use crate::mapped_file::MappedFile;
|
||||
use crate::reader::under_root;
|
||||
|
||||
/// The Apple ld64 version whose command line this linker implements,
|
||||
@@ -1334,110 +1333,6 @@ fn sectcreate_name(kind: &str, name: &[u8]) -> Vec<u8> {
|
||||
cut
|
||||
}
|
||||
|
||||
/// The response file an argument names, if it names one: an argument
|
||||
/// starting with '@', but a dylib path starting "@rpath", "@loader_path"
|
||||
/// or "@executable_path", wherever it is, even an option's argument.
|
||||
fn response_file(arg: &[u8]) -> Option<&Path> {
|
||||
const DYLIB_PATHS: [&[u8]; 3] = [b"@rpath", b"@loader_path", b"@executable_path"];
|
||||
if DYLIB_PATHS.iter().any(|prefix| arg.starts_with(prefix)) {
|
||||
return None;
|
||||
}
|
||||
arg.strip_prefix(b"@").map(|path| Path::new(os_str(path)))
|
||||
}
|
||||
|
||||
// If a command line argument is in the form of `@path/to/some/file`
|
||||
// (i.e. it starts with an atsign), the linker reads the given file and
|
||||
// interprets its contents as a list of command line arguments. A file
|
||||
// containing command line arguments is called a "response file".
|
||||
//
|
||||
// A response file is often used to pass a very large number of arguments
|
||||
// to the linker without exceeding the kernel's command line length limit.
|
||||
//
|
||||
// This function opens a given file, tokenizes its contents, and returns a
|
||||
// list of tokens.
|
||||
fn read_response_file(path: &Path, depth: usize) -> Vec<Cow<'static, OsStr>> {
|
||||
if depth > 10 {
|
||||
fatal!("{}: response file nesting too deep", path.display());
|
||||
}
|
||||
|
||||
let data = MappedFile::must_open(path).data();
|
||||
|
||||
// Arguments are passed on as C strings, e.g. to the LTO plugin, so they
|
||||
// must not contain a NUL byte. Arguments given by the OS never do.
|
||||
if data.contains(&0) {
|
||||
fatal!("{}: response file contains a NUL byte", path.display());
|
||||
}
|
||||
|
||||
let mut expanded = Vec::new();
|
||||
let mut i = 0;
|
||||
|
||||
while i < data.len() {
|
||||
if is_space(data[i]) {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Plain tokens can borrow the mapping, which lives for the complete
|
||||
// link. Copy only when removing quotes or backslashes.
|
||||
let start = i;
|
||||
while i < data.len() && !is_space(data[i]) && !matches!(data[i], b'\\' | b'\'' | b'"') {
|
||||
i += 1;
|
||||
}
|
||||
let mut tok = Cow::Borrowed(&data[start..i]);
|
||||
let mut quote = None;
|
||||
while i < data.len() {
|
||||
let c = data[i];
|
||||
if c == b'\\' {
|
||||
if i + 1 == data.len() {
|
||||
fatal!("{}: premature end of input", path.display());
|
||||
}
|
||||
tok.to_mut().push(data[i + 1]);
|
||||
i += 2;
|
||||
} else if let Some(q) = quote {
|
||||
if c == q {
|
||||
quote = None;
|
||||
} else {
|
||||
tok.to_mut().push(c);
|
||||
}
|
||||
i += 1;
|
||||
} else if c == b'\'' || c == b'"' {
|
||||
quote = Some(c);
|
||||
i += 1;
|
||||
} else if is_space(c) {
|
||||
break;
|
||||
} else {
|
||||
tok.to_mut().push(c);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
if quote.is_some() {
|
||||
fatal!("{}: premature end of input", path.display());
|
||||
}
|
||||
if let Some(nested) = response_file(&tok) {
|
||||
expanded.extend(read_response_file(nested, depth + 1));
|
||||
} else {
|
||||
expanded.push(match tok {
|
||||
Cow::Borrowed(bytes) => Cow::Borrowed(os_str(bytes)),
|
||||
Cow::Owned(bytes) => Cow::Owned(os_str(&bytes).to_owned()),
|
||||
});
|
||||
}
|
||||
}
|
||||
expanded
|
||||
}
|
||||
|
||||
// Replace "@path/to/some/text/file" with its file contents.
|
||||
pub fn expand_response_files(argv: Vec<OsString>) -> Vec<Cow<'static, OsStr>> {
|
||||
let mut args = Vec::new();
|
||||
for arg in argv {
|
||||
if let Some(path) = response_file(arg.as_encoded_bytes()) {
|
||||
args.extend(read_response_file(path, 1));
|
||||
} else {
|
||||
args.push(Cow::Owned(arg));
|
||||
}
|
||||
}
|
||||
args
|
||||
}
|
||||
|
||||
/// Reads a -filelist file: one input path per line, in whatever bytes
|
||||
/// the file system uses, optionally under a directory given after a
|
||||
/// comma in the option's argument. Returns the file's path and the
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ pub fn main(
|
||||
initial_target: &str,
|
||||
link_for_target: impl Fn(&str, Cmdline) -> LinkResult,
|
||||
) -> i32 {
|
||||
let cmdline: Cmdline = cmdline::expand_response_files(argv).into();
|
||||
let cmdline: Cmdline = mold_common::response_file::expand_response_files(argv, true).into();
|
||||
|
||||
// Parse with an enabled target's defaults; if the target turns out to
|
||||
// be different, start over with the right one.
|
||||
|
||||
Reference in new issue
Block a user