linker_layout/
lib.rs

1//! This crate defines a format for providing information about where a linker put stuff.
2
3use anyhow::Context;
4use anyhow::Result;
5use serde::Deserialize;
6use serde::Serialize;
7use std::io::Write;
8use std::ops::Range;
9use std::path::Path;
10use std::path::PathBuf;
11
12#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
13pub struct Layout {
14    /// The input files to the linker.
15    pub files: Vec<InputFile>,
16}
17
18#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
19pub struct InputFile {
20    /// Path to the input file on disk. In case of archives, multiple inputs may have the same
21    /// path.
22    pub path: PathBuf,
23
24    /// If the input is an archive, then contains information about where in the archive the file
25    /// came from.
26    pub archive_entry: Option<ArchiveEntryInfo>,
27
28    /// Sections that were written to the output. Indexes correspond to the sections in the input
29    /// file. Contains None for sections that were discarded or weren't fully copied.
30    pub sections: Vec<Option<Section>>,
31}
32
33#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
34pub struct ArchiveEntryInfo {
35    /// The range within the file that contains the archive entry (not including the entry header).
36    pub range: Range<usize>,
37
38    pub identifier: Vec<u8>,
39}
40
41#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
42pub struct Section {
43    pub mem_range: Range<u64>,
44}
45
46impl Layout {
47    pub fn write(&self, writer: &mut impl Write) -> Result<()> {
48        postcard::to_io(self, writer)?;
49        Ok(())
50    }
51
52    pub fn to_bytes(&self) -> Result<Vec<u8>> {
53        Ok(postcard::to_stdvec(self)?)
54    }
55
56    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
57        postcard::from_bytes(bytes).context("Invalid linker layout")
58    }
59}
60
61#[must_use]
62pub fn layout_path(base_path: &Path) -> PathBuf {
63    // We always want to append, not use with_extension, since we don't want to remove any existing
64    // extension, otherwise we'd likely get collisions.
65    let mut s = base_path.as_os_str().to_owned();
66    s.push(".layout");
67    PathBuf::from(s)
68}
69
70impl std::fmt::Display for InputFile {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        self.path.display().fmt(f)?;
73        if let Some(e) = self.archive_entry.as_ref() {
74            write!(f, " @ {}", String::from_utf8_lossy(&e.identifier))?;
75        }
76        Ok(())
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_round_trip() {
86        let layout = Layout {
87            files: vec![InputFile {
88                path: PathBuf::new(),
89                archive_entry: None,
90                sections: vec![Some(Section { mem_range: 42..48 })],
91            }],
92        };
93        let bytes = layout.to_bytes().unwrap();
94        let layout2 = Layout::from_bytes(&bytes).unwrap();
95        assert_eq!(layout, layout2);
96    }
97}