fix(clippy): make clippy = host libs + riscv programs (shared PROGRAMS); libkos alloc spinlock (was unlocked across threads); keystore Account; gpu Rect; factory Entry; Default impls, let-chains, div_ceil, iter loops

This commit is contained in:
2026-10-28 01:17:21 +01:00
parent 91f7d28834
commit c4871320a0
20 changed files with 188 additions and 134 deletions

View File

@@ -28,8 +28,11 @@ check:
fmt:
cargo fmt --all -- --check
# Libraries on the host; programs (no_std, they link only for RISC-V) for
# their target, as `build` builds them. The adapter is `kernel-clippy`'s.
clippy:
cargo clippy --workspace -- -D warnings
cargo clippy --workspace --exclude adapter-riscv --lib -- -D warnings
cargo clippy --target $(RISCV_TARGET) --release $(PROGRAMS) -- -D warnings
test:
cargo test --workspace
@@ -48,14 +51,15 @@ kani-kernel:
kani: kani-types kani-domain kani-kernel
PROGRAMS = -p init -p echo -p sleep -p snake \
-p display-qemu -p input-qemu -p storage-qemu \
-p log-server -p permission-manager -p session-server -p smptest -p spawntest -p hello \
-p terminal-server -p shell -p cat -p ls -p counter -p gatekeeper
# Programs first: the kernel image packs init and storage (see the
# adapter's build.rs).
build:
cargo build --target $(RISCV_TARGET) --release \
-p init -p echo -p sleep -p snake \
-p display-qemu -p input-qemu -p storage-qemu \
-p log-server -p permission-manager -p session-server -p smptest -p spawntest -p hello \
-p terminal-server -p shell -p cat -p ls -p counter -p gatekeeper
cargo build --target $(RISCV_TARGET) --release $(PROGRAMS)
cargo build --target $(RISCV_TARGET) --release -p adapter-riscv
# The keystore holds the owner's sealed keys: made once, never rebuilt.

View File

@@ -44,6 +44,12 @@ pub struct Snake {
rng_state: u32,
}
impl Default for Snake {
fn default() -> Self {
Self::new()
}
}
impl Snake {
pub fn new() -> Self {
let start = Position { x: GRID_WIDTH / 2, y: GRID_HEIGHT / 2 };

View File

@@ -61,8 +61,7 @@ fn main() {
loop {
let count = renderer::render(&snake, &mut commands);
for i in 0..count {
let cmd = &commands[i];
for cmd in &commands[..count] {
draw_rect(cmd.x, cmd.y, cmd.w, cmd.h, cmd.color);
}
flush();

View File

@@ -148,7 +148,8 @@ impl Framebuffer for VirtioFramebuffer {
fn flush(&mut self) {
let w = self.width.raw() as u32;
let h = self.height.raw() as u32;
gpu::transfer_to_host(&self.mmio, &self.queue, &self.dma, RESOURCE_ID, 0, 0, w, h);
gpu::resource_flush(&self.mmio, &self.queue, &self.dma, RESOURCE_ID, 0, 0, w, h);
let whole = gpu::Rect { x: 0, y: 0, w, h };
gpu::transfer_to_host(&self.mmio, &self.queue, &self.dma, RESOURCE_ID, whole);
gpu::resource_flush(&self.mmio, &self.queue, &self.dma, RESOURCE_ID, whole);
}
}

View File

@@ -56,18 +56,27 @@ pub fn set_scanout(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddr
submit_cmd(mmio, queue, dma, 48)
}
pub fn transfer_to_host(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, x: u32, y: u32, w: u32, h: u32) -> bool {
/// A rectangle of a resource, in pixels.
#[derive(Clone, Copy, Debug)]
pub struct Rect {
pub x: u32,
pub y: u32,
pub w: u32,
pub h: u32,
}
pub fn transfer_to_host(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, rect: Rect) -> bool {
write_cmd_header(&dma.cmd, 0x0105);
put_rect(&dma.cmd, x, y, w, h);
put_rect(&dma.cmd, rect);
// Offset into the resource's memory, then the resource id.
dma.cmd.write(BODY + 16, 0u64);
dma.cmd.write(BODY + 24, resource_id);
submit_cmd(mmio, queue, dma, 56)
}
pub fn resource_flush(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, x: u32, y: u32, w: u32, h: u32) -> bool {
pub fn resource_flush(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, rect: Rect) -> bool {
write_cmd_header(&dma.cmd, 0x0104);
put_rect(&dma.cmd, x, y, w, h);
put_rect(&dma.cmd, rect);
dma.cmd.write(BODY + 16, resource_id);
submit_cmd(mmio, queue, dma, 48)
}
@@ -77,11 +86,11 @@ fn write_cmd_header(cmd: &Window, cmd_type: u32) {
cmd.write(TYPE, cmd_type);
}
fn put_rect(cmd: &Window, x: u32, y: u32, w: u32, h: u32) {
cmd.write(RECT_X, x);
cmd.write(RECT_Y, y);
cmd.write(RECT_W, w);
cmd.write(RECT_H, h);
fn put_rect(cmd: &Window, r: Rect) {
cmd.write(RECT_X, r.x);
cmd.write(RECT_Y, r.y);
cmd.write(RECT_W, r.w);
cmd.write(RECT_H, r.h);
}
pub fn get_display_info(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs) -> Option<(u32, u32)> {

View File

@@ -43,16 +43,30 @@ impl Path {
}
}
/// Up to `BATCH` entries of `dir`: names and whether each is a folder.
fn entries(fs: &mut Storage, dir: &Path) -> Option<([([u8; NAME], usize, bool); BATCH], usize)> {
let mut out = [([0u8; NAME], 0, false); BATCH];
/// One thing in a folder.
#[derive(Clone, Copy)]
struct Entry {
name: [u8; NAME],
len: usize,
is_dir: bool,
}
impl Entry {
fn name(&self) -> &[u8] {
&self.name[..self.len]
}
}
/// Up to `BATCH` entries of `dir`.
fn entries(fs: &mut Storage, dir: &Path) -> Option<([Entry; BATCH], usize)> {
let mut out = [Entry { name: [0; NAME], len: 0, is_dir: false }; BATCH];
let mut n = 0;
fs.list_dir(dir.as_bytes(), |e| {
let name = e.name();
if name != b"." && name != b".." && n < BATCH {
out[n].0[..name.len()].copy_from_slice(name);
out[n].1 = name.len();
out[n].2 = e.is_dir;
out[n].name[..name.len()].copy_from_slice(name);
out[n].len = name.len();
out[n].is_dir = e.is_dir;
n += 1;
}
})
@@ -83,9 +97,9 @@ fn copy_tree(fs: &mut Storage, from: &Path, to: &Path, buf: &mut [u8]) -> Result
// Nothing shipped for this area.
None => return Ok(()),
};
for (name, len, is_dir) in &list[..n] {
let (src, dst) = (from.join(&name[..*len]).ok_or("path too long")?, to.join(&name[..*len]).ok_or("path too long")?);
if *is_dir {
for entry in &list[..n] {
let (src, dst) = (from.join(entry.name()).ok_or("path too long")?, to.join(entry.name()).ok_or("path too long")?);
if entry.is_dir {
fs.mkdir(dst.as_bytes()).map_err(|_| "cannot make a folder")?;
copy_tree(fs, &src, &dst, buf)?;
} else {

View File

@@ -54,7 +54,7 @@ pub(crate) fn load_binary(
let page_start = segment.vaddr & !0xFFF;
let offset_in_page = segment.vaddr & 0xFFF;
let total_size = offset_in_page + segment.mem_size;
let seg_pages = (total_size + PAGE_SIZE - 1) / PAGE_SIZE;
let seg_pages = total_size.div_ceil(PAGE_SIZE);
let page_count = PageCount::new(u16::try_from(seg_pages).ok()?)?;
let local = libkos::window::request(page_count)?;

View File

@@ -21,6 +21,7 @@ mod user;
mod tests;
pub use record::{
Account,
Copies, Entry, KdfParams, Outcome, RECORD_SIZE, Record, Refused, SLOTS, Session, State,
};
pub use user::{Name, Role, User, UserId};

View File

@@ -154,6 +154,15 @@ fn parse_user(role: u8, id: &[u8], name: &[u8]) -> Option<User> {
})
}
/// A user about to be added: who, and the passcodes that will open their
/// slot (and, if given, their duress slot).
pub struct Account<'a> {
pub name: Name,
pub role: Role,
pub passcode: &'a [u8],
pub duress: Option<&'a [u8]>,
}
impl Record {
fn name_tag(&self, name: &Name) -> [u8; NAME_TAG] {
let mut m = <Blake2sMac256 as KeyInit>::new_from_slice(&self.pepper)
@@ -273,7 +282,7 @@ impl Record {
admin: Some(admin),
};
let (record, user) = first
.add_user(&owner, name, Role::Owner, passcode, duress, memory, rng)
.add_user(&owner, Account { name, role: Role::Owner, passcode, duress }, memory, rng)
.ok()?;
let session = record.unlock(name.as_bytes(), passcode, memory);
let _ = user;
@@ -371,16 +380,8 @@ impl Record {
}
/// An owner adds a user; the new record and the user.
pub fn add_user(
&self,
by: &Session,
name: Name,
role: Role,
passcode: &[u8],
duress: Option<&[u8]>,
memory: &mut [Block],
rng: &mut impl Random,
) -> Result<(Self, User), Refused> {
pub fn add_user(&self, by: &Session, account: Account, memory: &mut [Block], rng: &mut impl Random) -> Result<(Self, User), Refused> {
let Account { name, role, passcode, duress } = account;
let admin = by.admin.clone().ok_or(Refused::NotOwner)?;
let mut entries = self.entries(&admin).ok_or(Refused::Failed)?;
if entries.iter().flatten().any(|e| e.user.name == name) {
@@ -467,10 +468,10 @@ impl Record {
return Err(Refused::LastOwner);
}
let mut r = self.next(|_| {});
for i in 0..SLOTS {
if entries[i].is_some_and(|e| e.user.id == gone.id) {
r.slots[i] = Slot(noise(rng).ok_or(Refused::Failed)?);
entries[i] = None;
for (entry, slot) in entries.iter_mut().zip(r.slots.iter_mut()) {
if entry.is_some_and(|e| e.user.id == gone.id) {
*slot = Slot(noise(rng).ok_or(Refused::Failed)?);
*entry = None;
}
}
if !r.seal_directory(&admin, &entries, rng) {

View File

@@ -79,15 +79,7 @@ fn users_have_their_own_keys_and_may_even_share_a_passcode() {
let (r, owner) = setup(None, 2);
let mut rng = Counter(40);
let (r, work) = r
.add_user(
&owner,
name("work"),
Role::Standard,
b"1234",
None,
&mut memory(),
&mut rng,
)
.add_user(&owner, Account { name: name("work"), role: Role::Standard, passcode: b"1234", duress: None }, &mut memory(), &mut rng)
.unwrap();
let w = signed_in(&r, "work", b"1234").unwrap();
let g = signed_in(&r, "gabriel", b"1234").unwrap();
@@ -105,15 +97,7 @@ fn users_have_their_own_keys_and_may_even_share_a_passcode() {
"not even hidden in their slot"
);
assert_eq!(
r.add_user(
&w,
name("x"),
Role::Standard,
b"9",
None,
&mut memory(),
&mut rng
)
r.add_user(&w, Account { name: name("x"), role: Role::Standard, passcode: b"9", duress: None }, &mut memory(), &mut rng)
.err(),
Some(Refused::NotOwner)
);
@@ -125,26 +109,10 @@ fn owners_see_and_manage_the_users() {
let (r, owner) = setup(Some(b"0000"), 3);
let mut rng = Counter(50);
let (r, _) = r
.add_user(
&owner,
name("work"),
Role::Standard,
b"5678",
Some(b"8765"),
&mut memory(),
&mut rng,
)
.add_user(&owner, Account { name: name("work"), role: Role::Standard, passcode: b"5678", duress: Some(b"8765") }, &mut memory(), &mut rng)
.unwrap();
let (r, second) = r
.add_user(
&owner,
name("anna"),
Role::Owner,
b"4321",
None,
&mut memory(),
&mut rng,
)
.add_user(&owner, Account { name: name("anna"), role: Role::Owner, passcode: b"4321", duress: None }, &mut memory(), &mut rng)
.unwrap();
let names: Vec<_> = r
.users(&owner)
@@ -155,15 +123,7 @@ fn owners_see_and_manage_the_users() {
.collect();
assert_eq!(names.len(), 3, "duress slots are not listed: {names:?}");
assert_eq!(
r.add_user(
&owner,
name("work"),
Role::Standard,
b"x",
None,
&mut memory(),
&mut rng
)
r.add_user(&owner, Account { name: name("work"), role: Role::Standard, passcode: b"x", duress: None }, &mut memory(), &mut rng)
.err(),
Some(Refused::NameTaken)
);
@@ -205,15 +165,7 @@ fn slots_run_out() {
let mut added = 0;
for i in 0..SLOTS {
let n = std::format!("u{i}");
match r.add_user(
&owner,
name(&n),
Role::Standard,
b"pass",
None,
&mut memory(),
&mut rng,
) {
match r.add_user(&owner, Account { name: name(&n), role: Role::Standard, passcode: b"pass", duress: None }, &mut memory(), &mut rng) {
Ok((next, _)) => {
r = next;
added += 1;

View File

@@ -1,6 +1,8 @@
use core::alloc::{GlobalAlloc, Layout};
use core::cell::UnsafeCell;
use core::ops::{Deref, DerefMut};
use core::ptr;
use core::sync::atomic::{AtomicBool, Ordering};
const PAGE_SIZE: usize = 4096;
@@ -10,6 +12,8 @@ struct FreeBlock {
}
pub struct KosAllocator {
/// A program's threads share the heap: one at a time in the free list.
locked: AtomicBool,
inner: UnsafeCell<AllocatorInner>,
}
@@ -24,12 +28,45 @@ unsafe impl Sync for KosAllocator {}
impl KosAllocator {
pub const fn new() -> Self {
Self {
locked: AtomicBool::new(false),
inner: UnsafeCell::new(AllocatorInner { free_list: ptr::null_mut() }),
}
}
fn inner_mut(&self) -> &mut AllocatorInner {
unsafe { &mut *self.inner.get() }
fn lock(&self) -> Locked<'_> {
while self.locked.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed).is_err() {
core::hint::spin_loop();
}
Locked(self)
}
}
impl Default for KosAllocator {
fn default() -> Self {
Self::new()
}
}
/// The free list, held until dropped.
struct Locked<'a>(&'a KosAllocator);
impl Deref for Locked<'_> {
type Target = AllocatorInner;
fn deref(&self) -> &AllocatorInner {
// Only one `Locked` exists at a time.
unsafe { &*self.0.inner.get() }
}
}
impl DerefMut for Locked<'_> {
fn deref_mut(&mut self) -> &mut AllocatorInner {
unsafe { &mut *self.0.inner.get() }
}
}
impl Drop for Locked<'_> {
fn drop(&mut self) {
self.0.locked.store(false, Ordering::Release);
}
}
@@ -103,7 +140,7 @@ impl AllocatorInner {
unsafe impl GlobalAlloc for KosAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let inner = self.inner_mut();
let mut inner = self.lock();
let size = layout.size().max(core::mem::size_of::<FreeBlock>());
let align = layout.align();
@@ -113,7 +150,7 @@ unsafe impl GlobalAlloc for KosAllocator {
}
// Alignment can cost up to `align` bytes of the block.
let pages_needed = (size + align + PAGE_SIZE - 1) / PAGE_SIZE;
let pages_needed = (size + align).div_ceil(PAGE_SIZE);
let block = inner.request_pages(pages_needed);
if block.is_null() {
return ptr::null_mut();
@@ -124,7 +161,7 @@ unsafe impl GlobalAlloc for KosAllocator {
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
let inner = self.inner_mut();
let mut inner = self.lock();
let size = layout.size().max(core::mem::size_of::<FreeBlock>());
inner.add_to_free_list(ptr, size);
}

View File

@@ -12,12 +12,17 @@ use types::time::Ticks;
pub fn exit() -> ! {
let _ = call0(SYS_EXIT);
loop {}
// The kernel does not return from an exit.
loop {
core::hint::spin_loop();
}
}
pub fn exit_thread() -> ! {
let _ = call0(SYS_EXIT_THREAD);
loop {}
loop {
core::hint::spin_loop();
}
}
pub fn yield_now() {
@@ -28,7 +33,9 @@ pub fn yield_now() {
/// call fails and this spins, since there is nothing sensible to return.
pub fn halt(control: CapSlot) -> ! {
let _ = raw(SYS_HALT, [control.value() as usize, 0, 0, 0, 0, 0]);
loop {}
loop {
yield_now();
}
}
pub fn get_time() -> Ticks {

View File

@@ -2,7 +2,7 @@
//! randomness, and the owner (`Prompt`). No kernel, no hardware: tested on
//! the host.
use keystore::{Block, Copies, KdfParams, Key, Name, Outcome, RECORD_SIZE, Random, Record, Refused, Role, SLOTS, Session, User, UserId};
use keystore::{Account, Block, Copies, KdfParams, Key, Name, Outcome, RECORD_SIZE, Random, Record, Refused, Role, SLOTS, Session, User, UserId};
use zeroize::Zeroize;
/// The keystore device: the record's two copies.
@@ -228,7 +228,7 @@ fn manage(ks: &mut impl Keystore, rng: &mut impl Random, prompt: &mut impl Promp
let (mut real, mut duress) = (Secret::new(), Secret::new());
ask_passcodes(prompt, &mut real, &mut duress)?;
let duress = (duress.len > 0).then_some(duress.as_bytes());
record.add_user(session, name, role, real.as_bytes(), duress, memory, rng).map(|(r, _)| r)
record.add_user(session, Account { name, role, passcode: real.as_bytes(), duress }, memory, rng).map(|(r, _)| r)
}
b"r" => {
let name = ask_name(prompt, "Remove whom: ")?;

View File

@@ -6,6 +6,12 @@ pub struct MockInputDevice {
tail: usize,
}
impl Default for MockInputDevice {
fn default() -> Self {
Self::new()
}
}
impl MockInputDevice {
pub fn new() -> Self {
Self {

View File

@@ -18,6 +18,12 @@ pub struct LogServer<O: FnMut(&[u8]) = fn(&[u8])> {
out: O,
}
impl Default for LogServer {
fn default() -> Self {
Self::new()
}
}
impl LogServer {
/// Text goes nowhere.
pub fn new() -> Self {

View File

@@ -44,16 +44,21 @@ impl MockFileSystem {
fn find_by_name(&self, path: &[u8]) -> Option<usize> {
for i in 0..MAX_FILES {
if let Some(entry) = &self.files[i] {
if entry.name == *path {
if let Some(entry) = &self.files[i]
&& entry.name == *path {
return Some(i);
}
}
}
None
}
}
impl Default for MockFileSystem {
fn default() -> Self {
Self::new()
}
}
impl FileSystem for MockFileSystem {
fn writable(&self) -> bool {
!self.read_only

View File

@@ -39,6 +39,10 @@ impl FileName {
pub fn len(&self) -> usize {
self.len as usize
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
impl PartialEq<[u8]> for FileName {

View File

@@ -37,11 +37,10 @@ pub fn find_entry<B: BlockDevice>(
let name_len = block_buf[pos + 6] as usize;
if rec_len == 0 { break; }
if entry_inode != 0 && name_len == name.len() {
if &block_buf[pos + 8..pos + 8 + name_len] == name {
if entry_inode != 0 && name_len == name.len()
&& &block_buf[pos + 8..pos + 8 + name_len] == name {
return Some(entry_inode);
}
}
pos += rec_len;
}
}
@@ -147,8 +146,8 @@ pub fn remove_entry<B: BlockDevice>(
let name_len = block_buf[pos + 6] as usize;
if rec_len == 0 { break; }
if entry_inode != 0 && name_len == name.len() {
if &block_buf[pos + 8..pos + 8 + name_len] == name {
if entry_inode != 0 && name_len == name.len()
&& &block_buf[pos + 8..pos + 8 + name_len] == name {
if let Some(pp) = prev_pos {
let prev_rec = read_u16(&block_buf, pp + 4) as usize;
write_u16(&mut block_buf, pp + 4, (prev_rec + rec_len) as u16);
@@ -158,7 +157,6 @@ pub fn remove_entry<B: BlockDevice>(
write_block(device, phys, spb, &block_buf);
return Some(entry_inode);
}
}
prev_pos = Some(pos);
pos += rec_len;
}

View File

@@ -131,7 +131,9 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
done += chunk;
}
self.open_files[handle.raw() as usize].as_mut().map(|f| f.offset += done);
if let Some(f) = self.open_files[handle.raw() as usize].as_mut() {
f.offset += done;
}
Some(done)
}
@@ -204,13 +206,12 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
fn close(&mut self, handle: FileHandle) {
if handle.raw() as usize >= MAX_OPEN_FILES { return; }
if let Some(file) = self.open_files[handle.raw() as usize].take() {
if let Some(mut inode) = self.read_inode(file.inode_num) {
if let Some(file) = self.open_files[handle.raw() as usize].take()
&& let Some(mut inode) = self.read_inode(file.inode_num) {
inode.size = file.size as u64;
inode.block_data = file.block_data;
self.write_inode(file.inode_num, &inode);
}
}
}
fn list_dir(&mut self, path: &[u8], entries: &mut [DirEntry]) -> Option<usize> {
@@ -222,14 +223,13 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
let inode = self.read_inode(dir_inode_num)?;
let count = dir::list_entries(&mut self.device, &self.sb, &inode, entries)?;
for i in 0..count {
if !entries[i].is_dir {
let name = entries[i].name;
if let Some(child_ino) = dir::find_entry(&mut self.device, &self.sb, &inode, name.as_bytes()) {
if let Some(child) = self.read_inode(child_ino) {
entries[i].size = child.size as usize;
for entry in entries.iter_mut().take(count) {
if !entry.is_dir {
let name = entry.name;
if let Some(child_ino) = dir::find_entry(&mut self.device, &self.sb, &inode, name.as_bytes())
&& let Some(child) = self.read_inode(child_ino) {
entry.size = child.size as usize;
}
}
}
}
Some(count)
@@ -279,11 +279,10 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
None => return false,
};
if let Some(child_inode) = self.read_inode(child_inode_num) {
if child_inode.is_dir() {
if let Some(child_inode) = self.read_inode(child_inode_num)
&& child_inode.is_dir() {
return false;
}
}
let parent_inode = match self.read_inode(parent_inode_num) {
Some(i) => i,
@@ -299,11 +298,10 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
self.clear_inode(child_inode_num);
self.free_inode(child_inode_num);
for slot in self.open_files.iter_mut() {
if let Some(f) = slot {
if f.inode_num == child_inode_num {
if let Some(f) = slot
&& f.inode_num == child_inode_num {
*slot = None;
}
}
}
true
}

View File

@@ -102,3 +102,9 @@ impl Grants {
}
}
}
impl Default for Grants {
fn default() -> Self {
Self::new()
}
}