Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: CI

on:
pull_request:
branches: [main]
push:
branches: [main]

permissions:
contents: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2

- name: cargo test
run: cargo test --locked

clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable
with:
components: clippy

- uses: Swatinem/rust-cache@v2

- name: cargo clippy (-D warnings)
run: cargo clippy --all-targets --locked -- -D warnings

build:
name: Build (release)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2

- name: cargo build --release
run: cargo build --release --locked
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@

### Added

- **GitHub Actions CI (#38).** Pull requests and pushes to `main` now run
`cargo test --locked`, `cargo clippy --all-targets --locked -- -D warnings` and
`cargo build --release --locked` on stable Rust with cached artifacts. The five
clippy warnings that would have broken the new gate are fixed as well: unused
`UNIX_EPOCH` import, manual `div_ceil`, needless borrow, needless range loop,
and a complex tuple type in `image.rs` factored into `FreeRegion` /
`FreelistPlan` aliases.

- **Image builder: package `snapshot.nxe` (#569).** Add `snapshot` to the
`System/Tools` list so the NeoFS v2 snapshot command is installed at
`C:\System\Tools\snapshot.nxe` (NeoDOS #569).
Expand Down
18 changes: 13 additions & 5 deletions src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,25 @@ const PERM_X: u16 = 0x0004;
/// `(4096 - 8 header - 8 next_lba) / 12`.
const REGIONS_PER_NODE: usize = 340;

/// One NE2 free-list region: `(start_lba, length_in_sectors)`.
type FreeRegion = (u64, u32);

/// The plan produced by [`plan_freelist`]: `(node_lbas, adjusted_regions)`.
type FreelistPlan = (Vec<u64>, Vec<FreeRegion>);

/// Plan the free-list node chain.
///
/// The first node is placed at `first_lba`. If the region list needs more than
/// one node, the extra nodes are reserved from the front of the first free
/// region (which is shrunk accordingly). Returns `(node_lbas, adjusted_regions)`.
fn plan_freelist(
mut free_regions: Vec<(u64, u32)>,
mut free_regions: Vec<FreeRegion>,
first_lba: u64,
) -> Result<(Vec<u64>, Vec<(u64, u32)>)> {
) -> Result<FreelistPlan> {
if free_regions.is_empty() {
return Ok((Vec::new(), free_regions));
}
let num_nodes = (free_regions.len() + REGIONS_PER_NODE - 1) / REGIONS_PER_NODE;
let num_nodes = free_regions.len().div_ceil(REGIONS_PER_NODE);
let mut node_lbas = vec![first_lba];
let extra = num_nodes - 1;
if extra > 0 {
Expand Down Expand Up @@ -552,7 +558,7 @@ fn collect_files(cfg: &Config, _disc: &Discovery) -> Result<Vec<FileEntry>> {
("net.nxl", "net.nxl"),
];
let mut nxl_files: Vec<(String, std::path::PathBuf)> = Vec::new();
if let Ok(entries) = std::fs::read_dir(&root) {
if let Ok(entries) = std::fs::read_dir(root) {
for entry in entries.flatten() {
let p = entry.path();
if p.is_file() && p.extension().map(|e| e == "nxl").unwrap_or(false) {
Expand Down Expand Up @@ -997,7 +1003,9 @@ mod tests {
assert!(ikeys[0].is_empty());
// Leaves hold all 60 keys in order.
let mut all = Vec::new();
for l in 0..chunks.len() { all.extend(parse_leaf_keys(&nodes[l])); }
for node in nodes.iter().take(chunks.len()) {
all.extend(parse_leaf_keys(node));
}
assert_eq!(all.len(), 60);
let mut sorted = all.clone();
sorted.sort();
Expand Down
17 changes: 14 additions & 3 deletions src/vmm/vbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,6 @@ fn has_ip_address(iface: &str) -> bool {
#[cfg(test)]
mod tests {
use super::*;
use std::time::UNIX_EPOCH;

#[test]
fn test_detect_bridge_interface_not_empty() {
Expand All @@ -553,7 +552,11 @@ mod tests {
let parts: Vec<&str> = line.split(':').collect();
if parts.len() < 2 { return None; }
let iface = parts[1].trim().to_string();
if iface == "lo" { return None; }
// Mirror the exclusions of `detect_bridge_interface` so both sets agree.
if iface == "lo" || iface.starts_with("tap") || iface.starts_with("docker")
|| iface.starts_with("vbox") || iface.starts_with("virbr") || iface.starts_with("br-") {
return None;
}
let up = std::fs::read_to_string(format!("/sys/class/net/{}/operstate", iface)).map(|s| s.trim() == "up").unwrap_or(false);
let car = std::fs::read_to_string(format!("/sys/class/net/{}/carrier", iface)).map(|s| s.trim() == "1").unwrap_or(false);
if up && car { Some(iface) } else { None }
Expand All @@ -562,7 +565,15 @@ mod tests {
if !active_eth.is_empty() {
let selected = detect_bridge_interface();
assert!(!selected.is_empty());
assert_ne!(selected, "eth0", "should detect real interface, not fallback");
// With a live interface present, detection must pick one of them rather
// than the hardcoded default. Comparing against the literal "eth0" was
// wrong: on some hosts — GitHub Actions runners included — the live
// interface is itself named eth0, so the old assertion failed even
// though detection had worked.
assert!(
active_eth.contains(&selected),
"detection must pick a live interface, not the hardcoded fallback: selected {selected}, live {active_eth:?}"
);
}
}

Expand Down
Loading