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
10 changes: 10 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ jobs:

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

- name: Install zig and cargo-zigbuild
run: |
brew install zig
cargo install cargo-zigbuild
- name: Build NBD server binary
run: make nbd-server-aarch64

- name: Check build
run: cargo check --all-targets
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.

16 changes: 15 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,18 @@ 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-aarch64 nbd-server-x86_64

.PHONY: nbd-server-aarch64
nbd-server-aarch64:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will we ever care about a case where the podman machine architecture is different from the bcvk binary's? I don't think we will...so we can probably just have nbd-server as a target which builds for the Linux target matching the host.

Though I guess we need to handle arch on macOS being arm64 and map it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also let's abstract things for the user here, I would say we should have just build which wraps this + cargo build --release at least and use that in CI.

PATH="$(HOME)/.rustup/toolchains/stable-$(shell rustc -vV | awk '/^host:/ {print $$2}')/bin:$(HOME)/.cargo/bin:$(PATH)" \
cargo zigbuild --target aarch64-unknown-linux-gnu --release -p bcvk-nbd
cp target/aarch64-unknown-linux-gnu/release/bcvk-nbd \
crates/kit/bcvk-nbd-aarch64

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's ugly to drop a binary in the sourcedir like this - that's how the binary leaked in before. I think instead we should just keep it in target/.

Hmm, doing this "properly" might involve https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script


.PHONY: nbd-server-x86_64
nbd-server-x86_64:
PATH="$(HOME)/.rustup/toolchains/stable-$(shell rustc -vV | awk '/^host:/ {print $$2}')/bin:$(HOME)/.cargo/bin:$(PATH)" \
cargo zigbuild --target x86_64-unknown-linux-gnu --release -p bcvk-nbd
cp target/x86_64-unknown-linux-gnu/release/bcvk-nbd \
crates/kit/bcvk-nbd-x86_64
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