Skip to content
Open
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
12 changes: 11 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,18 @@ jobs:

- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-unknown-linux-gnu

- name: Install zig, cargo-zigbuild, and just
run: |
brew install zig just
cargo install cargo-zigbuild

- name: Build
run: just build

- name: Check build
- name: Check all targets
run: cargo check --all-targets

- name: Run unit tests
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
target/
.flox
*.so
bcvk-nbd-*
61 changes: 61 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ PRIMARY_IMAGE := "quay.io/centos-bootc/centos-bootc:stream10"
# <https://github.com/bootc-dev/bcvk/issues/153>
ALL_BASE_IMAGES := "quay.io/fedora/fedora-bootc:43 quay.io/fedora/fedora-bootc:44 quay.io/centos-bootc/centos-bootc:stream9 quay.io/centos-bootc/centos-bootc:stream10 quay.io/almalinuxorg/almalinux-bootc:10.0"

# Build the native binary
# Build the native binary (cross-compiles NBD server on non-Linux hosts)
build:
make
make nbd-server
make bin

# Static checks
validate:
Expand Down
16 changes: 14 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ all: bin manpages

.PHONY: bin
bin:
cargo check --workspace
cargo build --release

# Generate man pages from markdown sources
Expand Down Expand Up @@ -67,4 +66,17 @@ update-manpages:

update-generated: sync-manpages manpages

.PHONY: all bin install manpages update-generated makesudoinstall sync-manpages update-manpages sync-cli-options
.PHONY: all bin install manpages update-generated makesudoinstall sync-manpages update-manpages sync-cli-options nbd-server

# Cross-compile NBD server for podman machine (needed on macOS/Windows, no-op on Linux)
NBD_ARCH := $(shell rustc -vV | awk '/^host:/ {split($$2, a, "-"); print a[1]}')
NBD_TARGET := $(NBD_ARCH)-unknown-linux-gnu

.PHONY: nbd-server
nbd-server:
ifneq ($(shell uname -s),Linux)
PATH="$(HOME)/.rustup/toolchains/stable-$(shell rustc -vV | awk '/^host:/ {print $$2}')/bin:$(HOME)/.cargo/bin:$(PATH)" \
cargo zigbuild --target $(NBD_TARGET) --release -p bcvk-nbd
mkdir -p target/nbd-server
cp target/$(NBD_TARGET)/release/bcvk-nbd target/nbd-server/bcvk-nbd
endif
16 changes: 16 additions & 0 deletions crates/bcvk-nbd/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "bcvk-nbd"
version = "0.1.0"
edition = "2021"
publish = false

[[bin]]
name = "bcvk-nbd"
path = "src/main.rs"

[dependencies]
cpio = "0.4"
crc32fast = "1.4"
cap-std = "4"
rustix = { version = "1", features = ["fs"] }
libc = "0.2"
149 changes: 149 additions & 0 deletions crates/bcvk-nbd/src/dir_walk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
use std::ffi::OsString;
use std::os::unix::io::AsFd;
use std::path::{Path, PathBuf};

use cap_std::fs::Dir;

#[derive(Debug)]
pub struct FileEntry {
pub host_path: PathBuf,
pub size: u64,
pub mode: u32,
pub uid: u32,
pub gid: u32,
pub mtime: u64,
pub nlink: u32,
pub inode_id: u64,
}

#[derive(Debug)]
pub struct DirInfo {
pub name: OsString,
pub mode: u32,
pub uid: u32,
pub gid: u32,
pub mtime: u64,
pub inode_id: u64,
pub parent_inode_id: u64,
pub children: Vec<ChildRef>,
}

#[derive(Debug)]
pub struct SymlinkEntry {
pub name: Vec<u8>,
pub target: Vec<u8>,
pub mode: u32,
pub uid: u32,
pub gid: u32,
pub mtime: u64,
pub inode_id: u64,
}

/// Child entry in a directory: either a file index, dir index, or symlink index
#[derive(Debug, Clone, Copy)]
pub enum ChildRef {
File(usize),
Dir(usize),
Symlink(usize),
}

#[derive(Debug)]
pub struct WalkResult {
pub dirs: Vec<DirInfo>,
pub files: Vec<FileEntry>,
pub symlinks: Vec<SymlinkEntry>,
}

pub fn walk_directory(root_dir: &Dir, root_path: &Path) -> std::io::Result<WalkResult> {
let mut result = WalkResult {
dirs: Vec::new(),
files: Vec::new(),
symlinks: Vec::new(),
};
let mut next_inode: u64 = 0;

walk_recursive(root_dir, root_path, &mut result, &mut next_inode, 0)?;
Ok(result)
}

fn statat(dir: &Dir, name: &Path) -> std::io::Result<rustix::fs::Stat> {
rustix::fs::statat(dir.as_fd(), name, rustix::fs::AtFlags::SYMLINK_NOFOLLOW)
.map_err(std::io::Error::from)
}

fn walk_recursive(
cap_dir: &Dir,
current_path: &Path,
result: &mut WalkResult,
next_inode: &mut u64,
parent_inode_id: u64,
) -> std::io::Result<usize> {
let dir_stat = rustix::fs::fstat(cap_dir.as_fd()).map_err(std::io::Error::from)?;
let dir_inode = *next_inode;
*next_inode += 1;

let di = result.dirs.len();
result.dirs.push(DirInfo {
name: current_path.file_name().unwrap_or_default().to_os_string(),
mode: dir_stat.st_mode as u32,
uid: dir_stat.st_uid,
gid: dir_stat.st_gid,
mtime: dir_stat.st_mtime as u64,
inode_id: dir_inode,
parent_inode_id,
children: Vec::new(),
});

let mut entries: Vec<_> = cap_dir.entries()?.filter_map(|e| e.ok()).collect();
entries.sort_by_key(|e| e.file_name());

for entry in entries {
let name = entry.file_name();
let name_path: &Path = name.as_ref();
let stat = statat(cap_dir, name_path)?;
let raw_mode = stat.st_mode;
let mode = raw_mode as u32;
let ft = rustix::fs::FileType::from_raw_mode(raw_mode);

if ft == rustix::fs::FileType::Directory {
let child_dir = cap_dir.open_dir(&name)?;
let child_path = current_path.join(&name);
let child_di = walk_recursive(&child_dir, &child_path, result, next_inode, dir_inode)?;
result.dirs[di].children.push(ChildRef::Dir(child_di));
} else if ft == rustix::fs::FileType::Symlink {
let target = rustix::fs::readlinkat(cap_dir.as_fd(), name_path, [])?;
let target_bytes = target.into_bytes();
let name_bytes = name.as_encoded_bytes().to_vec();
let si = result.symlinks.len();
let inode = *next_inode;
*next_inode += 1;
result.symlinks.push(SymlinkEntry {
name: name_bytes,
target: target_bytes,
mode,
uid: stat.st_uid,
gid: stat.st_gid,
mtime: stat.st_mtime as u64,
inode_id: inode,
});
result.dirs[di].children.push(ChildRef::Symlink(si));
} else if ft == rustix::fs::FileType::RegularFile {
let fi = result.files.len();
let inode = *next_inode;
*next_inode += 1;
result.files.push(FileEntry {
host_path: current_path.join(&name),
size: stat.st_size as u64,
mode,
uid: stat.st_uid,
gid: stat.st_gid,
mtime: stat.st_mtime as u64,
nlink: stat.st_nlink as u32,
inode_id: inode,
});
result.dirs[di].children.push(ChildRef::File(fi));
}
}

Ok(di)
}
Loading
Loading