feat: build system transition to release fork + archive hardening
Release fork infrastructure: - REDBEAR_RELEASE=0.1.1 with offline enforcement (fetch/distclean/unfetch blocked) - 195 BLAKE3-verified source archives in standard format - Atomic provisioning via provision-release.sh (staging + .complete sentry) - 5-phase improvement plan: restore format auto-detection, source tree validation (validate-source-trees.py), archive-map.json, REPO_BINARY fallback Archive normalization: - Removed 87 duplicate/unversioned archives from shared pool - Regenerated all archives in consistent format with source/ + recipe.toml - BLAKE3SUMS and manifest.json generated from stable tarball set Patch management: - verify-patches.sh: pre-sync dry-run report (OK/REVERSED/CONFLICT) - 121 upstream-absorbed patches moved to absorbed/ directories - 43 active patches verified clean against rebased sources - Stress test: base updated to upstream HEAD, relibc reset and patched Compilation fixes: - relibc: Vec imports in redox-rt (proc.rs, lib.rs, sys.rs) - relibc: unsafe from_raw_parts in mod.rs (2024 edition) - fetch.rs: rev comparison handles short/full hash prefixes - kibi recipe: corrected rev mismatch New scripts: restore-sources.sh, provision-release.sh, verify-sources-archived.sh, check-upstream-releases.sh, validate-source-trees.py, verify-patches.sh, repair-archive-format.sh, generate-manifest.py Documentation: AGENTS.md, README.md, local/AGENTS.md updated for release fork model
This commit is contained in:
@@ -0,0 +1,479 @@
|
||||
diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs
|
||||
index bb9f39a3..b6f8711e 100644
|
||||
--- a/drivers/pcid/src/scheme.rs
|
||||
+++ b/drivers/pcid/src/scheme.rs
|
||||
@@ -1,28 +1,100 @@
|
||||
-use std::collections::{BTreeMap, VecDeque};
|
||||
+use std::collections::{BTreeMap, HashMap, VecDeque};
|
||||
+use std::fmt::Write;
|
||||
|
||||
use pci_types::{ConfigRegionAccess, PciAddress};
|
||||
use redox_scheme::scheme::SchemeSync;
|
||||
use redox_scheme::{CallerCtx, OpenResult};
|
||||
use scheme_utils::HandleMap;
|
||||
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
|
||||
-use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR};
|
||||
+use syscall::error::{
|
||||
+ Error, Result, EACCES, EALREADY, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EROFS,
|
||||
+};
|
||||
use syscall::flag::{MODE_CHR, MODE_DIR, O_DIRECTORY, O_STAT};
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
use syscall::ENOLCK;
|
||||
|
||||
use crate::cfg_access::Pcie;
|
||||
|
||||
+const PCIE_EXTENDED_CAPABILITY_AER: u16 = 0x0001;
|
||||
+
|
||||
+#[derive(Clone, Copy)]
|
||||
+enum AerRegisterName {
|
||||
+ UncorStatus,
|
||||
+ UncorMask,
|
||||
+ UncorSeverity,
|
||||
+ CorStatus,
|
||||
+ CorMask,
|
||||
+ Cap,
|
||||
+ HeaderLog,
|
||||
+}
|
||||
+
|
||||
+impl AerRegisterName {
|
||||
+ fn from_path(path: &str) -> Option<Self> {
|
||||
+ Some(match path {
|
||||
+ "uncor_status" => Self::UncorStatus,
|
||||
+ "uncor_mask" => Self::UncorMask,
|
||||
+ "uncor_severity" => Self::UncorSeverity,
|
||||
+ "cor_status" => Self::CorStatus,
|
||||
+ "cor_mask" => Self::CorMask,
|
||||
+ "cap" => Self::Cap,
|
||||
+ "header_log" => Self::HeaderLog,
|
||||
+ _ => return None,
|
||||
+ })
|
||||
+ }
|
||||
+
|
||||
+ const fn offset(self) -> u16 {
|
||||
+ match self {
|
||||
+ Self::UncorStatus => 0x00,
|
||||
+ Self::UncorMask => 0x04,
|
||||
+ Self::UncorSeverity => 0x08,
|
||||
+ Self::CorStatus => 0x0C,
|
||||
+ Self::CorMask => 0x10,
|
||||
+ Self::Cap => 0x14,
|
||||
+ Self::HeaderLog => 0x18,
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ const fn len(self) -> usize {
|
||||
+ match self {
|
||||
+ Self::HeaderLog => 16,
|
||||
+ _ => 4,
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
pub struct PciScheme {
|
||||
handles: HandleMap<HandleWrapper>,
|
||||
pub pcie: Pcie,
|
||||
pub tree: BTreeMap<PciAddress, crate::Func>,
|
||||
+ /// Maps device address string (e.g. "0000:00:14.0") to owning PID
|
||||
+ binds: HashMap<String, u32>,
|
||||
}
|
||||
enum Handle {
|
||||
- TopLevel { entries: Vec<String> },
|
||||
+ TopLevel {
|
||||
+ entries: Vec<String>,
|
||||
+ },
|
||||
Access,
|
||||
- Device,
|
||||
- Channel { addr: PciAddress, st: ChannelState },
|
||||
+ Device {
|
||||
+ addr: PciAddress,
|
||||
+ },
|
||||
+ Channel {
|
||||
+ addr: PciAddress,
|
||||
+ st: ChannelState,
|
||||
+ },
|
||||
SchemeRoot,
|
||||
+ /// Represents an open handle to a device's bind endpoint
|
||||
+ Bind {
|
||||
+ addr: PciAddress,
|
||||
+ },
|
||||
+ AerDir,
|
||||
+ Aer {
|
||||
+ addr: PciAddress,
|
||||
+ register: AerRegisterName,
|
||||
+ },
|
||||
+ /// Uevent surface for hotplug consumers. Opening uevent returns an object
|
||||
+ /// from which device add/remove events can be read. Since pcid currently
|
||||
+ /// only scans at startup, this surface is ready for hotplug polling consumers.
|
||||
+ Uevent,
|
||||
}
|
||||
struct HandleWrapper {
|
||||
inner: Handle,
|
||||
@@ -30,14 +102,23 @@ struct HandleWrapper {
|
||||
}
|
||||
impl Handle {
|
||||
fn is_file(&self) -> bool {
|
||||
- matches!(self, Self::Access | Self::Channel { .. })
|
||||
+ matches!(
|
||||
+ self,
|
||||
+ Self::Access
|
||||
+ | Self::Channel { .. }
|
||||
+ | Self::Bind { .. }
|
||||
+ | Self::Aer { .. }
|
||||
+ | Self::Uevent
|
||||
+ )
|
||||
}
|
||||
fn is_dir(&self) -> bool {
|
||||
!self.is_file()
|
||||
}
|
||||
- // TODO: capability rather than root
|
||||
fn requires_root(&self) -> bool {
|
||||
- matches!(self, Self::Access | Self::Channel { .. })
|
||||
+ matches!(
|
||||
+ self,
|
||||
+ Self::Access | Self::Channel { .. } | Self::Bind { .. }
|
||||
+ )
|
||||
}
|
||||
fn is_scheme_root(&self) -> bool {
|
||||
matches!(self, Self::SchemeRoot)
|
||||
@@ -49,7 +130,17 @@ enum ChannelState {
|
||||
AwaitingResponseRead(VecDeque<u8>),
|
||||
}
|
||||
|
||||
-const DEVICE_CONTENTS: &[&str] = &["channel"];
|
||||
+const DEVICE_CONTENTS: &[&str] = &["channel", "bind"];
|
||||
+const DEVICE_AER_CONTENTS: &[&str] = &["channel", "bind", "aer"];
|
||||
+const AER_CONTENTS: &[&str] = &[
|
||||
+ "uncor_status",
|
||||
+ "uncor_mask",
|
||||
+ "uncor_severity",
|
||||
+ "cor_status",
|
||||
+ "cor_mask",
|
||||
+ "cap",
|
||||
+ "header_log",
|
||||
+];
|
||||
|
||||
impl PciScheme {
|
||||
pub fn access(&mut self) -> usize {
|
||||
@@ -88,22 +179,25 @@ impl SchemeSync for PciScheme {
|
||||
let path = path.trim_matches('/');
|
||||
|
||||
let handle = if path.is_empty() {
|
||||
- Handle::TopLevel {
|
||||
- entries: self
|
||||
- .tree
|
||||
- .iter()
|
||||
- // FIXME remove replacement of : once the old scheme format is no longer supported.
|
||||
- .map(|(addr, _)| format!("{}", addr).replace(':', "--"))
|
||||
- .collect::<Vec<_>>(),
|
||||
- }
|
||||
+ let mut entries: Vec<String> = self
|
||||
+ .tree
|
||||
+ .iter()
|
||||
+ // FIXME remove replacement of : once the old scheme format is no longer supported.
|
||||
+ .map(|(addr, _)| format!("{}", addr).replace(':', "--"))
|
||||
+ .collect();
|
||||
+ entries.push(String::from("uevent"));
|
||||
+ entries.push(String::from("access"));
|
||||
+ Handle::TopLevel { entries }
|
||||
} else if path == "access" {
|
||||
Handle::Access
|
||||
+ } else if path == "uevent" {
|
||||
+ Handle::Uevent
|
||||
} else {
|
||||
let idx = path.find('/').unwrap_or(path.len());
|
||||
let (addr_str, after) = path.split_at(idx);
|
||||
let addr = parse_pci_addr(addr_str).ok_or(Error::new(ENOENT))?;
|
||||
|
||||
- self.parse_after_pci_addr(addr, after)?
|
||||
+ self.parse_after_pci_addr(addr, after, ctx)?
|
||||
};
|
||||
|
||||
let stat = flags & O_STAT != 0;
|
||||
@@ -131,8 +225,14 @@ impl SchemeSync for PciScheme {
|
||||
|
||||
let (len, mode) = match handle.inner {
|
||||
Handle::TopLevel { ref entries } => (entries.len(), MODE_DIR | 0o755),
|
||||
- Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755),
|
||||
- Handle::Access | Handle::Channel { .. } => (0, MODE_CHR | 0o600),
|
||||
+ Handle::Device { addr } => (
|
||||
+ Self::device_entries(&self.pcie, addr).len(),
|
||||
+ MODE_DIR | 0o755,
|
||||
+ ),
|
||||
+ Handle::AerDir => (AER_CONTENTS.len(), MODE_DIR | 0o755),
|
||||
+ Handle::Aer { register, .. } => (register.len(), MODE_CHR | 0o444),
|
||||
+ Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } => (0, MODE_CHR | 0o600),
|
||||
+ Handle::Uevent => (0, MODE_CHR | 0o644),
|
||||
Handle::SchemeRoot => return Err(Error::new(EBADF)),
|
||||
};
|
||||
stat.st_size = len as u64;
|
||||
@@ -143,7 +243,7 @@ impl SchemeSync for PciScheme {
|
||||
&mut self,
|
||||
id: usize,
|
||||
buf: &mut [u8],
|
||||
- _offset: u64,
|
||||
+ offset: u64,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
@@ -155,12 +255,45 @@ impl SchemeSync for PciScheme {
|
||||
|
||||
match handle.inner {
|
||||
Handle::TopLevel { .. } => Err(Error::new(EISDIR)),
|
||||
- Handle::Device => Err(Error::new(EISDIR)),
|
||||
+ Handle::Device { .. } | Handle::AerDir => Err(Error::new(EISDIR)),
|
||||
Handle::Channel {
|
||||
addr: _,
|
||||
ref mut st,
|
||||
} => Self::read_channel(st, buf),
|
||||
- Handle::SchemeRoot => Err(Error::new(EBADF)),
|
||||
+ Handle::Aer { addr, register } => {
|
||||
+ Self::read_aer_register(&self.pcie, addr, register, buf, offset)
|
||||
+ }
|
||||
+ Handle::Uevent => {
|
||||
+ // Uevent surface for hotplug polling consumers.
|
||||
+ // pcid currently only scans at startup, so return the current
|
||||
+ // device tree as "add" events. Consumers can poll and re-read
|
||||
+ // to check for new events.
|
||||
+ let mut o = String::new();
|
||||
+ for (a, f) in &self.tree {
|
||||
+ let _ = write!(
|
||||
+ o,
|
||||
+ "add device {:02x}:{:02x}.{:x}.{:x} vendor=0x{:04x} device=0x{:04x} class=0x{:02x}.{:02x}\n",
|
||||
+ a.segment(),
|
||||
+ a.bus(),
|
||||
+ a.device(),
|
||||
+ a.function(),
|
||||
+ f.inner.full_device_id.vendor_id,
|
||||
+ f.inner.full_device_id.device_id,
|
||||
+ f.inner.full_device_id.class,
|
||||
+ f.inner.full_device_id.subclass
|
||||
+ );
|
||||
+ }
|
||||
+ let b = o.as_bytes();
|
||||
+ let s = offset as usize;
|
||||
+ if s < b.len() {
|
||||
+ let n = (b.len() - s).min(buf.len());
|
||||
+ buf[..n].copy_from_slice(&b[s..s + n]);
|
||||
+ Ok(n)
|
||||
+ } else {
|
||||
+ Ok(0)
|
||||
+ }
|
||||
+ }
|
||||
+ Handle::SchemeRoot | Handle::Bind { .. } => Err(Error::new(EBADF)),
|
||||
_ => Err(Error::new(EBADF)),
|
||||
}
|
||||
}
|
||||
@@ -192,8 +325,15 @@ impl SchemeSync for PciScheme {
|
||||
}
|
||||
return Ok(buf);
|
||||
}
|
||||
- Handle::Device => DEVICE_CONTENTS,
|
||||
- Handle::Access | Handle::Channel { .. } => return Err(Error::new(ENOTDIR)),
|
||||
+ Handle::Device { addr } => Self::device_entries(&self.pcie, addr),
|
||||
+ Handle::AerDir => AER_CONTENTS,
|
||||
+ Handle::Access
|
||||
+ | Handle::Channel { .. }
|
||||
+ | Handle::Bind { .. }
|
||||
+ | Handle::Aer { .. }
|
||||
+ | Handle::Uevent => {
|
||||
+ return Err(Error::new(ENOTDIR));
|
||||
+ }
|
||||
Handle::SchemeRoot => return Err(Error::new(EBADF)),
|
||||
};
|
||||
|
||||
@@ -226,6 +366,7 @@ impl SchemeSync for PciScheme {
|
||||
Handle::Channel { addr, ref mut st } => {
|
||||
Self::write_channel(&self.pcie, &mut self.tree, addr, st, buf)
|
||||
}
|
||||
+ Handle::Aer { .. } => Err(Error::new(EROFS)),
|
||||
|
||||
_ => Err(Error::new(EBADF)),
|
||||
}
|
||||
@@ -316,6 +457,16 @@ impl SchemeSync for PciScheme {
|
||||
func.enabled = false;
|
||||
}
|
||||
}
|
||||
+ Some(HandleWrapper {
|
||||
+ inner: Handle::Bind { addr },
|
||||
+ ..
|
||||
+ }) => {
|
||||
+ let addr_str = format!("{}", addr);
|
||||
+ if let Some(&owner_pid) = self.binds.get(&addr_str) {
|
||||
+ log::info!("pcid: device {} unbound by pid {}", addr_str, owner_pid);
|
||||
+ }
|
||||
+ self.binds.remove(&addr_str);
|
||||
+ }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -327,36 +478,154 @@ impl PciScheme {
|
||||
handles: HandleMap::new(),
|
||||
pcie,
|
||||
tree: BTreeMap::new(),
|
||||
+ binds: HashMap::new(),
|
||||
+ }
|
||||
+ }
|
||||
+ fn device_entries(pcie: &Pcie, addr: PciAddress) -> &'static [&'static str] {
|
||||
+ if Self::find_pcie_extended_capability(pcie, addr, PCIE_EXTENDED_CAPABILITY_AER).is_some() {
|
||||
+ DEVICE_AER_CONTENTS
|
||||
+ } else {
|
||||
+ DEVICE_CONTENTS
|
||||
}
|
||||
}
|
||||
- fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str) -> Result<Handle> {
|
||||
+ fn find_pcie_extended_capability(
|
||||
+ pcie: &Pcie,
|
||||
+ addr: PciAddress,
|
||||
+ capability_id: u16,
|
||||
+ ) -> Option<u16> {
|
||||
+ if !pcie.has_extended_config(addr) {
|
||||
+ return None;
|
||||
+ }
|
||||
+
|
||||
+ let mut offset = 0x100_u16;
|
||||
+
|
||||
+ while offset <= 0xFFC {
|
||||
+ let header = unsafe { pcie.read(addr, offset) };
|
||||
+ if header == 0 || header == u32::MAX {
|
||||
+ return None;
|
||||
+ }
|
||||
+
|
||||
+ if (header & 0xFFFF) as u16 == capability_id {
|
||||
+ return Some(offset);
|
||||
+ }
|
||||
+
|
||||
+ let next = ((header >> 20) & 0xFFF) as u16;
|
||||
+ if next < 0x100 || next <= offset || next > 0xFFC || next % 4 != 0 {
|
||||
+ return None;
|
||||
+ }
|
||||
+ offset = next;
|
||||
+ }
|
||||
+
|
||||
+ None
|
||||
+ }
|
||||
+ fn read_file_bytes(data: &[u8], buf: &mut [u8], offset: u64) -> Result<usize> {
|
||||
+ let Ok(offset) = usize::try_from(offset) else {
|
||||
+ return Ok(0);
|
||||
+ };
|
||||
+ if offset >= data.len() {
|
||||
+ return Ok(0);
|
||||
+ }
|
||||
+
|
||||
+ let count = std::cmp::min(buf.len(), data.len() - offset);
|
||||
+ buf[..count].copy_from_slice(&data[offset..offset + count]);
|
||||
+ Ok(count)
|
||||
+ }
|
||||
+ fn read_aer_register(
|
||||
+ pcie: &Pcie,
|
||||
+ addr: PciAddress,
|
||||
+ register: AerRegisterName,
|
||||
+ buf: &mut [u8],
|
||||
+ offset: u64,
|
||||
+ ) -> Result<usize> {
|
||||
+ let Some(aer_base) =
|
||||
+ Self::find_pcie_extended_capability(pcie, addr, PCIE_EXTENDED_CAPABILITY_AER)
|
||||
+ else {
|
||||
+ return Err(Error::new(ENOENT));
|
||||
+ };
|
||||
+
|
||||
+ let mut data = [0_u8; 16];
|
||||
+ for (index, chunk) in data[..register.len()].chunks_exact_mut(4).enumerate() {
|
||||
+ let index = u16::try_from(index).map_err(|_| Error::new(EIO))?;
|
||||
+ let value = unsafe { pcie.read(addr, aer_base + register.offset() + index * 4) };
|
||||
+ chunk.copy_from_slice(&value.to_le_bytes());
|
||||
+ }
|
||||
+
|
||||
+ Self::read_file_bytes(&data[..register.len()], buf, offset)
|
||||
+ }
|
||||
+ fn parse_after_pci_addr(
|
||||
+ &mut self,
|
||||
+ addr: PciAddress,
|
||||
+ after: &str,
|
||||
+ ctx: &CallerCtx,
|
||||
+ ) -> Result<Handle> {
|
||||
if after.chars().next().map_or(false, |c| c != '/') {
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
let func = self.tree.get_mut(&addr).ok_or(Error::new(ENOENT))?;
|
||||
|
||||
Ok(if after.is_empty() {
|
||||
- Handle::Device
|
||||
+ Handle::Device { addr }
|
||||
} else {
|
||||
let path = &after[1..];
|
||||
|
||||
- match path {
|
||||
- "channel" => {
|
||||
- if func.enabled {
|
||||
- return Err(Error::new(ENOLCK));
|
||||
+ if path == "aer" {
|
||||
+ if Self::find_pcie_extended_capability(
|
||||
+ &self.pcie,
|
||||
+ addr,
|
||||
+ PCIE_EXTENDED_CAPABILITY_AER,
|
||||
+ )
|
||||
+ .is_none()
|
||||
+ {
|
||||
+ return Err(Error::new(ENOENT));
|
||||
+ }
|
||||
+ Handle::AerDir
|
||||
+ } else if let Some(register_name) = path.strip_prefix("aer/") {
|
||||
+ let register =
|
||||
+ AerRegisterName::from_path(register_name).ok_or(Error::new(ENOENT))?;
|
||||
+ if Self::find_pcie_extended_capability(
|
||||
+ &self.pcie,
|
||||
+ addr,
|
||||
+ PCIE_EXTENDED_CAPABILITY_AER,
|
||||
+ )
|
||||
+ .is_none()
|
||||
+ {
|
||||
+ return Err(Error::new(ENOENT));
|
||||
+ }
|
||||
+ Handle::Aer { addr, register }
|
||||
+ } else {
|
||||
+ match path {
|
||||
+ "channel" => {
|
||||
+ if func.enabled {
|
||||
+ return Err(Error::new(ENOLCK));
|
||||
+ }
|
||||
+ func.inner.legacy_interrupt_line = crate::enable_function(
|
||||
+ &self.pcie,
|
||||
+ &mut func.endpoint_header,
|
||||
+ &mut func.capabilities,
|
||||
+ );
|
||||
+ func.enabled = true;
|
||||
+ Handle::Channel {
|
||||
+ addr,
|
||||
+ st: ChannelState::AwaitingData,
|
||||
+ }
|
||||
}
|
||||
- func.inner.legacy_interrupt_line = crate::enable_function(
|
||||
- &self.pcie,
|
||||
- &mut func.endpoint_header,
|
||||
- &mut func.capabilities,
|
||||
- );
|
||||
- func.enabled = true;
|
||||
- Handle::Channel {
|
||||
- addr,
|
||||
- st: ChannelState::AwaitingData,
|
||||
+ "bind" => {
|
||||
+ let addr_str = format!("{}", addr);
|
||||
+ if let Some(&owner_pid) = self.binds.get(&addr_str) {
|
||||
+ log::info!(
|
||||
+ "pcid: device {} already bound by pid {}",
|
||||
+ addr_str,
|
||||
+ owner_pid
|
||||
+ );
|
||||
+ return Err(Error::new(EALREADY));
|
||||
+ }
|
||||
+ let caller_pid = u32::try_from(ctx.pid).map_err(|_| Error::new(EINVAL))?;
|
||||
+ self.binds.insert(addr_str.clone(), caller_pid);
|
||||
+ log::info!("pcid: device {} bound by pid {}", addr_str, caller_pid);
|
||||
+ Handle::Bind { addr }
|
||||
}
|
||||
+ _ => return Err(Error::new(ENOENT)),
|
||||
}
|
||||
- _ => return Err(Error::new(ENOENT)),
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user