Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3cc7ed909c | |||
| 89c68d0738 |
+2
-4
@@ -1,4 +1,2 @@
|
||||
target
|
||||
image.bin
|
||||
image
|
||||
image*
|
||||
/build
|
||||
/target
|
||||
|
||||
+29
-24
@@ -1,30 +1,35 @@
|
||||
image: "redoxos/redoxer"
|
||||
image: "redoxos/redoxer:latest"
|
||||
|
||||
before_script:
|
||||
- apt-get install nasm
|
||||
- rustup component add rust-src
|
||||
|
||||
stages:
|
||||
- build
|
||||
- test
|
||||
- host
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- target/
|
||||
build:i686:
|
||||
stage: host
|
||||
script:
|
||||
- mkdir -p target/i686
|
||||
- cd target/i686
|
||||
- TARGET=x86-unknown-none make -f ${CI_PROJECT_DIR}/Makefile -C `pwd` `pwd`/bootloader.bin `pwd`/bootloader-live.bin
|
||||
|
||||
build:linux:
|
||||
stage: build
|
||||
script: cargo +nightly build --verbose
|
||||
build:x86_64:
|
||||
stage: host
|
||||
script:
|
||||
- mkdir -p target/x86_64
|
||||
- cd target/x86_64
|
||||
- TARGET=x86_64-unknown-uefi make -f ${CI_PROJECT_DIR}/Makefile -C `pwd` `pwd`/bootloader.efi `pwd`/bootloader-live.efi
|
||||
|
||||
build:redox:
|
||||
stage: build
|
||||
script: redoxer build --verbose
|
||||
build:aarch64:
|
||||
stage: host
|
||||
script:
|
||||
- mkdir -p target/aarch64
|
||||
- cd target/aarch64
|
||||
- TARGET=aarch64-unknown-uefi make -f ${CI_PROJECT_DIR}/Makefile -C `pwd` `pwd`/bootloader.efi `pwd`/bootloader-live.efi
|
||||
|
||||
test:linux:
|
||||
stage: test
|
||||
dependencies:
|
||||
- build:linux
|
||||
script: cargo +nightly test --verbose
|
||||
|
||||
test:redox:
|
||||
stage: test
|
||||
dependencies:
|
||||
- build:redox
|
||||
# only run integration test as without KVM unit tests is super slow
|
||||
script: redoxer test --verbose -- --test '*' -- --nocapture
|
||||
fmt:
|
||||
stage: host
|
||||
script:
|
||||
- rustup component add rustfmt-preview
|
||||
- cargo fmt -- --check
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[editor]
|
||||
auto-format = false
|
||||
@@ -0,0 +1,5 @@
|
||||
[[language]]
|
||||
name = "rust"
|
||||
# TODO: Add more targets (BIOS, x86_32)
|
||||
# Uncomment this line and set cargo.target to your target to get accurate completions
|
||||
# config = { cargo.target = "aarch64-unknown-uefi", check.targets = ["x86_64-unknown-uefi", "aarch64-unknown-uefi"] }
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
sudo: required
|
||||
language: rust
|
||||
rust:
|
||||
- nightly
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
dist: trusty
|
||||
before_install:
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then
|
||||
sudo apt-get install -qq pkg-config fuse libfuse-dev;
|
||||
sudo modprobe fuse;
|
||||
sudo chmod 666 /dev/fuse;
|
||||
sudo chown root:$USER /etc/fuse.conf;
|
||||
fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then
|
||||
brew update;
|
||||
brew install Caskroom/cask/osxfuse;
|
||||
fi
|
||||
notifications:
|
||||
email: false
|
||||
Generated
+120
-777
File diff suppressed because it is too large
Load Diff
+44
-88
@@ -1,96 +1,52 @@
|
||||
[package]
|
||||
name = "redoxfs"
|
||||
description = "The Redox Filesystem"
|
||||
repository = "https://gitlab.redox-os.org/redox-os/redoxfs"
|
||||
version = "0.9.0"
|
||||
license-file = "LICENSE"
|
||||
readme = "README.md"
|
||||
authors = ["Jeremy Soller <jackpot51@gmail.com>"]
|
||||
edition = "2021"
|
||||
name = "redox_bootloader"
|
||||
version = "1.0.0"
|
||||
edition = "2024"
|
||||
|
||||
# UEFI uses bin target
|
||||
[[bin]]
|
||||
name = "bootloader"
|
||||
path = "src/main.rs"
|
||||
|
||||
# BIOS uses lib target
|
||||
[lib]
|
||||
name = "redoxfs"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "redoxfs"
|
||||
path = "src/bin/mount.rs"
|
||||
doc = false
|
||||
required-features = ["std"]
|
||||
|
||||
[[bin]]
|
||||
name = "redoxfs-ar"
|
||||
path = "src/bin/ar.rs"
|
||||
doc = false
|
||||
required-features = ["std"]
|
||||
|
||||
[[bin]]
|
||||
name = "redoxfs-clone"
|
||||
path = "src/bin/clone.rs"
|
||||
doc = false
|
||||
required-features = ["std"]
|
||||
|
||||
[[bin]]
|
||||
name = "redoxfs-mkfs"
|
||||
path = "src/bin/mkfs.rs"
|
||||
doc = false
|
||||
required-features = ["std"]
|
||||
|
||||
[[bin]]
|
||||
name = "redoxfs-resize"
|
||||
path = "src/bin/resize.rs"
|
||||
doc = false
|
||||
required-features = ["std"]
|
||||
name = "bootloader"
|
||||
path = "src/main.rs"
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
aes = { version = "0.8", default-features = false }
|
||||
argon2 = { version = "0.4", default-features = false, features = ["alloc"] }
|
||||
base64ct = { version = "1", default-features = false }
|
||||
bitflags = "2"
|
||||
endian-num = "0.1"
|
||||
env_logger = { version = "0.11", optional = true }
|
||||
getrandom = { version = "0.2.5", optional = true }
|
||||
humansize = { version = "2", optional = true }
|
||||
libc = "0.2"
|
||||
log = { version = "0.4.14", default-features = false, optional = true }
|
||||
lz4_flex = { version = "0.11", default-features = false, features = ["checked-decode"] }
|
||||
parse-size = { version = "1", optional = true }
|
||||
range-tree = { version = "0.1", optional = true }
|
||||
redox_syscall = "0.7.3"
|
||||
seahash = { version = "4.1.0", default-features = false }
|
||||
termion = { version = "4", optional = true }
|
||||
uuid = { version = "1.4", default-features = false }
|
||||
xts-mode = { version = "0.5", default-features = false }
|
||||
bitflags = "1.3.2"
|
||||
linked_list_allocator = "0.10.5"
|
||||
log = "0.4.17"
|
||||
redox_syscall = "0.5"
|
||||
spin = "0.9.5"
|
||||
|
||||
[dependencies.redoxfs]
|
||||
version = "0.8"
|
||||
default-features = false
|
||||
features = ["log"]
|
||||
|
||||
[target.'cfg(target_os = "uefi")'.dependencies]
|
||||
redox_uefi = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" }
|
||||
redox_uefi_std = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" }
|
||||
|
||||
#TODO: riscv cannot use target_os = "uefi" at this time
|
||||
[target.'cfg(target_arch = "riscv64")'.dependencies]
|
||||
redox_uefi = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" }
|
||||
redox_uefi_std = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" }
|
||||
|
||||
[target."aarch64-unknown-uefi".dependencies]
|
||||
dmidecode = "0.8.0"
|
||||
|
||||
[target."x86_64-unknown-uefi".dependencies]
|
||||
x86 = "0.52.0"
|
||||
|
||||
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
|
||||
byteorder = { version = "1", default-features = false }
|
||||
fdt = { git = "https://github.com/repnop/fdt.git", rev = "2fb1409edd1877c714a0aa36b6a7c5351004be54" }
|
||||
|
||||
[features]
|
||||
default = ["std", "log", "fuse"]
|
||||
fuse = [
|
||||
"fuser",
|
||||
"std",
|
||||
]
|
||||
std = [
|
||||
"env_logger",
|
||||
"getrandom",
|
||||
"humansize",
|
||||
"libredox",
|
||||
"parse-size",
|
||||
"range-tree",
|
||||
"termion",
|
||||
"uuid/v4",
|
||||
"redox_syscall/std",
|
||||
"redox-scheme",
|
||||
]
|
||||
default = []
|
||||
live = []
|
||||
serial_debug = []
|
||||
|
||||
[target.'cfg(not(target_os = "redox"))'.dependencies]
|
||||
fuser = { version = "0.16", optional = true }
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
libredox = { version = "0.1.13", optional = true }
|
||||
redox-path = "0.3.0"
|
||||
redox-scheme = { version = "0.11.0", optional = true }
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2.0.17"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Jeremy Soller
|
||||
Copyright (c) 2017-2022 Redox OS
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,37 +1,24 @@
|
||||
UNAME := $(shell uname)
|
||||
TARGET?=x86_64-unknown-uefi
|
||||
SOURCE:=$(dir $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||
BUILD:=$(CURDIR)
|
||||
export RUST_TARGET_PATH?=$(SOURCE)/targets
|
||||
|
||||
ifeq ($(UNAME),Darwin)
|
||||
FUMOUNT=umount
|
||||
else ifeq ($(UNAME),FreeBSD)
|
||||
FUMOUNT=sudo umount
|
||||
else
|
||||
# Detect which version of the fusermount binary is available.
|
||||
ifneq (, $(shell which fusermount3))
|
||||
FUMOUNT=fusermount3 -u
|
||||
else
|
||||
FUMOUNT=fusermount -u
|
||||
endif
|
||||
endif
|
||||
|
||||
image.bin:
|
||||
cargo build --release --bin redoxfs-mkfs
|
||||
dd if=/dev/zero of=image.bin bs=1048576 count=1024
|
||||
target/release/redoxfs-mkfs image.bin
|
||||
include $(SOURCE)/mk/$(TARGET).mk
|
||||
|
||||
mount: image.bin FORCE
|
||||
mkdir -p image
|
||||
cargo build --release --bin redoxfs
|
||||
target/release/redoxfs image.bin image
|
||||
clean:
|
||||
rm -rf build target
|
||||
|
||||
unmount: FORCE
|
||||
sync
|
||||
-${FUMOUNT} image
|
||||
rm -rf image
|
||||
$(BUILD)/filesystem:
|
||||
mkdir -p $(BUILD)
|
||||
rm -f $@.partial
|
||||
mkdir $@.partial
|
||||
fallocate -l 1MiB $@.partial/kernel
|
||||
mv $@.partial $@
|
||||
|
||||
clean: FORCE
|
||||
sync
|
||||
-${FUMOUNT} image
|
||||
rm -rf image image.bin
|
||||
cargo clean
|
||||
|
||||
FORCE:
|
||||
$(BUILD)/filesystem.bin: $(BUILD)/filesystem
|
||||
mkdir -p $(BUILD)
|
||||
rm -f $@.partial
|
||||
fallocate -l 254MiB $@.partial
|
||||
redoxfs-ar $@.partial $<
|
||||
mv $@.partial $@
|
||||
|
||||
@@ -1,53 +1,62 @@
|
||||
# RedoxFS
|
||||
|
||||
This is the default filesystem of Redox OS inspired by [ZFS](https://docs.freebsd.org/en/books/handbook/zfs/) and adapted to a microkernel architecture.
|
||||
|
||||
(It's a replacement for [TFS](https://gitlab.redox-os.org/redox-os/tfs))
|
||||
|
||||
Current features:
|
||||
|
||||
- Compatible with Redox and Linux (FUSE)
|
||||
- Copy-on-write
|
||||
- Data/metadata checksums
|
||||
- Transparent encryption
|
||||
- Standard Unix file attributes
|
||||
- File/directory size limit up to 193TiB (212TB)
|
||||
- File/directory quantity limit up to 4 billion per 193TiB (2^32 - 1 = 4294967295)
|
||||
- MIT licensed
|
||||
- Disk encryption fully supported by the Redox bootloader, letting it load the kernel off an encrypted partition.
|
||||
|
||||
Being MIT licensed, RedoxFS can be bundled on GPL-licensed operating systems (Linux, for example).
|
||||
|
||||
### Install RedoxFS
|
||||
|
||||
```sh
|
||||
cargo install redoxfs
|
||||
```
|
||||
|
||||
You can also build RedoxFS from this repository.
|
||||
|
||||
### Configure your storage device to allow rootless usage
|
||||
|
||||
If you are on Linux you need root permission to acess block devices (storage), but it's recommended to run RedoxFS as rootless.
|
||||
|
||||
To do that you need to configure your storage device permission to your user with the following command:
|
||||
|
||||
```sh
|
||||
sudo setfacl -m u:your-username:rw /path/to/disk
|
||||
```
|
||||
|
||||
### Create, mount and customize your RedoxFS partition
|
||||
|
||||
See [the instructions in the book](https://doc.redox-os.org/book/redoxfs.html) for RedoxFS tooling usage.
|
||||
|
||||
Currently RedoxFS tooling are:
|
||||
|
||||
- `redoxfs` mount a RedoxFS disk
|
||||
- `redoxfs-ar` write files to a RedoxFS disk
|
||||
- `redoxfs-clone` clone a RedoxFS disk
|
||||
- `redoxfs-mkfs` create an empty RedoxFS disk
|
||||
- `redoxfs-resize` resize a RedoxFS disk
|
||||
|
||||
[](./LICENSE)
|
||||
[](https://crates.io/crates/redoxfs)
|
||||
[](https://docs.rs/redoxfs)
|
||||
# Bootloader
|
||||
|
||||
Redox OS Bootloader
|
||||
|
||||
## Requirements
|
||||
|
||||
These software needs to be available on the PATH at build time:
|
||||
|
||||
+ [mtools](https://www.gnu.org/software/mtools/)
|
||||
+ [nasm](https://nasm.us/)
|
||||
+ [redoxfs-ar](https://gitlab.redox-os.org/redox-os/redoxfs)
|
||||
|
||||
## Building
|
||||
|
||||
```sh
|
||||
make TARGET=<triplet> BUILD=build all
|
||||
```
|
||||
|
||||
The `<triplet>` is one of:
|
||||
|
||||
| ARCH | Boot Mode | Triplets |
|
||||
|---|---|---|
|
||||
| `i686` | BIOS | `x86-unknown-none` |
|
||||
| `x86_64` | BIOS | `x86-unknown-none` |
|
||||
| `x86_64` | UEFI | `x86_64-unknown-uefi` |
|
||||
| `aarch64` | UEFI | `aarch64-unknown-uefi` |
|
||||
| `riscv64gc` | UEFI | `riscv64gc-unknown-uefi` |
|
||||
|
||||
See [mk directory](./mk) for more information of how the build is working.
|
||||
|
||||
## Entry points
|
||||
|
||||
Please read [Boot Process](https://doc.redox-os.org/book/boot-process.html) in the Redox OS Book for an introductory guide.
|
||||
|
||||
In this source code, some interesting files for entry points are:
|
||||
|
||||
+ BIOS boot stages: [asm/x86-unknown-none/bootloader.asm](./asm/x86-unknown-none/bootloader.asm)
|
||||
+ BIOS boot entry: `fn start` at [src/os/bios/mod.rs](./src/os/bios/mod.rs)
|
||||
+ UEFI boot entry: `fn main` at [src/os/uefi/mod.rs](src/os/uefi/mod.rs)
|
||||
+ Common boot process: `fn main` at [src/main.rs](src/main.rs)
|
||||
+ UEFI kernel entry: `fn kernel_entry` in each arch:
|
||||
- `x86_64`: [src/os/uefi/arch/x86_64.rs](src/os/uefi/arch/x86_64.rs)
|
||||
- `aarch64`: [src/os/uefi/arch/aarch64.rs](src/os/uefi/arch/aarch64.rs)
|
||||
- `riscv64gc`: [src/os/uefi/arch/riscv64/mod.rs](src/os/uefi/arch/riscv64/mod.rs)
|
||||
|
||||
## Debugging
|
||||
|
||||
### QEMU
|
||||
|
||||
```sh
|
||||
make TARGET=<triplet> BUILD=build qemu
|
||||
```
|
||||
|
||||
## How To Contribute
|
||||
|
||||
To learn how to contribute to this system component you need to read the following document:
|
||||
|
||||
- [CONTRIBUTING.md](https://gitlab.redox-os.org/redox-os/redox/-/blob/master/CONTRIBUTING.md)
|
||||
|
||||
## Development
|
||||
|
||||
To learn how to do development with this system component inside the Redox build system you need to read the [Build System](https://doc.redox-os.org/book/build-system-reference.html) and [Coding and Building](https://doc.redox-os.org/book/coding-and-building.html) pages.
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
interrupt_vector_table:
|
||||
b . @ Reset
|
||||
b .
|
||||
b . @ SWI instruction
|
||||
b .
|
||||
b .
|
||||
b .
|
||||
b .
|
||||
b .
|
||||
|
||||
.comm stack, 0x10000 @ Reserve 64k stack in the BSS
|
||||
_start:
|
||||
.globl _start
|
||||
ldr sp, =stack+0x10000 @ Set up the stack
|
||||
bl kstart @ Jump to the main function
|
||||
1:
|
||||
b 1b @ Halt
|
||||
@@ -0,0 +1,31 @@
|
||||
sectalign off
|
||||
|
||||
; stage 1 is sector 0, loaded at 0x7C00
|
||||
%include "stage1.asm"
|
||||
|
||||
; GPT area from sector 1 to 33, loaded at 0x7E00
|
||||
times (33*512) db 0
|
||||
|
||||
; stage 2, loaded at 0xC000
|
||||
stage2:
|
||||
%include "stage2.asm"
|
||||
align 512, db 0
|
||||
stage2.end:
|
||||
|
||||
; the maximum size of stage2 is 4 KiB
|
||||
times (4*1024)-($-stage2) db 0
|
||||
|
||||
; ISO compatibility, uses up space until 0x12400
|
||||
%include "iso.asm"
|
||||
|
||||
times 3072 db 0 ; Pad to 0x13000
|
||||
|
||||
; stage3, loaded at 0x13000
|
||||
stage3:
|
||||
%defstr STAGE3_STR %[STAGE3]
|
||||
incbin STAGE3_STR
|
||||
align 512, db 0
|
||||
.end:
|
||||
|
||||
; the maximum size of the boot loader portion is 384 KiB
|
||||
times (384*1024)-($-$$) db 0
|
||||
@@ -0,0 +1,176 @@
|
||||
SECTION .text
|
||||
USE16
|
||||
|
||||
cpuid_required_features:
|
||||
.edx equ cpuid_edx.fpu | cpuid_edx.pse | cpuid_edx.pge | cpuid_edx.fxsr
|
||||
.ecx equ 0
|
||||
|
||||
cpuid_check:
|
||||
; If bit 21 of EFLAGS can be changed, then CPUID is supported
|
||||
pushfd ;Save EFLAGS
|
||||
pushfd ;Store EFLAGS
|
||||
xor dword [esp],0x00200000 ;Invert the ID bit in stored EFLAGS
|
||||
popfd ;Load stored EFLAGS (with ID bit inverted)
|
||||
pushfd ;Store EFLAGS again (ID bit may or may not be inverted)
|
||||
pop eax ;eax = modified EFLAGS (ID bit may or may not be inverted)
|
||||
xor eax,[esp] ;eax = whichever bits were changed
|
||||
popfd ;Restore original EFLAGS
|
||||
test eax,0x00200000 ;eax = zero if ID bit can't be changed, else non-zero
|
||||
jz .no_cpuid
|
||||
|
||||
mov eax, 1
|
||||
cpuid
|
||||
|
||||
and edx, cpuid_required_features.edx
|
||||
cmp edx, cpuid_required_features.edx
|
||||
jne .error
|
||||
|
||||
and ecx, cpuid_required_features.ecx
|
||||
cmp ecx, cpuid_required_features.ecx
|
||||
jne .error
|
||||
|
||||
ret
|
||||
|
||||
.no_cpuid:
|
||||
mov si, .msg_cpuid
|
||||
call print
|
||||
|
||||
mov si, .msg_line
|
||||
call print
|
||||
|
||||
jmp .halt
|
||||
|
||||
.error:
|
||||
push ecx
|
||||
push edx
|
||||
|
||||
mov si, .msg_features
|
||||
call print
|
||||
|
||||
mov si, .msg_line
|
||||
call print
|
||||
|
||||
mov si, .msg_edx
|
||||
call print
|
||||
|
||||
pop ebx
|
||||
push ebx
|
||||
shr ebx, 16
|
||||
call print_hex
|
||||
|
||||
pop ebx
|
||||
call print_hex
|
||||
|
||||
mov si, .msg_must_contain
|
||||
call print
|
||||
|
||||
mov ebx, cpuid_required_features.edx
|
||||
shr ebx, 16
|
||||
call print_hex
|
||||
|
||||
mov ebx, cpuid_required_features.edx
|
||||
call print_hex
|
||||
|
||||
mov si, .msg_line
|
||||
call print
|
||||
|
||||
mov si, .msg_ecx
|
||||
call print
|
||||
|
||||
pop ebx
|
||||
push ebx
|
||||
shr ebx, 16
|
||||
call print_hex
|
||||
|
||||
pop ebx
|
||||
call print_hex
|
||||
|
||||
mov si, .msg_must_contain
|
||||
call print
|
||||
|
||||
mov ebx, cpuid_required_features.ecx
|
||||
shr ebx, 16
|
||||
call print_hex
|
||||
|
||||
mov ebx, cpuid_required_features.ecx
|
||||
call print_hex
|
||||
|
||||
mov si, .msg_line
|
||||
call print
|
||||
|
||||
.halt:
|
||||
cli
|
||||
hlt
|
||||
jmp .halt
|
||||
|
||||
.msg_cpuid: db "CPUID not supported",0
|
||||
.msg_features: db "Required CPU features are not present",0
|
||||
.msg_line: db 13,10,0
|
||||
.msg_edx: db "EDX ",0
|
||||
.msg_ecx: db "ECX ",0
|
||||
.msg_must_contain: db " must contain ",0
|
||||
|
||||
cpuid_edx:
|
||||
.fpu equ 1 << 0
|
||||
.vme equ 1 << 1
|
||||
.de equ 1 << 2
|
||||
.pse equ 1 << 3
|
||||
.tsc equ 1 << 4
|
||||
.msr equ 1 << 5
|
||||
.pae equ 1 << 6
|
||||
.mce equ 1 << 7
|
||||
.cx8 equ 1 << 8
|
||||
.apic equ 1 << 9
|
||||
.sep equ 1 << 11
|
||||
.mtrr equ 1 << 12
|
||||
.pge equ 1 << 13
|
||||
.mca equ 1 << 14
|
||||
.cmov equ 1 << 15
|
||||
.pat equ 1 << 16
|
||||
.pse_36 equ 1 << 17
|
||||
.psn equ 1 << 18
|
||||
.clfsh equ 1 << 19
|
||||
.ds equ 1 << 21
|
||||
.acpi equ 1 << 22
|
||||
.mmx equ 1 << 23
|
||||
.fxsr equ 1 << 24
|
||||
.sse equ 1 << 25
|
||||
.sse2 equ 1 << 26
|
||||
.ss equ 1 << 27
|
||||
.htt equ 1 << 28
|
||||
.tm equ 1 << 29
|
||||
.ia64 equ 1 << 30
|
||||
.pbe equ 1 << 31
|
||||
|
||||
cpuid_ecx:
|
||||
.sse3 equ 1 << 0
|
||||
.pclmulqdq equ 1 << 1
|
||||
.dtes64 equ 1 << 2
|
||||
.monitor equ 1 << 3
|
||||
.ds_cpl equ 1 << 4
|
||||
.vmx equ 1 << 5
|
||||
.smx equ 1 << 6
|
||||
.est equ 1 << 7
|
||||
.tm2 equ 1 << 8
|
||||
.ssse3 equ 1 << 9
|
||||
.cnxt_id equ 1 << 10
|
||||
.sdbg equ 1 << 11
|
||||
.fma equ 1 << 12
|
||||
.cmpxchg16b equ 1 << 13
|
||||
.xtpr equ 1 << 14
|
||||
.pdcm equ 1 << 15
|
||||
.pcid equ 1 << 17
|
||||
.dca equ 1 << 18
|
||||
.sse4_1 equ 1 << 19
|
||||
.sse4_2 equ 1 << 20
|
||||
.x2apic equ 1 << 21
|
||||
.movbe equ 1 << 22
|
||||
.popcnt equ 1 << 23
|
||||
.tsc_deadline equ 1 << 24
|
||||
.aes equ 1 << 25
|
||||
.xsave equ 1 << 26
|
||||
.osxsave equ 1 << 27
|
||||
.avx equ 1 << 28
|
||||
.f16c equ 1 << 29
|
||||
.rdrand equ 1 << 30
|
||||
.hypervisor equ 1 << 31
|
||||
@@ -0,0 +1,128 @@
|
||||
SECTION .text ; cannot use .data
|
||||
|
||||
struc GDTEntry
|
||||
.limitl resw 1
|
||||
.basel resw 1
|
||||
.basem resb 1
|
||||
.attribute resb 1
|
||||
.flags__limith resb 1
|
||||
.baseh resb 1
|
||||
endstruc
|
||||
|
||||
gdt_attr:
|
||||
.present equ 1 << 7
|
||||
.ring1 equ 1 << 5
|
||||
.ring2 equ 1 << 6
|
||||
.ring3 equ 1 << 5 | 1 << 6
|
||||
.user equ 1 << 4
|
||||
;user
|
||||
.code equ 1 << 3
|
||||
; code
|
||||
.conforming equ 1 << 2
|
||||
.readable equ 1 << 1
|
||||
; data
|
||||
.expand_down equ 1 << 2
|
||||
.writable equ 1 << 1
|
||||
.accessed equ 1 << 0
|
||||
;system
|
||||
; legacy
|
||||
.tssAvailabe16 equ 0x1
|
||||
.ldt equ 0x2
|
||||
.tssBusy16 equ 0x3
|
||||
.call16 equ 0x4
|
||||
.task equ 0x5
|
||||
.interrupt16 equ 0x6
|
||||
.trap16 equ 0x7
|
||||
.tssAvailabe32 equ 0x9
|
||||
.tssBusy32 equ 0xB
|
||||
.call32 equ 0xC
|
||||
.interrupt32 equ 0xE
|
||||
.trap32 equ 0xF
|
||||
; long mode
|
||||
.ldt32 equ 0x2
|
||||
.tssAvailabe64 equ 0x9
|
||||
.tssBusy64 equ 0xB
|
||||
.call64 equ 0xC
|
||||
.interrupt64 equ 0xE
|
||||
.trap64 equ 0xF
|
||||
|
||||
gdt_flag:
|
||||
.granularity equ 1 << 7
|
||||
.available equ 1 << 4
|
||||
;user
|
||||
.default_operand_size equ 1 << 6
|
||||
; code
|
||||
.long_mode equ 1 << 5
|
||||
; data
|
||||
.reserved equ 1 << 5
|
||||
|
||||
gdtr:
|
||||
dw gdt.end + 1 ; size
|
||||
dq gdt ; offset
|
||||
|
||||
gdt:
|
||||
.null equ $ - gdt
|
||||
dq 0
|
||||
|
||||
.lm64_code equ $ - gdt
|
||||
istruc GDTEntry
|
||||
at GDTEntry.limitl, dw 0
|
||||
at GDTEntry.basel, dw 0
|
||||
at GDTEntry.basem, db 0
|
||||
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code
|
||||
at GDTEntry.flags__limith, db gdt_flag.long_mode
|
||||
at GDTEntry.baseh, db 0
|
||||
iend
|
||||
|
||||
.lm64_data equ $ - gdt
|
||||
istruc GDTEntry
|
||||
at GDTEntry.limitl, dw 0
|
||||
at GDTEntry.basel, dw 0
|
||||
at GDTEntry.basem, db 0
|
||||
; AMD System Programming Manual states that the writeable bit is ignored in long mode, but ss can not be set to this descriptor without it
|
||||
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable
|
||||
at GDTEntry.flags__limith, db 0
|
||||
at GDTEntry.baseh, db 0
|
||||
iend
|
||||
|
||||
.pm32_code equ $ - gdt
|
||||
istruc GDTEntry
|
||||
at GDTEntry.limitl, dw 0xFFFF
|
||||
at GDTEntry.basel, dw 0
|
||||
at GDTEntry.basem, db 0
|
||||
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code | gdt_attr.readable
|
||||
at GDTEntry.flags__limith, db 0xF | gdt_flag.granularity | gdt_flag.default_operand_size
|
||||
at GDTEntry.baseh, db 0
|
||||
iend
|
||||
|
||||
.pm32_data equ $ - gdt
|
||||
istruc GDTEntry
|
||||
at GDTEntry.limitl, dw 0xFFFF
|
||||
at GDTEntry.basel, dw 0
|
||||
at GDTEntry.basem, db 0
|
||||
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable
|
||||
at GDTEntry.flags__limith, db 0xF | gdt_flag.granularity | gdt_flag.default_operand_size
|
||||
at GDTEntry.baseh, db 0
|
||||
iend
|
||||
|
||||
.pm16_code equ $ - gdt
|
||||
istruc GDTEntry
|
||||
at GDTEntry.limitl, dw 0xFFFF
|
||||
at GDTEntry.basel, dw 0
|
||||
at GDTEntry.basem, db 0
|
||||
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code | gdt_attr.readable
|
||||
at GDTEntry.flags__limith, db 0xF
|
||||
at GDTEntry.baseh, db 0
|
||||
iend
|
||||
|
||||
.pm16_data equ $ - gdt
|
||||
istruc GDTEntry
|
||||
at GDTEntry.limitl, dw 0xFFFF
|
||||
at GDTEntry.basel, dw 0
|
||||
at GDTEntry.basem, db 0
|
||||
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable
|
||||
at GDTEntry.flags__limith, db 0xF
|
||||
at GDTEntry.baseh, db 0
|
||||
iend
|
||||
|
||||
.end equ $ - gdt
|
||||
@@ -0,0 +1,161 @@
|
||||
; Simple ISO emulation with el torito
|
||||
|
||||
; Fill until CD sector 0x10
|
||||
times (0x10*2048)-($-$$) db 0
|
||||
|
||||
; Volume record
|
||||
;TODO: fill in more fields
|
||||
iso_volume_record:
|
||||
db 1 ; Type volume record
|
||||
db "CD001" ; Identifier
|
||||
db 1 ; Version
|
||||
db 0 ; Unused
|
||||
times 32 db ' ' ; System identifier
|
||||
.volume_id: ; Volume identifier
|
||||
db 'Redox OS'
|
||||
times 32-($-.volume_id) db ' '
|
||||
times 8 db 0 ; Unused
|
||||
db 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15 ; Volume space size (0x15)
|
||||
times 32 db 0 ; Unused
|
||||
db 0x01, 0x00, 0x00, 0x01 ; Volume set size
|
||||
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
|
||||
db 0x00, 0x08, 0x08, 0x00 ; Logical block size in little and big endian
|
||||
|
||||
times 156-($-iso_volume_record) db 0
|
||||
|
||||
; Root directory entry
|
||||
.root_directory:
|
||||
db 0x22 ; Length of entry
|
||||
db 0x00 ; Length of extended attributes
|
||||
db 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14 ; Location of extent (0x14)
|
||||
db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent
|
||||
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time
|
||||
db 0x02 ; File flags
|
||||
db 0x00 ; Interleaved file unit size
|
||||
db 0x00 ; Interleaved gap size
|
||||
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
|
||||
db 0x01 ; Length of file identifier
|
||||
db 0x00 ; File identifier
|
||||
|
||||
times 128 db ' ' ; Volume set identifier
|
||||
times 128 db ' ' ; Publisher identifier
|
||||
times 128 db ' ' ; Data preparer identifier
|
||||
times 128 db ' ' ; Application identifier
|
||||
times 37 db ' ' ; Copyright file ID
|
||||
times 37 db ' ' ; Abstract file ID
|
||||
times 37 db ' ' ; Bibliographic file ID
|
||||
|
||||
times 881-($-iso_volume_record) db 0
|
||||
|
||||
db 1 ; File structure version
|
||||
|
||||
; Fill until CD sector 0x11
|
||||
times (0x11*2048)-($-$$) db 0
|
||||
|
||||
; Boot record
|
||||
iso_boot_record:
|
||||
db 0 ; Type boot record
|
||||
db "CD001" ; Identifier
|
||||
db 1 ; Version
|
||||
db "EL TORITO SPECIFICATION" ; Boot system identifier
|
||||
times 0x47-($ - iso_boot_record) db 0 ; Padding
|
||||
dd 0x13 ; Sector of boot catalog
|
||||
|
||||
; Fill until CD sector 0x12
|
||||
times (0x12*2048)-($-$$) db 0
|
||||
|
||||
; Terminator
|
||||
iso_terminator:
|
||||
db 0xFF ; Type terminator
|
||||
db "CD001" ; Identifier
|
||||
db 1 ; Version
|
||||
|
||||
; Fill until CD sector 0x13
|
||||
times (0x13*2048)-($-$$) db 0
|
||||
|
||||
; Boot catalog
|
||||
iso_boot_catalog:
|
||||
|
||||
; Validation entry
|
||||
.validation:
|
||||
db 1 ; Header ID
|
||||
db 0 ; Platform ID (x86)
|
||||
dw 0 ; Reserved
|
||||
times 24 db 0 ; ID string
|
||||
dw 0x55aa ; Checksum
|
||||
dw 0xaa55 ; Key
|
||||
|
||||
; Default entry
|
||||
.default:
|
||||
db 0x88 ; Bootable
|
||||
db 4 ; Hard drive emulation
|
||||
dw 0 ; Load segment (0 is platform default)
|
||||
db 0xEE ; Partition type (0xEE is protective MBR)
|
||||
db 0 ; Unused
|
||||
dw 1 ; Sector count
|
||||
dd 0 ; Start address for virtual disk
|
||||
times 20 db 0 ; Padding
|
||||
|
||||
; EFI section header entry
|
||||
.efi_section_header:
|
||||
db 0x91 ; Final header
|
||||
db 0xEF ; Platform ID (EFI)
|
||||
dw 1 ; Number of section header entries
|
||||
times 28 db 0 ; ID string
|
||||
|
||||
; EFI section entry
|
||||
.efi_section_entry:
|
||||
db 0x88 ; Bootable
|
||||
db 0 ; No emulation
|
||||
dw 0 ; Load segment (0 is platform default)
|
||||
db 0 ; Partition type (not used)
|
||||
db 0 ; Unused
|
||||
dw 512 ; Sector count (1 MiB = 512 CD sectors)
|
||||
dd 512 ; Start address for virtual disk (1 MiB = 512 CD sectors)
|
||||
times 20 db 0 ; Padding
|
||||
|
||||
; Fill until CD sector 0x14
|
||||
times (0x14*2048)-($-$$) db 0
|
||||
|
||||
iso_root_directory:
|
||||
.self:
|
||||
db 0x22 ; Length of entry
|
||||
db 0x00 ; Length of extended attributes
|
||||
db 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14 ; Location of extent (0x14)
|
||||
db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent
|
||||
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time
|
||||
db 0x02 ; File flags
|
||||
db 0x00 ; Interleaved file unit size
|
||||
db 0x00 ; Interleaved gap size
|
||||
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
|
||||
db 0x01 ; Length of file identifier
|
||||
db 0x00 ; File identifier
|
||||
|
||||
.parent:
|
||||
db 0x22 ; Length of entry
|
||||
db 0x00 ; Length of extended attributes
|
||||
db 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14 ; Location of extent (0x14)
|
||||
db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent
|
||||
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time
|
||||
db 0x02 ; File flags
|
||||
db 0x00 ; Interleaved file unit size
|
||||
db 0x00 ; Interleaved gap size
|
||||
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
|
||||
db 0x01 ; Length of file identifier
|
||||
db 0x01 ; File identifier
|
||||
|
||||
.boot_cat:
|
||||
db 0x2C ; Length of entry
|
||||
db 0x00 ; Length of extended attributes
|
||||
db 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13 ; Location of extent (0x13)
|
||||
db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent
|
||||
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time
|
||||
db 0x00 ; File flags
|
||||
db 0x00 ; Interleaved file unit size
|
||||
db 0x00 ; Interleaved gap size
|
||||
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
|
||||
db 0x0A ; Length of file identifier
|
||||
db "BOOT.CAT;1",0 ; File identifier
|
||||
|
||||
; Fill until CD sector 0x15
|
||||
times (0x15*2048)-($-$$) db 0
|
||||
@@ -0,0 +1,56 @@
|
||||
SECTION .text
|
||||
USE32
|
||||
|
||||
long_mode:
|
||||
.func: dq 0
|
||||
.page_table: dd 0
|
||||
|
||||
.entry:
|
||||
; disable interrupts
|
||||
cli
|
||||
|
||||
; disable paging
|
||||
mov eax, cr0
|
||||
and eax, 0x7FFFFFFF
|
||||
mov cr0, eax
|
||||
|
||||
; enable FXSAVE/FXRSTOR, Page Global, Page Address Extension, and Page Size Extension
|
||||
mov eax, cr4
|
||||
or eax, 1 << 9 | 1 << 7 | 1 << 5 | 1 << 4
|
||||
mov cr4, eax
|
||||
|
||||
; load long mode GDT
|
||||
lgdt [gdtr]
|
||||
|
||||
; enable long mode
|
||||
mov ecx, 0xC0000080 ; Read from the EFER MSR.
|
||||
rdmsr
|
||||
or eax, 1 << 11 | 1 << 8 ; Set the Long-Mode-Enable and NXE bit.
|
||||
wrmsr
|
||||
|
||||
; set page table
|
||||
mov eax, [.page_table]
|
||||
mov cr3, eax
|
||||
|
||||
; enabling paging and protection simultaneously
|
||||
mov eax, cr0
|
||||
or eax, 1 << 31 | 1 << 16 | 1 ;Bit 31: Paging, Bit 16: write protect kernel, Bit 0: Protected Mode
|
||||
mov cr0, eax
|
||||
|
||||
; far jump to enable Long Mode and load CS with 64 bit segment
|
||||
jmp gdt.lm64_code:.inner
|
||||
|
||||
USE64
|
||||
|
||||
.inner:
|
||||
; load all the other segments with 64 bit data segments
|
||||
mov rax, gdt.lm64_data
|
||||
mov ds, rax
|
||||
mov es, rax
|
||||
mov fs, rax
|
||||
mov gs, rax
|
||||
mov ss, rax
|
||||
|
||||
; jump to specified function
|
||||
mov rax, [.func]
|
||||
jmp rax
|
||||
@@ -0,0 +1,67 @@
|
||||
SECTION .text
|
||||
USE16
|
||||
|
||||
; provide function for printing in x86 real mode
|
||||
|
||||
; print a string and a newline
|
||||
; CLOBBER
|
||||
; ax
|
||||
print_line:
|
||||
mov al, 13
|
||||
call print_char
|
||||
mov al, 10
|
||||
jmp print_char
|
||||
|
||||
; print a string
|
||||
; IN
|
||||
; si: points at zero-terminated String
|
||||
; CLOBBER
|
||||
; si, ax
|
||||
print:
|
||||
pushf
|
||||
cld
|
||||
.loop:
|
||||
lodsb
|
||||
test al, al
|
||||
jz .done
|
||||
call print_char
|
||||
jmp .loop
|
||||
.done:
|
||||
popf
|
||||
ret
|
||||
|
||||
; print a character
|
||||
; IN
|
||||
; al: character to print
|
||||
print_char:
|
||||
pusha
|
||||
mov bx, 7
|
||||
mov ah, 0x0e
|
||||
int 0x10
|
||||
popa
|
||||
ret
|
||||
|
||||
; print a number in hex
|
||||
; IN
|
||||
; bx: the number
|
||||
; CLOBBER
|
||||
; al, cx
|
||||
print_hex:
|
||||
mov cx, 4
|
||||
.lp:
|
||||
mov al, bh
|
||||
shr al, 4
|
||||
|
||||
cmp al, 0xA
|
||||
jb .below_0xA
|
||||
|
||||
add al, 'A' - 0xA - '0'
|
||||
.below_0xA:
|
||||
add al, '0'
|
||||
|
||||
call print_char
|
||||
|
||||
shl bx, 4
|
||||
loop .lp
|
||||
|
||||
ret
|
||||
@@ -0,0 +1,36 @@
|
||||
SECTION .text
|
||||
USE16
|
||||
|
||||
protected_mode:
|
||||
|
||||
.func: dd 0
|
||||
|
||||
.entry:
|
||||
; disable interrupts
|
||||
cli
|
||||
|
||||
; load protected mode GDT
|
||||
lgdt [gdtr]
|
||||
|
||||
; set protected mode bit of cr0
|
||||
mov eax, cr0
|
||||
or eax, 1
|
||||
mov cr0, eax
|
||||
|
||||
; far jump to load CS with 32 bit segment
|
||||
jmp gdt.pm32_code:.inner
|
||||
|
||||
USE32
|
||||
|
||||
.inner:
|
||||
; load all the other segments with 32 bit data segments
|
||||
mov eax, gdt.pm32_data
|
||||
mov ds, eax
|
||||
mov es, eax
|
||||
mov fs, eax
|
||||
mov gs, eax
|
||||
mov ss, eax
|
||||
|
||||
; jump to specified function
|
||||
mov eax, [.func]
|
||||
jmp eax
|
||||
@@ -0,0 +1,222 @@
|
||||
ORG 0x7C00
|
||||
SECTION .text
|
||||
USE16
|
||||
|
||||
stage1: ; dl comes with disk
|
||||
; initialize segment registers
|
||||
xor ax, ax
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov ss, ax
|
||||
|
||||
; initialize stack
|
||||
mov sp, 0x7C00
|
||||
|
||||
; initialize CS
|
||||
push ax
|
||||
push word .set_cs
|
||||
retf
|
||||
|
||||
.set_cs:
|
||||
|
||||
; save disk number
|
||||
mov [disk], dl
|
||||
|
||||
mov si, stage_msg
|
||||
call print
|
||||
mov al, '1'
|
||||
call print_char
|
||||
call print_line
|
||||
|
||||
; read CHS gemotry
|
||||
; CL (bits 0-5) = maximum sector number
|
||||
; CL (bits 6-7) = high bits of max cylinder number
|
||||
; CH = low bits of maximum cylinder number
|
||||
; DH = maximum head number
|
||||
mov ah, 0x08
|
||||
mov dl, [disk]
|
||||
xor di, di
|
||||
int 0x13
|
||||
jc error ; carry flag set on error
|
||||
mov bl, ch
|
||||
mov bh, cl
|
||||
shr bh, 6
|
||||
mov [chs.c], bx
|
||||
shr dx, 8
|
||||
inc dx ; returns heads - 1
|
||||
mov [chs.h], dx
|
||||
and cl, 0x3f
|
||||
mov [chs.s], cl
|
||||
|
||||
mov eax, (stage2 - stage1) / 512
|
||||
mov bx, stage2
|
||||
mov cx, (stage3.end - stage2) / 512
|
||||
mov dx, 0
|
||||
call load
|
||||
|
||||
mov si, stage_msg
|
||||
call print
|
||||
mov al, '2'
|
||||
call print_char
|
||||
call print_line
|
||||
|
||||
jmp stage2.entry
|
||||
|
||||
; load some sectors from disk to a buffer in memory
|
||||
; buffer has to be below 1MiB
|
||||
; IN
|
||||
; ax: start sector
|
||||
; bx: offset of buffer
|
||||
; cx: number of sectors (512 Bytes each)
|
||||
; dx: segment of buffer
|
||||
; CLOBBER
|
||||
; ax, bx, cx, dx, si
|
||||
; TODO rewrite to (eventually) move larger parts at once
|
||||
; if that is done increase buffer_size_sectors in startup-common to that (max 0x80000 - startup_end)
|
||||
load:
|
||||
cmp cx, 127
|
||||
jbe .good_size
|
||||
|
||||
pusha
|
||||
mov cx, 127
|
||||
call load
|
||||
popa
|
||||
add eax, 127
|
||||
add dx, 127 * 512 / 16
|
||||
sub cx, 127
|
||||
|
||||
jmp load
|
||||
.good_size:
|
||||
mov [DAPACK.addr], eax
|
||||
mov [DAPACK.buf], bx
|
||||
mov [DAPACK.count], cx
|
||||
mov [DAPACK.seg], dx
|
||||
|
||||
call print_dapack
|
||||
|
||||
cmp byte [chs.s], 0
|
||||
jne .chs
|
||||
;INT 0x13 extended read does not work on CDROM!
|
||||
mov dl, [disk]
|
||||
mov si, DAPACK
|
||||
mov ah, 0x42
|
||||
int 0x13
|
||||
jc error ; carry flag set on error
|
||||
ret
|
||||
|
||||
.chs:
|
||||
; calculate CHS
|
||||
xor edx, edx
|
||||
mov eax, [DAPACK.addr]
|
||||
div dword [chs.s] ; divide by sectors
|
||||
mov ecx, edx ; move sector remainder to ecx
|
||||
xor edx, edx
|
||||
div dword [chs.h] ; divide by heads
|
||||
; eax has cylinders, edx has heads, ecx has sectors
|
||||
|
||||
; Sector cannot be greater than 63
|
||||
inc ecx ; Sector is base 1
|
||||
cmp ecx, 63
|
||||
ja error_chs
|
||||
|
||||
; Head cannot be greater than 255
|
||||
cmp edx, 255
|
||||
ja error_chs
|
||||
|
||||
; Cylinder cannot be greater than 1023
|
||||
cmp eax, 1023
|
||||
ja error_chs
|
||||
|
||||
; Move CHS values to parameters
|
||||
mov ch, al
|
||||
shl ah, 6
|
||||
and cl, 0x3f
|
||||
or cl, ah
|
||||
shl dx, 8
|
||||
|
||||
; read from disk using CHS
|
||||
mov al, [DAPACK.count]
|
||||
mov ah, 0x02 ; disk read (CHS)
|
||||
mov bx, [DAPACK.buf]
|
||||
mov dl, [disk]
|
||||
push es ; save ES
|
||||
mov es, [DAPACK.seg]
|
||||
int 0x13
|
||||
pop es ; restore EC
|
||||
jc error ; carry flag set on error
|
||||
ret
|
||||
|
||||
print_dapack:
|
||||
mov bx, [DAPACK.addr + 2]
|
||||
call print_hex
|
||||
|
||||
mov bx, [DAPACK.addr]
|
||||
call print_hex
|
||||
|
||||
mov al, '#'
|
||||
call print_char
|
||||
|
||||
mov bx, [DAPACK.count]
|
||||
call print_hex
|
||||
|
||||
mov al, ' '
|
||||
call print_char
|
||||
|
||||
mov bx, [DAPACK.seg]
|
||||
call print_hex
|
||||
|
||||
mov al, ':'
|
||||
call print_char
|
||||
|
||||
mov bx, [DAPACK.buf]
|
||||
call print_hex
|
||||
|
||||
call print_line
|
||||
|
||||
ret
|
||||
|
||||
error_chs:
|
||||
mov ah, 0
|
||||
|
||||
error:
|
||||
call print_line
|
||||
|
||||
mov bh, 0
|
||||
mov bl, ah
|
||||
call print_hex
|
||||
|
||||
mov al, ' '
|
||||
call print_char
|
||||
|
||||
mov si, error_msg
|
||||
call print
|
||||
call print_line
|
||||
.halt:
|
||||
cli
|
||||
hlt
|
||||
jmp .halt
|
||||
|
||||
%include "print.asm"
|
||||
|
||||
stage_msg: db "Stage ",0
|
||||
error_msg: db "ERROR",0
|
||||
|
||||
disk: db 0
|
||||
|
||||
chs:
|
||||
.c: dd 0
|
||||
.h: dd 0
|
||||
.s: dd 0
|
||||
|
||||
DAPACK:
|
||||
db 0x10
|
||||
db 0
|
||||
.count: dw 0 ; int 13 resets this to # of blocks actually read/written
|
||||
.buf: dw 0 ; memory buffer destination address (0:7c00)
|
||||
.seg: dw 0 ; in memory page zero
|
||||
.addr: dq 0 ; put the lba to read in this spot
|
||||
|
||||
times 446-($-$$) db 0
|
||||
partitions: times 4 * 16 db 0
|
||||
db 0x55
|
||||
db 0xaa
|
||||
@@ -0,0 +1,134 @@
|
||||
SECTION .text
|
||||
USE16
|
||||
|
||||
stage2.entry:
|
||||
; check for required features
|
||||
call cpuid_check
|
||||
|
||||
; enable A20-Line via IO-Port 92, might not work on all motherboards
|
||||
in al, 0x92
|
||||
or al, 2
|
||||
out 0x92, al
|
||||
|
||||
mov dword [protected_mode.func], stage3.entry
|
||||
jmp protected_mode.entry
|
||||
|
||||
%include "cpuid.asm"
|
||||
%include "gdt.asm"
|
||||
%include "long_mode.asm"
|
||||
%include "protected_mode.asm"
|
||||
%include "thunk.asm"
|
||||
|
||||
USE32
|
||||
|
||||
stage3.entry:
|
||||
; stage3 stack at 448 KiB (512KiB minus 64KiB disk buffer)
|
||||
mov esp, 0x70000
|
||||
|
||||
; push arguments
|
||||
mov eax, thunk.int16
|
||||
push eax
|
||||
mov eax, thunk.int15
|
||||
push eax
|
||||
mov eax, thunk.int13
|
||||
push eax
|
||||
mov eax, thunk.int10
|
||||
push eax
|
||||
xor eax, eax
|
||||
mov al, [disk]
|
||||
push eax
|
||||
mov eax, kernel.entry
|
||||
push eax
|
||||
mov eax, [stage3 + 0x18]
|
||||
call eax
|
||||
.halt:
|
||||
cli
|
||||
hlt
|
||||
jmp .halt
|
||||
|
||||
kernel:
|
||||
.stack: dq 0
|
||||
.func: dq 0
|
||||
.args: dq 0
|
||||
|
||||
.entry:
|
||||
; page_table: usize
|
||||
mov eax, [esp + 4]
|
||||
mov [long_mode.page_table], eax
|
||||
|
||||
; stack: u64
|
||||
mov eax, [esp + 8]
|
||||
mov [.stack], eax
|
||||
mov eax, [esp + 12]
|
||||
mov [.stack + 4], eax
|
||||
|
||||
; func: u64
|
||||
mov eax, [esp + 16]
|
||||
mov [.func], eax
|
||||
mov eax, [esp + 20]
|
||||
mov [.func + 4], eax
|
||||
|
||||
; args: *const KernelArgs
|
||||
mov eax, [esp + 24]
|
||||
mov [.args], eax
|
||||
|
||||
; long_mode: usize
|
||||
mov eax, [esp + 28]
|
||||
test eax, eax
|
||||
jz .inner32
|
||||
|
||||
mov eax, .inner64
|
||||
mov [long_mode.func], eax
|
||||
jmp long_mode.entry
|
||||
|
||||
.inner32:
|
||||
; disable paging
|
||||
mov eax, cr0
|
||||
and eax, 0x7FFFFFFF
|
||||
mov cr0, eax
|
||||
|
||||
;TODO: PAE (1 << 5)
|
||||
; enable FXSAVE/FXRSTOR, Page Global, and Page Size Extension
|
||||
mov eax, cr4
|
||||
or eax, 1 << 9 | 1 << 7 | 1 << 4
|
||||
mov cr4, eax
|
||||
|
||||
; set page table
|
||||
mov eax, [long_mode.page_table]
|
||||
mov cr3, eax
|
||||
|
||||
; enabling paging and protection simultaneously
|
||||
mov eax, cr0
|
||||
; Bit 31: Paging, Bit 16: write protect kernel, Bit 0: Protected Mode
|
||||
or eax, 1 << 31 | 1 << 16 | 1
|
||||
mov cr0, eax
|
||||
|
||||
; enable FPU
|
||||
;TODO: move to Rust
|
||||
mov eax, cr0
|
||||
and al, 11110011b ; Clear task switched (3) and emulation (2)
|
||||
or al, 00100010b ; Set numeric error (5) monitor co-processor (1)
|
||||
mov cr0, eax
|
||||
fninit
|
||||
|
||||
mov esp, [.stack]
|
||||
mov eax, [.args]
|
||||
push eax
|
||||
mov eax, [.func]
|
||||
call eax
|
||||
.halt32:
|
||||
cli
|
||||
hlt
|
||||
jmp .halt32
|
||||
|
||||
USE64
|
||||
|
||||
.inner64:
|
||||
mov rsp, [.stack]
|
||||
mov rax, [.func]
|
||||
mov rdi, [.args]
|
||||
call rax
|
||||
.halt64:
|
||||
cli
|
||||
hlt
|
||||
jmp .halt64
|
||||
@@ -0,0 +1,149 @@
|
||||
SECTION .text
|
||||
USE32
|
||||
|
||||
thunk:
|
||||
.int10:
|
||||
mov dword [.func], .int10_real
|
||||
jmp .enter
|
||||
|
||||
.int13:
|
||||
mov dword [.func], .int13_real
|
||||
jmp .enter
|
||||
|
||||
.int15:
|
||||
mov dword [.func], .int15_real
|
||||
jmp .enter
|
||||
|
||||
.int16:
|
||||
mov dword [.func], .int16_real
|
||||
jmp .enter
|
||||
|
||||
.func: dd 0
|
||||
.esp: dd 0
|
||||
.cr0: dd 0
|
||||
|
||||
.enter:
|
||||
; save flags
|
||||
pushfd
|
||||
|
||||
; save registers
|
||||
pushad
|
||||
|
||||
; save esp
|
||||
mov [.esp], esp
|
||||
|
||||
; load gdt
|
||||
lgdt [gdtr]
|
||||
|
||||
; far jump to protected mode 16-bit
|
||||
jmp gdt.pm16_code:.pm16
|
||||
|
||||
.exit:
|
||||
; set segment selectors to 32-bit protected mode
|
||||
mov eax, gdt.pm32_data
|
||||
mov ds, eax
|
||||
mov es, eax
|
||||
mov fs, eax
|
||||
mov gs, eax
|
||||
mov ss, eax
|
||||
|
||||
; restore esp
|
||||
mov esp, [.esp]
|
||||
|
||||
; restore registers
|
||||
popad
|
||||
|
||||
; restore flags
|
||||
popfd
|
||||
|
||||
; return
|
||||
ret
|
||||
|
||||
USE16
|
||||
|
||||
.int10_real:
|
||||
int 0x10
|
||||
ret
|
||||
|
||||
.int13_real:
|
||||
int 0x13
|
||||
ret
|
||||
|
||||
.int15_real:
|
||||
int 0x15
|
||||
ret
|
||||
|
||||
.int16_real:
|
||||
int 0x16
|
||||
ret
|
||||
|
||||
.pm16:
|
||||
; set segment selectors to protected mode 16-bit
|
||||
mov eax, gdt.pm16_data
|
||||
mov ds, eax
|
||||
mov es, eax
|
||||
mov fs, eax
|
||||
mov gs, eax
|
||||
mov ss, eax
|
||||
|
||||
; save cr0
|
||||
mov eax, cr0
|
||||
mov [.cr0], eax
|
||||
|
||||
; disable paging and protected mode
|
||||
and eax, 0x7FFFFFFE
|
||||
mov cr0, eax
|
||||
|
||||
; far jump to real mode
|
||||
jmp 0:.real
|
||||
|
||||
.real:
|
||||
; set segment selectors to real mode
|
||||
mov eax, 0
|
||||
mov ds, eax
|
||||
mov es, eax
|
||||
mov fs, eax
|
||||
mov gs, eax
|
||||
mov ss, eax
|
||||
|
||||
; set stack
|
||||
mov esp, 0x7C00 - 64
|
||||
|
||||
; load registers and ES
|
||||
pop es
|
||||
pop edi
|
||||
pop esi
|
||||
pop ebp
|
||||
pop ebx
|
||||
pop edx
|
||||
pop ecx
|
||||
pop eax
|
||||
|
||||
; enable interrupts
|
||||
sti
|
||||
|
||||
; call real mode function
|
||||
call [.func]
|
||||
|
||||
; disable interrupts
|
||||
cli
|
||||
|
||||
; save registers and ES
|
||||
push eax
|
||||
push ecx
|
||||
push edx
|
||||
push ebx
|
||||
push ebp
|
||||
push esi
|
||||
push edi
|
||||
push es
|
||||
|
||||
; load gdt (BIOS sometimes overwrites this)
|
||||
lgdt [gdtr]
|
||||
|
||||
; restore cr0, will enable protected mode
|
||||
mov eax, [.cr0]
|
||||
mov cr0, eax
|
||||
|
||||
; far jump to protected mode 32-bit
|
||||
jmp gdt.pm32_code:.exit
|
||||
@@ -1,4 +0,0 @@
|
||||
target
|
||||
corpus
|
||||
artifacts
|
||||
coverage
|
||||
Generated
-858
@@ -1,858 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db4ce4441f99dbd377ca8a8f57b698c44d0d6e712d8329b5040da5a64aa1ce73"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"blake2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endian-num"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad847bb2094f110bbdd6fa564894ca4556fd978958e93985420d680d3cb6d14"
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"env_filter",
|
||||
"jiff",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
|
||||
|
||||
[[package]]
|
||||
name = "fuser"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bb29a3ae32279fe3e79a958fe01899f5fb23eadccee919cf88e145b54ed9367"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"memchr",
|
||||
"nix",
|
||||
"page_size",
|
||||
"smallvec",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humansize"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
|
||||
dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5ad87c89110f55e4cd4dc2893a9790820206729eaf221555f742d540b0724a0"
|
||||
dependencies = [
|
||||
"jiff-static",
|
||||
"log",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d076d5b64a7e2fe6f0743f02c43ca4a6725c0f904203bfe276a5b3e793103605"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.155"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
"redox_syscall 0.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
|
||||
[[package]]
|
||||
name = "lz4_flex"
|
||||
version = "0.11.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numtoa"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "page_size"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parse-size"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "487f2ccd1e17ce8c1bfab3a65c89525af41cfad4c8659021a1e9a2aacd73b89b"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "range-tree"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384c2842d4e069d5ccacf5fe1dca4ef8d07a5444329715f0fc3c61813502d4d1"
|
||||
|
||||
[[package]]
|
||||
name = "redox-path"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717"
|
||||
|
||||
[[package]]
|
||||
name = "redox-scheme"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cea2668a5932f878a4298a1d7f8950249bbbb77120fb263da252c589152f5ea"
|
||||
dependencies = [
|
||||
"libredox",
|
||||
"redox_syscall 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_termios"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb"
|
||||
|
||||
[[package]]
|
||||
name = "redoxfs"
|
||||
version = "0.8.4"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"argon2",
|
||||
"base64ct",
|
||||
"bitflags",
|
||||
"endian-num",
|
||||
"env_logger",
|
||||
"fuser",
|
||||
"getrandom",
|
||||
"humansize",
|
||||
"libc",
|
||||
"libredox",
|
||||
"log",
|
||||
"lz4_flex",
|
||||
"parse-size",
|
||||
"range-tree",
|
||||
"redox-path",
|
||||
"redox-scheme",
|
||||
"redox_syscall 0.6.0",
|
||||
"seahash",
|
||||
"termion",
|
||||
"uuid",
|
||||
"xts-mode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redoxfs-fuzz"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arbitrary",
|
||||
"fuser",
|
||||
"libfuzzer-sys",
|
||||
"nix",
|
||||
"redoxfs",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "seahash"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termion"
|
||||
version = "4.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3669a69de26799d6321a5aa713f55f7e2cd37bd47be044b50f2acafc42c122bb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libredox",
|
||||
"numtoa",
|
||||
"redox_termios",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "xts-mode"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cbddb7545ca0b9ffa7bdc653e8743303e1712687a6918ced25f2cdbed42520"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
@@ -1,30 +0,0 @@
|
||||
[package]
|
||||
name = "redoxfs-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
log = []
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.86"
|
||||
arbitrary = { version = "1.3.2", features = ["derive"] }
|
||||
fuser = { version = "0.16" }
|
||||
libfuzzer-sys = "0.4"
|
||||
nix = { version = "0.29.0", features = ["fs"] }
|
||||
tempfile = "3.10.1"
|
||||
|
||||
[dependencies.redoxfs]
|
||||
path = ".."
|
||||
|
||||
[[bin]]
|
||||
name = "fuse_fuzz_target"
|
||||
path = "fuzz_targets/fuse_fuzz_target.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
@@ -1,338 +0,0 @@
|
||||
//! Fuzzer that exercises random file system operations against a FUSE-mounted redoxfs.
|
||||
|
||||
#![no_main]
|
||||
|
||||
use anyhow::{ensure, Result};
|
||||
use fuser;
|
||||
use libfuzzer_sys::{arbitrary::Arbitrary, fuzz_target, Corpus};
|
||||
use nix::sys::statvfs::statvfs;
|
||||
use std::{
|
||||
fs::{self, File, FileTimes, OpenOptions},
|
||||
io::{Read, Seek, SeekFrom, Write},
|
||||
os::unix::fs::{self as unix_fs, PermissionsExt},
|
||||
path::{Path, PathBuf},
|
||||
thread,
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
use tempfile;
|
||||
|
||||
use redoxfs::{mount::fuse::Fuse, DiskSparse, FileSystem};
|
||||
|
||||
/// Maximum size for files and buffers. Chosen arbitrarily with fuzzing performance in mind.
|
||||
const MAX_SIZE: u64 = 10_000_000;
|
||||
/// Limit on the number of remounts in a single test case. Chosen arbitrarily with fuzzing
|
||||
/// performance in mind: remounts are costly.
|
||||
const MAX_MOUNT_SEQUENCES: usize = 3;
|
||||
|
||||
/// An operation to be performed by the fuzzer.
|
||||
#[derive(Arbitrary, Clone, Debug)]
|
||||
enum Operation {
|
||||
Chown {
|
||||
path: PathBuf,
|
||||
uid: Option<u32>,
|
||||
gid: Option<u32>,
|
||||
},
|
||||
CreateDir {
|
||||
path: PathBuf,
|
||||
},
|
||||
HardLink {
|
||||
original: PathBuf,
|
||||
link: PathBuf,
|
||||
},
|
||||
Metadata {
|
||||
path: PathBuf,
|
||||
},
|
||||
Read {
|
||||
path: PathBuf,
|
||||
},
|
||||
ReadDir {
|
||||
path: PathBuf,
|
||||
},
|
||||
ReadLink {
|
||||
path: PathBuf,
|
||||
},
|
||||
RemoveDir {
|
||||
path: PathBuf,
|
||||
},
|
||||
RemoveFile {
|
||||
path: PathBuf,
|
||||
},
|
||||
Rename {
|
||||
from: PathBuf,
|
||||
to: PathBuf,
|
||||
},
|
||||
SeekRead {
|
||||
path: PathBuf,
|
||||
seek_pos: u64,
|
||||
buf_size: usize,
|
||||
},
|
||||
SeekWrite {
|
||||
path: PathBuf,
|
||||
seek_pos: u64,
|
||||
buf_size: usize,
|
||||
},
|
||||
SetLen {
|
||||
path: PathBuf,
|
||||
size: u64,
|
||||
},
|
||||
SetPermissions {
|
||||
path: PathBuf,
|
||||
readonly: Option<bool>,
|
||||
mode: Option<u32>,
|
||||
},
|
||||
SetTimes {
|
||||
path: PathBuf,
|
||||
accessed_since_epoch: Option<Duration>,
|
||||
modified_since_epoch: Option<Duration>,
|
||||
},
|
||||
Statvfs {},
|
||||
SymLink {
|
||||
original: PathBuf,
|
||||
link: PathBuf,
|
||||
},
|
||||
Write {
|
||||
path: PathBuf,
|
||||
buf_size: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// Parameters for mounting the file system and operations to be performed afterwards.
|
||||
#[derive(Arbitrary, Clone, Debug)]
|
||||
struct MountSequence {
|
||||
cleanup: bool,
|
||||
operations: Vec<Operation>,
|
||||
}
|
||||
|
||||
/// The whole input to a single fuzzer invocation.
|
||||
#[derive(Arbitrary, Clone, Debug)]
|
||||
struct TestCase {
|
||||
disk_size: u64,
|
||||
reserved_size: u64,
|
||||
mount_sequences: Vec<MountSequence>,
|
||||
}
|
||||
|
||||
/// Creates the disk for backing the Redoxfs.
|
||||
fn create_disk(temp_path: &Path, disk_size: u64) -> DiskSparse {
|
||||
let disk_path = temp_path.join("disk.img");
|
||||
DiskSparse::create(disk_path, disk_size).unwrap()
|
||||
}
|
||||
|
||||
/// Creates an empty Redoxfs.
|
||||
fn create_redoxfs(disk: DiskSparse, reserved_size: u64) -> bool {
|
||||
let password = None;
|
||||
let reserved = vec![0; reserved_size as usize];
|
||||
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
FileSystem::create_reserved(
|
||||
disk,
|
||||
password,
|
||||
&reserved,
|
||||
ctime.as_secs(),
|
||||
ctime.subsec_nanos(),
|
||||
)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Mounts an existing Redoxfs, runs the callback and performs the unmount.
|
||||
fn with_redoxfs_mount<F>(temp_path: &Path, disk: DiskSparse, cleanup: bool, callback: F)
|
||||
where
|
||||
F: FnOnce(&Path) + Send + 'static,
|
||||
{
|
||||
let password = None;
|
||||
let block = None;
|
||||
let mut fs = FileSystem::open(disk, password, block, cleanup).unwrap();
|
||||
|
||||
let mount_path = temp_path.join("mount");
|
||||
fs::create_dir_all(&mount_path).unwrap();
|
||||
let mut session = fuser::Session::new(Fuse { fs: &mut fs }, &mount_path, &[]).unwrap();
|
||||
let mut unmounter = session.unmount_callable();
|
||||
|
||||
let join_handle = thread::spawn(move || {
|
||||
callback(&mount_path);
|
||||
unmounter.unmount().unwrap();
|
||||
});
|
||||
|
||||
session.run().unwrap();
|
||||
join_handle.join().unwrap();
|
||||
}
|
||||
|
||||
fn get_path_within_fs(fs_path: &Path, path_to_add: &Path) -> Result<PathBuf> {
|
||||
ensure!(path_to_add.is_relative());
|
||||
ensure!(path_to_add
|
||||
.components()
|
||||
.all(|c| c != std::path::Component::ParentDir));
|
||||
Ok(fs_path.join(path_to_add))
|
||||
}
|
||||
|
||||
fn do_operation(fs_path: &Path, op: &Operation) -> Result<()> {
|
||||
match op {
|
||||
Operation::Chown { path, uid, gid } => {
|
||||
let path = get_path_within_fs(fs_path, path)?;
|
||||
unix_fs::chown(path, *uid, *gid)?;
|
||||
}
|
||||
Operation::CreateDir { path } => {
|
||||
let path = get_path_within_fs(fs_path, path)?;
|
||||
fs::create_dir(path)?;
|
||||
}
|
||||
Operation::HardLink { original, link } => {
|
||||
let original = get_path_within_fs(fs_path, original)?;
|
||||
let link = get_path_within_fs(fs_path, link)?;
|
||||
fs::hard_link(original, link)?;
|
||||
}
|
||||
Operation::Metadata { path } => {
|
||||
let path = get_path_within_fs(fs_path, path)?;
|
||||
fs::metadata(path)?;
|
||||
}
|
||||
Operation::Read { path } => {
|
||||
let path = get_path_within_fs(fs_path, path)?;
|
||||
fs::read(path)?;
|
||||
}
|
||||
Operation::ReadDir { path } => {
|
||||
let path = get_path_within_fs(fs_path, path)?;
|
||||
let _ = fs::read_dir(path)?.count();
|
||||
}
|
||||
Operation::ReadLink { path } => {
|
||||
let path = get_path_within_fs(fs_path, path)?;
|
||||
fs::read_link(path)?;
|
||||
}
|
||||
Operation::RemoveDir { path } => {
|
||||
let path = get_path_within_fs(fs_path, path)?;
|
||||
fs::remove_dir(path)?;
|
||||
}
|
||||
Operation::RemoveFile { path } => {
|
||||
let path = get_path_within_fs(fs_path, path)?;
|
||||
fs::remove_file(path)?;
|
||||
}
|
||||
Operation::Rename { from, to } => {
|
||||
let from = get_path_within_fs(fs_path, from)?;
|
||||
let to = get_path_within_fs(fs_path, to)?;
|
||||
fs::rename(from, to)?;
|
||||
}
|
||||
Operation::SeekRead {
|
||||
path,
|
||||
seek_pos,
|
||||
buf_size,
|
||||
} => {
|
||||
ensure!(*buf_size as u64 <= MAX_SIZE);
|
||||
let path = get_path_within_fs(fs_path, path)?;
|
||||
let mut file = File::open(path)?;
|
||||
file.seek(SeekFrom::Start(*seek_pos))?;
|
||||
let mut buf = vec![0; *buf_size];
|
||||
file.read(&mut buf)?;
|
||||
}
|
||||
Operation::SeekWrite {
|
||||
path,
|
||||
seek_pos,
|
||||
buf_size,
|
||||
} => {
|
||||
ensure!(*seek_pos <= MAX_SIZE);
|
||||
ensure!(*buf_size as u64 <= MAX_SIZE);
|
||||
let path = get_path_within_fs(fs_path, path)?;
|
||||
let mut file = OpenOptions::new().write(true).open(path)?;
|
||||
file.seek(SeekFrom::Start(*seek_pos))?;
|
||||
let buf = vec![0; *buf_size];
|
||||
file.write(&buf)?;
|
||||
}
|
||||
Operation::SetLen { path, size } => {
|
||||
let path = get_path_within_fs(fs_path, path)?;
|
||||
let file = OpenOptions::new().write(true).open(path)?;
|
||||
file.set_len(*size)?;
|
||||
}
|
||||
Operation::SetPermissions {
|
||||
path,
|
||||
readonly,
|
||||
mode,
|
||||
} => {
|
||||
let path = get_path_within_fs(fs_path, path)?;
|
||||
let metadata = fs::metadata(&path)?;
|
||||
let mut perms = metadata.permissions();
|
||||
if let Some(readonly) = readonly {
|
||||
perms.set_readonly(*readonly);
|
||||
}
|
||||
if let Some(mode) = mode {
|
||||
perms.set_mode(*mode);
|
||||
}
|
||||
fs::set_permissions(path, perms)?;
|
||||
}
|
||||
Operation::SetTimes {
|
||||
path,
|
||||
accessed_since_epoch,
|
||||
modified_since_epoch,
|
||||
} => {
|
||||
let path = get_path_within_fs(fs_path, path)?;
|
||||
let file = File::options().write(true).open(path)?;
|
||||
let mut times = FileTimes::new();
|
||||
if let Some(accessed_since_epoch) = accessed_since_epoch {
|
||||
if let Some(accessed) = UNIX_EPOCH.checked_add(*accessed_since_epoch) {
|
||||
times = times.set_accessed(accessed);
|
||||
}
|
||||
}
|
||||
if let Some(modified_since_epoch) = modified_since_epoch {
|
||||
if let Some(modified) = UNIX_EPOCH.checked_add(*modified_since_epoch) {
|
||||
times = times.set_modified(modified);
|
||||
}
|
||||
}
|
||||
file.set_times(times)?;
|
||||
}
|
||||
Operation::Statvfs {} => {
|
||||
statvfs(fs_path)?;
|
||||
}
|
||||
Operation::SymLink { original, link } => {
|
||||
let original = get_path_within_fs(fs_path, original)?;
|
||||
let link = get_path_within_fs(fs_path, link)?;
|
||||
unix_fs::symlink(original, link)?;
|
||||
}
|
||||
Operation::Write { path, buf_size } => {
|
||||
ensure!(*buf_size as u64 <= MAX_SIZE);
|
||||
let path = get_path_within_fs(fs_path, path)?;
|
||||
let buf = vec![0; *buf_size];
|
||||
fs::write(path, &buf)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fuzz_target!(|test_case: TestCase| -> Corpus {
|
||||
if test_case.disk_size > MAX_SIZE
|
||||
|| test_case.reserved_size > MAX_SIZE
|
||||
|| test_case.mount_sequences.len() > MAX_MOUNT_SEQUENCES
|
||||
{
|
||||
return Corpus::Reject;
|
||||
}
|
||||
|
||||
let temp_dir = tempfile::Builder::new()
|
||||
.prefix("fuse_fuzz_target")
|
||||
.tempdir()
|
||||
.unwrap();
|
||||
|
||||
#[cfg(feature = "log")]
|
||||
eprintln!("create fs");
|
||||
let disk = create_disk(temp_dir.path(), test_case.disk_size);
|
||||
if !create_redoxfs(disk, test_case.reserved_size) {
|
||||
// File system creation failed (e.g., due to insufficient space) so we bail out, still
|
||||
// exercising this code path is useful.
|
||||
return Corpus::Keep;
|
||||
}
|
||||
|
||||
for mount_seq in test_case.mount_sequences.iter() {
|
||||
#[cfg(feature = "log")]
|
||||
eprintln!("mount fs: path {:?}, size{}", temp_dir.path(), test_case.disk_size);
|
||||
|
||||
let disk = create_disk(temp_dir.path(), test_case.disk_size);
|
||||
let operations = mount_seq.operations.clone();
|
||||
with_redoxfs_mount(temp_dir.path(), disk, mount_seq.cleanup, move |fs_path| {
|
||||
for operation in operations.iter() {
|
||||
#[cfg(feature = "log")]
|
||||
eprintln!("do operation {operation:?}");
|
||||
|
||||
let _result = do_operation(fs_path, operation);
|
||||
|
||||
#[cfg(feature = "log")]
|
||||
eprintln!("operation result {:?}", _result.err());
|
||||
}
|
||||
});
|
||||
|
||||
#[cfg(feature = "log")]
|
||||
eprintln!("unmounted fs");
|
||||
}
|
||||
Corpus::Keep
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
OUTPUT_FORMAT("elf64-littleriscv", "elf64-littleriscv", "elf64-littleriscv")
|
||||
OUTPUT_ARCH(riscv)
|
||||
ENTRY(coff_start)
|
||||
SECTIONS
|
||||
{
|
||||
PROVIDE(ImageBase = .);
|
||||
. = SEGMENT_START("text-segment", 0) + SIZEOF_HEADERS;
|
||||
.note.gnu.build-id : { *(.note.gnu.build-id) }
|
||||
.hash : { *(.hash) *(.gnu.hash) }
|
||||
|
||||
. = ALIGN(4096);
|
||||
.text :
|
||||
{
|
||||
PROVIDE(_text = .);
|
||||
*(.text.unlikely .text.*_unlikely .text.unlikely.*)
|
||||
*(.text.exit .text.exit.*)
|
||||
*(.text.startup .text.startup.*)
|
||||
*(.text.hot .text.hot.*)
|
||||
*(SORT(.text.sorted.*))
|
||||
*(.text .stub .text.* .gnu.linkonce.t.*)
|
||||
/* .gnu.warning sections are handled specially by elf.em. */
|
||||
*(.gnu.warning)
|
||||
}
|
||||
PROVIDE (__etext = .);
|
||||
PROVIDE (_etext = .);
|
||||
PROVIDE (etext = .);
|
||||
. = ALIGN(4096);
|
||||
|
||||
.rdata :
|
||||
{
|
||||
*(.rodata .rodata.* .gnu.linkonce.r.*)
|
||||
*(.rodata1)
|
||||
KEEP (*(.eh_frame))
|
||||
*(.eh_frame.*)
|
||||
*(.dynamic)
|
||||
}
|
||||
. = ALIGN(4096);
|
||||
.data :
|
||||
{
|
||||
*(.got) *(.igot)
|
||||
*(.got.plt) *(.igot.plt)
|
||||
*(.data .data.* .gnu.linkonce.d.*)
|
||||
*(.data1)
|
||||
*(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2) *(.srodata .srodata.*)
|
||||
*(.sdata2 .sdata2.* .gnu.linkonce.s2.*)
|
||||
*(.sdata .sdata.* .gnu.linkonce.s.*)
|
||||
PROVIDE (_edata = .); PROVIDE (edata = .);
|
||||
. = ALIGN(4096);
|
||||
PROVIDE (__bss_start = .);
|
||||
*(.sbss2 .sbss2.* .gnu.linkonce.sb2.*)
|
||||
*(.dynsbss)
|
||||
*(.sbss .sbss.* .gnu.linkonce.sb.*)
|
||||
*(.scommon)
|
||||
*(.dynbss)
|
||||
*(.bss .bss.* .gnu.linkonce.b.*)
|
||||
*(COMMON)
|
||||
. = ALIGN(4096);
|
||||
}
|
||||
.reloc :
|
||||
{
|
||||
KEEP(*(.reloc*))
|
||||
}
|
||||
.rela :
|
||||
{
|
||||
*(.rela.*)
|
||||
}
|
||||
.data.rel.ro :
|
||||
{
|
||||
*(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*)
|
||||
*(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*)
|
||||
}
|
||||
. = ALIGN(4096);
|
||||
|
||||
.dynsym : { *(.dynsym) }
|
||||
.dynstr : { *(.dynstr) }
|
||||
|
||||
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
ENTRY(start)
|
||||
OUTPUT_FORMAT(elf32-i386)
|
||||
|
||||
SECTIONS {
|
||||
/* The start address must match bootloader.asm */
|
||||
. = 0x13000;
|
||||
|
||||
. += SIZEOF_HEADERS;
|
||||
. = ALIGN(4096);
|
||||
|
||||
.text : {
|
||||
__text_start = .;
|
||||
*(.text*)
|
||||
. = ALIGN(4096);
|
||||
__text_end = .;
|
||||
}
|
||||
|
||||
.rodata : {
|
||||
__rodata_start = .;
|
||||
*(.rodata*)
|
||||
. = ALIGN(4096);
|
||||
__rodata_end = .;
|
||||
}
|
||||
|
||||
.data : {
|
||||
__data_start = .;
|
||||
*(.data*)
|
||||
. = ALIGN(4096);
|
||||
__data_end = .;
|
||||
__bss_start = .;
|
||||
*(.bss*)
|
||||
. = ALIGN(4096);
|
||||
__bss_end = .;
|
||||
}
|
||||
|
||||
.tdata : {
|
||||
__tdata_start = .;
|
||||
*(.tdata*)
|
||||
. = ALIGN(4096);
|
||||
__tdata_end = .;
|
||||
__tbss_start = .;
|
||||
*(.tbss*)
|
||||
. += 8;
|
||||
. = ALIGN(4096);
|
||||
__tbss_end = .;
|
||||
}
|
||||
|
||||
__end = .;
|
||||
|
||||
/DISCARD/ : {
|
||||
*(.comment*)
|
||||
*(.eh_frame*)
|
||||
*(.gcc_except_table*)
|
||||
*(.note*)
|
||||
*(.rel.eh_frame*)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
export PARTED?=parted
|
||||
export QEMU?=qemu-system-aarch64
|
||||
|
||||
all: $(BUILD)/bootloader.efi
|
||||
|
||||
$(BUILD)/bootloader.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
|
||||
mkdir -p "$(BUILD)"
|
||||
env RUSTFLAGS="--cfg aes_force_soft" \
|
||||
cargo rustc \
|
||||
--manifest-path="$<" \
|
||||
-Z build-std=core,alloc \
|
||||
-Z build-std-features=compiler-builtins-mem \
|
||||
--target $(TARGET) \
|
||||
--bin bootloader \
|
||||
--release \
|
||||
-- \
|
||||
--emit link="$@"
|
||||
|
||||
$(BUILD)/bootloader-live.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
|
||||
mkdir -p "$(BUILD)"
|
||||
env RUSTFLAGS="--cfg aes_force_soft" \
|
||||
cargo rustc \
|
||||
--manifest-path="$<" \
|
||||
-Z build-std=core,alloc \
|
||||
-Z build-std-features=compiler-builtins-mem \
|
||||
--target $(TARGET) \
|
||||
--bin bootloader \
|
||||
--release \
|
||||
--features live \
|
||||
-- \
|
||||
--emit link="$@"
|
||||
|
||||
$(BUILD)/esp.bin: $(BUILD)/bootloader.efi
|
||||
rm -f "$@.partial"
|
||||
fallocate -l 64MiB "$@.partial"
|
||||
mkfs.vfat -F 32 "$@.partial"
|
||||
mmd -i "$@.partial" efi
|
||||
mmd -i "$@.partial" efi/boot
|
||||
mcopy -i "$@.partial" "$<" ::efi/boot/bootaa64.efi
|
||||
mv "$@.partial" "$@"
|
||||
|
||||
$(BUILD)/harddrive.bin: $(BUILD)/esp.bin $(BUILD)/filesystem.bin
|
||||
rm -f "$@.partial"
|
||||
fallocate -l 320MiB "$@.partial"
|
||||
$(PARTED) -s -a minimal "$@.partial" mklabel gpt
|
||||
$(PARTED) -s -a minimal "$@.partial" mkpart ESP FAT32 1MiB 65MiB
|
||||
$(PARTED) -s -a minimal "$@.partial" mkpart REDOXFS 65MiB 100%
|
||||
$(PARTED) -s -a minimal "$@.partial" toggle 1 boot
|
||||
dd if="$(BUILD)/esp.bin" of="$@.partial" bs=1MiB seek=1 conv=notrunc
|
||||
dd if="$(BUILD)/filesystem.bin" of="$@.partial" bs=1MiB seek=65 conv=notrunc
|
||||
mv "$@.partial" "$@"
|
||||
|
||||
$(BUILD)/firmware.rom: /usr/share/AAVMF/AAVMF_CODE.fd
|
||||
cp "$<" "$@"
|
||||
|
||||
qemu: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom
|
||||
$(QEMU) \
|
||||
-d cpu_reset \
|
||||
-no-reboot \
|
||||
-smp 4 -m 2048 \
|
||||
-chardev stdio,id=debug,signal=off,mux=on \
|
||||
-serial chardev:debug \
|
||||
-mon chardev=debug \
|
||||
-device virtio-gpu-pci \
|
||||
-machine virt \
|
||||
-net none \
|
||||
-cpu max \
|
||||
-bios "$(BUILD)/firmware.rom" \
|
||||
-drive file="$<",format=raw
|
||||
@@ -0,0 +1,108 @@
|
||||
LD=riscv64-unknown-redox-ld
|
||||
OBJCOPY=riscv64-unknown-redox-objcopy
|
||||
SCRIPT=$(SOURCE)/linkers/riscv64-unknown-uefi.ld
|
||||
PARTED?=parted
|
||||
QEMU?=qemu-system-riscv64
|
||||
|
||||
all: $(BUILD)/bootloader.efi
|
||||
|
||||
$(BUILD)/%.efi: $(BUILD)/%.efi.elf $(BUILD)/%.efi.sym
|
||||
$(OBJCOPY) -j .text -j .data -j .rdata -j .rela -j .reloc --target pei-riscv64-little \
|
||||
--file-alignment 512 --section-alignment 4096 --subsystem 10 "$<" "$@"
|
||||
|
||||
.PRECIOUS: $(BUILD)/%.efi.sym
|
||||
$(BUILD)/%.efi.sym: $(BUILD)/%.efi.elf
|
||||
$(OBJCOPY) --only-keep-debug "$<" "$@"
|
||||
|
||||
$(BUILD)/%.efi.elf: $(BUILD)/%.a $(SCRIPT)
|
||||
$(LD) --gc-sections -z max-page-size=0x1000 --warn-common --no-undefined -z nocombreloc -shared \
|
||||
--fatal-warnings -Bsymbolic --entry coff_start -T "$(SCRIPT)" -o "$@" "$<"
|
||||
|
||||
$(BUILD)/bootloader.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
|
||||
mkdir -p "$(BUILD)"
|
||||
env RUSTFLAGS="--cfg aes_force_soft" \
|
||||
cargo rustc \
|
||||
--manifest-path="$<" \
|
||||
-Z build-std=core,alloc \
|
||||
-Z build-std-features=compiler-builtins-mem \
|
||||
--target $(TARGET) \
|
||||
--lib \
|
||||
--release \
|
||||
-- \
|
||||
--emit link=$@
|
||||
|
||||
$(BUILD)/bootloader-live.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
|
||||
mkdir -p "$(BUILD)"
|
||||
env RUSTFLAGS="--cfg aes_force_soft" \
|
||||
cargo rustc \
|
||||
--manifest-path="$<" \
|
||||
-Z build-std=core,alloc \
|
||||
-Z build-std-features=compiler-builtins-mem \
|
||||
--target $(TARGET) \
|
||||
--lib \
|
||||
--release \
|
||||
--features live \
|
||||
-- \
|
||||
--emit link=$@
|
||||
|
||||
|
||||
$(BUILD)/esp.bin: $(BUILD)/bootloader.efi
|
||||
rm -f $@.partial
|
||||
fallocate -l 64MiB $@.partial
|
||||
mkfs.vfat -F 32 $@.partial
|
||||
mmd -i $@.partial EFI
|
||||
mmd -i $@.partial EFI/BOOT
|
||||
mcopy -i $@.partial $< ::EFI/BOOT/BOOTRISCV64.EFI
|
||||
mv $@.partial $@
|
||||
|
||||
$(BUILD)/harddrive.bin: $(BUILD)/esp.bin $(BUILD)/filesystem.bin
|
||||
rm -f $@.partial
|
||||
fallocate -l 320MiB $@.partial
|
||||
$(PARTED) -s -a minimal $@.partial mklabel gpt
|
||||
$(PARTED) -s -a minimal $@.partial mkpart ESP FAT32 1MiB 65MiB
|
||||
$(PARTED) -s -a minimal $@.partial mkpart REDOXFS 65MiB 100%
|
||||
$(PARTED) -s -a minimal $@.partial toggle 1 boot
|
||||
dd if=$(BUILD)/esp.bin of=$@.partial bs=1MiB seek=1 conv=notrunc
|
||||
dd if=$(BUILD)/filesystem.bin of=$@.partial bs=1MiB seek=65 conv=notrunc
|
||||
mv $@.partial $@
|
||||
|
||||
$(BUILD)/fw_vars.img: /usr/share/qemu-efi-riscv64/RISCV_VIRT_VARS.fd
|
||||
cp "$<" "$@"
|
||||
|
||||
$(BUILD)/firmware.rom: /usr/share/qemu-efi-riscv64/RISCV_VIRT_CODE.fd
|
||||
cp "$<" "$@"
|
||||
|
||||
qemu-acpi: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom $(BUILD)/fw_vars.img
|
||||
$(QEMU) \
|
||||
-M virt \
|
||||
-d cpu_reset \
|
||||
-no-reboot \
|
||||
-smp 4 -m 2048 \
|
||||
-chardev stdio,id=debug,signal=off,mux=on \
|
||||
-serial chardev:debug \
|
||||
-mon chardev=debug \
|
||||
-device virtio-gpu-pci \
|
||||
-machine virt \
|
||||
-net none \
|
||||
-cpu max \
|
||||
-drive if=pflash,format=raw,unit=0,file=$(BUILD)/firmware.rom,readonly=on \
|
||||
-drive if=pflash,format=raw,unit=1,file=$(BUILD)/fw_vars.img \
|
||||
-drive file=$(BUILD)/harddrive.bin,format=raw,if=virtio
|
||||
|
||||
|
||||
qemu-dtb: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom $(BUILD)/fw_vars.img
|
||||
$(QEMU) \
|
||||
-M virt,acpi=off \
|
||||
-d cpu_reset \
|
||||
-no-reboot \
|
||||
-smp 4 -m 2048 \
|
||||
-chardev stdio,id=debug,signal=off,mux=on \
|
||||
-serial chardev:debug \
|
||||
-mon chardev=debug \
|
||||
-device virtio-gpu-pci \
|
||||
-machine virt \
|
||||
-net none \
|
||||
-cpu max \
|
||||
-drive if=pflash,format=raw,unit=0,file=$(BUILD)/firmware.rom,readonly=on \
|
||||
-drive if=pflash,format=raw,unit=1,file=$(BUILD)/fw_vars.img \
|
||||
-drive file=$(BUILD)/harddrive.bin,format=raw,if=virtio -s
|
||||
@@ -0,0 +1,65 @@
|
||||
export LD?=ld
|
||||
export OBJCOPY?=objcopy
|
||||
export PARTED?=parted
|
||||
export QEMU?=qemu-system-x86_64
|
||||
|
||||
all: $(BUILD)/bootloader.bin
|
||||
|
||||
$(BUILD)/libbootloader.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
|
||||
mkdir -p "$(BUILD)"
|
||||
env RUSTFLAGS="--cfg aes_force_soft -Zunstable-options" \
|
||||
cargo rustc \
|
||||
--manifest-path="$<" \
|
||||
-Z build-std=core,alloc \
|
||||
-Z build-std-features=compiler-builtins-mem \
|
||||
--target "$(TARGET)" \
|
||||
--lib \
|
||||
--release \
|
||||
-- \
|
||||
--emit link="$@"
|
||||
|
||||
$(BUILD)/libbootloader-live.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
|
||||
mkdir -p "$(BUILD)"
|
||||
env RUSTFLAGS="--cfg aes_force_soft -Zunstable-options" \
|
||||
cargo rustc \
|
||||
--manifest-path="$<" \
|
||||
-Z build-std=core,alloc \
|
||||
-Z build-std-features=compiler-builtins-mem \
|
||||
--target "$(TARGET)" \
|
||||
--lib \
|
||||
--release \
|
||||
--features live \
|
||||
-- \
|
||||
--emit link="$@"
|
||||
|
||||
$(BUILD)/%.elf: $(BUILD)/lib%.a $(SOURCE)/linkers/$(TARGET).ld
|
||||
$(LD) -m elf_i386 --gc-sections -z max-page-size=0x1000 -T "$(SOURCE)/linkers/$(TARGET).ld" -o "$@" "$<"
|
||||
$(OBJCOPY) --only-keep-debug "$@" "$@.sym"
|
||||
$(OBJCOPY) --strip-debug "$@"
|
||||
|
||||
$(BUILD)/%.bin: $(BUILD)/%.elf $(shell find $(SOURCE)/asm/$(TARGET) -type f)
|
||||
nasm -f bin -o "$@" -l "$@.lst" -D STAGE3="$<" -i"$(SOURCE)/asm/$(TARGET)/" "$(SOURCE)/asm/$(TARGET)/bootloader.asm"
|
||||
|
||||
$(BUILD)/harddrive.bin: $(BUILD)/bootloader.bin $(BUILD)/filesystem.bin
|
||||
rm -f "$@.partial"
|
||||
fallocate -l 256MiB "$@.partial"
|
||||
$(PARTED) -s -a minimal "$@.partial" mklabel msdos
|
||||
$(PARTED) -s -a minimal "$@.partial" mkpart primary 2MiB 100%
|
||||
dd if="$<" of="$@.partial" bs=1 count=446 conv=notrunc
|
||||
dd if="$<" of="$@.partial" bs=512 skip=1 seek=1 conv=notrunc
|
||||
dd if="$(BUILD)/filesystem.bin" of="$@.partial" bs=1MiB seek=2 conv=notrunc
|
||||
mv "$@.partial" "$@"
|
||||
|
||||
qemu: $(BUILD)/harddrive.bin
|
||||
$(QEMU) \
|
||||
-d cpu_reset \
|
||||
-no-reboot \
|
||||
-smp 4 -m 2048 \
|
||||
-chardev stdio,id=debug,signal=off,mux=on \
|
||||
-serial chardev:debug \
|
||||
-mon chardev=debug \
|
||||
-machine q35 \
|
||||
-net none \
|
||||
-enable-kvm \
|
||||
-cpu host \
|
||||
-drive file="$<",format=raw
|
||||
@@ -0,0 +1,70 @@
|
||||
export PARTED?=parted
|
||||
export QEMU?=qemu-system-x86_64
|
||||
|
||||
all: $(BUILD)/bootloader.efi
|
||||
|
||||
$(BUILD)/bootloader.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
|
||||
mkdir -p "$(BUILD)"
|
||||
env RUSTFLAGS="--cfg aes_force_soft" \
|
||||
cargo rustc \
|
||||
--manifest-path="$<" \
|
||||
-Z build-std=core,alloc \
|
||||
-Z build-std-features=compiler-builtins-mem \
|
||||
--target $(TARGET) \
|
||||
--bin bootloader \
|
||||
--release \
|
||||
-- \
|
||||
--emit link="$@"
|
||||
|
||||
$(BUILD)/bootloader-live.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
|
||||
mkdir -p $(BUILD)
|
||||
cd "$(SOURCE)"
|
||||
env RUSTFLAGS="--cfg aes_force_soft" \
|
||||
cargo rustc \
|
||||
--manifest-path="$<" \
|
||||
-Z build-std=core,alloc \
|
||||
-Z build-std-features=compiler-builtins-mem \
|
||||
--target $(TARGET) \
|
||||
--bin bootloader \
|
||||
--release \
|
||||
--features live \
|
||||
-- \
|
||||
--emit link="$@"
|
||||
|
||||
$(BUILD)/esp.bin: $(BUILD)/bootloader.efi
|
||||
rm -f "$@.partial"
|
||||
fallocate -l 1MiB $@.partial
|
||||
mkfs.vfat "$@.partial"
|
||||
mmd -i "$@.partial" efi
|
||||
mmd -i "$@.partial" efi/boot
|
||||
mcopy -i "$@.partial" "$<" ::efi/boot/bootx64.efi
|
||||
mv "$@.partial" "$@"
|
||||
|
||||
$(BUILD)/harddrive.bin: $(BUILD)/esp.bin $(BUILD)/filesystem.bin
|
||||
rm -f "$@.partial"
|
||||
fallocate -l 320MiB "$@.partial"
|
||||
$(PARTED) -s -a minimal "$@.partial" mklabel gpt
|
||||
$(PARTED) -s -a minimal "$@.partial" mkpart ESP FAT32 1MiB 2MiB
|
||||
$(PARTED) -s -a minimal "$@.partial" mkpart REDOXFS 2MiB 100%
|
||||
$(PARTED) -s -a minimal "$@.partial" toggle 1 boot
|
||||
dd if="$(BUILD)/esp.bin" of="$@.partial" bs=1MiB seek=1 conv=notrunc
|
||||
dd if="$(BUILD)/filesystem.bin" of="$@.partial" bs=1MiB seek=2 conv=notrunc
|
||||
mv "$@.partial" "$@"
|
||||
|
||||
$(BUILD)/firmware.rom: /usr/share/OVMF/OVMF_CODE.fd
|
||||
cp "$<" "$@"
|
||||
|
||||
qemu: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom
|
||||
$(QEMU) \
|
||||
-d cpu_reset \
|
||||
-no-reboot \
|
||||
-smp 4 -m 2048 \
|
||||
-chardev stdio,id=debug,signal=off,mux=on \
|
||||
-serial chardev:debug \
|
||||
-mon chardev=debug \
|
||||
-machine q35 \
|
||||
-net none \
|
||||
-enable-kvm \
|
||||
-cpu host \
|
||||
-bios "$(BUILD)/firmware.rom" \
|
||||
-drive file="$<",format=raw
|
||||
@@ -0,0 +1,3 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2025-10-03"
|
||||
components = ["rust-src"]
|
||||
@@ -1,394 +0,0 @@
|
||||
use alloc::{collections::BTreeSet, vec::Vec};
|
||||
use core::{fmt, mem, ops, slice};
|
||||
use endian_num::Le;
|
||||
|
||||
use crate::{BlockAddr, BlockLevel, BlockMeta, BlockPtr, BlockTrait, Node, TreePtr, BLOCK_SIZE};
|
||||
|
||||
pub const ALLOC_LIST_ENTRIES: usize =
|
||||
(BLOCK_SIZE as usize - mem::size_of::<BlockPtr<AllocList>>()) / mem::size_of::<AllocEntry>();
|
||||
pub const RELEASE_LIST_ENTRIES: usize = (BLOCK_SIZE as usize
|
||||
- mem::size_of::<BlockPtr<ReleaseList>>())
|
||||
/ mem::size_of::<TreePtr<Node>>();
|
||||
|
||||
/// The RedoxFS block allocator. This struct manages all "data" blocks in RedoxFS
|
||||
/// (i.e, all blocks that aren't reserved or part of the header chain).
|
||||
///
|
||||
/// [`Allocator`] can allocate blocks of many "levels"---that is, it can
|
||||
/// allocate multiple consecutive [`BLOCK_SIZE`] blocks in one operation.
|
||||
///
|
||||
/// This reduces the amount of memory that the [`Allocator`] uses:
|
||||
/// Instead of storing the index of each free [`BLOCK_SIZE`] block,
|
||||
/// the `levels` array can keep track of higher-level blocks, splitting
|
||||
/// them when a smaller block is requested.
|
||||
///
|
||||
/// Higher-level blocks also allow us to more efficiently allocate memory
|
||||
/// for large files.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Allocator {
|
||||
/// This array keeps track of all free blocks of each level,
|
||||
/// and is initialized using the AllocList chain when we open the filesystem.
|
||||
///
|
||||
/// Every element of the outer array represents a block level:
|
||||
/// - item 0: free level 0 blocks (with size [`BLOCK_SIZE`])
|
||||
/// - item 1: free level 1 blocks (with size 2*[`BLOCK_SIZE`])
|
||||
/// - item 2: free level 2 blocks (with size 4*[`BLOCK_SIZE`])
|
||||
/// ...and so on.
|
||||
///
|
||||
/// Each inner array contains a list of free block indices,
|
||||
levels: Vec<BTreeSet<u64>>,
|
||||
}
|
||||
|
||||
impl Allocator {
|
||||
pub fn levels(&self) -> &Vec<BTreeSet<u64>> {
|
||||
&self.levels
|
||||
}
|
||||
|
||||
/// Count the number of free [`BLOCK_SIZE`] available to this [`Allocator`].
|
||||
pub fn free(&self) -> u64 {
|
||||
let mut free = 0;
|
||||
for level in 0..self.levels.len() {
|
||||
let level_size = 1 << level;
|
||||
free += self.levels[level].len() as u64 * level_size;
|
||||
}
|
||||
free
|
||||
}
|
||||
|
||||
/// Find a free block of the given level, mark it as "used", and return its address.
|
||||
/// Returns [`None`] if there are no free blocks with this level.
|
||||
pub fn allocate(&mut self, meta: BlockMeta) -> Option<BlockAddr> {
|
||||
// First, find the lowest level with a free block
|
||||
let mut free_opt = None;
|
||||
{
|
||||
let mut level = meta.level.0;
|
||||
// Start searching at the level we want. Smaller levels are too small!
|
||||
while level < self.levels.len() {
|
||||
if let Some(&index) = self.levels[level].first() {
|
||||
// Find the index closest to the start of the filesystem
|
||||
free_opt = match free_opt {
|
||||
Some((free_level, free_index)) if free_index <= index => {
|
||||
Some((free_level, free_index))
|
||||
}
|
||||
_ => Some((level, index)),
|
||||
};
|
||||
}
|
||||
level += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If a free block was found, split it until we find a usable block of the right level.
|
||||
// The left side of the split block is kept free, and the right side is allocated.
|
||||
let (mut level, index) = free_opt?;
|
||||
self.levels[level].remove(&index);
|
||||
while level > meta.level.0 {
|
||||
level -= 1;
|
||||
let level_size = 1 << level;
|
||||
self.levels[level].insert(index + level_size);
|
||||
}
|
||||
|
||||
Some(unsafe { BlockAddr::new(index, meta) })
|
||||
}
|
||||
|
||||
/// Try to allocate the exact block specified, making all necessary splits.
|
||||
/// Returns [`None`] if this some (or all) of this block is already allocated.
|
||||
///
|
||||
/// Note that [`BlockAddr`] encodes the blocks location _and_ level.
|
||||
pub fn allocate_exact(&mut self, exact_addr: BlockAddr) -> Option<BlockAddr> {
|
||||
// This function only supports level 0 right now
|
||||
assert_eq!(exact_addr.level().0, 0);
|
||||
let exact_index = exact_addr.index();
|
||||
|
||||
let mut index_opt = None;
|
||||
|
||||
// Go from the highest to the lowest level
|
||||
for level in (0..self.levels.len()).rev() {
|
||||
let level_size = 1 << level;
|
||||
|
||||
// Split higher block if found
|
||||
if let Some(index) = index_opt.take() {
|
||||
self.levels[level].insert(index);
|
||||
self.levels[level].insert(index + level_size);
|
||||
}
|
||||
|
||||
// Look for matching block and remove it
|
||||
for &start in self.levels[level].iter() {
|
||||
if start <= exact_index {
|
||||
let end = start + level_size;
|
||||
if end > exact_index {
|
||||
self.levels[level].remove(&start);
|
||||
index_opt = Some(start);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(unsafe { BlockAddr::new(index_opt?, exact_addr.meta()) })
|
||||
}
|
||||
|
||||
/// Deallocate the given block, marking it "free" so that it can be re-used later.
|
||||
pub fn deallocate(&mut self, addr: BlockAddr) {
|
||||
// When we deallocate, we check if block we're deallocating has a free sibling.
|
||||
// If it does, we join the two to create one free block in the next (higher) level.
|
||||
//
|
||||
// We repeat this until we no longer have a sibling to join.
|
||||
let mut index = addr.index();
|
||||
let mut level = addr.level().0;
|
||||
loop {
|
||||
while level >= self.levels.len() {
|
||||
self.levels.push(BTreeSet::new());
|
||||
}
|
||||
|
||||
let level_size = 1 << level;
|
||||
let next_size = level_size << 1;
|
||||
|
||||
let mut found = false;
|
||||
// look at all free blocks in the current level...
|
||||
for &level_index in self.levels[level].iter() {
|
||||
// - the block we just freed aligns with the next largest block, and
|
||||
// - the second block we're looking at is the right sibling of this block
|
||||
if index % next_size == 0 && index + level_size == level_index {
|
||||
// "alloc" the next highest block, repeat deallocation process.
|
||||
self.levels[level].remove(&level_index);
|
||||
found = true;
|
||||
break;
|
||||
// - the index of this block doesn't align with the next largest block, and
|
||||
// - the block we're looking at is the left neighbor of this block
|
||||
} else if level_index % next_size == 0 && level_index + level_size == index {
|
||||
// "alloc" the next highest block, repeat deallocation process.
|
||||
self.levels[level].remove(&level_index);
|
||||
index = level_index; // index moves to left block
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We couldn't find a higher block,
|
||||
// deallocate this one and finish
|
||||
if !found {
|
||||
self.levels[level].insert(index);
|
||||
return;
|
||||
}
|
||||
|
||||
// repeat deallocation process on the
|
||||
// higher-level block we just created.
|
||||
level += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Default, Debug)]
|
||||
pub struct AllocEntry {
|
||||
/// The index of the first block this [`AllocEntry`] refers to
|
||||
index: Le<u64>,
|
||||
|
||||
/// The number of blocks after (and including) `index` that are are free or used.
|
||||
/// If negative, they are used; if positive, they are free.
|
||||
count: Le<i64>,
|
||||
}
|
||||
|
||||
impl AllocEntry {
|
||||
pub fn new(index: u64, count: i64) -> Self {
|
||||
Self {
|
||||
index: index.into(),
|
||||
count: count.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allocate(addr: BlockAddr) -> Self {
|
||||
Self::new(addr.index(), -addr.level().blocks::<i64>())
|
||||
}
|
||||
|
||||
pub fn deallocate(addr: BlockAddr) -> Self {
|
||||
Self::new(addr.index(), addr.level().blocks::<i64>())
|
||||
}
|
||||
|
||||
pub fn index(&self) -> u64 {
|
||||
self.index.to_ne()
|
||||
}
|
||||
|
||||
pub fn count(&self) -> i64 {
|
||||
self.count.to_ne()
|
||||
}
|
||||
|
||||
pub fn is_null(&self) -> bool {
|
||||
self.count() == 0
|
||||
}
|
||||
}
|
||||
|
||||
/// A node in the allocation chain.
|
||||
#[repr(C, packed)]
|
||||
pub struct AllocList {
|
||||
/// A pointer to the previous AllocList.
|
||||
/// If this is the null pointer, this is the first element of the chain.
|
||||
pub prev: BlockPtr<AllocList>,
|
||||
|
||||
/// Allocation entries.
|
||||
pub entries: [AllocEntry; ALLOC_LIST_ENTRIES],
|
||||
}
|
||||
|
||||
unsafe impl BlockTrait for AllocList {
|
||||
fn empty(level: BlockLevel) -> Option<Self> {
|
||||
if level.0 == 0 {
|
||||
Some(Self {
|
||||
prev: BlockPtr::default(),
|
||||
entries: [AllocEntry::default(); ALLOC_LIST_ENTRIES],
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for AllocList {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let prev = self.prev;
|
||||
let entries: Vec<&AllocEntry> = self
|
||||
.entries
|
||||
.iter()
|
||||
.filter(|entry| entry.count() > 0)
|
||||
.collect();
|
||||
f.debug_struct("AllocList")
|
||||
.field("prev", &prev)
|
||||
.field("entries", &entries)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for AllocList {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts(
|
||||
self as *const AllocList as *const u8,
|
||||
mem::size_of::<AllocList>(),
|
||||
) as &[u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::DerefMut for AllocList {
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts_mut(
|
||||
self as *mut AllocList as *mut u8,
|
||||
mem::size_of::<AllocList>(),
|
||||
) as &mut [u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of nodes pending release.
|
||||
#[repr(C, packed)]
|
||||
pub struct ReleaseList {
|
||||
/// A pointer to the previous ReleaseList.
|
||||
/// If this is the null pointer, this is the first element of the chain.
|
||||
pub prev: BlockPtr<ReleaseList>,
|
||||
|
||||
/// Allocation entries.
|
||||
pub entries: [TreePtr<Node>; RELEASE_LIST_ENTRIES],
|
||||
}
|
||||
|
||||
unsafe impl BlockTrait for ReleaseList {
|
||||
fn empty(level: BlockLevel) -> Option<Self> {
|
||||
if level.0 == 0 {
|
||||
Some(Self {
|
||||
prev: BlockPtr::default(),
|
||||
entries: [TreePtr::<Node>::default(); RELEASE_LIST_ENTRIES],
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ReleaseList {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let prev = self.prev;
|
||||
let entries: Vec<_> = self
|
||||
.entries
|
||||
.iter()
|
||||
.filter(|entry| !entry.is_null())
|
||||
.map(|entry| entry.id())
|
||||
.collect();
|
||||
f.debug_struct("ReleaseList")
|
||||
.field("prev", &prev)
|
||||
.field("entries", &entries)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for ReleaseList {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts(
|
||||
self as *const ReleaseList as *const u8,
|
||||
mem::size_of::<ReleaseList>(),
|
||||
) as &[u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::DerefMut for ReleaseList {
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts_mut(
|
||||
self as *mut ReleaseList as *mut u8,
|
||||
mem::size_of::<ReleaseList>(),
|
||||
) as &mut [u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alloc_node_size_test() {
|
||||
assert_eq!(mem::size_of::<AllocList>(), crate::BLOCK_SIZE as usize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_node_size_test() {
|
||||
assert_eq!(mem::size_of::<ReleaseList>(), crate::BLOCK_SIZE as usize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allocator_test() {
|
||||
let mut alloc = Allocator::default();
|
||||
|
||||
assert_eq!(alloc.allocate(BlockMeta::default()), None);
|
||||
|
||||
alloc.deallocate(unsafe { BlockAddr::new(1, BlockMeta::default()) });
|
||||
assert_eq!(
|
||||
alloc.allocate(BlockMeta::default()),
|
||||
Some(unsafe { BlockAddr::new(1, BlockMeta::default()) })
|
||||
);
|
||||
assert_eq!(alloc.allocate(BlockMeta::default()), None);
|
||||
|
||||
for addr in 1023..2048 {
|
||||
alloc.deallocate(unsafe { BlockAddr::new(addr, BlockMeta::default()) });
|
||||
}
|
||||
|
||||
assert_eq!(alloc.levels.len(), 11);
|
||||
for level in 0..alloc.levels.len() {
|
||||
if level == 0 {
|
||||
assert_eq!(alloc.levels[level], [1023].into());
|
||||
} else if level == 10 {
|
||||
assert_eq!(alloc.levels[level], [1024].into());
|
||||
} else {
|
||||
assert_eq!(alloc.levels[level], [0u64; 0].into());
|
||||
}
|
||||
}
|
||||
|
||||
for addr in 1023..2048 {
|
||||
assert_eq!(
|
||||
alloc.allocate(BlockMeta::default()),
|
||||
Some(unsafe { BlockAddr::new(addr, BlockMeta::default()) })
|
||||
);
|
||||
}
|
||||
assert_eq!(alloc.allocate(BlockMeta::default()), None);
|
||||
|
||||
assert_eq!(alloc.levels.len(), 11);
|
||||
for level in 0..alloc.levels.len() {
|
||||
assert_eq!(alloc.levels[level], [0u64; 0].into());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
use crate::area_add;
|
||||
use crate::os::{Os, OsMemoryEntry, OsMemoryKind, dtb::is_in_dev_mem_region};
|
||||
use core::slice;
|
||||
|
||||
pub(crate) const PF_PRESENT: u64 = 1 << 0;
|
||||
pub(crate) const PF_TABLE: u64 = 1 << 1;
|
||||
pub(crate) const PF_OUTER_SHAREABLE: u64 = 0b01 << 8;
|
||||
pub(crate) const PF_INNER_SHAREABLE: u64 = 0b11 << 8;
|
||||
pub(crate) const PF_ACCESS: u64 = 1 << 10;
|
||||
|
||||
pub(crate) const PF_DEV: u64 = PF_OUTER_SHAREABLE | 2 << 2;
|
||||
pub(crate) const PF_RAM: u64 = PF_INNER_SHAREABLE;
|
||||
|
||||
pub(crate) const ENTRY_ADDRESS_MASK: u64 = 0x000F_FFFF_FFFF_F000;
|
||||
pub(crate) const PAGE_ENTRIES: usize = 512;
|
||||
const PAGE_SIZE: usize = 4096;
|
||||
pub(crate) const PHYS_OFFSET: u64 = 0xFFFF_8000_0000_0000;
|
||||
|
||||
unsafe fn paging_allocate(os: &impl Os) -> Option<&'static mut [u64]> {
|
||||
unsafe {
|
||||
let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE);
|
||||
if !ptr.is_null() {
|
||||
area_add(OsMemoryEntry {
|
||||
base: ptr as u64,
|
||||
size: PAGE_SIZE as u64,
|
||||
kind: OsMemoryKind::Reclaim,
|
||||
});
|
||||
Some(slice::from_raw_parts_mut(ptr as *mut u64, PAGE_ENTRIES))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option<usize> {
|
||||
unsafe {
|
||||
// Create L0
|
||||
let l0 = paging_allocate(os)?;
|
||||
|
||||
{
|
||||
// Create L1 for identity mapping
|
||||
let l1 = paging_allocate(os)?;
|
||||
|
||||
// Link first user and first kernel L0 entry to L1
|
||||
l0[0] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
|
||||
l0[256] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
|
||||
|
||||
// Identity map 8 GiB using 1 GiB pages
|
||||
for l1_i in 0..8 {
|
||||
let addr = l1_i as u64 * 0x4000_0000;
|
||||
//TODO: is PF_RAM okay?
|
||||
l1[l1_i] = addr | PF_ACCESS | PF_DEV | PF_PRESENT;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Create L1 for kernel mapping
|
||||
let l1 = paging_allocate(os)?;
|
||||
|
||||
// Link second to last L0 entry to L1
|
||||
l0[510] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
|
||||
|
||||
// Map kernel_size at kernel offset
|
||||
let mut kernel_mapped = 0;
|
||||
let mut l1_i = 0;
|
||||
while kernel_mapped < kernel_size && l1_i < l1.len() {
|
||||
let l2 = paging_allocate(os)?;
|
||||
l1[l1_i] = l2.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
|
||||
l1_i += 1;
|
||||
|
||||
let mut l2_i = 0;
|
||||
while kernel_mapped < kernel_size && l2_i < l2.len() {
|
||||
let l3 = paging_allocate(os)?;
|
||||
l2[l2_i] = l3.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
|
||||
l2_i += 1;
|
||||
|
||||
let mut l3_i = 0;
|
||||
while kernel_mapped < kernel_size && l3_i < l3.len() {
|
||||
let addr = kernel_phys + kernel_mapped;
|
||||
l3[l3_i] = addr | PF_ACCESS | PF_RAM | PF_TABLE | PF_PRESENT;
|
||||
l3_i += 1;
|
||||
kernel_mapped += PAGE_SIZE as u64;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(kernel_mapped >= kernel_size);
|
||||
}
|
||||
|
||||
Some(l0.as_ptr() as usize)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn paging_framebuffer(
|
||||
os: &impl Os,
|
||||
page_phys: usize,
|
||||
framebuffer_phys: u64,
|
||||
framebuffer_size: u64,
|
||||
) -> Option<u64> {
|
||||
unsafe {
|
||||
//TODO: smarter test for framebuffer already mapped
|
||||
if framebuffer_phys + framebuffer_size <= 0x2_0000_0000 {
|
||||
return Some(framebuffer_phys + PHYS_OFFSET);
|
||||
}
|
||||
|
||||
let l0_i = ((framebuffer_phys / 0x80_0000_0000) + 256) as usize;
|
||||
let mut l1_i = ((framebuffer_phys % 0x80_0000_0000) / 0x4000_0000) as usize;
|
||||
let mut l2_i = ((framebuffer_phys % 0x4000_0000) / 0x20_0000) as usize;
|
||||
let mut l3_i = ((framebuffer_phys % 0x20_0000) / (PAGE_SIZE as u64)) as usize;
|
||||
assert_eq!(framebuffer_phys % (PAGE_SIZE as u64), 0);
|
||||
|
||||
let l0 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES);
|
||||
|
||||
// Create l1 for framebuffer mapping
|
||||
let l1 = if l0[l0_i] == 0 {
|
||||
let l1 = paging_allocate(os)?;
|
||||
l0[l0_i] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
|
||||
l1
|
||||
} else {
|
||||
slice::from_raw_parts_mut((l0[l0_i] & ENTRY_ADDRESS_MASK) as *mut u64, PAGE_ENTRIES)
|
||||
};
|
||||
|
||||
// Map framebuffer_size at framebuffer offset
|
||||
let mut framebuffer_mapped = 0;
|
||||
while framebuffer_mapped < framebuffer_size && l1_i < l1.len() {
|
||||
let l2 = paging_allocate(os)?;
|
||||
assert_eq!(l1[l1_i], 0);
|
||||
l1[l1_i] = l2.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
|
||||
|
||||
while framebuffer_mapped < framebuffer_size && l2_i < l2.len() {
|
||||
let l3 = paging_allocate(os)?;
|
||||
assert_eq!(l2[l2_i], 0);
|
||||
l2[l2_i] = l3.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
|
||||
|
||||
while framebuffer_mapped < framebuffer_size && l3_i < l3.len() {
|
||||
let addr = framebuffer_phys + framebuffer_mapped;
|
||||
assert_eq!(l3[l3_i], 0);
|
||||
//TODO: is PF_RAM okay?
|
||||
l3[l3_i] = addr | PF_ACCESS | PF_RAM | PF_TABLE | PF_PRESENT;
|
||||
framebuffer_mapped += PAGE_SIZE as u64;
|
||||
l3_i += 1;
|
||||
}
|
||||
|
||||
l2_i += 1;
|
||||
l3_i = 0;
|
||||
}
|
||||
|
||||
l1_i += 1;
|
||||
l2_i = 0;
|
||||
}
|
||||
assert!(framebuffer_mapped >= framebuffer_size);
|
||||
|
||||
Some(framebuffer_phys + PHYS_OFFSET)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use self::aarch64::*;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
mod aarch64;
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
pub use self::x86::*;
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
mod x86;
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
pub use self::riscv64::*;
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
mod riscv64;
|
||||
@@ -0,0 +1,59 @@
|
||||
use core::slice;
|
||||
|
||||
use crate::area_add;
|
||||
use crate::os::{Os, OsMemoryEntry, OsMemoryKind};
|
||||
|
||||
pub(crate) mod sv39;
|
||||
pub(crate) mod sv48;
|
||||
pub(crate) mod sv57;
|
||||
|
||||
// Common constants
|
||||
const PAGE_SHIFT: usize = 12;
|
||||
const TABLE_SHIFT: usize = 9;
|
||||
const TABLE_MASK: usize = (1 << TABLE_SHIFT) - 1;
|
||||
const PAGE_ENTRIES: usize = 512;
|
||||
const PAGE_SIZE: usize = 4096;
|
||||
const PHYS_MASK: usize = (1usize << 44) - 1;
|
||||
|
||||
const VALID: u64 = 1;
|
||||
const RWX: u64 = 7 << 1;
|
||||
const ACCESSED: u64 = 1 << 6;
|
||||
const DIRTY: u64 = 1 << 7;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub(crate) use sv39::PHYS_OFFSET;
|
||||
pub(crate) use sv39::SATP_BITS;
|
||||
pub(crate) use sv39::paging_create;
|
||||
pub(crate) use sv39::paging_physmem as paging_framebuffer;
|
||||
|
||||
unsafe fn paging_allocate(os: &impl Os) -> Option<&'static mut [u64]> {
|
||||
unsafe {
|
||||
let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE);
|
||||
if !ptr.is_null() {
|
||||
area_add(OsMemoryEntry {
|
||||
base: ptr as u64,
|
||||
size: PAGE_SIZE as u64,
|
||||
kind: OsMemoryKind::Reclaim,
|
||||
});
|
||||
Some(slice::from_raw_parts_mut(ptr as *mut u64, PAGE_ENTRIES))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn get_table(os: &impl Os, parent: &mut [u64], index: usize) -> Option<&'static mut [u64]> {
|
||||
unsafe {
|
||||
if parent[index] == 0 {
|
||||
let table = paging_allocate(os)?;
|
||||
parent[index] = table.as_ptr() as u64 >> 2 | VALID;
|
||||
Some(table)
|
||||
} else {
|
||||
Some(slice::from_raw_parts_mut(
|
||||
(((parent[index] >> 10) & PHYS_MASK as u64) << 12) as *mut u64,
|
||||
PAGE_ENTRIES,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
use core::slice;
|
||||
|
||||
use super::*;
|
||||
use crate::os::Os;
|
||||
|
||||
// Sv39 scheme
|
||||
|
||||
pub(crate) const PHYS_OFFSET: u64 = 0xFFFF_FFC0_0000_0000;
|
||||
pub(crate) const SATP_BITS: usize = 8;
|
||||
|
||||
pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option<usize> {
|
||||
unsafe {
|
||||
// Create L2
|
||||
let l2 = paging_allocate(os)?;
|
||||
|
||||
{
|
||||
// Create L1 for identity mapping
|
||||
for l2_i in 0..8 {
|
||||
let addr = l2_i as u64 * 0x4000_0000;
|
||||
// Identity map 8 GiB using 1GB pages
|
||||
l2[l2_i] = addr >> 2 | RWX | VALID | ACCESSED | DIRTY;
|
||||
// map phys into kernel VAS
|
||||
l2[(PAGE_ENTRIES / 2) + l2_i] = addr >> 2 | RWX | VALID | ACCESSED | DIRTY;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Create L1 for kernel mapping
|
||||
let l1 = paging_allocate(os)?;
|
||||
|
||||
// Link second to last L0 entry to L1
|
||||
l2[510] = l1.as_ptr() as u64 >> 2 | VALID;
|
||||
|
||||
// Map kernel_size at kernel offset
|
||||
let mut kernel_mapped = 0;
|
||||
let mut l1_i = 0;
|
||||
while kernel_mapped < kernel_size && l1_i < l1.len() {
|
||||
let l0 = paging_allocate(os)?;
|
||||
l1[l1_i] = l0.as_ptr() as u64 >> 2 | VALID;
|
||||
l1_i += 1;
|
||||
|
||||
let mut l0_i = 0;
|
||||
while kernel_mapped < kernel_size && l0_i < l2.len() {
|
||||
let addr = kernel_phys + kernel_mapped;
|
||||
l0[l0_i] = addr >> 2 | RWX | VALID | ACCESSED | DIRTY;
|
||||
l0_i += 1;
|
||||
kernel_mapped += PAGE_SIZE as u64;
|
||||
}
|
||||
}
|
||||
assert!(kernel_mapped >= kernel_size);
|
||||
}
|
||||
|
||||
Some(l2.as_ptr() as usize)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn paging_physmem(os: &impl Os, page_phys: usize, phys: u64, size: u64) -> Option<u64> {
|
||||
unsafe {
|
||||
if phys + size <= 0x2_0000_0000 {
|
||||
return Some(phys + PHYS_OFFSET);
|
||||
}
|
||||
|
||||
let mut l1_i = (phys as usize >> (PAGE_SHIFT + 2 * TABLE_SHIFT)) + PAGE_ENTRIES / 2;
|
||||
let mut l0_i = (phys as usize >> (PAGE_SHIFT + TABLE_SHIFT)) & TABLE_MASK;
|
||||
assert_eq!(phys & ((1 << (PAGE_SHIFT + TABLE_SHIFT)) - 1), 0);
|
||||
|
||||
let l1 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES);
|
||||
|
||||
// Map framebuffer_size at framebuffer offset
|
||||
let mut mapped = 0;
|
||||
while mapped < size && l1_i < l1.len() {
|
||||
let l0 = get_table(os, l1, l1_i)?;
|
||||
|
||||
while mapped < size && l0_i < l0.len() {
|
||||
let addr = phys + mapped;
|
||||
assert_eq!(l0[l0_i], 0);
|
||||
l0[l0_i] = (addr >> 2) | RWX | VALID | ACCESSED | DIRTY;
|
||||
mapped += 1 << (PAGE_SHIFT + TABLE_SHIFT); // Map with 2mb mega-pages
|
||||
l0_i += 1;
|
||||
}
|
||||
|
||||
l1_i += 1;
|
||||
l0_i = 0;
|
||||
}
|
||||
|
||||
assert!(mapped >= size);
|
||||
|
||||
Some(phys + PHYS_OFFSET)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
use core::slice;
|
||||
|
||||
use super::*;
|
||||
use crate::os::Os;
|
||||
|
||||
// Sv48 scheme
|
||||
|
||||
pub(crate) const PHYS_OFFSET: u64 = 0xFFFF_8000_0000_0000;
|
||||
pub(crate) const SATP_BITS: usize = 9;
|
||||
|
||||
pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option<usize> {
|
||||
unsafe {
|
||||
// Create L3
|
||||
let l3 = paging_allocate(os)?;
|
||||
|
||||
{
|
||||
// Create L2 for identity mapping
|
||||
let l2 = paging_allocate(os)?;
|
||||
|
||||
// Map L2 into beginning of userspace and kernelspace
|
||||
l3[0] = (l2.as_ptr() as u64 >> 2) | VALID;
|
||||
l3[PAGE_ENTRIES / 2] = (l2.as_ptr() as u64 >> 2) | VALID;
|
||||
|
||||
// Identity map 8 GiB using 1GB pages
|
||||
for l2_i in 0..8 {
|
||||
let addr = l2_i as u64 * 0x4000_0000;
|
||||
l2[l2_i] = addr >> 2 | RWX | VALID;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Create L2 for kernel mapping
|
||||
let l2 = paging_allocate(os)?;
|
||||
|
||||
// Link last L3 entry to L2
|
||||
l3[511] = l2.as_ptr() as u64 >> 2 | VALID;
|
||||
|
||||
// Create L1 for kernel mapping
|
||||
let l1 = paging_allocate(os)?;
|
||||
|
||||
// Link last L1 entry to L2
|
||||
l2[510] = l1.as_ptr() as u64 >> 2 | VALID;
|
||||
|
||||
// Map kernel_size at kernel offset
|
||||
let mut kernel_mapped = 0;
|
||||
let mut l1_i = 0;
|
||||
while kernel_mapped < kernel_size && l1_i < l1.len() {
|
||||
let l0 = paging_allocate(os)?;
|
||||
l1[l1_i] = l0.as_ptr() as u64 >> 2 | VALID;
|
||||
l1_i += 1;
|
||||
|
||||
let mut l0_i = 0;
|
||||
while kernel_mapped < kernel_size && l0_i < l2.len() {
|
||||
let addr = kernel_phys + kernel_mapped;
|
||||
l0[l0_i] = addr >> 2 | RWX | VALID | ACCESSED | DIRTY;
|
||||
l0_i += 1;
|
||||
kernel_mapped += PAGE_SIZE as u64;
|
||||
}
|
||||
}
|
||||
assert!(kernel_mapped >= kernel_size);
|
||||
}
|
||||
|
||||
Some(l3.as_ptr() as usize)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn paging_physmem(os: &impl Os, page_phys: usize, phys: u64, size: u64) -> Option<u64> {
|
||||
unsafe {
|
||||
if phys + size <= 0x2_0000_0000 {
|
||||
return Some(phys + PHYS_OFFSET);
|
||||
}
|
||||
|
||||
let mut l2_i = (phys as usize >> (PAGE_SHIFT + 3 * TABLE_SHIFT)) + PAGE_ENTRIES / 2;
|
||||
let mut l1_i = (phys as usize >> (PAGE_SHIFT + 2 * TABLE_SHIFT)) & TABLE_MASK;
|
||||
let mut l0_i = (phys as usize >> (PAGE_SHIFT + TABLE_SHIFT)) & TABLE_MASK;
|
||||
assert_eq!(phys & ((1 << (PAGE_SHIFT + TABLE_SHIFT)) - 1), 0);
|
||||
|
||||
let l2 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES);
|
||||
|
||||
// Map framebuffer_size at framebuffer offset
|
||||
let mut mapped = 0;
|
||||
while mapped < size && l2_i < l2.len() {
|
||||
let l1 = get_table(os, l2, l2_i)?;
|
||||
|
||||
while mapped < size && l1_i < l1.len() {
|
||||
let l0 = get_table(os, l1, l1_i)?;
|
||||
|
||||
while mapped < size && l0_i < l0.len() {
|
||||
let addr = phys + mapped;
|
||||
assert_eq!(l0[l0_i], 0);
|
||||
l0[l0_i] = (addr >> 2) | RWX | VALID;
|
||||
mapped += 1 << (PAGE_SHIFT + TABLE_SHIFT);
|
||||
l0_i += 1;
|
||||
}
|
||||
|
||||
l1_i += 1;
|
||||
l0_i = 0;
|
||||
}
|
||||
|
||||
l2_i += 1;
|
||||
l1_i = 0;
|
||||
}
|
||||
|
||||
assert!(mapped >= size);
|
||||
|
||||
Some(phys + PHYS_OFFSET)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
use core::slice;
|
||||
|
||||
use super::*;
|
||||
use crate::os::Os;
|
||||
|
||||
// Sv57 scheme
|
||||
|
||||
pub(crate) const PHYS_OFFSET: u64 = 0xFF00_0000_0000_0000;
|
||||
pub(crate) const SATP_BIT: usize = 10;
|
||||
|
||||
pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option<usize> {
|
||||
unsafe {
|
||||
// Create L4
|
||||
let l4 = paging_allocate(os)?;
|
||||
|
||||
{
|
||||
// Create L3
|
||||
let l3 = paging_allocate(os)?;
|
||||
|
||||
// Map L3 into beginning of userspace and kernelspace
|
||||
l4[0] = (l3.as_ptr() as u64 >> 2) | VALID;
|
||||
l4[PAGE_ENTRIES / 2] = (l3.as_ptr() as u64 >> 2) | VALID;
|
||||
|
||||
// Create L2 for identity mapping
|
||||
let l2 = paging_allocate(os)?;
|
||||
|
||||
// Identity map 8 GiB using 1GB pages
|
||||
for l2_i in 0..8 {
|
||||
let addr = l2_i as u64 * 0x4000_0000;
|
||||
l2[l2_i] = addr >> 2 | RWX | VALID;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Create L3
|
||||
let l3 = paging_allocate(os)?;
|
||||
|
||||
// Link last L4 entry to L3
|
||||
l4[511] = l3.as_ptr() as u64 >> 2 | VALID;
|
||||
|
||||
// Create L2 for kernel mapping
|
||||
let l2 = paging_allocate(os)?;
|
||||
|
||||
// Link last L3 entry to L2
|
||||
l3[511] = l2.as_ptr() as u64 >> 2 | VALID;
|
||||
|
||||
// Create L1 for kernel mapping
|
||||
let l1 = paging_allocate(os)?;
|
||||
|
||||
// Link last L1 entry to L2
|
||||
l2[510] = l1.as_ptr() as u64 >> 2 | VALID;
|
||||
|
||||
// Map kernel_size at kernel offset
|
||||
let mut kernel_mapped = 0;
|
||||
let mut l1_i = 0;
|
||||
while kernel_mapped < kernel_size && l1_i < l1.len() {
|
||||
let l0 = paging_allocate(os)?;
|
||||
l1[l1_i] = l0.as_ptr() as u64 >> 2 | VALID;
|
||||
l1_i += 1;
|
||||
|
||||
let mut l0_i = 0;
|
||||
while kernel_mapped < kernel_size && l0_i < l2.len() {
|
||||
let addr = kernel_phys + kernel_mapped;
|
||||
l0[l0_i] = addr >> 2 | RWX | VALID | ACCESSED | DIRTY;
|
||||
l0_i += 1;
|
||||
kernel_mapped += PAGE_SIZE as u64;
|
||||
}
|
||||
}
|
||||
assert!(kernel_mapped >= kernel_size);
|
||||
}
|
||||
|
||||
Some(l4.as_ptr() as usize)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn paging_physmem(os: &impl Os, page_phys: usize, phys: u64, size: u64) -> Option<u64> {
|
||||
unsafe {
|
||||
if phys + size <= 0x2_0000_0000 {
|
||||
return Some(phys + PHYS_OFFSET);
|
||||
}
|
||||
let mut l3_i = (phys as usize >> (PAGE_SHIFT + 4 * TABLE_SHIFT)) + PAGE_ENTRIES / 2;
|
||||
let mut l2_i = (phys as usize >> (PAGE_SHIFT + 3 * TABLE_SHIFT)) & TABLE_MASK;
|
||||
let mut l1_i = (phys as usize >> (PAGE_SHIFT + 2 * TABLE_SHIFT)) & TABLE_MASK;
|
||||
let mut l0_i = (phys as usize >> (PAGE_SHIFT + TABLE_SHIFT)) & TABLE_MASK;
|
||||
assert_eq!(phys & ((1 << (PAGE_SHIFT + TABLE_SHIFT)) - 1), 0);
|
||||
|
||||
let l3 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES);
|
||||
|
||||
// Map framebuffer_size at framebuffer offset
|
||||
let mut mapped = 0;
|
||||
|
||||
while mapped < size && l3_i < l3.len() {
|
||||
let l2 = get_table(os, l3, l3_i)?;
|
||||
|
||||
while mapped < size && l2_i < l2.len() {
|
||||
let l1 = get_table(os, l2, l2_i)?;
|
||||
|
||||
while mapped < size && l1_i < l1.len() {
|
||||
let l0 = get_table(os, l1, l1_i)?;
|
||||
|
||||
while mapped < size && l0_i < l0.len() {
|
||||
let addr = phys + mapped;
|
||||
assert_eq!(l0[l0_i], 0);
|
||||
l0[l0_i] = (addr >> 2) | RWX | VALID;
|
||||
mapped += 1 << (PAGE_SHIFT + TABLE_SHIFT);
|
||||
l0_i += 1;
|
||||
}
|
||||
|
||||
l1_i += 1;
|
||||
l0_i = 0;
|
||||
}
|
||||
|
||||
l2_i += 1;
|
||||
l1_i = 0;
|
||||
}
|
||||
l3_i += 1;
|
||||
l2_i += 0;
|
||||
}
|
||||
|
||||
assert!(mapped >= size);
|
||||
|
||||
Some(phys + PHYS_OFFSET)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
use crate::os::Os;
|
||||
|
||||
pub(crate) mod x32;
|
||||
pub(crate) mod x64;
|
||||
|
||||
pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option<usize> {
|
||||
unsafe {
|
||||
if crate::KERNEL_64BIT {
|
||||
x64::paging_create(os, kernel_phys, kernel_size)
|
||||
} else {
|
||||
x32::paging_create(os, kernel_phys, kernel_size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn paging_framebuffer(
|
||||
os: &impl Os,
|
||||
page_phys: usize,
|
||||
framebuffer_phys: u64,
|
||||
framebuffer_size: u64,
|
||||
) -> Option<u64> {
|
||||
unsafe {
|
||||
if crate::KERNEL_64BIT {
|
||||
x64::paging_framebuffer(os, page_phys, framebuffer_phys, framebuffer_size)
|
||||
} else {
|
||||
x32::paging_framebuffer(os, page_phys, framebuffer_phys, framebuffer_size)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
use crate::area_add;
|
||||
use crate::os::{Os, OsMemoryEntry, OsMemoryKind};
|
||||
use core::slice;
|
||||
|
||||
const PAGE_ENTRIES: usize = 1024;
|
||||
const PAGE_SIZE: usize = 4096;
|
||||
pub(crate) const PHYS_OFFSET: u32 = 0x8000_0000;
|
||||
|
||||
unsafe fn paging_allocate(os: &impl Os) -> Option<&'static mut [u32]> {
|
||||
unsafe {
|
||||
let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE);
|
||||
if !ptr.is_null() {
|
||||
area_add(OsMemoryEntry {
|
||||
base: ptr as u64,
|
||||
size: PAGE_SIZE as u64,
|
||||
kind: OsMemoryKind::Reclaim,
|
||||
});
|
||||
Some(slice::from_raw_parts_mut(ptr as *mut u32, PAGE_ENTRIES))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option<usize> {
|
||||
unsafe {
|
||||
let pd = paging_allocate(os)?;
|
||||
//Identity map 1 GiB using 4 MiB pages, also map at PHYS_OFFSET
|
||||
for pd_i in 0..256 {
|
||||
let addr = pd_i as u32 * 0x40_0000;
|
||||
pd[pd_i] = addr | 1 << 7 | 1 << 1 | 1;
|
||||
pd[pd_i + 512] = addr | 1 << 7 | 1 << 1 | 1;
|
||||
}
|
||||
|
||||
// Map kernel_size at kernel offset
|
||||
let mut kernel_mapped = 0;
|
||||
let mut pd_i = 0xC000_0000 / 0x40_0000;
|
||||
while kernel_mapped < kernel_size && pd_i < pd.len() {
|
||||
let pt = paging_allocate(os)?;
|
||||
pd[pd_i] = pt.as_ptr() as u32 | 1 << 1 | 1;
|
||||
pd_i += 1;
|
||||
|
||||
let mut pt_i = 0;
|
||||
while kernel_mapped < kernel_size && pt_i < pt.len() {
|
||||
let addr = kernel_phys + kernel_mapped;
|
||||
pt[pt_i] = addr as u32 | 1 << 1 | 1;
|
||||
pt_i += 1;
|
||||
kernel_mapped += PAGE_SIZE as u64;
|
||||
}
|
||||
}
|
||||
assert!(kernel_mapped >= kernel_size);
|
||||
|
||||
Some(pd.as_ptr() as usize)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn paging_framebuffer(
|
||||
os: &impl Os,
|
||||
page_phys: usize,
|
||||
framebuffer_phys: u64,
|
||||
framebuffer_size: u64,
|
||||
) -> Option<u64> {
|
||||
unsafe {
|
||||
let framebuffer_virt = 0xD000_0000; // 256 MiB after kernel mapping, but before heap mapping
|
||||
|
||||
let pd = slice::from_raw_parts_mut(page_phys as *mut u32, PAGE_ENTRIES);
|
||||
|
||||
// Map framebuffer_size at framebuffer offset
|
||||
let mut framebuffer_mapped = 0;
|
||||
let mut pd_i = framebuffer_virt / 0x40_0000;
|
||||
while framebuffer_mapped < framebuffer_size && pd_i < pd.len() {
|
||||
let pt = paging_allocate(os)?;
|
||||
pd[pd_i] = pt.as_ptr() as u32 | 1 << 1 | 1;
|
||||
pd_i += 1;
|
||||
|
||||
let mut pt_i = 0;
|
||||
while framebuffer_mapped < framebuffer_size && pt_i < pt.len() {
|
||||
let addr = framebuffer_phys + framebuffer_mapped;
|
||||
pt[pt_i] = addr as u32 | 1 << 1 | 1;
|
||||
pt_i += 1;
|
||||
framebuffer_mapped += PAGE_SIZE as u64;
|
||||
}
|
||||
}
|
||||
assert!(framebuffer_mapped >= framebuffer_size);
|
||||
|
||||
Some(framebuffer_virt as u64)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
use core::slice;
|
||||
|
||||
use crate::area_add;
|
||||
use crate::os::{Os, OsMemoryEntry, OsMemoryKind};
|
||||
|
||||
const ENTRY_ADDRESS_MASK: u64 = 0x000F_FFFF_FFFF_F000;
|
||||
const PAGE_ENTRIES: usize = 512;
|
||||
const PAGE_SIZE: usize = 4096;
|
||||
pub(crate) const PHYS_OFFSET: u64 = 0xFFFF_8000_0000_0000;
|
||||
|
||||
unsafe fn paging_allocate(os: &impl Os) -> Option<&'static mut [u64]> {
|
||||
unsafe {
|
||||
let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE);
|
||||
if !ptr.is_null() {
|
||||
area_add(OsMemoryEntry {
|
||||
base: ptr as u64,
|
||||
size: PAGE_SIZE as u64,
|
||||
kind: OsMemoryKind::Reclaim,
|
||||
});
|
||||
|
||||
Some(slice::from_raw_parts_mut(ptr as *mut u64, PAGE_ENTRIES))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const PRESENT: u64 = 1;
|
||||
const WRITABLE: u64 = 1 << 1;
|
||||
const LARGE: u64 = 1 << 7;
|
||||
|
||||
pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option<usize> {
|
||||
unsafe {
|
||||
// Create PML4
|
||||
let pml4 = paging_allocate(os)?;
|
||||
|
||||
{
|
||||
// Create PDP for identity mapping
|
||||
let pdp = paging_allocate(os)?;
|
||||
|
||||
// Link first user and first kernel PML4 entry to PDP
|
||||
pml4[0] = pdp.as_ptr() as u64 | WRITABLE | PRESENT;
|
||||
pml4[256] = pdp.as_ptr() as u64 | WRITABLE | PRESENT;
|
||||
|
||||
// Identity map 8 GiB using 2 MiB pages
|
||||
for pdp_i in 0..8 {
|
||||
let pd = paging_allocate(os)?;
|
||||
pdp[pdp_i] = pd.as_ptr() as u64 | WRITABLE | PRESENT;
|
||||
for pd_i in 0..pd.len() {
|
||||
let addr = pdp_i as u64 * 0x4000_0000 + pd_i as u64 * 0x20_0000;
|
||||
pd[pd_i] = addr | LARGE | WRITABLE | PRESENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Create PDP (spanning 512 GiB) for kernel mapping
|
||||
let pdp = paging_allocate(os)?;
|
||||
|
||||
// Link last PML4 entry to PDP
|
||||
pml4[511] = pdp.as_ptr() as u64 | WRITABLE | PRESENT;
|
||||
|
||||
// Create PD (spanning 1 GiB) for kernel mapping.
|
||||
let pd = paging_allocate(os)?;
|
||||
|
||||
// The kernel is mapped at -2^31, i.e. 0xFFFF_FFFF_8000_0000. Since a PD is 1 GiB, link
|
||||
// the second last PDP entry to PD.
|
||||
pdp[510] = pd.as_ptr() as u64 | WRITABLE | PRESENT;
|
||||
|
||||
// Map kernel_size bytes to kernel offset, i.e. to the start of the PD.
|
||||
|
||||
let mut kernel_mapped = 0;
|
||||
|
||||
let mut pd_idx = 0;
|
||||
while kernel_mapped < kernel_size && pd_idx < pd.len() {
|
||||
let pt = paging_allocate(os)?;
|
||||
pd[pd_idx] = pt.as_ptr() as u64 | WRITABLE | PRESENT;
|
||||
pd_idx += 1;
|
||||
|
||||
let mut pt_idx = 0;
|
||||
while kernel_mapped < kernel_size && pt_idx < pt.len() {
|
||||
let addr = kernel_phys + kernel_mapped;
|
||||
pt[pt_idx] = addr | WRITABLE | PRESENT;
|
||||
pt_idx += 1;
|
||||
kernel_mapped += PAGE_SIZE as u64;
|
||||
}
|
||||
}
|
||||
assert!(kernel_mapped >= kernel_size);
|
||||
}
|
||||
|
||||
Some(pml4.as_ptr() as usize)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn paging_framebuffer(
|
||||
os: &impl Os,
|
||||
page_phys: usize,
|
||||
framebuffer_phys: u64,
|
||||
framebuffer_size: u64,
|
||||
) -> Option<u64> {
|
||||
unsafe {
|
||||
//TODO: smarter test for framebuffer already mapped
|
||||
if framebuffer_phys + framebuffer_size <= 0x2_0000_0000 {
|
||||
return Some(framebuffer_phys + PHYS_OFFSET);
|
||||
}
|
||||
|
||||
let pml4_i = ((framebuffer_phys / 0x80_0000_0000) + 256) as usize;
|
||||
let mut pdp_i = ((framebuffer_phys % 0x80_0000_0000) / 0x4000_0000) as usize;
|
||||
let mut pd_i = ((framebuffer_phys % 0x4000_0000) / 0x20_0000) as usize;
|
||||
assert_eq!(framebuffer_phys % 0x20_0000, 0);
|
||||
|
||||
let pml4 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES);
|
||||
|
||||
// Create PDP for framebuffer mapping
|
||||
let pdp = if pml4[pml4_i] == 0 {
|
||||
let pdp = paging_allocate(os)?;
|
||||
pml4[pml4_i] = pdp.as_ptr() as u64 | 1 << 1 | 1;
|
||||
pdp
|
||||
} else {
|
||||
slice::from_raw_parts_mut(
|
||||
(pml4[pml4_i] & ENTRY_ADDRESS_MASK) as *mut u64,
|
||||
PAGE_ENTRIES,
|
||||
)
|
||||
};
|
||||
|
||||
// Map framebuffer_size at framebuffer offset
|
||||
let mut framebuffer_mapped = 0;
|
||||
while framebuffer_mapped < framebuffer_size && pdp_i < pdp.len() {
|
||||
let pd = paging_allocate(os)?;
|
||||
assert_eq!(pdp[pdp_i], 0);
|
||||
pdp[pdp_i] = pd.as_ptr() as u64 | 1 << 1 | 1;
|
||||
|
||||
while framebuffer_mapped < framebuffer_size && pd_i < pd.len() {
|
||||
let addr = framebuffer_phys + framebuffer_mapped;
|
||||
assert_eq!(pd[pd_i], 0);
|
||||
pd[pd_i] = addr | 1 << 7 | 1 << 1 | 1;
|
||||
framebuffer_mapped += 0x20_0000;
|
||||
pd_i += 1;
|
||||
}
|
||||
|
||||
pdp_i += 1;
|
||||
pd_i = 0;
|
||||
}
|
||||
assert!(framebuffer_mapped >= framebuffer_size);
|
||||
|
||||
Some(framebuffer_phys + PHYS_OFFSET)
|
||||
}
|
||||
}
|
||||
-146
@@ -1,146 +0,0 @@
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::{Disk, FileSystem, Node, Transaction, TreePtr, BLOCK_SIZE};
|
||||
|
||||
fn syscall_err(err: syscall::Error) -> io::Error {
|
||||
io::Error::from_raw_os_error(err.errno)
|
||||
}
|
||||
|
||||
pub fn archive_at<D: Disk, P: AsRef<Path>>(
|
||||
tx: &mut Transaction<D>,
|
||||
parent_path: P,
|
||||
parent_ptr: TreePtr<Node>,
|
||||
) -> io::Result<()> {
|
||||
for entry_res in fs::read_dir(parent_path)? {
|
||||
let entry = entry_res?;
|
||||
|
||||
let metadata = entry.metadata()?;
|
||||
let file_type = metadata.file_type();
|
||||
|
||||
let name = entry.file_name().into_string().map_err(|_| {
|
||||
io::Error::new(io::ErrorKind::InvalidData, "filename is not valid UTF-8")
|
||||
})?;
|
||||
|
||||
let mode_type = if file_type.is_dir() {
|
||||
Node::MODE_DIR
|
||||
} else if file_type.is_file() {
|
||||
Node::MODE_FILE
|
||||
} else if file_type.is_symlink() {
|
||||
Node::MODE_SYMLINK
|
||||
} else {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Does not support parsing {:?}", file_type),
|
||||
));
|
||||
};
|
||||
|
||||
let node_ptr;
|
||||
{
|
||||
let mode = mode_type | (metadata.mode() as u16 & Node::MODE_PERM);
|
||||
let mut node = tx
|
||||
.create_node(
|
||||
parent_ptr,
|
||||
&name,
|
||||
mode,
|
||||
metadata.ctime() as u64,
|
||||
metadata.ctime_nsec() as u32,
|
||||
)
|
||||
.map_err(syscall_err)?;
|
||||
|
||||
node_ptr = node.ptr();
|
||||
|
||||
if node.data().uid() != metadata.uid() || node.data().gid() != metadata.gid() {
|
||||
node.data_mut().set_uid(metadata.uid());
|
||||
node.data_mut().set_gid(metadata.gid());
|
||||
tx.sync_tree(node).map_err(syscall_err)?;
|
||||
}
|
||||
}
|
||||
|
||||
let path = entry.path();
|
||||
if file_type.is_dir() {
|
||||
archive_at(tx, path, node_ptr)?;
|
||||
} else if file_type.is_file() {
|
||||
let data = fs::read(path)?;
|
||||
let count = tx
|
||||
.write_node(
|
||||
node_ptr,
|
||||
0,
|
||||
&data,
|
||||
metadata.mtime() as u64,
|
||||
metadata.mtime_nsec() as u32,
|
||||
)
|
||||
.map_err(syscall_err)?;
|
||||
if count != data.len() {
|
||||
panic!("file write count {} != {}", count, data.len());
|
||||
}
|
||||
} else if file_type.is_symlink() {
|
||||
let destination = fs::read_link(path)?;
|
||||
let data = destination.as_os_str().as_bytes();
|
||||
let count = tx
|
||||
.write_node(
|
||||
node_ptr,
|
||||
0,
|
||||
data,
|
||||
metadata.mtime() as u64,
|
||||
metadata.mtime_nsec() as u32,
|
||||
)
|
||||
.map_err(syscall_err)?;
|
||||
if count != data.len() {
|
||||
panic!("symlink write count {} != {}", count, data.len());
|
||||
}
|
||||
} else {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Does not support creating {:?}", file_type),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn archive<D: Disk, P: AsRef<Path>>(fs: &mut FileSystem<D>, parent_path: P) -> io::Result<u64> {
|
||||
let end_block = fs
|
||||
.tx(|tx| {
|
||||
// Archive_at root node
|
||||
archive_at(tx, parent_path, TreePtr::root())
|
||||
.map_err(|err| syscall::Error::new(err.raw_os_error().unwrap()))?;
|
||||
|
||||
// Squash alloc log
|
||||
tx.sync(true)?;
|
||||
|
||||
let end_block = tx.header.size() / BLOCK_SIZE;
|
||||
/* TODO: Cut off any free blocks at the end of the filesystem
|
||||
let mut end_changed = true;
|
||||
while end_changed {
|
||||
end_changed = false;
|
||||
|
||||
let allocator = fs.allocator();
|
||||
let levels = allocator.levels();
|
||||
for level in 0..levels.len() {
|
||||
let level_size = 1 << level;
|
||||
for &block in levels[level].iter() {
|
||||
if block < end_block && block + level_size >= end_block {
|
||||
end_block = block;
|
||||
end_changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Update header
|
||||
tx.header.size = (end_block * BLOCK_SIZE).into();
|
||||
tx.header_changed = true;
|
||||
tx.sync(false)?;
|
||||
|
||||
Ok(end_block)
|
||||
})
|
||||
.map_err(syscall_err)?;
|
||||
|
||||
Ok((fs.block + end_block) * BLOCK_SIZE)
|
||||
}
|
||||
-108
@@ -1,108 +0,0 @@
|
||||
extern crate redoxfs;
|
||||
extern crate syscall;
|
||||
extern crate uuid;
|
||||
|
||||
use std::io::Read;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::{env, fs, process};
|
||||
|
||||
use redoxfs::{archive, DiskFile, FileSystem};
|
||||
use uuid::Uuid;
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let mut args = env::args().skip(1);
|
||||
|
||||
let disk_path = if let Some(path) = args.next() {
|
||||
path
|
||||
} else {
|
||||
println!("redoxfs-ar: no disk image provided");
|
||||
println!("redoxfs-ar DISK FOLDER [BOOTLOADER]");
|
||||
process::exit(1);
|
||||
};
|
||||
|
||||
let folder_path = if let Some(path) = args.next() {
|
||||
path
|
||||
} else {
|
||||
println!("redoxfs-ar: no folder provided");
|
||||
println!("redoxfs-ar DISK FOLDER [BOOTLOADER]");
|
||||
process::exit(1);
|
||||
};
|
||||
|
||||
let bootloader_path_opt = args.next();
|
||||
|
||||
let disk = match DiskFile::open(&disk_path) {
|
||||
Ok(disk) => disk,
|
||||
Err(err) => {
|
||||
println!("redoxfs-ar: failed to open image {}: {}", disk_path, err);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let mut bootloader = vec![];
|
||||
if let Some(bootloader_path) = bootloader_path_opt {
|
||||
match fs::File::open(&bootloader_path) {
|
||||
Ok(mut file) => match file.read_to_end(&mut bootloader) {
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
println!(
|
||||
"redoxfs-ar: failed to read bootloader {}: {}",
|
||||
bootloader_path, err
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
println!(
|
||||
"redoxfs-ar: failed to open bootloader {}: {}",
|
||||
bootloader_path, err
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
match FileSystem::create_reserved(
|
||||
disk,
|
||||
None,
|
||||
&bootloader,
|
||||
ctime.as_secs(),
|
||||
ctime.subsec_nanos(),
|
||||
) {
|
||||
Ok(mut fs) => {
|
||||
let size = match archive(&mut fs, &folder_path) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
println!("redoxfs-ar: failed to archive {}: {}", folder_path, err);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = fs.disk.file.set_len(size) {
|
||||
println!(
|
||||
"redoxfs-ar: failed to truncate {} to {}: {}",
|
||||
disk_path, size, err
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
let uuid = Uuid::from_bytes(fs.header.uuid());
|
||||
println!(
|
||||
"redoxfs-ar: created filesystem on {}, reserved {} blocks, size {} MB, uuid {}",
|
||||
disk_path,
|
||||
fs.block,
|
||||
fs.header.size() / 1000 / 1000,
|
||||
uuid.hyphenated()
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
println!(
|
||||
"redoxfs-ar: failed to create filesystem on {}: {}",
|
||||
disk_path, err
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
extern crate redoxfs;
|
||||
extern crate syscall;
|
||||
extern crate uuid;
|
||||
|
||||
use std::io::Read;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::{env, fs, process};
|
||||
|
||||
use redoxfs::{clone, DiskFile, FileSystem};
|
||||
use uuid::Uuid;
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let mut args = env::args().skip(1);
|
||||
|
||||
let disk_path_old = if let Some(path) = args.next() {
|
||||
path
|
||||
} else {
|
||||
println!("redoxfs-clone: no old disk image provided");
|
||||
println!("redoxfs-clone NEW-DISK OLD-DISK [BOOTLOADER]");
|
||||
process::exit(1);
|
||||
};
|
||||
|
||||
let disk_path = if let Some(path) = args.next() {
|
||||
path
|
||||
} else {
|
||||
println!("redoxfs-clone: no new disk image provided");
|
||||
println!("redoxfs-clone NEW-DISK OLD-DISK [BOOTLOADER]");
|
||||
process::exit(1);
|
||||
};
|
||||
|
||||
let bootloader_path_opt = args.next();
|
||||
|
||||
// Open old disk in readonly mode
|
||||
let disk_old = match fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.write(false)
|
||||
.open(&disk_path_old)
|
||||
.map(DiskFile::from)
|
||||
{
|
||||
Ok(disk) => disk,
|
||||
Err(err) => {
|
||||
println!(
|
||||
"redoxfs-clone: failed to open old disk image {}: {}",
|
||||
disk_path_old, err
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let mut fs_old = match FileSystem::open(disk_old, None, None, false) {
|
||||
Ok(fs) => fs,
|
||||
Err(err) => {
|
||||
println!(
|
||||
"redoxfs-clone: failed to open filesystem on {}: {}",
|
||||
disk_path_old, err
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let disk = match DiskFile::open(&disk_path) {
|
||||
Ok(disk) => disk,
|
||||
Err(err) => {
|
||||
println!(
|
||||
"redoxfs-clone: failed to open new disk image {}: {}",
|
||||
disk_path, err
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let mut bootloader = vec![];
|
||||
if let Some(bootloader_path) = bootloader_path_opt {
|
||||
match fs::File::open(&bootloader_path) {
|
||||
Ok(mut file) => match file.read_to_end(&mut bootloader) {
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
println!(
|
||||
"redoxfs-clone: failed to read bootloader {}: {}",
|
||||
bootloader_path, err
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
println!(
|
||||
"redoxfs-clone: failed to open bootloader {}: {}",
|
||||
bootloader_path, err
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
let mut fs = match FileSystem::create_reserved(
|
||||
disk,
|
||||
None,
|
||||
&bootloader,
|
||||
ctime.as_secs(),
|
||||
ctime.subsec_nanos(),
|
||||
) {
|
||||
Ok(fs) => fs,
|
||||
Err(err) => {
|
||||
println!(
|
||||
"redoxfs-clone: failed to create filesystem on {}: {}",
|
||||
disk_path, err
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let size_old = fs_old.header.size();
|
||||
let free_old = fs_old.allocator().free() * redoxfs::BLOCK_SIZE;
|
||||
let used_old = size_old - free_old;
|
||||
let mut last_percent = 0;
|
||||
let clone_res = clone(&mut fs_old, &mut fs, move |used| {
|
||||
let percent = (used * 100) / used_old;
|
||||
if percent != last_percent {
|
||||
eprint!(
|
||||
"\r{}%: {} MB/{} MB",
|
||||
percent,
|
||||
used / 1000 / 1000,
|
||||
used_old / 1000 / 1000
|
||||
);
|
||||
last_percent = percent;
|
||||
}
|
||||
});
|
||||
eprintln!();
|
||||
match clone_res {
|
||||
Ok(()) => (),
|
||||
Err(err) => {
|
||||
println!(
|
||||
"redoxfs-clone: failed to clone {} to {}: {}",
|
||||
disk_path_old, disk_path, err
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
let uuid = Uuid::from_bytes(fs.header.uuid());
|
||||
let size = fs.header.size();
|
||||
let free = fs.allocator().free() * redoxfs::BLOCK_SIZE;
|
||||
let used = size - free;
|
||||
println!("redoxfs-clone: created filesystem on {}", disk_path,);
|
||||
println!("\treserved: {} blocks", fs.block);
|
||||
println!("\tuuid: {}", uuid.hyphenated());
|
||||
println!("\tsize: {} MB", size / 1000 / 1000);
|
||||
println!("\tused: {} MB", used / 1000 / 1000);
|
||||
println!("\tfree: {} MB", free / 1000 / 1000);
|
||||
}
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
extern crate redoxfs;
|
||||
extern crate uuid;
|
||||
|
||||
use std::io::Read;
|
||||
use std::{env, fs, io, process, time};
|
||||
|
||||
use redoxfs::{DiskFile, FileSystem};
|
||||
use termion::input::TermRead;
|
||||
use uuid::Uuid;
|
||||
|
||||
fn usage() -> ! {
|
||||
eprintln!("redoxfs-mkfs [--encrypt] DISK [BOOTLOADER]");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let mut encrypt = false;
|
||||
let mut disk_path_opt = None;
|
||||
let mut bootloader_path_opt = None;
|
||||
for arg in env::args().skip(1) {
|
||||
if arg == "--encrypt" {
|
||||
encrypt = true;
|
||||
} else if disk_path_opt.is_none() {
|
||||
disk_path_opt = Some(arg);
|
||||
} else if bootloader_path_opt.is_none() {
|
||||
bootloader_path_opt = Some(arg);
|
||||
} else {
|
||||
eprintln!("redoxfs-mkfs: too many arguments provided");
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
let disk_path = if let Some(path) = disk_path_opt {
|
||||
path
|
||||
} else {
|
||||
eprintln!("redoxfs-mkfs: no disk image provided");
|
||||
usage();
|
||||
};
|
||||
|
||||
let disk = match DiskFile::open(&disk_path) {
|
||||
Ok(disk) => disk,
|
||||
Err(err) => {
|
||||
eprintln!("redoxfs-mkfs: failed to open image {}: {}", disk_path, err);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let mut bootloader = vec![];
|
||||
if let Some(bootloader_path) = bootloader_path_opt {
|
||||
match fs::File::open(&bootloader_path) {
|
||||
Ok(mut file) => match file.read_to_end(&mut bootloader) {
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"redoxfs-mkfs: failed to read bootloader {}: {}",
|
||||
bootloader_path, err
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"redoxfs-mkfs: failed to open bootloader {}: {}",
|
||||
bootloader_path, err
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let password_opt = if encrypt {
|
||||
eprint!("redoxfs-mkfs: password: ");
|
||||
|
||||
let password = io::stdin()
|
||||
.read_passwd(&mut io::stderr())
|
||||
.unwrap()
|
||||
.unwrap_or_default();
|
||||
|
||||
eprintln!();
|
||||
|
||||
if password.is_empty() {
|
||||
eprintln!("redoxfs-mkfs: empty password, giving up");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
Some(password)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let ctime = time::SystemTime::now()
|
||||
.duration_since(time::UNIX_EPOCH)
|
||||
.unwrap();
|
||||
match FileSystem::create_reserved(
|
||||
disk,
|
||||
password_opt.as_ref().map(|x| x.as_bytes()),
|
||||
&bootloader,
|
||||
ctime.as_secs(),
|
||||
ctime.subsec_nanos(),
|
||||
) {
|
||||
Ok(filesystem) => {
|
||||
let uuid = Uuid::from_bytes(filesystem.header.uuid());
|
||||
eprintln!(
|
||||
"redoxfs-mkfs: created filesystem on {}, reserved {} blocks, size {} MB, uuid {}",
|
||||
disk_path,
|
||||
filesystem.block,
|
||||
filesystem.header.size() / 1000 / 1000,
|
||||
uuid.hyphenated()
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"redoxfs-mkfs: failed to create filesystem on {}: {}",
|
||||
disk_path, err
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,409 +0,0 @@
|
||||
extern crate libc;
|
||||
extern crate redoxfs;
|
||||
#[cfg(target_os = "redox")]
|
||||
extern crate syscall;
|
||||
extern crate uuid;
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::os::unix::io::{FromRawFd, RawFd};
|
||||
use std::process;
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
use std::{mem::MaybeUninit, ptr::addr_of_mut, sync::atomic::Ordering};
|
||||
|
||||
use redoxfs::{mount, DiskCache, DiskFile, FileSystem};
|
||||
use termion::input::TermRead;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
extern "C" fn unmount_handler(_s: usize) {
|
||||
redoxfs::IS_UMT.store(1, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
//set up a signal handler on redox, this implements unmounting. I have no idea what sa_flags is
|
||||
//for, so I put 2. I don't think 0,0 is a valid sa_mask. I don't know what i'm doing here. When u
|
||||
//send it a sigkill, it shuts off the filesystem
|
||||
fn setsig() {
|
||||
// TODO: High-level wrapper like the nix crate?
|
||||
unsafe {
|
||||
let mut action = MaybeUninit::<libc::sigaction>::uninit();
|
||||
|
||||
assert_eq!(
|
||||
libc::sigemptyset(addr_of_mut!((*action.as_mut_ptr()).sa_mask)),
|
||||
0
|
||||
);
|
||||
addr_of_mut!((*action.as_mut_ptr()).sa_flags).write(0);
|
||||
addr_of_mut!((*action.as_mut_ptr()).sa_sigaction).write(unmount_handler as usize);
|
||||
|
||||
assert_eq!(
|
||||
libc::sigaction(libc::SIGTERM, action.as_ptr(), core::ptr::null_mut()),
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
// on linux, this is implemented properly, so no need for this unscrupulous nonsense!
|
||||
fn setsig() {}
|
||||
|
||||
fn fork() -> isize {
|
||||
unsafe { libc::fork() as isize }
|
||||
}
|
||||
|
||||
fn pipe(pipes: &mut [i32; 2]) -> isize {
|
||||
unsafe { libc::pipe(pipes.as_mut_ptr()) as isize }
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
fn capability_mode() {}
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
fn bootloader_password() -> Option<Vec<u8>> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn capability_mode() {
|
||||
libredox::call::setrens(0, 0).expect("redoxfs: failed to enter null namespace");
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn bootloader_password() -> Option<Vec<u8>> {
|
||||
use libredox::call::MmapArgs;
|
||||
|
||||
let addr_env = env::var_os("REDOXFS_PASSWORD_ADDR")?;
|
||||
let size_env = env::var_os("REDOXFS_PASSWORD_SIZE")?;
|
||||
|
||||
let addr = usize::from_str_radix(
|
||||
addr_env.to_str().expect("REDOXFS_PASSWORD_ADDR not valid"),
|
||||
16,
|
||||
)
|
||||
.expect("failed to parse REDOXFS_PASSWORD_ADDR");
|
||||
|
||||
let size = usize::from_str_radix(
|
||||
size_env.to_str().expect("REDOXFS_PASSWORD_SIZE not valid"),
|
||||
16,
|
||||
)
|
||||
.expect("failed to parse REDOXFS_PASSWORD_SIZE");
|
||||
|
||||
let mut password = Vec::with_capacity(size);
|
||||
unsafe {
|
||||
let aligned_size = size.next_multiple_of(syscall::PAGE_SIZE);
|
||||
|
||||
let fd = libredox::Fd::open("memory:physical", libredox::flag::O_CLOEXEC, 0)
|
||||
.expect("failed to open physical memory file");
|
||||
|
||||
let password_map = libredox::call::mmap(MmapArgs {
|
||||
addr: core::ptr::null_mut(),
|
||||
length: aligned_size,
|
||||
prot: libredox::flag::PROT_READ,
|
||||
flags: libredox::flag::MAP_SHARED,
|
||||
fd: fd.raw(),
|
||||
offset: addr as u64,
|
||||
})
|
||||
.expect("failed to map REDOXFS_PASSWORD")
|
||||
.cast::<u8>();
|
||||
|
||||
for i in 0..size {
|
||||
password.push(password_map.add(i).read());
|
||||
}
|
||||
|
||||
let _ = libredox::call::munmap(password_map.cast(), aligned_size);
|
||||
}
|
||||
Some(password)
|
||||
}
|
||||
|
||||
fn print_err_exit(err: impl AsRef<str>) -> ! {
|
||||
eprintln!("redoxfs: {}", err.as_ref());
|
||||
usage();
|
||||
process::exit(1)
|
||||
}
|
||||
|
||||
fn print_usage_exit() -> ! {
|
||||
usage();
|
||||
process::exit(1)
|
||||
}
|
||||
|
||||
fn usage() {
|
||||
eprintln!("redoxfs [--no-daemon|-d] [--uuid] [disk or uuid] [mountpoint] [block in hex]");
|
||||
}
|
||||
|
||||
enum DiskId {
|
||||
Path(String),
|
||||
Uuid(Uuid),
|
||||
}
|
||||
|
||||
fn filesystem_by_path(
|
||||
path: &str,
|
||||
block_opt: Option<u64>,
|
||||
log_errors: bool,
|
||||
) -> Option<(String, FileSystem<DiskCache<DiskFile>>)> {
|
||||
log::debug!("opening {}", path);
|
||||
let attempts = 10;
|
||||
for attempt in 0..=attempts {
|
||||
let password_opt = if attempt > 0 {
|
||||
eprint!("redoxfs: password: ");
|
||||
|
||||
let password = io::stdin()
|
||||
.read_passwd(&mut io::stderr())
|
||||
.unwrap()
|
||||
.unwrap_or_default();
|
||||
|
||||
eprintln!();
|
||||
|
||||
if password.is_empty() {
|
||||
eprintln!("redoxfs: empty password, giving up");
|
||||
|
||||
// Password is empty, exit loop
|
||||
break;
|
||||
}
|
||||
|
||||
Some(password.into_bytes())
|
||||
} else {
|
||||
bootloader_password()
|
||||
};
|
||||
|
||||
match DiskFile::open(path).map(DiskCache::new) {
|
||||
Ok(disk) => {
|
||||
match redoxfs::FileSystem::open(disk, password_opt.as_deref(), block_opt, true) {
|
||||
Ok(filesystem) => {
|
||||
log::debug!(
|
||||
"opened filesystem on {} with uuid {}",
|
||||
path,
|
||||
Uuid::from_bytes(filesystem.header.uuid()).hyphenated()
|
||||
);
|
||||
|
||||
return Some((path.to_string(), filesystem));
|
||||
}
|
||||
Err(err) => match err.errno {
|
||||
syscall::ENOKEY => {
|
||||
if password_opt.is_some() {
|
||||
eprintln!("redoxfs: incorrect password ({}/{})", attempt, attempts);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if log_errors {
|
||||
log::error!("failed to open filesystem {}: {}", path, err);
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
if log_errors {
|
||||
log::error!("failed to open image {}: {}", path, err);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
fn filesystem_by_uuid(
|
||||
_uuid: &Uuid,
|
||||
_block_opt: Option<u64>,
|
||||
) -> Option<(String, FileSystem<DiskCache<DiskFile>>)> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn filesystem_by_uuid(
|
||||
uuid: &Uuid,
|
||||
block_opt: Option<u64>,
|
||||
) -> Option<(String, FileSystem<DiskCache<DiskFile>>)> {
|
||||
use std::fs;
|
||||
|
||||
use redox_path::RedoxPath;
|
||||
|
||||
match fs::read_dir("/scheme") {
|
||||
Ok(entries) => {
|
||||
for entry_res in entries {
|
||||
if let Ok(entry) = entry_res {
|
||||
if let Some(disk) = entry.path().to_str() {
|
||||
if RedoxPath::from_absolute(disk)
|
||||
.unwrap_or(RedoxPath::from_absolute("/")?)
|
||||
.is_scheme_category("disk")
|
||||
{
|
||||
log::debug!("found scheme {}", disk);
|
||||
match fs::read_dir(disk) {
|
||||
Ok(entries) => {
|
||||
for entry_res in entries {
|
||||
if let Ok(entry) = entry_res {
|
||||
if let Ok(path) =
|
||||
entry.path().into_os_string().into_string()
|
||||
{
|
||||
log::debug!("found path {}", path);
|
||||
if let Some((path, filesystem)) =
|
||||
filesystem_by_path(&path, block_opt, false)
|
||||
{
|
||||
if &filesystem.header.uuid() == uuid.as_bytes()
|
||||
{
|
||||
log::debug!(
|
||||
"filesystem on {} matches uuid {}",
|
||||
path,
|
||||
uuid.hyphenated()
|
||||
);
|
||||
return Some((path, filesystem));
|
||||
} else {
|
||||
log::debug!(
|
||||
"filesystem on {} does not match uuid {}",
|
||||
path,
|
||||
uuid.hyphenated()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::debug!("failed to list '{}': {}", disk, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("failed to list schemes: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn daemon(
|
||||
disk_id: &DiskId,
|
||||
mountpoint: &str,
|
||||
block_opt: Option<u64>,
|
||||
mut write: Option<File>,
|
||||
) -> ! {
|
||||
setsig();
|
||||
|
||||
let filesystem_opt = match *disk_id {
|
||||
DiskId::Path(ref path) => filesystem_by_path(path, block_opt, true),
|
||||
DiskId::Uuid(ref uuid) => filesystem_by_uuid(uuid, block_opt),
|
||||
};
|
||||
|
||||
if let Some((path, filesystem)) = filesystem_opt {
|
||||
match mount(filesystem, mountpoint, |mounted_path| {
|
||||
capability_mode();
|
||||
|
||||
log::info!(
|
||||
"mounted filesystem on {} to {}",
|
||||
path,
|
||||
mounted_path.display()
|
||||
);
|
||||
|
||||
if let Some(ref mut write) = write {
|
||||
let _ = write.write(&[0]);
|
||||
}
|
||||
}) {
|
||||
Ok(()) => {
|
||||
process::exit(0);
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("failed to mount {} to {}: {}", path, mountpoint, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match *disk_id {
|
||||
DiskId::Path(ref path) => {
|
||||
log::error!("not able to mount path {}", path);
|
||||
}
|
||||
DiskId::Uuid(ref uuid) => {
|
||||
log::error!("not able to mount uuid {}", uuid.hyphenated());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref mut write) = write {
|
||||
let _ = write.write(&[1]);
|
||||
}
|
||||
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let mut args = env::args().skip(1);
|
||||
|
||||
let mut daemonise = true;
|
||||
let mut disk_id: Option<DiskId> = None;
|
||||
let mut mountpoint: Option<String> = None;
|
||||
let mut block_opt: Option<u64> = None;
|
||||
|
||||
while let Some(arg) = args.next() {
|
||||
match arg.as_str() {
|
||||
"--no-daemon" | "-d" => daemonise = false,
|
||||
|
||||
"--uuid" if disk_id.is_none() => {
|
||||
disk_id = Some(DiskId::Uuid(
|
||||
match args.next().as_deref().map(Uuid::parse_str) {
|
||||
Some(Ok(uuid)) => uuid,
|
||||
Some(Err(err)) => {
|
||||
print_err_exit(format!("invalid uuid '{}': {}", arg, err))
|
||||
}
|
||||
None => print_err_exit("no uuid provided"),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
disk if disk_id.is_none() => disk_id = Some(DiskId::Path(disk.to_owned())),
|
||||
|
||||
mnt if disk_id.is_some() && mountpoint.is_none() => mountpoint = Some(mnt.to_owned()),
|
||||
|
||||
opts if mountpoint.is_some() => match u64::from_str_radix(opts, 16) {
|
||||
Ok(block) => block_opt = Some(block),
|
||||
Err(err) => print_err_exit(format!("invalid block '{}': {}", opts, err)),
|
||||
},
|
||||
|
||||
_ => print_usage_exit(),
|
||||
}
|
||||
}
|
||||
|
||||
let Some(disk_id) = disk_id else {
|
||||
print_err_exit("no disk provided");
|
||||
};
|
||||
|
||||
let Some(mountpoint) = mountpoint else {
|
||||
print_err_exit("no mountpoint provided");
|
||||
};
|
||||
|
||||
if daemonise {
|
||||
let mut pipes = [0; 2];
|
||||
if pipe(&mut pipes) == 0 {
|
||||
let mut read = unsafe { File::from_raw_fd(pipes[0] as RawFd) };
|
||||
let write = unsafe { File::from_raw_fd(pipes[1] as RawFd) };
|
||||
|
||||
let pid = fork();
|
||||
if pid == 0 {
|
||||
drop(read);
|
||||
|
||||
daemon(&disk_id, &mountpoint, block_opt, Some(write));
|
||||
} else if pid > 0 {
|
||||
drop(write);
|
||||
|
||||
let mut res = [0];
|
||||
read.read_exact(&mut res).unwrap();
|
||||
|
||||
process::exit(res[0] as i32);
|
||||
} else {
|
||||
panic!("redoxfs: failed to fork");
|
||||
}
|
||||
} else {
|
||||
panic!("redoxfs: failed to create pipe");
|
||||
}
|
||||
} else {
|
||||
log::info!("running in foreground");
|
||||
daemon(&disk_id, &mountpoint, block_opt, None);
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
use std::{env, process};
|
||||
|
||||
use humansize::{format_size, BINARY, DECIMAL};
|
||||
use redoxfs::{BlockAddr, BlockMeta, Disk, DiskFile, FileSystem};
|
||||
use uuid::Uuid;
|
||||
|
||||
fn resize<D: Disk>(fs: &mut FileSystem<D>, size_arg: String) -> Result<(), String> {
|
||||
let disk_size = fs
|
||||
.disk
|
||||
.size()
|
||||
.map_err(|err| format!("failed to read disk size: {}", err))?;
|
||||
|
||||
// Find contiguous free region
|
||||
//TODO: better error management
|
||||
let mut last_free = None;
|
||||
let mut last_end = 0;
|
||||
fs.tx(|tx| {
|
||||
let mut alloc_ptr = tx.header.alloc;
|
||||
while !alloc_ptr.is_null() {
|
||||
let alloc = tx.read_block(alloc_ptr)?;
|
||||
alloc_ptr = alloc.data().prev;
|
||||
for entry in alloc.data().entries.iter() {
|
||||
let count = entry.count();
|
||||
if count <= 0 {
|
||||
continue;
|
||||
}
|
||||
let end = entry.index() + count as u64;
|
||||
if end > last_end {
|
||||
last_free = Some(*entry);
|
||||
last_end = end;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|err| format!("failed to read alloc log: {}", err))?;
|
||||
|
||||
let old_size = fs.header.size();
|
||||
let min_size = if let Some(entry) = last_free {
|
||||
entry.index() * redoxfs::BLOCK_SIZE
|
||||
} else {
|
||||
old_size
|
||||
};
|
||||
let max_size = disk_size - (fs.block * redoxfs::BLOCK_SIZE);
|
||||
|
||||
let new_size = match size_arg.to_lowercase().as_str() {
|
||||
"min" | "minimum" => min_size,
|
||||
"" | "max" | "maximum" => max_size,
|
||||
_ => match parse_size::parse_size(&size_arg) {
|
||||
Ok(new_size) => {
|
||||
if new_size < min_size {
|
||||
return Err(format!(
|
||||
"requested size {} is smaller than {} by {}",
|
||||
new_size,
|
||||
min_size,
|
||||
min_size - new_size
|
||||
));
|
||||
}
|
||||
|
||||
if new_size > max_size {
|
||||
return Err(format!(
|
||||
"requested size {} is larger than {} by {}",
|
||||
new_size,
|
||||
max_size,
|
||||
new_size - max_size
|
||||
));
|
||||
}
|
||||
|
||||
new_size
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(format!(
|
||||
"failed to parse size argument {:?}: {}",
|
||||
size_arg, err
|
||||
));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
println!(
|
||||
"minimum size: {} ({})",
|
||||
format_size(min_size, DECIMAL),
|
||||
format_size(min_size, BINARY)
|
||||
);
|
||||
println!(
|
||||
"maximum size: {} ({})",
|
||||
format_size(max_size, DECIMAL),
|
||||
format_size(max_size, BINARY)
|
||||
);
|
||||
println!(
|
||||
"new size: {} ({})",
|
||||
format_size(new_size, DECIMAL),
|
||||
format_size(new_size, BINARY)
|
||||
);
|
||||
|
||||
let old_blocks = old_size / redoxfs::BLOCK_SIZE;
|
||||
let new_blocks = new_size / redoxfs::BLOCK_SIZE;
|
||||
let (start, end, shrink) = if new_size == old_size {
|
||||
println!("already requested size");
|
||||
return Ok(());
|
||||
} else if new_size < old_size {
|
||||
println!("shrinking by {}", old_size - new_size);
|
||||
(new_blocks, old_blocks, true)
|
||||
} else {
|
||||
println!("growing by {}", new_size - old_size);
|
||||
(old_blocks, new_blocks, false)
|
||||
};
|
||||
|
||||
// Allocate or deallocate blocks as needed
|
||||
unsafe {
|
||||
let allocator = fs.allocator_mut();
|
||||
for index in start..end {
|
||||
if shrink {
|
||||
//TODO: replace assert with error?
|
||||
let addr = BlockAddr::new(index as u64, BlockMeta::default());
|
||||
assert_eq!(allocator.allocate_exact(addr), Some(addr));
|
||||
} else {
|
||||
let addr = BlockAddr::new(index as u64, BlockMeta::default());
|
||||
allocator.deallocate(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs.tx(|tx| {
|
||||
// Update header
|
||||
tx.header.size = new_size.into();
|
||||
tx.header_changed = true;
|
||||
|
||||
// Sync with squash
|
||||
tx.sync(true)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|err| format!("transaction failed: {}", err))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let mut args = env::args().skip(1);
|
||||
|
||||
let disk_path = if let Some(path) = args.next() {
|
||||
path
|
||||
} else {
|
||||
eprintln!("redoxfs-resize: no new disk image provided");
|
||||
eprintln!("redoxfs-resize NEW-DISK [SIZE]");
|
||||
process::exit(1);
|
||||
};
|
||||
|
||||
let size_arg = args.next().unwrap_or_default();
|
||||
|
||||
let disk = match DiskFile::open(&disk_path) {
|
||||
Ok(disk) => disk,
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"redoxfs-resize: failed to open disk image {}: {}",
|
||||
disk_path, err
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let mut fs = match FileSystem::open(disk, None, None, true) {
|
||||
Ok(fs) => fs,
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"redoxfs-resize: failed to open filesystem on {}: {}",
|
||||
disk_path, err
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
match resize(&mut fs, size_arg) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"redoxfs-resize: failed to resize filesystem on {}: {}",
|
||||
disk_path, err
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
let uuid = Uuid::from_bytes(fs.header.uuid());
|
||||
let size = fs.header.size();
|
||||
let free = fs.allocator().free() * redoxfs::BLOCK_SIZE;
|
||||
let used = size - free;
|
||||
println!("redoxfs-resize: resized filesystem on {}", disk_path);
|
||||
println!("\tuuid: {}", uuid.hyphenated());
|
||||
println!(
|
||||
"\tsize: {} ({})",
|
||||
format_size(size, DECIMAL),
|
||||
format_size(size, BINARY)
|
||||
);
|
||||
println!(
|
||||
"\tused: {} ({})",
|
||||
format_size(used, DECIMAL),
|
||||
format_size(used, BINARY)
|
||||
);
|
||||
println!(
|
||||
"\tfree: {} ({})",
|
||||
format_size(free, DECIMAL),
|
||||
format_size(free, BINARY)
|
||||
);
|
||||
}
|
||||
-393
@@ -1,393 +0,0 @@
|
||||
use core::{fmt, marker::PhantomData, mem, ops, slice};
|
||||
use endian_num::Le;
|
||||
|
||||
use crate::BLOCK_SIZE;
|
||||
|
||||
const BLOCK_LIST_ENTRIES: usize = BLOCK_SIZE as usize / mem::size_of::<BlockPtr<BlockRaw>>();
|
||||
|
||||
/// An address of a data block.
|
||||
///
|
||||
/// This encodes a block's position _and_ [`BlockLevel`]:
|
||||
/// the first four bits of this `u64` encode the block's level,
|
||||
/// the next four bits indicates decompression level,
|
||||
/// the rest encode its index.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct BlockAddr(u64);
|
||||
|
||||
impl BlockAddr {
|
||||
const INDEX_SHIFT: u64 = 8;
|
||||
const DECOMP_LEVEL_MASK: u64 = 0xF0;
|
||||
const DECOMP_LEVEL_SHIFT: u64 = 4;
|
||||
const LEVEL_MASK: u64 = 0xF;
|
||||
|
||||
// Unsafe because this can create invalid blocks
|
||||
pub unsafe fn new(index: u64, meta: BlockMeta) -> Self {
|
||||
// Level must fit within LEVEL_MASK
|
||||
if meta.level.0 > Self::LEVEL_MASK as usize {
|
||||
panic!("block level too large");
|
||||
}
|
||||
|
||||
// Decomp level must fit within DECOMP_LEVEL_MASK
|
||||
let decomp_level = meta.decomp_level.unwrap_or_default();
|
||||
if (decomp_level.0 << Self::DECOMP_LEVEL_SHIFT) > Self::DECOMP_LEVEL_MASK as usize {
|
||||
panic!("decompressed block level too large");
|
||||
}
|
||||
|
||||
// Index must not use the metadata bits
|
||||
let inner = index
|
||||
.checked_shl(Self::INDEX_SHIFT as u32)
|
||||
.expect("block index too large")
|
||||
| ((decomp_level.0 as u64) << Self::DECOMP_LEVEL_SHIFT)
|
||||
| (meta.level.0 as u64);
|
||||
Self(inner)
|
||||
}
|
||||
|
||||
pub fn null(meta: BlockMeta) -> Self {
|
||||
unsafe { Self::new(0, meta) }
|
||||
}
|
||||
|
||||
pub fn index(&self) -> u64 {
|
||||
// The first four bits store the level
|
||||
self.0 >> Self::INDEX_SHIFT
|
||||
}
|
||||
|
||||
pub fn level(&self) -> BlockLevel {
|
||||
// The first four bits store the level
|
||||
BlockLevel((self.0 & Self::LEVEL_MASK) as usize)
|
||||
}
|
||||
|
||||
pub fn decomp_level(&self) -> Option<BlockLevel> {
|
||||
let value = (self.0 & Self::DECOMP_LEVEL_MASK) >> Self::DECOMP_LEVEL_SHIFT;
|
||||
if value != 0 {
|
||||
Some(BlockLevel(value as usize))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn meta(&self) -> BlockMeta {
|
||||
BlockMeta {
|
||||
level: self.level(),
|
||||
decomp_level: self.decomp_level(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_null(&self) -> bool {
|
||||
self.index() == 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
||||
pub struct BlockMeta {
|
||||
pub(crate) level: BlockLevel,
|
||||
pub(crate) decomp_level: Option<BlockLevel>,
|
||||
}
|
||||
|
||||
impl BlockMeta {
|
||||
pub fn new(level: BlockLevel) -> Self {
|
||||
Self {
|
||||
level,
|
||||
decomp_level: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_compressed(level: BlockLevel, decomp_level: BlockLevel) -> Self {
|
||||
Self {
|
||||
level,
|
||||
decomp_level: Some(decomp_level),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The size of a block.
|
||||
///
|
||||
/// Level 0 blocks are blocks of [`BLOCK_SIZE`] bytes.
|
||||
/// A level 1 block consists of two consecutive level 0 blocks.
|
||||
/// A level n block consists of two consecutive level n-1 blocks.
|
||||
///
|
||||
/// See [`crate::Allocator`] docs for more details.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct BlockLevel(pub(crate) usize);
|
||||
|
||||
impl BlockLevel {
|
||||
/// Returns the smallest block level that can contain
|
||||
/// the given number of bytes.
|
||||
pub(crate) fn for_bytes(bytes: u64) -> Self {
|
||||
if bytes == 0 {
|
||||
return BlockLevel(0);
|
||||
}
|
||||
let level = bytes
|
||||
.div_ceil(BLOCK_SIZE)
|
||||
.next_power_of_two()
|
||||
.trailing_zeros() as usize;
|
||||
BlockLevel(level)
|
||||
}
|
||||
|
||||
/// The number of [`BLOCK_SIZE`] blocks (i.e, level 0 blocks)
|
||||
/// in a block of this level
|
||||
pub fn blocks<T: From<u32>>(self) -> T {
|
||||
T::from(1u32 << self.0)
|
||||
}
|
||||
|
||||
/// The number of bytes in a block of this level
|
||||
pub fn bytes(self) -> u64 {
|
||||
BLOCK_SIZE << self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe trait BlockTrait {
|
||||
/// Create an empty block of this type.
|
||||
fn empty(level: BlockLevel) -> Option<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// A [`BlockAddr`] and the data it points to.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct BlockData<T> {
|
||||
addr: BlockAddr,
|
||||
data: T,
|
||||
}
|
||||
|
||||
impl<T> BlockData<T> {
|
||||
pub fn new(addr: BlockAddr, data: T) -> Self {
|
||||
Self { addr, data }
|
||||
}
|
||||
|
||||
pub fn addr(&self) -> BlockAddr {
|
||||
self.addr
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &T {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub fn data_mut(&mut self) -> &mut T {
|
||||
&mut self.data
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn into_parts(self) -> (BlockAddr, T) {
|
||||
(self.addr, self.data)
|
||||
}
|
||||
|
||||
/// Set the address of this [`BlockData`] to `addr`, returning this
|
||||
/// block's old address. This method does not update block data.
|
||||
///
|
||||
/// `addr` must point to a block with the same level as this block.
|
||||
#[must_use = "don't forget to de-allocate old block address"]
|
||||
pub fn swap_addr(&mut self, addr: BlockAddr) -> BlockAddr {
|
||||
// Address levels must match
|
||||
assert_eq!(self.addr.level(), addr.level());
|
||||
let old = self.addr;
|
||||
self.addr = addr;
|
||||
old
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BlockTrait> BlockData<T> {
|
||||
pub fn empty(addr: BlockAddr) -> Option<Self> {
|
||||
let empty = T::empty(addr.level())?;
|
||||
Some(Self::new(addr, empty))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ops::Deref<Target = [u8]>> BlockData<T> {
|
||||
pub fn create_ptr(&self) -> BlockPtr<T> {
|
||||
BlockPtr {
|
||||
addr: self.addr.0.into(),
|
||||
hash: seahash::hash(self.data.deref()).into(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct BlockList<T> {
|
||||
pub ptrs: [BlockPtr<T>; BLOCK_LIST_ENTRIES],
|
||||
}
|
||||
|
||||
unsafe impl<T> BlockTrait for BlockList<T> {
|
||||
fn empty(level: BlockLevel) -> Option<Self> {
|
||||
if level.0 == 0 {
|
||||
Some(Self {
|
||||
ptrs: [BlockPtr::default(); BLOCK_LIST_ENTRIES],
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> BlockList<T> {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.ptrs.iter().all(|ptr| ptr.is_null())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ops::Deref for BlockList<T> {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts(
|
||||
self as *const BlockList<T> as *const u8,
|
||||
mem::size_of::<BlockList<T>>(),
|
||||
) as &[u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ops::DerefMut for BlockList<T> {
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts_mut(
|
||||
self as *mut BlockList<T> as *mut u8,
|
||||
mem::size_of::<BlockList<T>>(),
|
||||
) as &mut [u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An address of a data block, along with a checksum of its data.
|
||||
///
|
||||
/// This encodes a block's position _and_ [`BlockLevel`].
|
||||
/// the first four bits of `addr` encode the block's level,
|
||||
/// the rest encode its index.
|
||||
///
|
||||
/// Also see [`BlockAddr`].
|
||||
#[repr(C, packed)]
|
||||
pub struct BlockPtr<T> {
|
||||
addr: Le<u64>,
|
||||
hash: Le<u64>,
|
||||
phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> BlockPtr<T> {
|
||||
pub fn null(meta: BlockMeta) -> Self {
|
||||
Self {
|
||||
addr: BlockAddr::null(meta).0.into(),
|
||||
hash: 0.into(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addr(&self) -> BlockAddr {
|
||||
BlockAddr(self.addr.to_ne())
|
||||
}
|
||||
|
||||
pub fn hash(&self) -> u64 {
|
||||
self.hash.to_ne()
|
||||
}
|
||||
|
||||
pub fn is_null(&self) -> bool {
|
||||
self.addr().is_null()
|
||||
}
|
||||
|
||||
pub fn marker(level: u8) -> Self {
|
||||
assert!(level <= 0xF);
|
||||
Self {
|
||||
addr: (0xFFFF_FFFF_FFFF_FFF0 | (level as u64)).into(),
|
||||
hash: u64::MAX.into(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_marker(&self) -> bool {
|
||||
(self.addr.to_ne() | 0xF) == u64::MAX && self.hash.to_ne() == u64::MAX
|
||||
}
|
||||
|
||||
/// Cast BlockPtr to another type
|
||||
///
|
||||
/// # Safety
|
||||
/// Unsafe because it can be used to transmute types
|
||||
pub unsafe fn cast<U>(self) -> BlockPtr<U> {
|
||||
BlockPtr {
|
||||
addr: self.addr,
|
||||
hash: self.hash,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use = "the returned pointer should usually be deallocated"]
|
||||
pub fn clear(&mut self) -> BlockPtr<T> {
|
||||
let mut ptr = Self::default();
|
||||
mem::swap(self, &mut ptr);
|
||||
ptr
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for BlockPtr<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for BlockPtr<T> {}
|
||||
|
||||
impl<T> Default for BlockPtr<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
addr: 0.into(),
|
||||
hash: 0.into(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for BlockPtr<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let addr = self.addr();
|
||||
let hash = self.hash();
|
||||
f.debug_struct("BlockPtr")
|
||||
.field("addr", &addr)
|
||||
.field("hash", &hash)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone)]
|
||||
pub struct BlockRaw([u8; BLOCK_SIZE as usize]);
|
||||
|
||||
unsafe impl BlockTrait for BlockRaw {
|
||||
fn empty(level: BlockLevel) -> Option<Self> {
|
||||
if level.0 == 0 {
|
||||
Some(Self([0; BLOCK_SIZE as usize]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for BlockRaw {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::DerefMut for BlockRaw {
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_list_size_test() {
|
||||
assert_eq!(mem::size_of::<BlockList<BlockRaw>>(), BLOCK_SIZE as usize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_raw_size_test() {
|
||||
assert_eq!(mem::size_of::<BlockRaw>(), BLOCK_SIZE as usize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_ptr_marker_test() {
|
||||
let ptr = BlockPtr::<BlockRaw>::marker(0);
|
||||
assert_eq!(ptr.addr().level().0, 0);
|
||||
assert!(ptr.is_marker());
|
||||
|
||||
let ptr = BlockPtr::<BlockRaw>::marker(2);
|
||||
assert_eq!(ptr.addr().level().0, 2);
|
||||
assert!(ptr.is_marker());
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
use crate::{Disk, FileSystem, Node, Transaction, TreePtr, BLOCK_SIZE};
|
||||
|
||||
fn tx_progress<D: Disk, F: FnMut(u64)>(tx: &mut Transaction<D>, progress: &mut F) {
|
||||
let size = tx.header.size();
|
||||
let free = tx.allocator.free() * BLOCK_SIZE;
|
||||
progress(size - free);
|
||||
}
|
||||
|
||||
//TODO: handle hard links
|
||||
fn clone_at<D: Disk, E: Disk, F: FnMut(u64)>(
|
||||
tx_old: &mut Transaction<D>,
|
||||
parent_ptr_old: TreePtr<Node>,
|
||||
tx: &mut Transaction<E>,
|
||||
parent_ptr: TreePtr<Node>,
|
||||
buf: &mut [u8],
|
||||
progress: &mut F,
|
||||
) -> syscall::Result<()> {
|
||||
let mut entries = Vec::new();
|
||||
tx_old.child_nodes(parent_ptr_old, &mut entries)?;
|
||||
for entry in entries {
|
||||
//TODO: return error instead?
|
||||
let Some(name) = entry.name() else {
|
||||
continue;
|
||||
};
|
||||
let node_ptr_old = entry.node_ptr();
|
||||
let node_old = tx_old.read_tree(node_ptr_old)?;
|
||||
|
||||
//TODO: this slows down the clone, but Redox has issues without this (Linux is fine)
|
||||
if tx.write_cache.len() > 64 {
|
||||
tx.sync(false)?;
|
||||
}
|
||||
|
||||
let node_ptr = {
|
||||
let mode = node_old.data().mode();
|
||||
let (ctime, ctime_nsec) = node_old.data().ctime();
|
||||
let (mtime, mtime_nsec) = node_old.data().mtime();
|
||||
let mut node = tx.create_node(parent_ptr, &name, mode, ctime, ctime_nsec)?;
|
||||
node.data_mut().set_uid(node_old.data().uid());
|
||||
node.data_mut().set_gid(node_old.data().gid());
|
||||
node.data_mut().set_mtime(mtime, mtime_nsec);
|
||||
|
||||
if !node_old.data().is_dir() {
|
||||
let mut offset = 0;
|
||||
loop {
|
||||
let count = tx_old.read_node_inner(&node_old, offset, buf)?;
|
||||
if count == 0 {
|
||||
break;
|
||||
}
|
||||
tx.write_node_inner(&mut node, &mut offset, &buf[..count])?;
|
||||
}
|
||||
}
|
||||
|
||||
let node_ptr = node.ptr();
|
||||
tx.sync_tree(node)?;
|
||||
node_ptr
|
||||
};
|
||||
|
||||
tx_progress(tx, progress);
|
||||
|
||||
if node_old.data().is_dir() {
|
||||
clone_at(tx_old, node_ptr_old, tx, node_ptr, buf, progress)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn clone<D: Disk, E: Disk, F: FnMut(u64)>(
|
||||
fs_old: &mut FileSystem<D>,
|
||||
fs: &mut FileSystem<E>,
|
||||
mut progress: F,
|
||||
) -> syscall::Result<()> {
|
||||
fs_old.tx(|tx_old| {
|
||||
let mut tx = Transaction::new(fs);
|
||||
|
||||
// Clone at root node
|
||||
let mut buf = vec![0; 4 * 1024 * 1024];
|
||||
clone_at(
|
||||
tx_old,
|
||||
TreePtr::root(),
|
||||
&mut tx,
|
||||
TreePtr::root(),
|
||||
&mut buf,
|
||||
&mut progress,
|
||||
)?;
|
||||
|
||||
// Commit and squash alloc log
|
||||
tx.commit(true)
|
||||
})
|
||||
}
|
||||
-300
@@ -1,300 +0,0 @@
|
||||
use core::{mem, ops, slice, str};
|
||||
|
||||
use crate::{BlockLevel, BlockTrait, Node, TreePtr, BLOCK_SIZE, DIR_ENTRY_MAX_LENGTH};
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct DirEntry {
|
||||
node_ptr: TreePtr<Node>,
|
||||
name: [u8; DIR_ENTRY_MAX_LENGTH],
|
||||
}
|
||||
|
||||
impl DirEntry {
|
||||
pub fn new(node_ptr: TreePtr<Node>, name: &str) -> DirEntry {
|
||||
let mut entry = DirEntry {
|
||||
node_ptr,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
entry.name[..name.len()].copy_from_slice(name.as_bytes());
|
||||
|
||||
entry
|
||||
}
|
||||
|
||||
pub fn node_ptr(&self) -> TreePtr<Node> {
|
||||
self.node_ptr
|
||||
}
|
||||
|
||||
fn name_len(&self) -> usize {
|
||||
let mut len = 0;
|
||||
while len < self.name.len() {
|
||||
if self.name[len] == 0 {
|
||||
break;
|
||||
}
|
||||
len += 1;
|
||||
}
|
||||
len
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<&str> {
|
||||
let len = self.name_len();
|
||||
//TODO: report utf8 error?
|
||||
str::from_utf8(&self.name[..len]).ok()
|
||||
}
|
||||
|
||||
// 4 bytes TreePtr
|
||||
// 1 byte name_len
|
||||
const SERIALIZED_PREFIX_SIZE: usize = mem::size_of::<TreePtr<Node>>() + 1;
|
||||
|
||||
pub fn serialized_size(&self) -> usize {
|
||||
DirEntry::SERIALIZED_PREFIX_SIZE + self.name_len()
|
||||
}
|
||||
|
||||
fn serialize_into(&self, buf: &mut [u8]) -> Option<usize> {
|
||||
let required = self.serialized_size();
|
||||
if buf.len() < required {
|
||||
return None;
|
||||
}
|
||||
|
||||
buf[0..4].copy_from_slice(&self.node_ptr().to_bytes());
|
||||
buf[4] = self.name_len() as u8;
|
||||
buf[5..5 + self.name_len()].copy_from_slice(&self.name[..self.name_len()]);
|
||||
|
||||
Some(required)
|
||||
}
|
||||
|
||||
fn deserialize_from(buf: &[u8]) -> Result<(Self, usize), &'static str> {
|
||||
if buf.len() <= DirEntry::SERIALIZED_PREFIX_SIZE {
|
||||
return Err("Buffer too small");
|
||||
}
|
||||
|
||||
let node_ptr: TreePtr<Node> =
|
||||
TreePtr::from_bytes(buf[0..4].try_into().expect("Slice must be 4 bytes long"));
|
||||
let name_len = buf[4] as usize;
|
||||
|
||||
if name_len < 1 || name_len > DIR_ENTRY_MAX_LENGTH {
|
||||
return Err("Invalid name length");
|
||||
}
|
||||
|
||||
if buf.len() < DirEntry::SERIALIZED_PREFIX_SIZE + name_len {
|
||||
return Err("Buffer too small");
|
||||
}
|
||||
|
||||
let mut name = [0u8; DIR_ENTRY_MAX_LENGTH];
|
||||
name[..name_len].copy_from_slice(
|
||||
&buf[DirEntry::SERIALIZED_PREFIX_SIZE..DirEntry::SERIALIZED_PREFIX_SIZE + name_len],
|
||||
);
|
||||
|
||||
Ok((
|
||||
DirEntry { node_ptr, name },
|
||||
DirEntry::SERIALIZED_PREFIX_SIZE + name_len,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DirEntry {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
node_ptr: TreePtr::default(),
|
||||
name: [0; DIR_ENTRY_MAX_LENGTH],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirList {
|
||||
count: u16,
|
||||
entry_bytes_len: u16,
|
||||
entry_bytes: [u8; BLOCK_SIZE as usize - 4],
|
||||
}
|
||||
|
||||
unsafe impl BlockTrait for DirList {
|
||||
fn empty(level: BlockLevel) -> Option<Self> {
|
||||
if level.0 == 0 {
|
||||
Some(Self {
|
||||
count: 0,
|
||||
entry_bytes_len: 0,
|
||||
entry_bytes: [0; BLOCK_SIZE as usize - 4],
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DirList {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.count == 0
|
||||
}
|
||||
|
||||
pub fn entries(&self) -> DirEntryIterator<'_> {
|
||||
DirEntryIterator {
|
||||
dir_list: self,
|
||||
emit_count: 0,
|
||||
position: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn entry_position_for_name(&self, name: &str) -> Option<usize> {
|
||||
let name_len = name.len();
|
||||
let mut position = 0;
|
||||
let mut entry_id = 0;
|
||||
|
||||
while entry_id < self.count {
|
||||
let entry_name_len = self.entry_bytes[position + 4] as usize;
|
||||
if entry_name_len == name_len {
|
||||
let start = DirEntry::SERIALIZED_PREFIX_SIZE + position;
|
||||
let entry_name = &self.entry_bytes[start..start + entry_name_len];
|
||||
if entry_name == name.as_bytes() {
|
||||
return Some(position);
|
||||
}
|
||||
}
|
||||
position += DirEntry::SERIALIZED_PREFIX_SIZE + entry_name_len;
|
||||
entry_id += 1;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn find_entry(&self, name: &str) -> Option<DirEntry> {
|
||||
if let Some(position) = self.entry_position_for_name(name) {
|
||||
let (entry, _) = DirEntry::deserialize_from(&self.entry_bytes[position..]).unwrap();
|
||||
return Some(entry);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn remove_entry(&mut self, name: &str) -> bool {
|
||||
if let Some(position) = self.entry_position_for_name(name) {
|
||||
let entry_size =
|
||||
DirEntry::SERIALIZED_PREFIX_SIZE + self.entry_bytes[position + 4] as usize;
|
||||
let remaining_size = self.entry_bytes_len as usize - position - entry_size;
|
||||
if remaining_size > 0 {
|
||||
self.entry_bytes.copy_within(
|
||||
position + entry_size..self.entry_bytes_len as usize,
|
||||
position,
|
||||
);
|
||||
}
|
||||
self.entry_bytes_len -= entry_size as u16;
|
||||
self.count -= 1;
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn for_each_entry<F>(&self, mut f: F)
|
||||
where
|
||||
F: FnMut(&[u8; 4], &[u8]),
|
||||
{
|
||||
let mut position = 0;
|
||||
let mut entry_id = 0;
|
||||
|
||||
while entry_id < self.count {
|
||||
let node_ptr_bytes = &self.entry_bytes[position..position + 4];
|
||||
//let node_ptr = TreePtr::<Node>::from_bytes(node_ptr_bytes.try_into().unwrap());
|
||||
let entry_name_len = self.entry_bytes[position + 4] as usize;
|
||||
let start = DirEntry::SERIALIZED_PREFIX_SIZE + position;
|
||||
let entry_name = &self.entry_bytes[start..start + entry_name_len];
|
||||
|
||||
f(node_ptr_bytes.try_into().unwrap(), entry_name);
|
||||
|
||||
position += DirEntry::SERIALIZED_PREFIX_SIZE + entry_name_len;
|
||||
entry_id += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append(&mut self, entry: &DirEntry) -> bool {
|
||||
let entry_bytes_len = self.entry_bytes_len as usize;
|
||||
if let Some(size) = entry.serialize_into(&mut self.entry_bytes[entry_bytes_len..]) {
|
||||
self.count += 1;
|
||||
self.entry_bytes_len += size as u16;
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn entry_count(&self) -> usize {
|
||||
self.count as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for DirList {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts(
|
||||
self as *const DirList as *const u8,
|
||||
mem::size_of::<DirList>(),
|
||||
) as &[u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::DerefMut for DirList {
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts_mut(self as *mut DirList as *mut u8, mem::size_of::<DirList>())
|
||||
as &mut [u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirEntryIterator<'a> {
|
||||
dir_list: &'a DirList,
|
||||
emit_count: usize,
|
||||
position: usize,
|
||||
}
|
||||
|
||||
impl Iterator for DirEntryIterator<'_> {
|
||||
type Item = DirEntry;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.emit_count < self.dir_list.entry_count() {
|
||||
let position = self.position;
|
||||
let (entry, bytes_read) =
|
||||
DirEntry::deserialize_from(&self.dir_list.entry_bytes[position..]).unwrap();
|
||||
|
||||
self.emit_count += 1;
|
||||
self.position += bytes_read;
|
||||
|
||||
Some(entry)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use alloc::format;
|
||||
|
||||
#[test]
|
||||
fn dir_list_size_test() {
|
||||
use core::ops::Deref;
|
||||
assert_eq!(
|
||||
DirList::empty(BlockLevel(0)).unwrap().deref().len(),
|
||||
BLOCK_SIZE as usize
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_append() {
|
||||
let mut dir_list = DirList::empty(BlockLevel(0)).unwrap();
|
||||
let dirent = DirEntry::new(TreePtr::new(123), "test000");
|
||||
|
||||
assert!(dir_list.append(&dirent));
|
||||
assert_eq!(dir_list.entry_count(), 1);
|
||||
assert_eq!(dir_list.entry_bytes_len as usize, dirent.serialized_size());
|
||||
|
||||
let max_entries = dir_list.entry_bytes.len() / dirent.serialized_size();
|
||||
for i in 1..max_entries {
|
||||
let dirent = DirEntry::new(TreePtr::new(123), format!("test{i:03}").as_str());
|
||||
assert!(dir_list.append(&dirent), "Failed on iteration {i}");
|
||||
}
|
||||
let dirent = DirEntry::new(TreePtr::new(123), format!("test{max_entries}").as_str());
|
||||
assert!(!dir_list.append(&dirent));
|
||||
|
||||
for (i, entry) in dir_list.entries().enumerate() {
|
||||
assert_eq!(entry.name().unwrap(), format!("test{i:03}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::{cmp, ptr};
|
||||
use syscall::error::Result;
|
||||
|
||||
use crate::disk::Disk;
|
||||
use crate::BLOCK_SIZE;
|
||||
|
||||
fn copy_memory(src: &[u8], dest: &mut [u8]) -> usize {
|
||||
let len = cmp::min(src.len(), dest.len());
|
||||
unsafe { ptr::copy(src.as_ptr(), dest.as_mut_ptr(), len) };
|
||||
len
|
||||
}
|
||||
|
||||
pub struct DiskCache<T> {
|
||||
inner: T,
|
||||
cache: HashMap<u64, [u8; BLOCK_SIZE as usize]>,
|
||||
order: VecDeque<u64>,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
impl<T: Disk> DiskCache<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
// 16 MB cache
|
||||
let size = 16 * 1024 * 1024 / BLOCK_SIZE as usize;
|
||||
DiskCache {
|
||||
inner,
|
||||
cache: HashMap::with_capacity(size),
|
||||
order: VecDeque::with_capacity(size),
|
||||
size,
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(&mut self, i: u64, data: [u8; BLOCK_SIZE as usize]) {
|
||||
while self.order.len() >= self.size {
|
||||
let removed = self.order.pop_front().unwrap();
|
||||
self.cache.remove(&removed);
|
||||
}
|
||||
|
||||
self.cache.insert(i, data);
|
||||
self.order.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Disk> Disk for DiskCache<T> {
|
||||
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
|
||||
// println!("Cache read at {}", block);
|
||||
|
||||
let mut read = 0;
|
||||
let mut failed = false;
|
||||
for i in 0..buffer.len().div_ceil(BLOCK_SIZE as usize) {
|
||||
let block_i = block + i as u64;
|
||||
|
||||
let buffer_i = i * BLOCK_SIZE as usize;
|
||||
let buffer_j = cmp::min(buffer_i + BLOCK_SIZE as usize, buffer.len());
|
||||
let buffer_slice = &mut buffer[buffer_i..buffer_j];
|
||||
|
||||
if let Some(cache_buf) = self.cache.get_mut(&block_i) {
|
||||
read += copy_memory(cache_buf, buffer_slice);
|
||||
} else {
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if failed {
|
||||
self.inner.read_at(block, buffer)?;
|
||||
|
||||
read = 0;
|
||||
for i in 0..buffer.len().div_ceil(BLOCK_SIZE as usize) {
|
||||
let block_i = block + i as u64;
|
||||
|
||||
let buffer_i = i * BLOCK_SIZE as usize;
|
||||
let buffer_j = cmp::min(buffer_i + BLOCK_SIZE as usize, buffer.len());
|
||||
let buffer_slice = &buffer[buffer_i..buffer_j];
|
||||
|
||||
let mut cache_buf = [0; BLOCK_SIZE as usize];
|
||||
read += copy_memory(buffer_slice, &mut cache_buf);
|
||||
self.insert(block_i, cache_buf);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(read)
|
||||
}
|
||||
|
||||
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
|
||||
//TODO: Write only blocks that have changed
|
||||
// println!("Cache write at {}", block);
|
||||
|
||||
self.inner.write_at(block, buffer)?;
|
||||
|
||||
let mut written = 0;
|
||||
for i in 0..buffer.len().div_ceil(BLOCK_SIZE as usize) {
|
||||
let block_i = block + i as u64;
|
||||
|
||||
let buffer_i = i * BLOCK_SIZE as usize;
|
||||
let buffer_j = cmp::min(buffer_i + BLOCK_SIZE as usize, buffer.len());
|
||||
let buffer_slice = &buffer[buffer_i..buffer_j];
|
||||
|
||||
let mut cache_buf = [0; BLOCK_SIZE as usize];
|
||||
written += copy_memory(buffer_slice, &mut cache_buf);
|
||||
self.insert(block_i, cache_buf);
|
||||
}
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
|
||||
fn size(&mut self) -> Result<u64> {
|
||||
self.inner.size()
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::os::unix::fs::FileExt;
|
||||
use std::path::Path;
|
||||
|
||||
use syscall::error::{Error, Result, EIO};
|
||||
|
||||
use crate::disk::Disk;
|
||||
use crate::BLOCK_SIZE;
|
||||
|
||||
pub struct DiskFile {
|
||||
pub file: File,
|
||||
}
|
||||
|
||||
trait ResultExt {
|
||||
type T;
|
||||
fn or_eio(self) -> Result<Self::T>;
|
||||
}
|
||||
impl<T> ResultExt for Result<T> {
|
||||
type T = T;
|
||||
fn or_eio(self) -> Result<Self::T> {
|
||||
match self {
|
||||
Ok(t) => Ok(t),
|
||||
Err(err) => {
|
||||
eprintln!("RedoxFS: IO ERROR: {err}");
|
||||
Err(Error::new(EIO))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> ResultExt for std::io::Result<T> {
|
||||
type T = T;
|
||||
fn or_eio(self) -> Result<Self::T> {
|
||||
match self {
|
||||
Ok(t) => Ok(t),
|
||||
Err(err) => {
|
||||
eprintln!("RedoxFS: IO ERROR: {err}");
|
||||
Err(Error::new(EIO))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile {
|
||||
pub fn open(path: impl AsRef<Path>) -> Result<DiskFile> {
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(path)
|
||||
.or_eio()?;
|
||||
Ok(DiskFile { file })
|
||||
}
|
||||
|
||||
pub fn create(path: impl AsRef<Path>, size: u64) -> Result<DiskFile> {
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(path)
|
||||
.or_eio()?;
|
||||
file.set_len(size).or_eio()?;
|
||||
Ok(DiskFile { file })
|
||||
}
|
||||
}
|
||||
|
||||
impl Disk for DiskFile {
|
||||
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
|
||||
self.file.read_at(buffer, block * BLOCK_SIZE).or_eio()
|
||||
}
|
||||
|
||||
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
|
||||
self.file.write_at(buffer, block * BLOCK_SIZE).or_eio()
|
||||
}
|
||||
|
||||
fn size(&mut self) -> Result<u64> {
|
||||
self.file.seek(SeekFrom::End(0)).or_eio()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<File> for DiskFile {
|
||||
fn from(file: File) -> Self {
|
||||
Self { file }
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use syscall::error::{Error, Result, EIO};
|
||||
|
||||
use crate::disk::Disk;
|
||||
use crate::BLOCK_SIZE;
|
||||
|
||||
macro_rules! try_disk {
|
||||
($expr:expr) => {
|
||||
match $expr {
|
||||
Ok(val) => val,
|
||||
Err(err) => {
|
||||
eprintln!("Disk I/O Error: {}", err);
|
||||
return Err(Error::new(EIO));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct DiskIo<T>(pub T);
|
||||
|
||||
impl<T: Read + Write + Seek> Disk for DiskIo<T> {
|
||||
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
|
||||
try_disk!(self.0.seek(SeekFrom::Start(block * BLOCK_SIZE)));
|
||||
let count = try_disk!(self.0.read(buffer));
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
|
||||
try_disk!(self.0.seek(SeekFrom::Start(block * BLOCK_SIZE)));
|
||||
let count = try_disk!(self.0.write(buffer));
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
fn size(&mut self) -> Result<u64> {
|
||||
let size = try_disk!(self.0.seek(SeekFrom::End(0)));
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
use syscall::error::{Error, Result, EIO};
|
||||
|
||||
use crate::disk::Disk;
|
||||
use crate::BLOCK_SIZE;
|
||||
|
||||
pub struct DiskMemory {
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl DiskMemory {
|
||||
pub fn new(size: u64) -> DiskMemory {
|
||||
DiskMemory {
|
||||
data: vec![0; size as usize],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Disk for DiskMemory {
|
||||
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
|
||||
let offset = (block * BLOCK_SIZE) as usize;
|
||||
let end = offset + buffer.len();
|
||||
if end > self.data.len() {
|
||||
return Err(Error::new(EIO));
|
||||
}
|
||||
buffer.copy_from_slice(&self.data[offset..end]);
|
||||
Ok(buffer.len())
|
||||
}
|
||||
|
||||
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
|
||||
let offset = (block * BLOCK_SIZE) as usize;
|
||||
let end = offset + buffer.len();
|
||||
if end > self.data.len() {
|
||||
return Err(Error::new(EIO));
|
||||
}
|
||||
self.data[offset..end].copy_from_slice(buffer);
|
||||
Ok(buffer.len())
|
||||
}
|
||||
|
||||
fn size(&mut self) -> Result<u64> {
|
||||
Ok(self.data.len() as u64)
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
use syscall::error::Result;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub use self::cache::DiskCache;
|
||||
#[cfg(feature = "std")]
|
||||
pub use self::file::DiskFile;
|
||||
#[cfg(feature = "std")]
|
||||
pub use self::io::DiskIo;
|
||||
#[cfg(feature = "std")]
|
||||
pub use self::memory::DiskMemory;
|
||||
#[cfg(feature = "std")]
|
||||
pub use self::sparse::DiskSparse;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
mod cache;
|
||||
#[cfg(feature = "std")]
|
||||
mod file;
|
||||
#[cfg(feature = "std")]
|
||||
mod io;
|
||||
#[cfg(feature = "std")]
|
||||
mod memory;
|
||||
#[cfg(feature = "std")]
|
||||
mod sparse;
|
||||
|
||||
/// A disk
|
||||
pub trait Disk {
|
||||
/// Read blocks from disk
|
||||
///
|
||||
/// # Safety
|
||||
/// Unsafe to discourage use, use filesystem wrappers instead
|
||||
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize>;
|
||||
|
||||
/// Write blocks from disk
|
||||
///
|
||||
/// # Safety
|
||||
/// Unsafe to discourage use, use filesystem wrappers instead
|
||||
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize>;
|
||||
|
||||
/// Get size of disk in bytes
|
||||
fn size(&mut self) -> Result<u64>;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::path::Path;
|
||||
use syscall::error::{Error, Result, EIO};
|
||||
|
||||
use crate::disk::Disk;
|
||||
use crate::BLOCK_SIZE;
|
||||
|
||||
macro_rules! try_disk {
|
||||
($expr:expr) => {
|
||||
match $expr {
|
||||
Ok(val) => val,
|
||||
Err(err) => {
|
||||
eprintln!("Disk I/O Error: {}", err);
|
||||
return Err(Error::new(EIO));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct DiskSparse {
|
||||
pub file: File,
|
||||
pub max_size: u64,
|
||||
}
|
||||
|
||||
impl DiskSparse {
|
||||
pub fn create<P: AsRef<Path>>(path: P, max_size: u64) -> Result<DiskSparse> {
|
||||
let file = try_disk!(OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(path));
|
||||
Ok(DiskSparse { file, max_size })
|
||||
}
|
||||
}
|
||||
|
||||
impl Disk for DiskSparse {
|
||||
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
|
||||
try_disk!(self.file.seek(SeekFrom::Start(block * BLOCK_SIZE)));
|
||||
let count = try_disk!(self.file.read(buffer));
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
|
||||
try_disk!(self.file.seek(SeekFrom::Start(block * BLOCK_SIZE)));
|
||||
let count = try_disk!(self.file.write(buffer));
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
fn size(&mut self) -> Result<u64> {
|
||||
Ok(self.max_size)
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
use crate::os::{Os, OsKey};
|
||||
|
||||
fn edit_banner(os: &impl Os) {
|
||||
os.clear_text();
|
||||
println!("--- Redox Bootloader Environment Editor ---");
|
||||
println!("ENTER twice to boot. UP/DOWN to edit lines.");
|
||||
println!("-------------------------------------------");
|
||||
}
|
||||
|
||||
pub fn edit_env(os: &impl Os, env_ptr: *mut u8, env_size: &mut usize, max_size: usize) {
|
||||
edit_banner(os);
|
||||
|
||||
let env_slice = unsafe { core::slice::from_raw_parts_mut(env_ptr, max_size) };
|
||||
// counting at line index
|
||||
let mut cursor = 0xFFF;
|
||||
// position at current line, not including LF
|
||||
let mut cursor_start = 0;
|
||||
let mut cursor_end = 0;
|
||||
let original_size = *env_size;
|
||||
|
||||
loop {
|
||||
os.set_text_position(0, 4);
|
||||
|
||||
let mut iline = 0;
|
||||
for i in 0..*env_size {
|
||||
let c = env_slice[i] as char;
|
||||
if c == '\n' {
|
||||
os.set_text_highlight(iline == cursor);
|
||||
print!(" ");
|
||||
os.set_text_highlight(false);
|
||||
print!("\n");
|
||||
iline += 1;
|
||||
if iline == cursor {
|
||||
cursor_start = i + 1;
|
||||
}
|
||||
} else {
|
||||
print!("{}", c);
|
||||
}
|
||||
if iline == cursor {
|
||||
cursor_end = i + 1;
|
||||
}
|
||||
}
|
||||
if cursor > iline {
|
||||
cursor = iline;
|
||||
// update cursors, should never hang
|
||||
continue;
|
||||
}
|
||||
os.set_text_highlight(iline == cursor);
|
||||
print!(" ");
|
||||
os.set_text_highlight(false);
|
||||
|
||||
match os.get_key() {
|
||||
OsKey::Enter => {
|
||||
if cursor_start == cursor_end {
|
||||
// blank line to boot
|
||||
break;
|
||||
}
|
||||
|
||||
if *env_size < max_size - 1 {
|
||||
if *env_size == max_size {
|
||||
continue;
|
||||
}
|
||||
for i in (cursor_end..*env_size).rev() {
|
||||
env_slice[i + 1] = env_slice[i];
|
||||
}
|
||||
env_slice[cursor_end] = b'\n';
|
||||
*env_size += 1;
|
||||
cursor += 1;
|
||||
edit_banner(os);
|
||||
}
|
||||
}
|
||||
OsKey::Backspace => {
|
||||
if cursor_end == 0 || *env_size == 0 {
|
||||
continue;
|
||||
}
|
||||
if cursor_start == cursor_end && iline > 0 {
|
||||
iline -= 1;
|
||||
}
|
||||
for i in cursor_end..*env_size {
|
||||
env_slice[i - 1] = env_slice[i];
|
||||
}
|
||||
*env_size -= 1;
|
||||
edit_banner(os);
|
||||
}
|
||||
OsKey::Up => {
|
||||
if cursor > 0 {
|
||||
cursor -= 1;
|
||||
}
|
||||
}
|
||||
OsKey::Down => {
|
||||
cursor += 1;
|
||||
}
|
||||
OsKey::Char(c) => {
|
||||
if *env_size == max_size {
|
||||
continue;
|
||||
}
|
||||
for i in (cursor_end..*env_size).rev() {
|
||||
env_slice[i + 1] = env_slice[i];
|
||||
}
|
||||
env_slice[cursor_end] = c as u8;
|
||||
*env_size += 1;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
if *env_size == 0 || env_slice[*env_size - 1] != b'\n' {
|
||||
if *env_size < max_size {
|
||||
env_slice[*env_size] = b'\n';
|
||||
*env_size += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if *env_size < original_size {
|
||||
for i in (*env_size..original_size).rev() {
|
||||
env_slice[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
println!("\nBooting...");
|
||||
}
|
||||
@@ -1,331 +0,0 @@
|
||||
use aes::Aes128;
|
||||
use alloc::{
|
||||
boxed::Box,
|
||||
collections::{BTreeMap, VecDeque},
|
||||
vec,
|
||||
};
|
||||
use syscall::error::{Error, Result, EKEYREJECTED, ENOENT, ENOKEY};
|
||||
use xts_mode::{get_tweak_default, Xts128};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use crate::{AllocEntry, AllocList, BlockData, BlockTrait, Key, KeySlot, Node, Salt, TreeList};
|
||||
use crate::{
|
||||
Allocator, BlockAddr, BlockLevel, BlockMeta, Disk, Header, Transaction, BLOCK_SIZE,
|
||||
HEADER_RING, RECORD_SIZE,
|
||||
};
|
||||
|
||||
fn compress_cache() -> Box<[u8]> {
|
||||
vec![0; lz4_flex::block::get_maximum_output_size(RECORD_SIZE as usize)].into_boxed_slice()
|
||||
}
|
||||
|
||||
/// A file system
|
||||
pub struct FileSystem<D: Disk> {
|
||||
//TODO: make private
|
||||
pub disk: D,
|
||||
//TODO: make private
|
||||
pub block: u64,
|
||||
//TODO: make private
|
||||
pub header: Header,
|
||||
pub(crate) allocator: Allocator,
|
||||
pub(crate) cipher_opt: Option<Xts128<Aes128>>,
|
||||
pub(crate) compress_cache: Box<[u8]>,
|
||||
pub node_usages: BTreeMap<u32, u64>,
|
||||
}
|
||||
|
||||
impl<D: Disk> FileSystem<D> {
|
||||
/// Open a file system on a disk
|
||||
pub fn open(
|
||||
mut disk: D,
|
||||
password_opt: Option<&[u8]>,
|
||||
block_opt: Option<u64>,
|
||||
cleanup: bool,
|
||||
) -> Result<Self> {
|
||||
for ring_block in block_opt.map_or(0..65536, |x| x..x + 1) {
|
||||
let mut header = Header::default();
|
||||
unsafe { disk.read_at(ring_block, &mut header)? };
|
||||
|
||||
// Skip invalid headers
|
||||
if !header.valid() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let block = ring_block - (header.generation() % HEADER_RING);
|
||||
for i in 0..HEADER_RING {
|
||||
let mut other_header = Header::default();
|
||||
unsafe { disk.read_at(block + i, &mut other_header)? };
|
||||
|
||||
// Skip invalid headers
|
||||
if !other_header.valid() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this is a newer header, use it
|
||||
if other_header.generation() > header.generation() {
|
||||
header = other_header;
|
||||
}
|
||||
}
|
||||
|
||||
let cipher_opt = match password_opt {
|
||||
Some(password) => {
|
||||
if !header.encrypted() {
|
||||
// Header not encrypted but password provided
|
||||
return Err(Error::new(EKEYREJECTED));
|
||||
}
|
||||
match header.cipher(password) {
|
||||
Some(cipher) => Some(cipher),
|
||||
None => {
|
||||
// Header encrypted with a different password
|
||||
return Err(Error::new(ENOKEY));
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if header.encrypted() {
|
||||
// Header encrypted but no password provided
|
||||
return Err(Error::new(ENOKEY));
|
||||
}
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let mut fs = FileSystem {
|
||||
disk,
|
||||
block,
|
||||
header,
|
||||
allocator: Allocator::default(),
|
||||
cipher_opt,
|
||||
compress_cache: compress_cache(),
|
||||
node_usages: BTreeMap::new(),
|
||||
};
|
||||
|
||||
unsafe { fs.reset_allocator()? };
|
||||
|
||||
if cleanup {
|
||||
fs.cleanup()?
|
||||
}
|
||||
|
||||
return Ok(fs);
|
||||
}
|
||||
|
||||
Err(Error::new(ENOENT))
|
||||
}
|
||||
|
||||
/// Create a file system on a disk
|
||||
#[cfg(feature = "std")]
|
||||
pub fn create(
|
||||
disk: D,
|
||||
password_opt: Option<&[u8]>,
|
||||
ctime: u64,
|
||||
ctime_nsec: u32,
|
||||
) -> Result<Self> {
|
||||
Self::create_reserved(disk, password_opt, &[], ctime, ctime_nsec)
|
||||
}
|
||||
|
||||
/// Create a file system on a disk, with reserved data at the beginning
|
||||
/// Reserved data will be zero padded up to the nearest block
|
||||
/// We need to pass ctime and ctime_nsec in order to initialize the unix timestamps
|
||||
#[cfg(feature = "std")]
|
||||
pub fn create_reserved(
|
||||
mut disk: D,
|
||||
password_opt: Option<&[u8]>,
|
||||
reserved: &[u8],
|
||||
ctime: u64,
|
||||
ctime_nsec: u32,
|
||||
) -> Result<Self> {
|
||||
let disk_size = disk.size()?;
|
||||
let disk_blocks = disk_size / BLOCK_SIZE;
|
||||
let block_offset = (reserved.len() as u64).div_ceil(BLOCK_SIZE);
|
||||
if disk_blocks < (block_offset + HEADER_RING + 4) {
|
||||
return Err(Error::new(syscall::error::ENOSPC));
|
||||
}
|
||||
let fs_blocks = disk_blocks - block_offset;
|
||||
|
||||
// Fill reserved data, pad with zeroes
|
||||
for block in 0..block_offset as usize {
|
||||
let mut data = [0; BLOCK_SIZE as usize];
|
||||
|
||||
let mut i = 0;
|
||||
while i < data.len() && block * BLOCK_SIZE as usize + i < reserved.len() {
|
||||
data[i] = reserved[block * BLOCK_SIZE as usize + i];
|
||||
i += 1;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
disk.write_at(block as u64, &data)?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut header = Header::new(fs_blocks * BLOCK_SIZE);
|
||||
|
||||
let cipher_opt = match password_opt {
|
||||
Some(password) => {
|
||||
//TODO: handle errors
|
||||
header.key_slots[0] = KeySlot::new(
|
||||
password,
|
||||
Salt::new().unwrap(),
|
||||
(Key::new().unwrap(), Key::new().unwrap()),
|
||||
)
|
||||
.unwrap();
|
||||
Some(header.key_slots[0].cipher(password).unwrap())
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let mut fs = FileSystem {
|
||||
disk,
|
||||
block: block_offset,
|
||||
header,
|
||||
allocator: Allocator::default(),
|
||||
cipher_opt,
|
||||
compress_cache: compress_cache(),
|
||||
node_usages: BTreeMap::new(),
|
||||
};
|
||||
|
||||
// Write header generation zero
|
||||
let count = unsafe { fs.disk.write_at(fs.block, &fs.header)? };
|
||||
if count != core::mem::size_of_val(&fs.header) {
|
||||
// Wrote wrong number of bytes
|
||||
#[cfg(feature = "log")]
|
||||
log::error!("CREATE: WRONG NUMBER OF BYTES");
|
||||
return Err(Error::new(syscall::error::EIO));
|
||||
}
|
||||
|
||||
// Set tree and alloc pointers and write header generation one
|
||||
fs.tx(|tx| unsafe {
|
||||
let tree = BlockData::new(
|
||||
BlockAddr::new(HEADER_RING + 1, BlockMeta::default()),
|
||||
TreeList::empty(BlockLevel::default()).unwrap(),
|
||||
);
|
||||
|
||||
let mut alloc = BlockData::new(
|
||||
BlockAddr::new(HEADER_RING + 2, BlockMeta::default()),
|
||||
AllocList::empty(BlockLevel::default()).unwrap(),
|
||||
);
|
||||
|
||||
let alloc_free = fs_blocks - (HEADER_RING + 4);
|
||||
alloc.data_mut().entries[0] = AllocEntry::new(HEADER_RING + 4, alloc_free as i64);
|
||||
|
||||
tx.header.tree = tx.write_block(tree)?;
|
||||
tx.header.alloc = tx.write_block(alloc)?;
|
||||
tx.header_changed = true;
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
unsafe {
|
||||
fs.reset_allocator()?;
|
||||
}
|
||||
|
||||
fs.tx(|tx| unsafe {
|
||||
let mut root = BlockData::new(
|
||||
BlockAddr::new(HEADER_RING + 3, BlockMeta::default()),
|
||||
Node::new(Node::MODE_DIR | 0o755, 0, 0, ctime, ctime_nsec),
|
||||
);
|
||||
root.data_mut().set_links(1);
|
||||
let root_ptr = tx.write_block(root)?;
|
||||
assert_eq!(tx.insert_tree(root_ptr)?.id(), 1);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
fs.cleanup()?;
|
||||
|
||||
Ok(fs)
|
||||
}
|
||||
|
||||
/// Release unused nodes and squash allocation log, happens on mount (with cleanup) and unmount
|
||||
pub fn cleanup(&mut self) -> Result<()> {
|
||||
let mut tx = Transaction::new(self);
|
||||
tx.release_unused_nodes()?;
|
||||
tx.commit(true)
|
||||
}
|
||||
|
||||
/// start a filesystem transaction, required for making any changes
|
||||
pub fn tx<F: FnOnce(&mut Transaction<D>) -> Result<T>, T>(&mut self, f: F) -> Result<T> {
|
||||
let mut tx = Transaction::new(self);
|
||||
let t = f(&mut tx)?;
|
||||
tx.commit(false)?;
|
||||
Ok(t)
|
||||
}
|
||||
|
||||
pub fn allocator(&self) -> &Allocator {
|
||||
&self.allocator
|
||||
}
|
||||
|
||||
/// Unsafe as it can corrupt the filesystem
|
||||
pub unsafe fn allocator_mut(&mut self) -> &mut Allocator {
|
||||
&mut self.allocator
|
||||
}
|
||||
|
||||
/// Reset allocator to state stored on disk
|
||||
///
|
||||
/// # Safety
|
||||
/// Unsafe, it must only be called when opening the filesystem
|
||||
unsafe fn reset_allocator(&mut self) -> Result<()> {
|
||||
self.allocator = Allocator::default();
|
||||
|
||||
// To avoid having to update all prior alloc blocks, there is only a previous pointer
|
||||
// This means we need to roll back all allocations. Currently we do this by reading the
|
||||
// alloc log into a buffer to reverse it.
|
||||
let mut allocs = VecDeque::new();
|
||||
self.tx(|tx| {
|
||||
let mut alloc_ptr = tx.header.alloc;
|
||||
while !alloc_ptr.is_null() {
|
||||
let alloc = tx.read_block(alloc_ptr)?;
|
||||
alloc_ptr = alloc.data().prev;
|
||||
allocs.push_front(alloc);
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
for alloc in allocs {
|
||||
for entry in alloc.data().entries.iter() {
|
||||
let index = entry.index();
|
||||
let count = entry.count();
|
||||
if count < 0 {
|
||||
for i in 0..-count {
|
||||
//TODO: replace assert with error?
|
||||
let addr = BlockAddr::new(index + i as u64, BlockMeta::default());
|
||||
assert_eq!(self.allocator.allocate_exact(addr), Some(addr));
|
||||
}
|
||||
} else {
|
||||
for i in 0..count {
|
||||
let addr = BlockAddr::new(index + i as u64, BlockMeta::default());
|
||||
self.allocator.deallocate(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn decrypt(&mut self, data: &mut [u8], addr: BlockAddr) -> bool {
|
||||
if let Some(ref cipher) = self.cipher_opt {
|
||||
cipher.decrypt_area(
|
||||
data,
|
||||
BLOCK_SIZE as usize,
|
||||
addr.index().into(),
|
||||
get_tweak_default,
|
||||
);
|
||||
true
|
||||
} else {
|
||||
// Do nothing if encryption is disabled
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn encrypt(&mut self, data: &mut [u8], addr: BlockAddr) -> bool {
|
||||
if let Some(ref cipher) = self.cipher_opt {
|
||||
cipher.encrypt_area(
|
||||
data,
|
||||
BLOCK_SIZE as usize,
|
||||
addr.index().into(),
|
||||
get_tweak_default,
|
||||
);
|
||||
true
|
||||
} else {
|
||||
// Do nothing if encryption is disabled
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
-242
@@ -1,242 +0,0 @@
|
||||
use core::ops::{Deref, DerefMut};
|
||||
use core::{fmt, mem, slice};
|
||||
use endian_num::Le;
|
||||
|
||||
use aes::Aes128;
|
||||
use xts_mode::{get_tweak_default, Xts128};
|
||||
|
||||
use crate::{AllocList, BlockPtr, KeySlot, ReleaseList, Tree, BLOCK_SIZE, SIGNATURE, VERSION};
|
||||
|
||||
pub const HEADER_RING: u64 = 256;
|
||||
|
||||
/// The header of the filesystem
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C, packed)]
|
||||
pub struct Header {
|
||||
/// Signature, should be SIGNATURE
|
||||
pub signature: [u8; 8],
|
||||
/// Version, should be VERSION
|
||||
pub version: Le<u64>,
|
||||
/// Disk ID, a 128-bit unique identifier
|
||||
pub uuid: [u8; 16],
|
||||
/// Disk size, in number of BLOCK_SIZE sectors
|
||||
pub size: Le<u64>,
|
||||
/// Generation of header
|
||||
pub generation: Le<u64>,
|
||||
/// Block of first tree node
|
||||
pub tree: BlockPtr<Tree>,
|
||||
/// Block of last alloc node
|
||||
pub alloc: BlockPtr<AllocList>,
|
||||
/// Key slots
|
||||
pub key_slots: [KeySlot; 64],
|
||||
/// Nodes pending release, may be null
|
||||
pub release: BlockPtr<ReleaseList>,
|
||||
/// Padding
|
||||
pub padding: [u8; BLOCK_SIZE as usize - 3192],
|
||||
/// encrypted hash of header data without hash, set to hash and padded if disk is not encrypted
|
||||
pub encrypted_hash: [u8; 16],
|
||||
/// hash of header data without hash
|
||||
pub hash: Le<u64>,
|
||||
}
|
||||
|
||||
impl Header {
|
||||
#[cfg(feature = "std")]
|
||||
pub fn new(size: u64) -> Header {
|
||||
let uuid = uuid::Uuid::new_v4();
|
||||
let mut header = Header {
|
||||
signature: *SIGNATURE,
|
||||
version: VERSION.into(),
|
||||
uuid: *uuid.as_bytes(),
|
||||
size: size.into(),
|
||||
..Default::default()
|
||||
};
|
||||
header.update_hash(None);
|
||||
header
|
||||
}
|
||||
|
||||
pub fn valid(&self) -> bool {
|
||||
if &self.signature != SIGNATURE {
|
||||
// Signature does not match
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.version.to_ne() != VERSION {
|
||||
// Version does not match
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.hash.to_ne() != self.create_hash() {
|
||||
// Hash does not match
|
||||
return false;
|
||||
}
|
||||
|
||||
// All tests passed, header is valid
|
||||
true
|
||||
}
|
||||
|
||||
pub fn uuid(&self) -> [u8; 16] {
|
||||
self.uuid
|
||||
}
|
||||
|
||||
pub fn size(&self) -> u64 {
|
||||
self.size.to_ne()
|
||||
}
|
||||
|
||||
pub fn generation(&self) -> u64 {
|
||||
self.generation.to_ne()
|
||||
}
|
||||
|
||||
fn create_hash(&self) -> u64 {
|
||||
// Calculate part of header to hash (everything before the hashes)
|
||||
let end = mem::size_of_val(self)
|
||||
- mem::size_of_val(&{ self.hash })
|
||||
- mem::size_of_val(&{ self.encrypted_hash });
|
||||
seahash::hash(&self[..end])
|
||||
}
|
||||
|
||||
fn create_encrypted_hash(&self, cipher_opt: Option<&Xts128<Aes128>>) -> [u8; 16] {
|
||||
let mut encrypted_hash = [0; 16];
|
||||
for (i, b) in self.hash.to_le_bytes().iter().enumerate() {
|
||||
encrypted_hash[i] = *b;
|
||||
}
|
||||
if let Some(cipher) = cipher_opt {
|
||||
let mut block = aes::Block::from(encrypted_hash);
|
||||
cipher.encrypt_area(
|
||||
&mut block,
|
||||
BLOCK_SIZE as usize,
|
||||
self.generation().into(),
|
||||
get_tweak_default,
|
||||
);
|
||||
encrypted_hash = block.into();
|
||||
}
|
||||
encrypted_hash
|
||||
}
|
||||
|
||||
pub fn encrypted(&self) -> bool {
|
||||
(self.encrypted_hash) != self.create_encrypted_hash(None)
|
||||
}
|
||||
|
||||
pub fn cipher(&self, password: &[u8]) -> Option<Xts128<Aes128>> {
|
||||
let hash = self.create_encrypted_hash(None);
|
||||
for slot in self.key_slots.iter() {
|
||||
//TODO: handle errors
|
||||
let cipher = slot.cipher(password).unwrap();
|
||||
let mut block = aes::Block::from(self.encrypted_hash);
|
||||
cipher.decrypt_area(
|
||||
&mut block,
|
||||
BLOCK_SIZE as usize,
|
||||
self.generation().into(),
|
||||
get_tweak_default,
|
||||
);
|
||||
if block == aes::Block::from(hash) {
|
||||
return Some(cipher);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn update_hash(&mut self, cipher_opt: Option<&Xts128<Aes128>>) {
|
||||
self.hash = self.create_hash().into();
|
||||
// Make sure to do this second, it relies on the hash being up to date
|
||||
self.encrypted_hash = self.create_encrypted_hash(cipher_opt);
|
||||
}
|
||||
|
||||
pub fn update(&mut self, cipher_opt: Option<&Xts128<Aes128>>) -> u64 {
|
||||
let mut generation = self.generation();
|
||||
generation += 1;
|
||||
self.generation = generation.into();
|
||||
self.update_hash(cipher_opt);
|
||||
generation
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Header {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
signature: [0; 8],
|
||||
version: 0.into(),
|
||||
uuid: [0; 16],
|
||||
size: 0.into(),
|
||||
generation: 0.into(),
|
||||
tree: BlockPtr::<Tree>::default(),
|
||||
alloc: BlockPtr::<AllocList>::default(),
|
||||
key_slots: [KeySlot::default(); 64],
|
||||
release: BlockPtr::<ReleaseList>::default(),
|
||||
padding: [0; BLOCK_SIZE as usize - 3192],
|
||||
encrypted_hash: [0; 16],
|
||||
hash: 0.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Header {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let signature = self.signature;
|
||||
let version = self.version;
|
||||
let uuid = self.uuid;
|
||||
let size = self.size;
|
||||
let generation = self.generation;
|
||||
let tree = self.tree;
|
||||
let alloc = self.alloc;
|
||||
let release = self.release;
|
||||
let hash = self.hash;
|
||||
f.debug_struct("Header")
|
||||
.field("signature", &signature)
|
||||
.field("version", &version)
|
||||
.field("uuid", &uuid)
|
||||
.field("size", &size)
|
||||
.field("generation", &generation)
|
||||
.field("tree", &tree)
|
||||
.field("alloc", &alloc)
|
||||
.field("release", &release)
|
||||
.field("hash", &hash)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Header {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts(self as *const Header as *const u8, mem::size_of::<Header>())
|
||||
as &[u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Header {
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts_mut(self as *mut Header as *mut u8, mem::size_of::<Header>())
|
||||
as &mut [u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_not_valid_test() {
|
||||
assert_eq!(Header::default().valid(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_size_test() {
|
||||
assert_eq!(mem::size_of::<Header>(), BLOCK_SIZE as usize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_hash_test() {
|
||||
let mut header = Header::default();
|
||||
assert_eq!(header.create_hash(), 0xe81ffcb86026ff96);
|
||||
header.update_hash(None);
|
||||
assert_eq!(header.hash.to_ne(), 0xe81ffcb86026ff96);
|
||||
assert_eq!(
|
||||
header.encrypted_hash,
|
||||
[0x96, 0xff, 0x26, 0x60, 0xb8, 0xfc, 0x1f, 0xe8, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[test]
|
||||
fn header_valid_test() {
|
||||
assert_eq!(Header::new(0).valid(), true);
|
||||
}
|
||||
-691
@@ -1,691 +0,0 @@
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use core::{fmt, mem, ops, slice};
|
||||
use endian_num::Le;
|
||||
use syscall::error::{Error, Result, EEXIST, EIO};
|
||||
|
||||
use crate::{
|
||||
BlockLevel, BlockPtr, BlockRaw, BlockTrait, DirEntry, DirList, BLOCK_SIZE, RECORD_LEVEL,
|
||||
};
|
||||
|
||||
pub const HTREE_IDX_ENTRIES: usize = BLOCK_SIZE as usize / mem::size_of::<HTreePtr<BlockRaw>>();
|
||||
const HTREE_IDX_PADDING: usize =
|
||||
BLOCK_SIZE as usize - mem::size_of::<[HTreePtr<BlockRaw>; HTREE_IDX_ENTRIES]>();
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
#[repr(C, packed)]
|
||||
pub struct HTreeHash(Le<u32>);
|
||||
|
||||
impl HTreeHash {
|
||||
// Create a MAX constant populated iwth the maximum value of Le<u32> minus 1
|
||||
pub const MAX: HTreeHash = HTreeHash(Le(u32::MAX - 1));
|
||||
|
||||
#[cfg(not(test))]
|
||||
pub fn from_name(name: &str) -> Self {
|
||||
let hash = seahash::hash(name.as_bytes()) as u32;
|
||||
// Don't allow the default hash value to be calculated for a real hash
|
||||
if hash == u32::MAX {
|
||||
return Self::MAX;
|
||||
}
|
||||
Self(hash.into())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn from_name(name: &str) -> Self {
|
||||
// Allow overriding the hashing function to something easily controled for testing.
|
||||
let hash = if let Some(pos) = name.rfind("__") {
|
||||
let number_str = &name[pos + 2..];
|
||||
number_str.parse::<u32>().unwrap()
|
||||
} else {
|
||||
seahash::hash(name.as_bytes()) as u32
|
||||
};
|
||||
|
||||
// Don't allow the default hash value to be calculated for a real hash
|
||||
if hash == u32::MAX {
|
||||
return Self::MAX;
|
||||
}
|
||||
Self(hash.into())
|
||||
}
|
||||
|
||||
/// Returns the maximum of two `HTreeHash` values, ignoring the default hash value.
|
||||
pub fn max_ignoring_default(&self, other: Self) -> Self {
|
||||
let default = HTreeHash::default();
|
||||
if *self == default {
|
||||
return other;
|
||||
}
|
||||
if other == default {
|
||||
return *self;
|
||||
}
|
||||
if *self > other {
|
||||
*self
|
||||
} else {
|
||||
other
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_max(dir_list: &DirList) -> Option<HTreeHash> {
|
||||
let mut max_hash = HTreeHash::default();
|
||||
dir_list.for_each_entry(|_ptr_bytes, name_bytes| {
|
||||
let name = String::from_utf8_lossy(name_bytes);
|
||||
let hash = HTreeHash::from_name(name.as_ref());
|
||||
max_hash = max_hash.max_ignoring_default(hash);
|
||||
});
|
||||
|
||||
if max_hash == HTreeHash::default() {
|
||||
None
|
||||
} else {
|
||||
Some(max_hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HTreeHash {
|
||||
/// The default hash value is the maximum possible value to push it to the end of the list when sorting.
|
||||
fn default() -> Self {
|
||||
Self(u32::MAX.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct HTreePtr<T> {
|
||||
pub htree_hash: HTreeHash,
|
||||
pub ptr: BlockPtr<T>,
|
||||
}
|
||||
|
||||
impl<T> HTreePtr<T> {
|
||||
pub fn new(htree_hash: HTreeHash, ptr: BlockPtr<T>) -> Self {
|
||||
Self { htree_hash, ptr }
|
||||
}
|
||||
|
||||
/// Cast HTreePtr to another type
|
||||
///
|
||||
/// # Safety
|
||||
/// Unsafe because it can be used to transmute types
|
||||
pub unsafe fn cast<U>(self) -> HTreePtr<U> {
|
||||
HTreePtr {
|
||||
htree_hash: self.htree_hash,
|
||||
ptr: self.ptr.cast(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> HTreePtr<T> {
|
||||
pub fn is_null(&self) -> bool {
|
||||
self.ptr.is_null()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for HTreePtr<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for HTreePtr<T> {}
|
||||
|
||||
impl<T> Default for HTreePtr<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
htree_hash: HTreeHash::default(),
|
||||
ptr: BlockPtr::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for HTreePtr<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let htree_hash = self.htree_hash;
|
||||
let ptr = self.ptr;
|
||||
f.debug_struct("BlockPtr")
|
||||
.field("htree_hash", &htree_hash)
|
||||
.field("ptr", &ptr)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct HTreeNode<T> {
|
||||
pub ptrs: [HTreePtr<T>; HTREE_IDX_ENTRIES],
|
||||
padding: [u8; HTREE_IDX_PADDING],
|
||||
}
|
||||
|
||||
impl<T> HTreeNode<T> {
|
||||
pub fn find_max_htree_hash(&self) -> Option<HTreeHash> {
|
||||
let mut hash = HTreeHash::default();
|
||||
for entry in self.ptrs.iter() {
|
||||
hash = hash.max_ignoring_default(entry.htree_hash);
|
||||
}
|
||||
|
||||
if hash != HTreeHash::default() {
|
||||
Some(hash)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_ptrs_for_read(
|
||||
&self,
|
||||
htree_hash: HTreeHash,
|
||||
) -> impl Iterator<Item = (usize, &HTreePtr<T>)> {
|
||||
let mut last_hash = HTreeHash(0.into());
|
||||
self.ptrs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(move |(_idx, entry)| entry.htree_hash >= htree_hash)
|
||||
.take_while(move |(_idx, entry)| {
|
||||
let should_take = !entry.is_null() && last_hash <= htree_hash;
|
||||
last_hash = entry.htree_hash;
|
||||
should_take
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> BlockTrait for HTreeNode<T> {
|
||||
fn empty(level: BlockLevel) -> Option<Self> {
|
||||
if level.0 <= RECORD_LEVEL {
|
||||
Some(Self {
|
||||
ptrs: [HTreePtr::default(); HTREE_IDX_ENTRIES],
|
||||
padding: [0; HTREE_IDX_PADDING],
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ops::Deref for HTreeNode<T> {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts(
|
||||
self as *const HTreeNode<T> as *const u8,
|
||||
mem::size_of::<HTreeNode<T>>(),
|
||||
) as &[u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ops::DerefMut for HTreeNode<T> {
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts_mut(
|
||||
self as *mut HTreeNode<T> as *mut u8,
|
||||
mem::size_of::<HTreeNode<T>>(),
|
||||
) as &mut [u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_inner_node<T>(
|
||||
parent: &mut HTreeNode<T>,
|
||||
new_ptr: HTreePtr<T>,
|
||||
) -> Result<Option<(HTreeHash, HTreeNode<T>)>> {
|
||||
// Update the input htree parameters in place
|
||||
for ptr in parent.ptrs.iter_mut() {
|
||||
if ptr.is_null() {
|
||||
*ptr = new_ptr;
|
||||
parent.ptrs.sort_by(|a, b| a.htree_hash.cmp(&b.htree_hash));
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
// The parent is full. We need to split it into two by half, ordered by the htree hash.
|
||||
let mut all_ptrs = Vec::with_capacity(parent.ptrs.len() + 1);
|
||||
for ptr in parent.ptrs.iter() {
|
||||
all_ptrs.push(*ptr);
|
||||
}
|
||||
all_ptrs.push(new_ptr);
|
||||
all_ptrs.sort_by(|a, b| a.htree_hash.cmp(&b.htree_hash));
|
||||
let half_idx = all_ptrs.len() / 2;
|
||||
|
||||
// Find if there are duplicate name hashes on the boundary of where we want to split
|
||||
let half_name_hash = all_ptrs[half_idx].htree_hash;
|
||||
let mut first_idx = half_idx;
|
||||
let mut last_idx = half_idx;
|
||||
for (i, ptr) in all_ptrs.iter().enumerate() {
|
||||
if ptr.htree_hash == half_name_hash {
|
||||
if i < first_idx {
|
||||
first_idx = i;
|
||||
}
|
||||
if i > last_idx {
|
||||
last_idx = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Split the entries_with_name_hash list at the index that minimizes the number of entries in each list while keeping the duplicate name hashes together
|
||||
let split = if (half_idx - first_idx) < (last_idx - half_idx) {
|
||||
first_idx
|
||||
} else {
|
||||
last_idx
|
||||
};
|
||||
|
||||
let (ptrs1, ptrs2) = all_ptrs.split_at(split);
|
||||
|
||||
// Update the existing parent with the first half of the entries
|
||||
let mut htree_idx1 = HTreeNode::empty(BlockLevel::default()).ok_or(Error::new(EIO))?;
|
||||
htree_idx1.ptrs[..ptrs1.len()].copy_from_slice(ptrs1);
|
||||
let _ = mem::replace(parent, htree_idx1);
|
||||
|
||||
// Return the second half as a new sibling parent
|
||||
let mut htree_idx2 = HTreeNode::empty(BlockLevel::default()).ok_or(Error::new(EIO))?;
|
||||
htree_idx2.ptrs[..ptrs2.len()].copy_from_slice(ptrs2);
|
||||
|
||||
let htree_hash2 = ptrs2[ptrs2.len() - 1].htree_hash;
|
||||
Ok(Some((htree_hash2, htree_idx2)))
|
||||
}
|
||||
|
||||
pub fn add_dir_entry(
|
||||
dir_list: &mut DirList,
|
||||
htree_hash: &mut HTreeHash,
|
||||
dirent: DirEntry,
|
||||
) -> Result<Option<(HTreeHash, DirList)>> {
|
||||
if let Some(name) = dirent.name() {
|
||||
if dir_list.find_entry(name).is_some() {
|
||||
return Err(Error::new(EEXIST));
|
||||
}
|
||||
}
|
||||
|
||||
// Update the input htree parameters in place
|
||||
let name = dirent.name().ok_or(Error::new(EIO))?;
|
||||
if dir_list.append(&dirent) {
|
||||
*htree_hash = HTreeHash::from_name(name).max_ignoring_default(*htree_hash);
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// The dir_list is full. We need to split it into two dir_lists by half, ordered by the name hash.
|
||||
let mut entries_with_name_hash = Vec::with_capacity(dir_list.entry_count() + 1);
|
||||
for entry in dir_list.entries() {
|
||||
entries_with_name_hash.push((
|
||||
HTreeHash::from_name(entry.name().ok_or(Error::new(EIO))?),
|
||||
entry,
|
||||
));
|
||||
}
|
||||
entries_with_name_hash.push((HTreeHash::from_name(dirent.name().unwrap()), dirent));
|
||||
entries_with_name_hash.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
let half = entries_with_name_hash.len() / 2;
|
||||
let half_name_hash = entries_with_name_hash[half].0;
|
||||
|
||||
// Find if there are duplicate name hashes on the boundary of where we want to split
|
||||
let mut first_idx = half;
|
||||
let mut last_idx = half;
|
||||
for (i, (name_hash, _)) in entries_with_name_hash.iter().enumerate() {
|
||||
if *name_hash == half_name_hash {
|
||||
if i < first_idx {
|
||||
first_idx = i;
|
||||
}
|
||||
if i > last_idx {
|
||||
last_idx = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
last_idx += 1;
|
||||
|
||||
// Split the entries_with_name_hash list at the index that minimizes the number of entries in each list while keeping the duplicate name hashes together
|
||||
let split = if (half - first_idx) < (last_idx - half) {
|
||||
first_idx
|
||||
} else {
|
||||
last_idx
|
||||
};
|
||||
let split = split.max(1);
|
||||
|
||||
let sorted_entries = entries_with_name_hash
|
||||
.iter()
|
||||
.map(|(_, entry)| *entry)
|
||||
.collect::<Vec<DirEntry>>();
|
||||
|
||||
let (entries1, entries2) = sorted_entries.split_at(split);
|
||||
|
||||
// Update the existing dir_list with the first half of the entries
|
||||
let mut new_dir_list = DirList::empty(BlockLevel::default()).ok_or(Error::new(EIO))?;
|
||||
for entry in entries1.iter() {
|
||||
new_dir_list.append(entry);
|
||||
}
|
||||
let _ = mem::replace(dir_list, new_dir_list);
|
||||
*htree_hash = entries_with_name_hash[entries1.len() - 1].0;
|
||||
|
||||
// Return the second half of the entries as a new dir_list
|
||||
let mut new_dir_list = DirList::empty(BlockLevel::default()).ok_or(Error::new(EIO))?;
|
||||
for entry in entries2.iter() {
|
||||
new_dir_list.append(entry);
|
||||
}
|
||||
let new_name_hash = entries_with_name_hash[entries_with_name_hash.len() - 1].0;
|
||||
Ok(Some((new_name_hash, new_dir_list)))
|
||||
}
|
||||
|
||||
//
|
||||
// MARK: Unit Tests
|
||||
//
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::alloc::string::ToString;
|
||||
use crate::TreePtr;
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
|
||||
#[test]
|
||||
fn htree_ptr_size_test() {
|
||||
assert_eq!(mem::size_of::<HTreePtr<BlockRaw>>(), 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn htree_node_size_test() {
|
||||
assert_eq!(mem::size_of::<HTreeNode<BlockRaw>>(), BLOCK_SIZE as usize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn htree_hash_max_test() {
|
||||
assert_eq!(HTreeHash::MAX, HTreeHash((u32::MAX - 1).into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn htree_hash_max_ignoring_default_test() {
|
||||
let default = HTreeHash::default();
|
||||
let hash1 = HTreeHash(0.into());
|
||||
let hash2 = HTreeHash(1.into());
|
||||
|
||||
assert_eq!(hash1.max_ignoring_default(default), hash1);
|
||||
assert_eq!(default.max_ignoring_default(hash1), hash1);
|
||||
assert_eq!(hash1.max_ignoring_default(hash2), hash2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn htree_node_find_max_htree_hash() {
|
||||
// In practice, the HTreeHash values should always be in sorted order
|
||||
let mut htree_node: HTreeNode<String> = HTreeNode::empty(BlockLevel::default()).unwrap();
|
||||
htree_node.ptrs[0] = HTreePtr::new(HTreeHash(0.into()), BlockPtr::marker(0));
|
||||
htree_node.ptrs[1] = HTreePtr::new(HTreeHash(1.into()), BlockPtr::marker(0));
|
||||
htree_node.ptrs[2] = HTreePtr::new(HTreeHash(2.into()), BlockPtr::marker(0));
|
||||
|
||||
assert_eq!(
|
||||
htree_node.find_max_htree_hash().unwrap(),
|
||||
HTreeHash(2.into())
|
||||
);
|
||||
|
||||
htree_node.ptrs[2] = HTreePtr::default();
|
||||
assert_eq!(
|
||||
htree_node.find_max_htree_hash().unwrap(),
|
||||
HTreeHash(1.into())
|
||||
);
|
||||
|
||||
htree_node.ptrs[1] = HTreePtr::default();
|
||||
assert_eq!(
|
||||
htree_node.find_max_htree_hash().unwrap(),
|
||||
HTreeHash(0.into())
|
||||
);
|
||||
|
||||
htree_node.ptrs[0] = HTreePtr::default();
|
||||
assert!(htree_node.find_max_htree_hash().is_none());
|
||||
|
||||
// For thoroughness, test with HTreeHash out of order
|
||||
htree_node.ptrs[2] = HTreePtr::new(HTreeHash(4.into()), BlockPtr::marker(0));
|
||||
htree_node.ptrs[4] = HTreePtr::new(HTreeHash(6.into()), BlockPtr::marker(0));
|
||||
htree_node.ptrs[6] = HTreePtr::new(HTreeHash(2.into()), BlockPtr::marker(0));
|
||||
assert_eq!(
|
||||
htree_node.find_max_htree_hash().unwrap(),
|
||||
HTreeHash(6.into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn htree_node_find_for_read() {
|
||||
let mut htree_node: HTreeNode<String> = HTreeNode::empty(BlockLevel::default()).unwrap();
|
||||
htree_node.ptrs[0] = HTreePtr::new(HTreeHash(0.into()), BlockPtr::marker(0));
|
||||
htree_node.ptrs[1] = HTreePtr::new(HTreeHash(1.into()), BlockPtr::marker(0));
|
||||
htree_node.ptrs[2] = HTreePtr::new(HTreeHash(2.into()), BlockPtr::marker(0));
|
||||
htree_node.ptrs[3] = HTreePtr::new(HTreeHash(2.into()), BlockPtr::marker(0));
|
||||
htree_node.ptrs[4] = HTreePtr::new(HTreeHash(3.into()), BlockPtr::marker(0));
|
||||
htree_node.ptrs[5] = HTreePtr::new(HTreeHash(3.into()), BlockPtr::marker(0));
|
||||
htree_node.ptrs[6] = HTreePtr::new(HTreeHash(5.into()), BlockPtr::marker(0));
|
||||
htree_node.ptrs[7] = HTreePtr::new(HTreeHash(6.into()), BlockPtr::marker(0));
|
||||
|
||||
// Confirm that a hash that does not exist, but is less than an existing hash results in a single entry
|
||||
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(4.into()));
|
||||
let mut val = iter.next().unwrap();
|
||||
assert_eq!(val.0, 6);
|
||||
assert_eq!(val.1.htree_hash, HTreeHash(5.into()));
|
||||
assert!(iter.next().is_none());
|
||||
|
||||
// Confirm that a hash that equals an existing hash results in the match and one following entry
|
||||
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(1.into()));
|
||||
val = iter.next().unwrap();
|
||||
assert_eq!(val.0, 1);
|
||||
assert_eq!(val.1.htree_hash, HTreeHash(1.into()));
|
||||
val = iter.next().unwrap();
|
||||
assert_eq!(val.0, 2);
|
||||
assert_eq!(val.1.htree_hash, HTreeHash(2.into()));
|
||||
assert!(iter.next().is_none());
|
||||
|
||||
// Confirm that multiple exact hash matches are all returned plus the next entry
|
||||
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(2.into()));
|
||||
val = iter.next().unwrap();
|
||||
assert_eq!(val.0, 2);
|
||||
assert_eq!(val.1.htree_hash, HTreeHash(2.into()));
|
||||
val = iter.next().unwrap();
|
||||
assert_eq!(val.0, 3);
|
||||
assert_eq!(val.1.htree_hash, HTreeHash(2.into()));
|
||||
val = iter.next().unwrap();
|
||||
assert_eq!(val.0, 4);
|
||||
assert_eq!(val.1.htree_hash, HTreeHash(3.into()));
|
||||
assert!(iter.next().is_none());
|
||||
|
||||
// Confirm that if the last entry matches and the next entry is null, only the match is returned
|
||||
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(6.into()));
|
||||
val = iter.next().unwrap();
|
||||
assert_eq!(val.0, 7);
|
||||
assert_eq!(val.1.htree_hash, HTreeHash(6.into()));
|
||||
assert!(iter.next().is_none());
|
||||
|
||||
// Confirm that if a hash that is larger than any existing entries, then no entries are returned
|
||||
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(7.into()));
|
||||
assert!(iter.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_dir_entry_exists_test() {
|
||||
let mut dir_list = DirList::empty(BlockLevel::default()).unwrap();
|
||||
let mut htree_hash = HTreeHash::default();
|
||||
let dirent = DirEntry::new(TreePtr::new(123), "test");
|
||||
let new_sibling = add_dir_entry(&mut dir_list, &mut htree_hash, dirent).unwrap();
|
||||
assert!(new_sibling.is_none());
|
||||
assert_eq!(htree_hash, HTreeHash::from_name("test"));
|
||||
assert_eq!(dir_list.entries().next().unwrap().name(), Some("test"));
|
||||
|
||||
// Add the same entry again, and it should fail with an appropriate IO error
|
||||
let dirent = DirEntry::new(TreePtr::new(123), "test");
|
||||
let error_expected = add_dir_entry(&mut dir_list, &mut htree_hash, dirent);
|
||||
assert!(error_expected.is_err());
|
||||
assert_eq!(error_expected.err().unwrap().errno, EEXIST);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_dir_entry_many_test() {
|
||||
let mut dir_list = DirList::empty(BlockLevel::default()).unwrap();
|
||||
let mut htree_hash = HTreeHash::default();
|
||||
let total_count = 16;
|
||||
|
||||
// Fill up the dir_list
|
||||
for i in 0..total_count {
|
||||
let v: usize = i % 10;
|
||||
let dirent = DirEntry::new(TreePtr::new(123), format!("test{v}_{i:0244}").as_str());
|
||||
let new_sibling = add_dir_entry(&mut dir_list, &mut htree_hash, dirent).unwrap();
|
||||
assert!(new_sibling.is_none());
|
||||
}
|
||||
|
||||
// The maximum htree_hash should be retained
|
||||
let max_tree_hash =
|
||||
dir_list
|
||||
.entries()
|
||||
.enumerate()
|
||||
.fold(HTreeHash::default(), |max, (i, _)| {
|
||||
let v = i % 10;
|
||||
let hash = HTreeHash::from_name(format!("test{v}_{i:0244}").as_str());
|
||||
max.max_ignoring_default(hash)
|
||||
});
|
||||
assert_eq!(htree_hash, max_tree_hash);
|
||||
|
||||
// Confirm all the entries exist. Note they happen to be in insert order
|
||||
for (i, entry) in dir_list.entries().enumerate() {
|
||||
let v = i % 10;
|
||||
assert_eq!(entry.name(), Some(format!("test{v}_{i:0244}").as_str()));
|
||||
}
|
||||
|
||||
// Test a split by adding one more entry
|
||||
let dirent = DirEntry::new(TreePtr::new(123), "test_split");
|
||||
let new_sibling = add_dir_entry(&mut dir_list, &mut htree_hash, dirent).unwrap();
|
||||
let (new_sibling_htree_hash, new_sibling_dir_list) =
|
||||
new_sibling.expect("new_sibling should be created");
|
||||
// assert!(new_sibling_dir_list.entries.len() );
|
||||
assert!(new_sibling_htree_hash > htree_hash);
|
||||
|
||||
// The htree_hash should be less than the minimum htree_hash in new_sibling_dir_list
|
||||
let new_sibling_min_htree_hash = new_sibling_dir_list
|
||||
.entries()
|
||||
.filter(|entry| !entry.node_ptr().is_null())
|
||||
.fold(HTreeHash::default(), |min, entry| {
|
||||
let hash = HTreeHash::from_name(entry.name().unwrap());
|
||||
min.min(hash)
|
||||
});
|
||||
assert!(htree_hash < new_sibling_min_htree_hash);
|
||||
|
||||
// Confirm all the entries exist across both dir_lists
|
||||
let mut expected_names: Vec<String> = (0..total_count)
|
||||
.map(|i| {
|
||||
let v = i % 10;
|
||||
format!("test{v}_{i:0244}")
|
||||
})
|
||||
.collect();
|
||||
expected_names.push("test_split".to_string());
|
||||
expected_names.sort();
|
||||
|
||||
let mut dir_list_entry_count = 0;
|
||||
for entry in dir_list.entries() {
|
||||
dir_list_entry_count += 1;
|
||||
let name = entry.name().unwrap().to_string();
|
||||
let _ = expected_names.remove(expected_names.binary_search(&name).unwrap());
|
||||
}
|
||||
|
||||
let mut new_sibling_entry_count = 0;
|
||||
for entry in new_sibling_dir_list.entries() {
|
||||
new_sibling_entry_count += 1;
|
||||
let name = entry.name().unwrap().to_string();
|
||||
let _ = expected_names.remove(expected_names.binary_search(&name).unwrap());
|
||||
}
|
||||
assert!(expected_names.is_empty());
|
||||
|
||||
// Confirm that the split is in half
|
||||
assert!((dir_list_entry_count as i32 - new_sibling_entry_count).abs() <= 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_inner_node_simple_test() {
|
||||
let mut htree_node: HTreeNode<_> = HTreeNode::empty(BlockLevel::default()).unwrap();
|
||||
let htree_ptr: HTreePtr<_> = HTreePtr::<BlockRaw> {
|
||||
htree_hash: HTreeHash::from_name("test"),
|
||||
ptr: BlockPtr::marker(0),
|
||||
};
|
||||
let new_sibling = add_inner_node(&mut htree_node, htree_ptr).unwrap();
|
||||
assert!(new_sibling.is_none());
|
||||
assert_eq!(htree_node.ptrs[0].htree_hash, HTreeHash::from_name("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_inner_node_multiple_test() {
|
||||
let mut htree_node: HTreeNode<_> = HTreeNode::empty(BlockLevel::default()).unwrap();
|
||||
|
||||
for i in 0..HTREE_IDX_ENTRIES {
|
||||
let htree_ptr: HTreePtr<_> = HTreePtr::<BlockRaw> {
|
||||
htree_hash: HTreeHash(((100_000 + (i % 10) * 1000 + i) as u32).into()),
|
||||
ptr: BlockPtr::marker(0),
|
||||
};
|
||||
let new_sibling = add_inner_node(&mut htree_node, htree_ptr).unwrap();
|
||||
assert!(new_sibling.is_none());
|
||||
|
||||
// Confirm that the htree_ptrs are in sorted order at the start of the ptrs list
|
||||
let mut prev_hash = HTreeHash::default();
|
||||
let mut count = 0;
|
||||
for ptr in htree_node.ptrs.iter() {
|
||||
if ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
assert!(
|
||||
ptr.htree_hash.max_ignoring_default(prev_hash) == ptr.htree_hash,
|
||||
"index {i}: {:?} > {:?}",
|
||||
ptr.htree_hash,
|
||||
prev_hash
|
||||
);
|
||||
prev_hash = ptr.htree_hash;
|
||||
count += 1;
|
||||
}
|
||||
assert_eq!(count, i + 1);
|
||||
}
|
||||
|
||||
// Confirm all expected hashes are present
|
||||
let mut expected_hashes: Vec<u32> = (0..HTREE_IDX_ENTRIES)
|
||||
.map(|i| (100_000 + (i % 10) * 1000 + i) as u32)
|
||||
.collect();
|
||||
expected_hashes.sort();
|
||||
|
||||
for ptr in htree_node.ptrs.iter() {
|
||||
if ptr.is_null() {
|
||||
break;
|
||||
}
|
||||
let idx = expected_hashes
|
||||
.binary_search(&ptr.htree_hash.0.into())
|
||||
.unwrap();
|
||||
expected_hashes.remove(idx);
|
||||
}
|
||||
assert!(expected_hashes.is_empty());
|
||||
|
||||
// Force a split by adding one more entry
|
||||
let htree_ptr: HTreePtr<_> = HTreePtr::<BlockRaw> {
|
||||
htree_hash: HTreeHash(130_000.into()),
|
||||
ptr: BlockPtr::marker(0),
|
||||
};
|
||||
|
||||
let mut expected_hashes: Vec<u32> = (0..HTREE_IDX_ENTRIES)
|
||||
.map(|i| (100_000 + (i % 10) * 1000 + i) as u32)
|
||||
.collect();
|
||||
expected_hashes.push(130_000);
|
||||
expected_hashes.sort();
|
||||
|
||||
let new_sibling = add_inner_node(&mut htree_node, htree_ptr).unwrap();
|
||||
let new_sibling = new_sibling.expect("new_sibling should be created");
|
||||
|
||||
// Confirm all the entries exist across both htree_nodes
|
||||
let mut htree_node_entry_count = 0;
|
||||
for ptr in htree_node.ptrs.iter() {
|
||||
if ptr.ptr.is_null() {
|
||||
break;
|
||||
}
|
||||
htree_node_entry_count += 1;
|
||||
let idx = expected_hashes
|
||||
.binary_search(&ptr.htree_hash.0.into())
|
||||
.unwrap();
|
||||
expected_hashes.remove(idx);
|
||||
}
|
||||
|
||||
let mut new_sibling_entry_count = 0;
|
||||
for ptr in new_sibling.1.ptrs.iter() {
|
||||
if ptr.ptr.is_null() {
|
||||
break;
|
||||
}
|
||||
new_sibling_entry_count += 1;
|
||||
let idx = expected_hashes
|
||||
.binary_search(&ptr.htree_hash.0.into())
|
||||
.unwrap();
|
||||
expected_hashes.remove(idx);
|
||||
}
|
||||
assert!(
|
||||
expected_hashes.is_empty(),
|
||||
"expected_hashes should be empty, but had length {}: {:?}",
|
||||
expected_hashes.len(),
|
||||
expected_hashes
|
||||
);
|
||||
|
||||
// Confirm that the split is in half
|
||||
assert!((htree_node_entry_count as i32 - new_sibling_entry_count).abs() <= 1);
|
||||
}
|
||||
}
|
||||
-104
@@ -1,104 +0,0 @@
|
||||
use aes::{
|
||||
cipher::{BlockDecrypt, BlockEncrypt, KeyInit},
|
||||
Aes128,
|
||||
};
|
||||
use xts_mode::Xts128;
|
||||
|
||||
// The raw key, keep secret!
|
||||
#[repr(transparent)]
|
||||
pub struct Key([u8; 16]);
|
||||
|
||||
impl Key {
|
||||
/// Generate a random key
|
||||
#[cfg(feature = "std")]
|
||||
pub fn new() -> Result<Self, getrandom::Error> {
|
||||
let mut bytes = [0; 16];
|
||||
getrandom::getrandom(&mut bytes)?;
|
||||
Ok(Self(bytes))
|
||||
}
|
||||
|
||||
pub fn encrypt(&self, password_aes: &Aes128) -> EncryptedKey {
|
||||
let mut block = aes::Block::from(self.0);
|
||||
password_aes.encrypt_block(&mut block);
|
||||
EncryptedKey(block.into())
|
||||
}
|
||||
|
||||
pub fn into_aes(self) -> Aes128 {
|
||||
Aes128::new(&aes::Block::from(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
/// The encrypted key, encrypted with AES using the salt and password
|
||||
#[derive(Clone, Copy, Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct EncryptedKey([u8; 16]);
|
||||
|
||||
impl EncryptedKey {
|
||||
pub fn decrypt(&self, password_aes: &Aes128) -> Key {
|
||||
let mut block = aes::Block::from(self.0);
|
||||
password_aes.decrypt_block(&mut block);
|
||||
Key(block.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Salt used to prevent rainbow table attacks on the encryption password
|
||||
#[derive(Clone, Copy, Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct Salt([u8; 16]);
|
||||
|
||||
impl Salt {
|
||||
/// Generate a random salt
|
||||
#[cfg(feature = "std")]
|
||||
pub fn new() -> Result<Self, getrandom::Error> {
|
||||
let mut bytes = [0; 16];
|
||||
getrandom::getrandom(&mut bytes)?;
|
||||
Ok(Self(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
/// The key slot, containing the salt and encrypted key that are used with one password
|
||||
#[derive(Clone, Copy, Default)]
|
||||
#[repr(C, packed)]
|
||||
pub struct KeySlot {
|
||||
salt: Salt,
|
||||
// Two keys for AES XTS 128
|
||||
encrypted_keys: (EncryptedKey, EncryptedKey),
|
||||
}
|
||||
|
||||
impl KeySlot {
|
||||
/// Get the password AES key (generated from the password and salt, encrypts the real key)
|
||||
pub fn password_aes(password: &[u8], salt: &Salt) -> Result<Aes128, argon2::Error> {
|
||||
let mut key = Key([0; 16]);
|
||||
|
||||
let mut params_builder = argon2::ParamsBuilder::new();
|
||||
params_builder.output_len(key.0.len())?;
|
||||
|
||||
let argon2 = argon2::Argon2::new(
|
||||
argon2::Algorithm::Argon2id,
|
||||
argon2::Version::V0x13,
|
||||
params_builder.params()?,
|
||||
);
|
||||
|
||||
argon2.hash_password_into(password, &salt.0, &mut key.0)?;
|
||||
|
||||
Ok(key.into_aes())
|
||||
}
|
||||
|
||||
/// Create a new key slot from a password, salt, and encryption key
|
||||
pub fn new(password: &[u8], salt: Salt, keys: (Key, Key)) -> Result<Self, argon2::Error> {
|
||||
let password_aes = Self::password_aes(password, &salt)?;
|
||||
Ok(Self {
|
||||
salt,
|
||||
encrypted_keys: (keys.0.encrypt(&password_aes), keys.1.encrypt(&password_aes)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the encryption cipher from this key slot
|
||||
pub fn cipher(&self, password: &[u8]) -> Result<Xts128<Aes128>, argon2::Error> {
|
||||
let password_aes = Self::password_aes(password, &self.salt)?;
|
||||
Ok(Xts128::new(
|
||||
self.encrypted_keys.0.decrypt(&password_aes).into_aes(),
|
||||
self.encrypted_keys.1.decrypt(&password_aes).into_aes(),
|
||||
))
|
||||
}
|
||||
}
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
#![crate_name = "redoxfs"]
|
||||
#![crate_type = "lib"]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
// Used often in generating redox_syscall errors
|
||||
#![allow(clippy::or_fun_call)]
|
||||
#![allow(unexpected_cfgs)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use core::sync::atomic::AtomicUsize;
|
||||
|
||||
// The alloc log grows by 1 block about every 21 generations
|
||||
pub const ALLOC_GC_THRESHOLD: u64 = 1024;
|
||||
pub const BLOCK_SIZE: u64 = 4096;
|
||||
// A record is 4KiB << 5 = 128KiB
|
||||
pub const RECORD_LEVEL: usize = 5;
|
||||
pub const RECORD_SIZE: u64 = BLOCK_SIZE << RECORD_LEVEL;
|
||||
pub const SIGNATURE: &[u8; 8] = b"RedoxFS\0";
|
||||
pub const VERSION: u64 = 8;
|
||||
pub const DIR_ENTRY_MAX_LENGTH: usize = 252;
|
||||
|
||||
pub static IS_UMT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
pub use self::allocator::{AllocEntry, AllocList, Allocator, ReleaseList, ALLOC_LIST_ENTRIES};
|
||||
#[cfg(feature = "std")]
|
||||
pub use self::archive::{archive, archive_at};
|
||||
pub use self::block::{
|
||||
BlockAddr, BlockData, BlockLevel, BlockList, BlockMeta, BlockPtr, BlockRaw, BlockTrait,
|
||||
};
|
||||
#[cfg(feature = "std")]
|
||||
pub use self::clone::clone;
|
||||
pub use self::dir::{DirEntry, DirList};
|
||||
pub use self::disk::*;
|
||||
pub use self::filesystem::FileSystem;
|
||||
pub use self::header::{Header, HEADER_RING};
|
||||
pub use self::key::{Key, KeySlot, Salt};
|
||||
#[cfg(feature = "std")]
|
||||
pub use self::mount::mount;
|
||||
pub use self::node::{Node, NodeFlags, NodeLevel, NodeLevelData};
|
||||
pub use self::record::RecordRaw;
|
||||
pub use self::transaction::Transaction;
|
||||
pub use self::tree::{Tree, TreeData, TreeList, TreePtr};
|
||||
#[cfg(feature = "std")]
|
||||
pub use self::unmount::unmount_path;
|
||||
|
||||
mod allocator;
|
||||
#[cfg(feature = "std")]
|
||||
mod archive;
|
||||
mod block;
|
||||
#[cfg(feature = "std")]
|
||||
mod clone;
|
||||
mod dir;
|
||||
mod disk;
|
||||
mod filesystem;
|
||||
mod header;
|
||||
mod htree;
|
||||
mod key;
|
||||
#[cfg(all(feature = "std", not(fuzzing)))]
|
||||
mod mount;
|
||||
#[cfg(all(feature = "std", fuzzing))]
|
||||
pub mod mount;
|
||||
mod node;
|
||||
mod record;
|
||||
mod transaction;
|
||||
mod tree;
|
||||
#[cfg(feature = "std")]
|
||||
mod unmount;
|
||||
|
||||
#[cfg(all(feature = "std", test))]
|
||||
mod tests;
|
||||
@@ -0,0 +1,26 @@
|
||||
use log::{LevelFilter, Log, Metadata, Record};
|
||||
|
||||
pub static LOGGER: Logger = Logger;
|
||||
|
||||
pub struct Logger;
|
||||
|
||||
impl Logger {
|
||||
pub fn init(&'static self) {
|
||||
log::set_logger(self).unwrap();
|
||||
log::set_max_level(LevelFilter::Info);
|
||||
}
|
||||
}
|
||||
|
||||
impl Log for Logger {
|
||||
fn enabled(&self, _metadata: &Metadata) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
println!("{} - {}", record.level(), record.args());
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
||||
+675
@@ -0,0 +1,675 @@
|
||||
#![no_std]
|
||||
#![cfg_attr(any(target_arch = "riscv64", target_os = "uefi"), no_main)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(any(target_arch = "riscv64", target_os = "uefi"))]
|
||||
#[macro_use]
|
||||
extern crate uefi_std as std;
|
||||
|
||||
use alloc::{format, string::String, vec::Vec};
|
||||
use core::{
|
||||
cmp,
|
||||
fmt::{self, Write},
|
||||
mem, ptr, slice, str,
|
||||
};
|
||||
use redoxfs::{Disk, Node, TreeData};
|
||||
|
||||
use self::arch::{paging_create, paging_framebuffer};
|
||||
use self::os::{Os, OsHwDesc, OsKey, OsMemoryEntry, OsMemoryKind, OsVideoMode};
|
||||
|
||||
#[macro_use]
|
||||
mod os;
|
||||
|
||||
mod arch;
|
||||
mod editor;
|
||||
mod logger;
|
||||
mod serial_16550;
|
||||
|
||||
const KIBI: usize = 1024;
|
||||
const MIBI: usize = KIBI * KIBI;
|
||||
|
||||
//TODO: allocate this in a more reasonable manner
|
||||
static mut AREAS: [OsMemoryEntry; 1024] = [OsMemoryEntry {
|
||||
base: 0,
|
||||
size: 0,
|
||||
kind: OsMemoryKind::Null,
|
||||
}; 1024];
|
||||
static mut AREAS_LEN: usize = 0;
|
||||
|
||||
pub fn area_add(area: OsMemoryEntry) {
|
||||
#[allow(static_mut_refs)]
|
||||
unsafe {
|
||||
for existing_area in &mut AREAS[0..AREAS_LEN] {
|
||||
if existing_area.kind == area.kind {
|
||||
if existing_area.base.unchecked_add(existing_area.size) == area.base {
|
||||
existing_area.size += area.size;
|
||||
return;
|
||||
}
|
||||
if area.base.unchecked_add(area.size) == existing_area.base {
|
||||
existing_area.size += area.size;
|
||||
existing_area.base = area.base;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
*AREAS.get_mut(AREAS_LEN).expect("AREAS overflowed!") = area;
|
||||
AREAS_LEN += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub static mut KERNEL_64BIT: bool = false;
|
||||
|
||||
pub static mut LIVE_OPT: Option<(u64, &'static [u8])> = None;
|
||||
|
||||
struct SliceWriter<'a> {
|
||||
slice: &'a mut [u8],
|
||||
i: usize,
|
||||
}
|
||||
|
||||
impl<'a> Write for SliceWriter<'a> {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
for b in s.bytes() {
|
||||
if let Some(slice_b) = self.slice.get_mut(self.i) {
|
||||
*slice_b = b;
|
||||
self.i += 1;
|
||||
} else {
|
||||
return Err(fmt::Error);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
#[repr(C, packed(8))]
|
||||
pub struct KernelArgs {
|
||||
kernel_base: u64,
|
||||
kernel_size: u64,
|
||||
stack_base: u64,
|
||||
stack_size: u64,
|
||||
env_base: u64,
|
||||
env_size: u64,
|
||||
|
||||
/// The base pointer to the saved RSDP.
|
||||
///
|
||||
/// This field can be NULL, and if so, the system has not booted with UEFI or in some other way
|
||||
/// retrieved the RSDPs. The kernel or a userspace driver will thus try searching the BIOS
|
||||
/// memory instead. On UEFI systems, searching is not guaranteed to actually work though.
|
||||
acpi_rsdp_base: u64,
|
||||
/// The size of the RSDP region.
|
||||
acpi_rsdp_size: u64,
|
||||
|
||||
areas_base: u64,
|
||||
areas_size: u64,
|
||||
|
||||
bootstrap_base: u64,
|
||||
bootstrap_size: u64,
|
||||
}
|
||||
|
||||
fn select_mode(
|
||||
os: &impl Os,
|
||||
output_i: usize,
|
||||
live: &mut bool,
|
||||
edit_env: &mut bool,
|
||||
) -> Option<OsVideoMode> {
|
||||
let mut modes = Vec::new();
|
||||
for mode in os.video_modes(output_i) {
|
||||
let mut aspect_w = mode.width;
|
||||
let mut aspect_h = mode.height;
|
||||
for i in 2..cmp::min(aspect_w / 2, aspect_h / 2) {
|
||||
while aspect_w % i == 0 && aspect_h % i == 0 {
|
||||
aspect_w /= i;
|
||||
aspect_h /= i;
|
||||
}
|
||||
}
|
||||
|
||||
modes.push((
|
||||
mode,
|
||||
format!(
|
||||
"{:>4}x{:<4} {:>3}:{:<3}",
|
||||
mode.width, mode.height, aspect_w, aspect_h
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
if modes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Sort modes by pixel area, reversed
|
||||
modes.sort_by(|a, b| (b.0.width * b.0.height).cmp(&(a.0.width * a.0.height)));
|
||||
|
||||
// Set selected based on best resolution
|
||||
print!("Output {}", output_i);
|
||||
let mut selected = modes.first().map_or(0, |x| x.0.id);
|
||||
if let Some((best_width, best_height)) = os.best_resolution(output_i) {
|
||||
print!(", best resolution: {}x{}", best_width, best_height);
|
||||
for (mode, _text) in modes.iter() {
|
||||
if mode.width == best_width && mode.height == best_height {
|
||||
selected = mode.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
||||
println!("Arrow keys and enter select mode");
|
||||
let live_mode = os.get_text_position();
|
||||
if *live {
|
||||
println!("Press l to disable live mode");
|
||||
} else {
|
||||
println!("Press l to enable live mode");
|
||||
}
|
||||
println!("Press e to edit boot environment");
|
||||
println!();
|
||||
print!(" ");
|
||||
|
||||
let (off_x, off_y) = os.get_text_position();
|
||||
let rows = 12;
|
||||
let mut mode_opt = None;
|
||||
while !modes.is_empty() {
|
||||
let mut row = 0;
|
||||
let mut col = 0;
|
||||
for (mode, text) in modes.iter() {
|
||||
if row >= rows {
|
||||
col += 1;
|
||||
row = 0;
|
||||
}
|
||||
|
||||
os.set_text_position(off_x + col * 20, off_y + row);
|
||||
os.set_text_highlight(mode.id == selected);
|
||||
|
||||
print!("{}", text);
|
||||
|
||||
row += 1;
|
||||
}
|
||||
|
||||
// Read keypress
|
||||
match os.get_key() {
|
||||
OsKey::Left => {
|
||||
if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) {
|
||||
if mode_i < rows {
|
||||
while mode_i < modes.len() {
|
||||
mode_i += rows;
|
||||
}
|
||||
}
|
||||
mode_i -= rows;
|
||||
if let Some(new) = modes.get(mode_i) {
|
||||
selected = new.0.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
OsKey::Right => {
|
||||
if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) {
|
||||
mode_i += rows;
|
||||
if mode_i >= modes.len() {
|
||||
mode_i %= rows;
|
||||
}
|
||||
if let Some(new) = modes.get(mode_i) {
|
||||
selected = new.0.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
OsKey::Up => {
|
||||
if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) {
|
||||
if mode_i % rows == 0 {
|
||||
mode_i += rows;
|
||||
if mode_i > modes.len() {
|
||||
mode_i = modes.len();
|
||||
}
|
||||
}
|
||||
mode_i -= 1;
|
||||
if let Some(new) = modes.get(mode_i) {
|
||||
selected = new.0.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
OsKey::Down => {
|
||||
if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) {
|
||||
mode_i += 1;
|
||||
if mode_i % rows == 0 {
|
||||
mode_i -= rows;
|
||||
}
|
||||
if mode_i >= modes.len() {
|
||||
mode_i = mode_i - mode_i % rows;
|
||||
}
|
||||
if let Some(new) = modes.get(mode_i) {
|
||||
selected = new.0.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
OsKey::Enter => {
|
||||
if let Some(mode_i) = modes.iter().position(|x| x.0.id == selected) {
|
||||
if let Some((mode, _text)) = modes.get(mode_i) {
|
||||
mode_opt = Some(*mode);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
OsKey::Char('l') => {
|
||||
*live = !*live;
|
||||
os.set_text_position(live_mode.0, live_mode.1);
|
||||
if *live {
|
||||
println!("Press l to disable live mode");
|
||||
} else {
|
||||
println!("Press l to enable live mode");
|
||||
}
|
||||
}
|
||||
OsKey::Char('e') => {
|
||||
if let Some(mode_i) = modes.iter().position(|x| x.0.id == selected) {
|
||||
if let Some((mode, _text)) = modes.get(mode_i) {
|
||||
*edit_env = true;
|
||||
mode_opt = Some(*mode);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
os.set_text_position(0, off_y + rows);
|
||||
os.set_text_highlight(false);
|
||||
println!();
|
||||
|
||||
mode_opt
|
||||
}
|
||||
|
||||
fn redoxfs<O: Os>(os: &O) -> (redoxfs::FileSystem<O::D>, Option<&'static [u8]>) {
|
||||
let attempts = 10;
|
||||
for attempt in 0..=attempts {
|
||||
let mut password_opt = None;
|
||||
if attempt > 0 {
|
||||
print!("\rRedoxFS password ({}/{}): ", attempt, attempts);
|
||||
|
||||
let mut password = String::new();
|
||||
|
||||
loop {
|
||||
match os.get_key() {
|
||||
OsKey::Backspace | OsKey::Delete => {
|
||||
if !password.is_empty() {
|
||||
print!("\x08 \x08");
|
||||
password.pop();
|
||||
}
|
||||
}
|
||||
OsKey::Char(c) => {
|
||||
print!("*");
|
||||
password.push(c)
|
||||
}
|
||||
OsKey::Enter => break,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
// Erase password information
|
||||
while os.get_text_position().0 > 0 {
|
||||
print!("\x08 \x08");
|
||||
}
|
||||
|
||||
if !password.is_empty() {
|
||||
password_opt = Some(password);
|
||||
}
|
||||
}
|
||||
match os.filesystem(password_opt.as_ref().map(|x| x.as_bytes())) {
|
||||
Ok(fs) => {
|
||||
return (
|
||||
fs,
|
||||
password_opt.map(|password| {
|
||||
// Copy password to page aligned memory
|
||||
let password_size = password.len();
|
||||
let password_base = os.alloc_zeroed_page_aligned(password_size);
|
||||
|
||||
area_add(OsMemoryEntry {
|
||||
base: password_base as u64,
|
||||
size: password_size as u64,
|
||||
kind: OsMemoryKind::Reserved,
|
||||
});
|
||||
|
||||
unsafe {
|
||||
ptr::copy(password.as_ptr(), password_base, password_size);
|
||||
slice::from_raw_parts(password_base, password_size)
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
Err(err) => match err.errno {
|
||||
// Incorrect password, try again
|
||||
syscall::ENOKEY => (),
|
||||
_ => {
|
||||
panic!("Failed to open RedoxFS: {}", err);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
panic!("RedoxFS out of unlock attempts");
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum Filetype {
|
||||
Elf,
|
||||
Initfs,
|
||||
}
|
||||
fn load_to_memory<O: Os>(
|
||||
os: &O,
|
||||
fs: &mut redoxfs::FileSystem<O::D>,
|
||||
path: &str,
|
||||
filetype: Filetype,
|
||||
) -> &'static mut [u8] {
|
||||
fs.tx(|tx| {
|
||||
let mut node = None;
|
||||
for component in path.split('/') {
|
||||
node = Some(
|
||||
tx.find_node(
|
||||
node.map_or(redoxfs::TreePtr::root(), |node: TreeData<Node>| node.ptr()),
|
||||
component,
|
||||
)
|
||||
.unwrap_or_else(|err| panic!("Failed to find {component}: {err}")),
|
||||
);
|
||||
}
|
||||
let node = node.unwrap();
|
||||
|
||||
let size = node.data().size();
|
||||
|
||||
print!("{}: 0/{} MiB", path, size / MIBI as u64);
|
||||
|
||||
let ptr = os.alloc_zeroed_page_aligned(size as usize);
|
||||
if ptr.is_null() {
|
||||
panic!("Failed to allocate memory for {}", path);
|
||||
}
|
||||
|
||||
let slice = unsafe { slice::from_raw_parts_mut(ptr, size as usize) };
|
||||
|
||||
let mut i = 0;
|
||||
for chunk in slice.chunks_mut(MIBI) {
|
||||
print!("\r{}: {}/{} MiB", path, i / MIBI as u64, size / MIBI as u64);
|
||||
i += tx
|
||||
.read_node_inner(&node, i, chunk)
|
||||
.unwrap_or_else(|err| panic!("Failed to read `{}` file: {}", path, err))
|
||||
as u64;
|
||||
}
|
||||
println!("\r{}: {}/{} MiB", path, i / MIBI as u64, size / MIBI as u64);
|
||||
|
||||
if filetype == Filetype::Elf {
|
||||
let magic = &slice[..4];
|
||||
if magic != b"\x7FELF" {
|
||||
panic!("{} has invalid magic number {:#X?}", path, magic);
|
||||
}
|
||||
} else if filetype == Filetype::Initfs {
|
||||
let magic = &slice[..8];
|
||||
if magic != b"RedoxFtw" {
|
||||
panic!("{} has invalid magic number {:#X?}", path, magic);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(slice)
|
||||
})
|
||||
.unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"RedoxFS transaction failed while loading `{}`: {}",
|
||||
path, err
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn elf_entry(data: &[u8]) -> (u64, bool) {
|
||||
match (data[4], data[5]) {
|
||||
// 32-bit, little endian
|
||||
(1, 1) => (
|
||||
u32::from_le_bytes(
|
||||
<[u8; 4]>::try_from(&data[0x18..0x18 + 4]).expect("conversion cannot fail"),
|
||||
) as u64,
|
||||
false,
|
||||
),
|
||||
// 32-bit, big endian
|
||||
(1, 2) => (
|
||||
u32::from_be_bytes(
|
||||
<[u8; 4]>::try_from(&data[0x18..0x18 + 4]).expect("conversion cannot fail"),
|
||||
) as u64,
|
||||
false,
|
||||
),
|
||||
// 64-bit, little endian
|
||||
(2, 1) => (
|
||||
u64::from_le_bytes(
|
||||
<[u8; 8]>::try_from(&data[0x18..0x18 + 8]).expect("conversion cannot fail"),
|
||||
),
|
||||
true,
|
||||
),
|
||||
// 64-bit, big endian
|
||||
(2, 2) => (
|
||||
u64::from_be_bytes(
|
||||
<[u8; 8]>::try_from(&data[0x18..0x18 + 8]).expect("conversion cannot fail"),
|
||||
),
|
||||
true,
|
||||
),
|
||||
(ei_class, ei_data) => {
|
||||
panic!("Unsupported ELF EI_CLASS {} EI_DATA {}", ei_class, ei_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main(os: &impl Os) -> (usize, u64, KernelArgs) {
|
||||
println!(
|
||||
"Redox OS Bootloader {} on {}",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
os.name()
|
||||
);
|
||||
|
||||
let hwdesc = os.hwdesc();
|
||||
println!("Hardware descriptor: {:x?}", hwdesc);
|
||||
let (acpi_rsdp_base, acpi_rsdp_size) = match hwdesc {
|
||||
OsHwDesc::Acpi(base, size) => (base, size),
|
||||
OsHwDesc::DeviceTree(base, size) => (base, size),
|
||||
OsHwDesc::NotFound => (0, 0),
|
||||
};
|
||||
|
||||
let (mut fs, password_opt) = redoxfs(os);
|
||||
|
||||
print!("RedoxFS ");
|
||||
for i in 0..fs.header.uuid().len() {
|
||||
if i == 4 || i == 6 || i == 8 || i == 10 {
|
||||
print!("-");
|
||||
}
|
||||
|
||||
print!("{:>02x}", fs.header.uuid()[i]);
|
||||
}
|
||||
println!(": {} MiB", fs.header.size() / MIBI as u64);
|
||||
println!();
|
||||
|
||||
let mut mode_opts = Vec::new();
|
||||
let mut live = cfg!(feature = "live");
|
||||
let mut edit_env = false;
|
||||
for output_i in 0..os.video_outputs() {
|
||||
if output_i > 0 {
|
||||
os.clear_text();
|
||||
}
|
||||
mode_opts.push(select_mode(os, output_i, &mut live, &mut edit_env));
|
||||
}
|
||||
|
||||
let stack_size = 128 * KIBI;
|
||||
let stack_base = os.alloc_zeroed_page_aligned(stack_size);
|
||||
if stack_base.is_null() {
|
||||
panic!("Failed to allocate memory for stack");
|
||||
}
|
||||
|
||||
let live_opt = if live {
|
||||
let size = fs.header.size();
|
||||
|
||||
print!("live: 0/{} MiB", size / MIBI as u64);
|
||||
|
||||
let ptr = os.alloc_zeroed_page_aligned(size as usize);
|
||||
if ptr.is_null() {
|
||||
panic!("Failed to allocate memory for live");
|
||||
}
|
||||
|
||||
let live = unsafe { slice::from_raw_parts_mut(ptr, size as usize) };
|
||||
|
||||
let mut i = 0;
|
||||
for chunk in live.chunks_mut(MIBI) {
|
||||
print!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64);
|
||||
i += unsafe {
|
||||
fs.disk
|
||||
.read_at(fs.block + i / redoxfs::BLOCK_SIZE, chunk)
|
||||
.expect("Failed to read live disk") as u64
|
||||
};
|
||||
}
|
||||
println!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64);
|
||||
|
||||
println!("Switching to live disk");
|
||||
unsafe {
|
||||
LIVE_OPT = Some((fs.block, slice::from_raw_parts_mut(ptr, size as usize)));
|
||||
}
|
||||
|
||||
area_add(OsMemoryEntry {
|
||||
base: live.as_ptr() as u64,
|
||||
size: live.len() as u64,
|
||||
kind: OsMemoryKind::Reserved,
|
||||
});
|
||||
|
||||
Some(live)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let (kernel, kernel_entry) = {
|
||||
let kernel = load_to_memory(os, &mut fs, "usr/lib/boot/kernel", Filetype::Elf);
|
||||
let (kernel_entry, kernel_64bit) = elf_entry(kernel);
|
||||
unsafe {
|
||||
KERNEL_64BIT = kernel_64bit;
|
||||
}
|
||||
(kernel, kernel_entry)
|
||||
};
|
||||
|
||||
let (bootstrap_size, bootstrap_base) = {
|
||||
let initfs_slice = load_to_memory(os, &mut fs, "usr/lib/boot/initfs", Filetype::Initfs);
|
||||
|
||||
let memory = unsafe {
|
||||
let total_size = initfs_slice.len().next_multiple_of(4096);
|
||||
let ptr = os.alloc_zeroed_page_aligned(total_size);
|
||||
assert!(!ptr.is_null(), "failed to allocate bootstrap+initfs memory");
|
||||
core::slice::from_raw_parts_mut(ptr, total_size)
|
||||
};
|
||||
memory[..initfs_slice.len()].copy_from_slice(initfs_slice);
|
||||
|
||||
(memory.len() as u64, memory.as_mut_ptr() as u64)
|
||||
};
|
||||
|
||||
let page_phys = unsafe { paging_create(os, kernel.as_ptr() as u64, kernel.len() as u64) }
|
||||
.expect("Failed to set up paging");
|
||||
|
||||
let max_env_size = 64 * KIBI;
|
||||
let mut env_size = max_env_size;
|
||||
let env_base = os.alloc_zeroed_page_aligned(env_size);
|
||||
if env_base.is_null() {
|
||||
panic!("Failed to allocate memory for stack");
|
||||
}
|
||||
|
||||
{
|
||||
let mut w = SliceWriter {
|
||||
slice: unsafe { slice::from_raw_parts_mut(env_base, max_env_size) },
|
||||
i: 0,
|
||||
};
|
||||
|
||||
match hwdesc {
|
||||
OsHwDesc::Acpi(addr, size) => {
|
||||
writeln!(w, "RSDP_ADDR={addr:016x}").unwrap();
|
||||
writeln!(w, "RSDP_SIZE={size:016x}").unwrap();
|
||||
}
|
||||
OsHwDesc::DeviceTree(addr, size) => {
|
||||
writeln!(w, "DTB_ADDR={addr:016x}").unwrap();
|
||||
writeln!(w, "DTB_SIZE={size:016x}").unwrap();
|
||||
}
|
||||
OsHwDesc::NotFound => {}
|
||||
}
|
||||
|
||||
if let Some(live) = live_opt {
|
||||
writeln!(w, "DISK_LIVE_ADDR={:016x}", live.as_ptr() as usize).unwrap();
|
||||
writeln!(w, "DISK_LIVE_SIZE={:016x}", live.len()).unwrap();
|
||||
writeln!(w, "REDOXFS_BLOCK={:016x}", 0).unwrap();
|
||||
} else {
|
||||
writeln!(w, "REDOXFS_BLOCK={:016x}", fs.block).unwrap();
|
||||
}
|
||||
write!(w, "REDOXFS_UUID=").unwrap();
|
||||
for i in 0..fs.header.uuid().len() {
|
||||
if i == 4 || i == 6 || i == 8 || i == 10 {
|
||||
write!(w, "-").unwrap();
|
||||
}
|
||||
|
||||
write!(w, "{:>02x}", fs.header.uuid()[i]).unwrap();
|
||||
}
|
||||
writeln!(w).unwrap();
|
||||
if let Some(password) = password_opt {
|
||||
writeln!(
|
||||
w,
|
||||
"REDOXFS_PASSWORD_ADDR={:016x}",
|
||||
password.as_ptr() as usize
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(w, "REDOXFS_PASSWORD_SIZE={:016x}", password.len()).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
{
|
||||
let boot_hartid = os::efi_get_boot_hartid()
|
||||
.expect("Could not retrieve boot hart id from EFI implementation!");
|
||||
writeln!(w, "BOOT_HART_ID={:016x}", boot_hartid).unwrap();
|
||||
}
|
||||
if edit_env {
|
||||
editor::edit_env(os, env_base, &mut w.i, max_env_size);
|
||||
}
|
||||
|
||||
for output_i in 0..os.video_outputs() {
|
||||
if let Some(mut mode) = mode_opts[output_i] {
|
||||
// Set mode to get updated values
|
||||
os.set_video_mode(output_i, &mut mode);
|
||||
|
||||
if output_i == 0 {
|
||||
let virt = unsafe {
|
||||
paging_framebuffer(
|
||||
os,
|
||||
page_phys,
|
||||
mode.base,
|
||||
(mode.stride * mode.height * 4) as u64,
|
||||
)
|
||||
}
|
||||
.expect("Failed to map framebuffer");
|
||||
|
||||
writeln!(w, "FRAMEBUFFER_ADDR={:016x}", mode.base).unwrap();
|
||||
writeln!(w, "FRAMEBUFFER_VIRT={virt:016x}").unwrap();
|
||||
writeln!(w, "FRAMEBUFFER_WIDTH={:016x}", mode.width).unwrap();
|
||||
writeln!(w, "FRAMEBUFFER_HEIGHT={:016x}", mode.height).unwrap();
|
||||
writeln!(w, "FRAMEBUFFER_STRIDE={:016x}", mode.stride).unwrap();
|
||||
} else {
|
||||
writeln!(
|
||||
w,
|
||||
"FRAMEBUFFER{}={:#x},{},{},{}",
|
||||
output_i, mode.base, mode.width, mode.height, mode.stride,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
env_size = w.i;
|
||||
}
|
||||
|
||||
#[allow(static_mut_refs)]
|
||||
(
|
||||
page_phys,
|
||||
kernel_entry,
|
||||
KernelArgs {
|
||||
kernel_base: kernel.as_ptr() as u64,
|
||||
kernel_size: kernel.len() as u64,
|
||||
stack_base: stack_base as u64,
|
||||
stack_size: stack_size as u64,
|
||||
env_base: env_base as u64,
|
||||
env_size: env_size as u64,
|
||||
acpi_rsdp_base,
|
||||
acpi_rsdp_size,
|
||||
areas_base: unsafe { AREAS.as_ptr() as u64 },
|
||||
areas_size: unsafe { (AREAS.len() * mem::size_of::<OsMemoryEntry>()) as u64 },
|
||||
bootstrap_base,
|
||||
bootstrap_size,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1,580 +0,0 @@
|
||||
extern crate fuser;
|
||||
|
||||
use std::cmp;
|
||||
use std::ffi::OsStr;
|
||||
use std::io;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use self::fuser::MountOption;
|
||||
use self::fuser::TimeOrNow;
|
||||
use crate::mount::fuse::TimeOrNow::Now;
|
||||
use crate::mount::fuse::TimeOrNow::SpecificTime;
|
||||
|
||||
use crate::{filesystem, Disk, Node, TreeData, TreePtr, BLOCK_SIZE};
|
||||
|
||||
use self::fuser::{
|
||||
FileAttr, FileType, Filesystem, ReplyAttr, ReplyCreate, ReplyData, ReplyDirectory, ReplyEmpty,
|
||||
ReplyEntry, ReplyOpen, ReplyStatfs, ReplyWrite, Request, Session,
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
const TTL: Duration = Duration::new(1, 0); // 1 second
|
||||
|
||||
const NULL_TIME: Duration = Duration::new(0, 0);
|
||||
|
||||
pub fn mount<D, P, T, F>(
|
||||
mut filesystem: filesystem::FileSystem<D>,
|
||||
mountpoint: P,
|
||||
callback: F,
|
||||
) -> io::Result<T>
|
||||
where
|
||||
D: Disk,
|
||||
P: AsRef<Path>,
|
||||
F: FnOnce(&Path) -> T,
|
||||
{
|
||||
let mountpoint = mountpoint.as_ref();
|
||||
|
||||
// One of the uses of this redoxfs fuse wrapper is to populate a filesystem
|
||||
// while building the Redox OS kernel. This means that we need to write on
|
||||
// a filesystem that belongs to `root`, which in turn means that we need to
|
||||
// be `root`, thus that we need to allow `root` to have access.
|
||||
let defer_permissions = [MountOption::CUSTOM("defer_permissions".to_owned())];
|
||||
|
||||
let res = {
|
||||
let mut session = Session::new(
|
||||
Fuse {
|
||||
fs: &mut filesystem,
|
||||
},
|
||||
mountpoint,
|
||||
if cfg!(target_os = "macos") {
|
||||
&defer_permissions
|
||||
} else {
|
||||
&[]
|
||||
},
|
||||
)?;
|
||||
|
||||
let res = callback(mountpoint);
|
||||
|
||||
session.run()?;
|
||||
|
||||
res
|
||||
};
|
||||
|
||||
// Cleanup on unmount
|
||||
filesystem.cleanup()?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub struct Fuse<'f, D: Disk> {
|
||||
pub fs: &'f mut filesystem::FileSystem<D>,
|
||||
}
|
||||
|
||||
fn node_attr(node: &TreeData<Node>) -> FileAttr {
|
||||
FileAttr {
|
||||
ino: node.id() as u64,
|
||||
size: node.data().size(),
|
||||
// Blocks is in 512 byte blocks, not in our block size
|
||||
blocks: node.data().blocks() * (BLOCK_SIZE / 512),
|
||||
blksize: 512,
|
||||
atime: SystemTime::UNIX_EPOCH + Duration::new(node.data().atime().0, node.data().atime().1),
|
||||
mtime: SystemTime::UNIX_EPOCH + Duration::new(node.data().mtime().0, node.data().mtime().1),
|
||||
ctime: SystemTime::UNIX_EPOCH + Duration::new(node.data().ctime().0, node.data().ctime().1),
|
||||
crtime: UNIX_EPOCH + NULL_TIME,
|
||||
kind: if node.data().is_dir() {
|
||||
FileType::Directory
|
||||
} else if node.data().is_symlink() {
|
||||
FileType::Symlink
|
||||
} else {
|
||||
FileType::RegularFile
|
||||
},
|
||||
perm: node.data().mode() & Node::MODE_PERM,
|
||||
nlink: node.data().links(),
|
||||
uid: node.data().uid(),
|
||||
gid: node.data().gid(),
|
||||
rdev: 0,
|
||||
flags: 0,
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Disk> Filesystem for Fuse<'_, D> {
|
||||
fn lookup(&mut self, _req: &Request, parent_id: u64, name: &OsStr, reply: ReplyEntry) {
|
||||
let parent_ptr = TreePtr::new(parent_id as u32);
|
||||
match self
|
||||
.fs
|
||||
.tx(|tx| tx.find_node(parent_ptr, name.to_str().unwrap()))
|
||||
{
|
||||
Ok(node) => {
|
||||
reply.entry(&TTL, &node_attr(&node), 0);
|
||||
}
|
||||
Err(err) => {
|
||||
reply.error(err.errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn getattr(&mut self, _req: &Request, node_id: u64, _fh: Option<u64>, reply: ReplyAttr) {
|
||||
let node_ptr = TreePtr::<Node>::new(node_id as u32);
|
||||
match self.fs.tx(|tx| tx.read_tree(node_ptr)) {
|
||||
Ok(node) => {
|
||||
reply.attr(&TTL, &node_attr(&node));
|
||||
}
|
||||
Err(err) => {
|
||||
reply.error(err.errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setattr(
|
||||
&mut self,
|
||||
_req: &Request,
|
||||
node_id: u64,
|
||||
mode: Option<u32>,
|
||||
uid: Option<u32>,
|
||||
gid: Option<u32>,
|
||||
size: Option<u64>,
|
||||
atime: Option<TimeOrNow>,
|
||||
mtime: Option<TimeOrNow>,
|
||||
_ctime: Option<SystemTime>,
|
||||
_fh: Option<u64>,
|
||||
_crtime: Option<SystemTime>,
|
||||
_chgtime: Option<SystemTime>,
|
||||
_bkuptime: Option<SystemTime>,
|
||||
_flags: Option<u32>,
|
||||
reply: ReplyAttr,
|
||||
) {
|
||||
let node_ptr = TreePtr::<Node>::new(node_id as u32);
|
||||
|
||||
let mut node = match self.fs.tx(|tx| tx.read_tree(node_ptr)) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
reply.error(err.errno);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut node_changed = false;
|
||||
|
||||
if let Some(mode) = mode {
|
||||
if node.data().mode() & Node::MODE_PERM != mode as u16 & Node::MODE_PERM {
|
||||
let new_mode =
|
||||
(node.data().mode() & Node::MODE_TYPE) | (mode as u16 & Node::MODE_PERM);
|
||||
node.data_mut().set_mode(new_mode);
|
||||
node_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(uid) = uid {
|
||||
if node.data().uid() != uid {
|
||||
node.data_mut().set_uid(uid);
|
||||
node_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(gid) = gid {
|
||||
if node.data().gid() != gid {
|
||||
node.data_mut().set_gid(gid);
|
||||
node_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(atime) = atime {
|
||||
let atime_c = match atime {
|
||||
SpecificTime(st) => st.duration_since(UNIX_EPOCH).unwrap(),
|
||||
Now => SystemTime::now().duration_since(UNIX_EPOCH).unwrap(),
|
||||
};
|
||||
node.data_mut()
|
||||
.set_atime(atime_c.as_secs(), atime_c.subsec_nanos());
|
||||
node_changed = true;
|
||||
}
|
||||
|
||||
if let Some(mtime) = mtime {
|
||||
let mtime_c = match mtime {
|
||||
SpecificTime(st) => st.duration_since(UNIX_EPOCH).unwrap(),
|
||||
Now => SystemTime::now().duration_since(UNIX_EPOCH).unwrap(),
|
||||
};
|
||||
node.data_mut()
|
||||
.set_mtime(mtime_c.as_secs(), mtime_c.subsec_nanos());
|
||||
node_changed = true;
|
||||
}
|
||||
|
||||
if let Some(size) = size {
|
||||
match self.fs.tx(|tx| tx.truncate_node_inner(&mut node, size)) {
|
||||
Ok(ok) => {
|
||||
if ok {
|
||||
node_changed = true;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
reply.error(err.errno);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let attr = node_attr(&node);
|
||||
|
||||
if node_changed {
|
||||
if let Err(err) = self.fs.tx(|tx| tx.sync_tree(node)) {
|
||||
reply.error(err.errno);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
reply.attr(&TTL, &attr);
|
||||
}
|
||||
|
||||
fn open(&mut self, _req: &Request<'_>, node_id: u64, _flags: i32, reply: ReplyOpen) {
|
||||
let node_ptr = TreePtr::<Node>::new(node_id as u32);
|
||||
match self.fs.tx(|tx| tx.on_open_node(node_ptr)) {
|
||||
Ok(()) => reply.opened(0, 0),
|
||||
Err(err) => reply.error(err.errno),
|
||||
}
|
||||
}
|
||||
|
||||
fn read(
|
||||
&mut self,
|
||||
_req: &Request,
|
||||
node_id: u64,
|
||||
_fh: u64,
|
||||
offset: i64,
|
||||
size: u32,
|
||||
_flags: i32,
|
||||
_lock_owner: Option<u64>,
|
||||
reply: ReplyData,
|
||||
) {
|
||||
let node_ptr = TreePtr::<Node>::new(node_id as u32);
|
||||
|
||||
let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
let mut data = vec![0; size as usize];
|
||||
match self.fs.tx(|tx| {
|
||||
tx.read_node(
|
||||
node_ptr,
|
||||
cmp::max(0, offset) as u64,
|
||||
&mut data,
|
||||
atime.as_secs(),
|
||||
atime.subsec_nanos(),
|
||||
)
|
||||
}) {
|
||||
Ok(count) => {
|
||||
reply.data(&data[..count]);
|
||||
}
|
||||
Err(err) => {
|
||||
reply.error(err.errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write(
|
||||
&mut self,
|
||||
_req: &Request,
|
||||
node_id: u64,
|
||||
_fh: u64,
|
||||
offset: i64,
|
||||
data: &[u8],
|
||||
_write_flags: u32,
|
||||
_flags: i32,
|
||||
_lock_owner: Option<u64>,
|
||||
reply: ReplyWrite,
|
||||
) {
|
||||
let node_ptr = TreePtr::<Node>::new(node_id as u32);
|
||||
|
||||
let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
match self.fs.tx(|tx| {
|
||||
tx.write_node(
|
||||
node_ptr,
|
||||
cmp::max(0, offset) as u64,
|
||||
data,
|
||||
mtime.as_secs(),
|
||||
mtime.subsec_nanos(),
|
||||
)
|
||||
}) {
|
||||
Ok(count) => {
|
||||
reply.written(count as u32);
|
||||
}
|
||||
Err(err) => {
|
||||
reply.error(err.errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self, _req: &Request, _ino: u64, _fh: u64, _lock_owner: u64, reply: ReplyEmpty) {
|
||||
reply.ok();
|
||||
}
|
||||
|
||||
fn release(
|
||||
&mut self,
|
||||
_req: &Request,
|
||||
node_id: u64,
|
||||
_fh: u64,
|
||||
_flags: i32,
|
||||
_lock_owner: Option<u64>,
|
||||
_flush: bool,
|
||||
reply: ReplyEmpty,
|
||||
) {
|
||||
let node_ptr = TreePtr::new(node_id as u32);
|
||||
match self.fs.tx(|tx| tx.on_close_node(node_ptr)) {
|
||||
Ok(()) => reply.ok(),
|
||||
Err(err) => reply.error(err.errno),
|
||||
}
|
||||
}
|
||||
|
||||
fn fsync(&mut self, _req: &Request, _ino: u64, _fh: u64, _datasync: bool, reply: ReplyEmpty) {
|
||||
reply.ok();
|
||||
}
|
||||
|
||||
fn readdir(
|
||||
&mut self,
|
||||
_req: &Request,
|
||||
parent_id: u64,
|
||||
_fh: u64,
|
||||
offset: i64,
|
||||
mut reply: ReplyDirectory,
|
||||
) {
|
||||
let parent_ptr = TreePtr::new(parent_id as u32);
|
||||
let mut children = Vec::new();
|
||||
match self.fs.tx(|tx| tx.child_nodes(parent_ptr, &mut children)) {
|
||||
Ok(()) => {
|
||||
let mut i;
|
||||
let skip;
|
||||
if offset == 0 {
|
||||
skip = 0;
|
||||
i = 0;
|
||||
let _full = reply.add(parent_id, i, FileType::Directory, ".");
|
||||
|
||||
i += 1;
|
||||
let _full = reply.add(
|
||||
//TODO: get parent?
|
||||
parent_id,
|
||||
i,
|
||||
FileType::Directory,
|
||||
"..",
|
||||
);
|
||||
i += 1;
|
||||
} else {
|
||||
i = offset + 1;
|
||||
skip = offset as usize - 1;
|
||||
}
|
||||
|
||||
for child in children.iter().skip(skip) {
|
||||
//TODO: make it possible to get file type from directory entry
|
||||
let node = match self.fs.tx(|tx| tx.read_tree(child.node_ptr())) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
reply.error(err.errno);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let full = reply.add(
|
||||
child.node_ptr().id() as u64,
|
||||
i,
|
||||
if node.data().is_dir() {
|
||||
FileType::Directory
|
||||
} else {
|
||||
FileType::RegularFile
|
||||
},
|
||||
child.name().unwrap(),
|
||||
);
|
||||
|
||||
if full {
|
||||
break;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
reply.ok();
|
||||
}
|
||||
Err(err) => {
|
||||
reply.error(err.errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create(
|
||||
&mut self,
|
||||
_req: &Request,
|
||||
parent_id: u64,
|
||||
name: &OsStr,
|
||||
mode: u32,
|
||||
_umask: u32,
|
||||
_flags: i32,
|
||||
reply: ReplyCreate,
|
||||
) {
|
||||
let parent_ptr = TreePtr::<Node>::new(parent_id as u32);
|
||||
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
match self.fs.tx(|tx| {
|
||||
let node = tx.create_node(
|
||||
parent_ptr,
|
||||
name.to_str().unwrap(),
|
||||
Node::MODE_FILE | (mode as u16 & Node::MODE_PERM),
|
||||
ctime.as_secs(),
|
||||
ctime.subsec_nanos(),
|
||||
)?;
|
||||
tx.on_open_node(node.ptr())?;
|
||||
Ok(node)
|
||||
}) {
|
||||
Ok(node) => {
|
||||
// println!("Create {:?}:{:o}:{:o}", node.1.name(), node.1.mode, mode);
|
||||
reply.created(&TTL, &node_attr(&node), 0, 0, 0);
|
||||
}
|
||||
Err(error) => {
|
||||
reply.error(error.errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mkdir(
|
||||
&mut self,
|
||||
_req: &Request,
|
||||
parent_id: u64,
|
||||
name: &OsStr,
|
||||
mode: u32,
|
||||
_umask: u32,
|
||||
reply: ReplyEntry,
|
||||
) {
|
||||
let parent_ptr = TreePtr::<Node>::new(parent_id as u32);
|
||||
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
match self.fs.tx(|tx| {
|
||||
tx.create_node(
|
||||
parent_ptr,
|
||||
name.to_str().unwrap(),
|
||||
Node::MODE_DIR | (mode as u16 & Node::MODE_PERM),
|
||||
ctime.as_secs(),
|
||||
ctime.subsec_nanos(),
|
||||
)
|
||||
}) {
|
||||
Ok(node) => {
|
||||
// println!("Mkdir {:?}:{:o}:{:o}", node.1.name(), node.1.mode, mode);
|
||||
reply.entry(&TTL, &node_attr(&node), 0);
|
||||
}
|
||||
Err(error) => {
|
||||
reply.error(error.errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rmdir(&mut self, _req: &Request, parent_id: u64, name: &OsStr, reply: ReplyEmpty) {
|
||||
let parent_ptr = TreePtr::<Node>::new(parent_id as u32);
|
||||
match self
|
||||
.fs
|
||||
.tx(|tx| tx.remove_node(parent_ptr, name.to_str().unwrap(), Node::MODE_DIR))
|
||||
{
|
||||
Ok(_) => {
|
||||
reply.ok();
|
||||
}
|
||||
Err(err) => {
|
||||
reply.error(err.errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unlink(&mut self, _req: &Request, parent_id: u64, name: &OsStr, reply: ReplyEmpty) {
|
||||
let parent_ptr = TreePtr::<Node>::new(parent_id as u32);
|
||||
match self
|
||||
.fs
|
||||
.tx(|tx| tx.remove_node(parent_ptr, name.to_str().unwrap(), Node::MODE_FILE))
|
||||
{
|
||||
Ok(_) => {
|
||||
reply.ok();
|
||||
}
|
||||
Err(err) => {
|
||||
reply.error(err.errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn statfs(&mut self, _req: &Request, _ino: u64, reply: ReplyStatfs) {
|
||||
let bsize = BLOCK_SIZE;
|
||||
let blocks = self.fs.header.size() / bsize;
|
||||
let bfree = self.fs.allocator().free();
|
||||
reply.statfs(blocks, bfree, bfree, 0, 0, bsize as u32, 256, 0);
|
||||
}
|
||||
|
||||
fn symlink(
|
||||
&mut self,
|
||||
_req: &Request,
|
||||
parent_id: u64,
|
||||
name: &OsStr,
|
||||
link: &Path,
|
||||
reply: ReplyEntry,
|
||||
) {
|
||||
let parent_ptr = TreePtr::<Node>::new(parent_id as u32);
|
||||
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
match self.fs.tx(|tx| {
|
||||
let node = tx.create_node(
|
||||
parent_ptr,
|
||||
name.to_str().unwrap(),
|
||||
Node::MODE_SYMLINK | 0o777,
|
||||
ctime.as_secs(),
|
||||
ctime.subsec_nanos(),
|
||||
)?;
|
||||
|
||||
let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
tx.write_node(
|
||||
node.ptr(),
|
||||
0,
|
||||
link.as_os_str().as_bytes(),
|
||||
mtime.as_secs(),
|
||||
mtime.subsec_nanos(),
|
||||
)?;
|
||||
|
||||
Ok(node)
|
||||
}) {
|
||||
Ok(node) => {
|
||||
reply.entry(&TTL, &node_attr(&node), 0);
|
||||
}
|
||||
Err(error) => {
|
||||
reply.error(error.errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn readlink(&mut self, _req: &Request, node_id: u64, reply: ReplyData) {
|
||||
let node_ptr = TreePtr::<Node>::new(node_id as u32);
|
||||
let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
let mut data = vec![0; 4096];
|
||||
match self.fs.tx(|tx| {
|
||||
tx.read_node(
|
||||
node_ptr,
|
||||
0,
|
||||
&mut data,
|
||||
atime.as_secs(),
|
||||
atime.subsec_nanos(),
|
||||
)
|
||||
}) {
|
||||
Ok(count) => {
|
||||
reply.data(&data[..count]);
|
||||
}
|
||||
Err(err) => {
|
||||
reply.error(err.errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rename(
|
||||
&mut self,
|
||||
_req: &Request,
|
||||
orig_parent: u64,
|
||||
orig_name: &OsStr,
|
||||
new_parent: u64,
|
||||
new_name: &OsStr,
|
||||
_flags: u32,
|
||||
reply: ReplyEmpty,
|
||||
) {
|
||||
let orig_parent_ptr = TreePtr::<Node>::new(orig_parent as u32);
|
||||
let orig_name = orig_name.to_str().expect("name is not utf-8");
|
||||
let new_parent_ptr = TreePtr::<Node>::new(new_parent as u32);
|
||||
let new_name = new_name.to_str().expect("name is not utf-8");
|
||||
|
||||
// TODO: improve performance
|
||||
match self
|
||||
.fs
|
||||
.tx(|tx| tx.rename_node(orig_parent_ptr, orig_name, new_parent_ptr, new_name))
|
||||
{
|
||||
Ok(()) => reply.ok(),
|
||||
Err(err) => reply.error(err.errno),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#[cfg(all(not(target_os = "redox"), not(fuzzing), feature = "fuse"))]
|
||||
mod fuse;
|
||||
|
||||
#[cfg(all(not(target_os = "redox"), fuzzing, feature = "fuse"))]
|
||||
pub mod fuse;
|
||||
|
||||
#[cfg(all(not(target_os = "redox"), feature = "fuse"))]
|
||||
pub use self::fuse::mount;
|
||||
|
||||
#[cfg(all(not(target_os = "redox"), not(fuzzing), not(feature = "fuse")))]
|
||||
mod stub;
|
||||
|
||||
#[cfg(all(not(target_os = "redox"), fuzzing, not(feature = "fuse")))]
|
||||
pub mod stub;
|
||||
|
||||
#[cfg(all(not(target_os = "redox"), not(feature = "fuse")))]
|
||||
pub use self::stub::mount;
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
mod redox;
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
pub use self::redox::mount;
|
||||
@@ -1,89 +0,0 @@
|
||||
use redox_scheme::{
|
||||
scheme::{SchemeState, SchemeSync},
|
||||
RequestKind, Response, SignalBehavior, Socket,
|
||||
};
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::{Disk, FileSystem, IS_UMT};
|
||||
|
||||
use self::scheme::FileScheme;
|
||||
|
||||
pub mod resource;
|
||||
pub mod scheme;
|
||||
|
||||
//FIXME: mut callback is not mut
|
||||
#[allow(unused_mut)]
|
||||
|
||||
pub fn mount<D, P, T, F>(filesystem: FileSystem<D>, mountpoint: P, mut callback: F) -> io::Result<T>
|
||||
where
|
||||
D: Disk,
|
||||
P: AsRef<Path>,
|
||||
F: FnOnce(&Path) -> T,
|
||||
{
|
||||
let mountpoint = mountpoint.as_ref();
|
||||
let socket = Socket::create()?;
|
||||
|
||||
let scheme_name = format!("{}", mountpoint.display());
|
||||
let mounted_path = format!("/scheme/{}", mountpoint.display());
|
||||
|
||||
let mut state = SchemeState::new();
|
||||
let mut scheme = FileScheme::new(scheme_name, mounted_path.clone(), filesystem, &socket)?;
|
||||
|
||||
redox_scheme::scheme::register_sync_scheme(
|
||||
&socket,
|
||||
&format!("{}", mountpoint.display()),
|
||||
&mut scheme,
|
||||
)?;
|
||||
|
||||
let res = callback(Path::new(&mounted_path));
|
||||
|
||||
while IS_UMT.load(Ordering::SeqCst) == 0 {
|
||||
let req = match socket.next_request(SignalBehavior::Restart)? {
|
||||
None => break,
|
||||
Some(req) => {
|
||||
match req.kind() {
|
||||
RequestKind::Call(r) => r,
|
||||
RequestKind::SendFd(sendfd_request) => {
|
||||
let result = scheme.on_sendfd(&sendfd_request);
|
||||
let response = Response::new(result, sendfd_request);
|
||||
|
||||
if !socket.write_response(response, SignalBehavior::Restart)? {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
RequestKind::OnClose { id } => {
|
||||
scheme.on_close(id);
|
||||
state.on_close(id);
|
||||
continue;
|
||||
}
|
||||
RequestKind::OnDetach { id, pid } => {
|
||||
let Ok(inode) = scheme.inode(id) else {
|
||||
log::warn!("RequestKind::OnDetach with invalid `id`");
|
||||
continue;
|
||||
};
|
||||
state.on_detach(id, inode, pid);
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
// TODO: Redoxfs does not yet support asynchronous file IO. It might still make
|
||||
// sense to implement cancellation for huge buffers, e.g. dd bs=1G
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let response = req.handle_sync(&mut scheme, &mut state);
|
||||
|
||||
if !socket.write_response(response, SignalBehavior::Restart)? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup on unmount
|
||||
scheme.fs.cleanup()?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
@@ -1,794 +0,0 @@
|
||||
use std::slice;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use libredox::call::MmapArgs;
|
||||
use range_tree::RangeTree;
|
||||
|
||||
use syscall::data::{Stat, TimeSpec};
|
||||
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
|
||||
use syscall::error::{Error, Result, EBADF, EINVAL, EISDIR, ENOTDIR, EPERM};
|
||||
use syscall::flag::{
|
||||
MapFlags, F_GETFL, F_SETFL, MODE_PERM, O_ACCMODE, O_APPEND, O_RDONLY, O_RDWR, O_WRONLY,
|
||||
PROT_READ, PROT_WRITE,
|
||||
};
|
||||
use syscall::{EBADFD, ENOENT, PAGE_SIZE};
|
||||
|
||||
use crate::{Disk, Node, Transaction, TreePtr, BLOCK_SIZE};
|
||||
|
||||
pub type Fmaps = BTreeMap<u32, FileMmapInfo>;
|
||||
|
||||
pub trait Resource<D: Disk> {
|
||||
fn parent_ptr_opt(&self) -> Option<TreePtr<Node>>;
|
||||
|
||||
fn node_ptr(&self) -> TreePtr<Node>;
|
||||
|
||||
fn uid(&self) -> u32;
|
||||
|
||||
fn set_path(&mut self, path: &str);
|
||||
|
||||
fn read(&mut self, buf: &mut [u8], offset: u64, tx: &mut Transaction<D>) -> Result<usize>;
|
||||
|
||||
fn write(&mut self, buf: &[u8], offset: u64, tx: &mut Transaction<D>) -> Result<usize>;
|
||||
|
||||
fn fsize(&mut self, tx: &mut Transaction<D>) -> Result<u64>;
|
||||
|
||||
fn fmap(
|
||||
&mut self,
|
||||
fmaps: &mut Fmaps,
|
||||
flags: MapFlags,
|
||||
size: usize,
|
||||
offset: u64,
|
||||
tx: &mut Transaction<D>,
|
||||
) -> Result<usize>;
|
||||
|
||||
fn funmap(
|
||||
&mut self,
|
||||
fmaps: &mut Fmaps,
|
||||
offset: u64,
|
||||
size: usize,
|
||||
tx: &mut Transaction<D>,
|
||||
) -> Result<()>;
|
||||
|
||||
fn fchmod(&mut self, mode: u16, tx: &mut Transaction<D>) -> Result<()> {
|
||||
let mut node = tx.read_tree(self.node_ptr())?;
|
||||
|
||||
if node.data().uid() == self.uid() || self.uid() == 0 {
|
||||
let old_mode = node.data().mode();
|
||||
let new_mode = (old_mode & !MODE_PERM) | (mode & MODE_PERM);
|
||||
if old_mode != new_mode {
|
||||
node.data_mut().set_mode(new_mode);
|
||||
tx.sync_tree(node)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(EPERM))
|
||||
}
|
||||
}
|
||||
|
||||
fn fchown(&mut self, uid: u32, gid: u32, tx: &mut Transaction<D>) -> Result<()> {
|
||||
let mut node = tx.read_tree(self.node_ptr())?;
|
||||
|
||||
let old_uid = node.data().uid();
|
||||
if old_uid == self.uid() || self.uid() == 0 {
|
||||
let mut node_changed = false;
|
||||
|
||||
if uid as i32 != -1 {
|
||||
if uid != old_uid {
|
||||
node.data_mut().set_uid(uid);
|
||||
node_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if gid as i32 != -1 {
|
||||
let old_gid = node.data().gid();
|
||||
if gid != old_gid {
|
||||
node.data_mut().set_gid(gid);
|
||||
node_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if node_changed {
|
||||
tx.sync_tree(node)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(EPERM))
|
||||
}
|
||||
}
|
||||
|
||||
fn fcntl(&mut self, cmd: usize, arg: usize) -> Result<usize>;
|
||||
|
||||
fn path(&self) -> &str;
|
||||
|
||||
fn stat(&self, stat: &mut Stat, tx: &mut Transaction<D>) -> Result<()> {
|
||||
let node = tx.read_tree(self.node_ptr())?;
|
||||
|
||||
let ctime = node.data().ctime();
|
||||
let mtime = node.data().mtime();
|
||||
let atime = node.data().atime();
|
||||
*stat = Stat {
|
||||
st_dev: 0, // TODO
|
||||
st_ino: node.id() as u64,
|
||||
st_mode: node.data().mode(),
|
||||
st_nlink: node.data().links(),
|
||||
st_uid: node.data().uid(),
|
||||
st_gid: node.data().gid(),
|
||||
st_size: node.data().size(),
|
||||
st_blksize: 512,
|
||||
// Blocks is in 512 byte blocks, not in our block size
|
||||
st_blocks: node.data().blocks() * (BLOCK_SIZE / 512),
|
||||
st_mtime: mtime.0,
|
||||
st_mtime_nsec: mtime.1,
|
||||
st_atime: atime.0,
|
||||
st_atime_nsec: atime.1,
|
||||
st_ctime: ctime.0,
|
||||
st_ctime_nsec: ctime.1,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sync(&mut self, fmaps: &mut Fmaps, tx: &mut Transaction<D>) -> Result<()>;
|
||||
|
||||
fn truncate(&mut self, len: u64, tx: &mut Transaction<D>) -> Result<()>;
|
||||
|
||||
fn utimens(&mut self, times: &[TimeSpec], tx: &mut Transaction<D>) -> Result<()>;
|
||||
|
||||
fn getdents<'buf>(
|
||||
&mut self,
|
||||
buf: DirentBuf<&'buf mut [u8]>,
|
||||
opaque_offset: u64,
|
||||
tx: &mut Transaction<D>,
|
||||
) -> Result<DirentBuf<&'buf mut [u8]>>;
|
||||
}
|
||||
|
||||
pub struct Entry {
|
||||
pub node_ptr: TreePtr<Node>,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
pub struct DirResource {
|
||||
path: String,
|
||||
parent_ptr_opt: Option<TreePtr<Node>>,
|
||||
node_ptr: TreePtr<Node>,
|
||||
data: Option<Vec<Entry>>,
|
||||
uid: u32,
|
||||
}
|
||||
|
||||
impl DirResource {
|
||||
pub fn new(
|
||||
path: String,
|
||||
parent_ptr_opt: Option<TreePtr<Node>>,
|
||||
node_ptr: TreePtr<Node>,
|
||||
data: Option<Vec<Entry>>,
|
||||
uid: u32,
|
||||
) -> DirResource {
|
||||
DirResource {
|
||||
path,
|
||||
parent_ptr_opt,
|
||||
node_ptr,
|
||||
data,
|
||||
uid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Disk> Resource<D> for DirResource {
|
||||
fn parent_ptr_opt(&self) -> Option<TreePtr<Node>> {
|
||||
self.parent_ptr_opt
|
||||
}
|
||||
|
||||
fn node_ptr(&self) -> TreePtr<Node> {
|
||||
self.node_ptr
|
||||
}
|
||||
|
||||
fn uid(&self) -> u32 {
|
||||
self.uid
|
||||
}
|
||||
|
||||
fn set_path(&mut self, path: &str) {
|
||||
self.path = path.to_string();
|
||||
}
|
||||
|
||||
fn read(&mut self, _buf: &mut [u8], _offset: u64, _tx: &mut Transaction<D>) -> Result<usize> {
|
||||
Err(Error::new(EISDIR))
|
||||
}
|
||||
|
||||
fn write(&mut self, _buf: &[u8], _offset: u64, _tx: &mut Transaction<D>) -> Result<usize> {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
|
||||
fn fsize(&mut self, _tx: &mut Transaction<D>) -> Result<u64> {
|
||||
Ok(self.data.as_ref().ok_or(Error::new(EBADF))?.len() as u64)
|
||||
}
|
||||
|
||||
fn fmap(
|
||||
&mut self,
|
||||
_fmaps: &mut Fmaps,
|
||||
_flags: MapFlags,
|
||||
_size: usize,
|
||||
_offset: u64,
|
||||
_tx: &mut Transaction<D>,
|
||||
) -> Result<usize> {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
fn funmap(
|
||||
&mut self,
|
||||
_fmaps: &mut Fmaps,
|
||||
_offset: u64,
|
||||
_size: usize,
|
||||
_tx: &mut Transaction<D>,
|
||||
) -> Result<()> {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
|
||||
fn fcntl(&mut self, _cmd: usize, _arg: usize) -> Result<usize> {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
|
||||
fn path(&self) -> &str {
|
||||
&self.path
|
||||
}
|
||||
|
||||
fn sync(&mut self, _fmaps: &mut Fmaps, _tx: &mut Transaction<D>) -> Result<()> {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
|
||||
fn truncate(&mut self, _len: u64, _tx: &mut Transaction<D>) -> Result<()> {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
|
||||
fn utimens(&mut self, times: &[TimeSpec], tx: &mut Transaction<D>) -> Result<()> {
|
||||
let mut node = tx.read_tree(self.node_ptr)?;
|
||||
|
||||
if node.data().uid() == self.uid || self.uid == 0 {
|
||||
if let &[atime, mtime] = times {
|
||||
let mut node_changed = false;
|
||||
|
||||
let old_mtime = node.data().mtime();
|
||||
let new_mtime = (mtime.tv_sec as u64, mtime.tv_nsec as u32);
|
||||
if old_mtime != new_mtime {
|
||||
node.data_mut().set_mtime(new_mtime.0, new_mtime.1);
|
||||
node_changed = true;
|
||||
}
|
||||
|
||||
let old_atime = node.data().atime();
|
||||
let new_atime = (atime.tv_sec as u64, atime.tv_nsec as u32);
|
||||
if old_atime != new_atime {
|
||||
node.data_mut().set_atime(new_atime.0, new_atime.1);
|
||||
node_changed = true;
|
||||
}
|
||||
|
||||
if node_changed {
|
||||
tx.sync_tree(node)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(EPERM))
|
||||
}
|
||||
}
|
||||
|
||||
fn getdents<'buf>(
|
||||
&mut self,
|
||||
mut buf: DirentBuf<&'buf mut [u8]>,
|
||||
opaque_offset: u64,
|
||||
tx: &mut Transaction<D>,
|
||||
) -> Result<DirentBuf<&'buf mut [u8]>> {
|
||||
match &self.data {
|
||||
Some(data) => {
|
||||
let opaque_offset = opaque_offset as usize;
|
||||
for (idx, entry) in data.iter().enumerate().skip(opaque_offset) {
|
||||
let child = match tx.read_tree(entry.node_ptr) {
|
||||
Ok(r) => r,
|
||||
Err(Error { errno: ENOENT }) => continue,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
let result = buf.entry(DirEntry {
|
||||
inode: child.id() as u64,
|
||||
next_opaque_id: idx as u64 + 1,
|
||||
name: &entry.name,
|
||||
kind: match child.data().mode() & Node::MODE_TYPE {
|
||||
Node::MODE_DIR => DirentKind::Directory,
|
||||
Node::MODE_FILE => DirentKind::Regular,
|
||||
Node::MODE_SYMLINK => DirentKind::Symlink,
|
||||
//TODO: more types?
|
||||
_ => DirentKind::Unspecified,
|
||||
},
|
||||
});
|
||||
if let Err(err) = result {
|
||||
if err.errno == EINVAL && idx > opaque_offset {
|
||||
// POSIX allows partial result of getdents
|
||||
break;
|
||||
} else {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(buf)
|
||||
}
|
||||
None => Err(Error::new(EBADF)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Fmap {
|
||||
rc: usize,
|
||||
flags: MapFlags,
|
||||
last_page_tail: u16,
|
||||
}
|
||||
|
||||
impl Fmap {
|
||||
pub unsafe fn new<D: Disk>(
|
||||
node_ptr: TreePtr<Node>,
|
||||
flags: MapFlags,
|
||||
unaligned_size: usize,
|
||||
offset: u64,
|
||||
base: *mut u8,
|
||||
tx: &mut Transaction<D>,
|
||||
) -> Result<Self> {
|
||||
// Memory provided to fmap must be page aligned and sized
|
||||
let aligned_size = unaligned_size.next_multiple_of(syscall::PAGE_SIZE);
|
||||
|
||||
let address = base.add(offset as usize);
|
||||
//println!("ADDR {:p} {:p}", base, address);
|
||||
|
||||
// Read buffer from disk
|
||||
let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
|
||||
let buf = slice::from_raw_parts_mut(address, unaligned_size);
|
||||
|
||||
let count = match tx.read_node(node_ptr, offset, buf, atime.as_secs(), atime.subsec_nanos())
|
||||
{
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
let _ = libredox::call::munmap(address.cast(), aligned_size);
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure remaining data is zeroed
|
||||
buf[count..].fill(0_u8);
|
||||
|
||||
Ok(Self {
|
||||
rc: 1,
|
||||
flags,
|
||||
last_page_tail: (unaligned_size % PAGE_SIZE) as u16,
|
||||
})
|
||||
}
|
||||
|
||||
pub unsafe fn sync<D: Disk>(
|
||||
&mut self,
|
||||
node_ptr: TreePtr<Node>,
|
||||
base: *mut u8,
|
||||
offset: u64,
|
||||
size: usize,
|
||||
tx: &mut Transaction<D>,
|
||||
) -> Result<()> {
|
||||
if self.flags & PROT_WRITE == PROT_WRITE {
|
||||
let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
tx.write_node(
|
||||
node_ptr,
|
||||
offset,
|
||||
unsafe { core::slice::from_raw_parts(base.add(offset as usize), size) },
|
||||
mtime.as_secs(),
|
||||
mtime.subsec_nanos(),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FileResource {
|
||||
path: String,
|
||||
parent_ptr_opt: Option<TreePtr<Node>>,
|
||||
node_ptr: TreePtr<Node>,
|
||||
flags: usize,
|
||||
uid: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileMmapInfo {
|
||||
base: *mut u8,
|
||||
size: usize,
|
||||
pub ranges: RangeTree<Fmap>,
|
||||
pub open_fds: usize,
|
||||
}
|
||||
|
||||
impl FileMmapInfo {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
base: core::ptr::null_mut(),
|
||||
size: 0,
|
||||
ranges: RangeTree::new(),
|
||||
open_fds: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn in_use(&self) -> bool {
|
||||
self.open_fds > 0 || !self.ranges.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FileMmapInfo {
|
||||
fn drop(&mut self) {
|
||||
if self.in_use() {
|
||||
log::error!("FileMmapInfo dropped while in use");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileResource {
|
||||
pub fn new(
|
||||
path: String,
|
||||
parent_ptr_opt: Option<TreePtr<Node>>,
|
||||
node_ptr: TreePtr<Node>,
|
||||
flags: usize,
|
||||
uid: u32,
|
||||
) -> FileResource {
|
||||
FileResource {
|
||||
path,
|
||||
parent_ptr_opt,
|
||||
node_ptr,
|
||||
flags,
|
||||
uid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Disk> Resource<D> for FileResource {
|
||||
fn parent_ptr_opt(&self) -> Option<TreePtr<Node>> {
|
||||
self.parent_ptr_opt
|
||||
}
|
||||
|
||||
fn node_ptr(&self) -> TreePtr<Node> {
|
||||
self.node_ptr
|
||||
}
|
||||
|
||||
fn uid(&self) -> u32 {
|
||||
self.uid
|
||||
}
|
||||
|
||||
fn set_path(&mut self, path: &str) {
|
||||
self.path = path.to_string();
|
||||
}
|
||||
|
||||
fn read(&mut self, buf: &mut [u8], offset: u64, tx: &mut Transaction<D>) -> Result<usize> {
|
||||
if self.flags & O_ACCMODE != O_RDWR && self.flags & O_ACCMODE != O_RDONLY {
|
||||
return Err(Error::new(EBADF));
|
||||
}
|
||||
let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
tx.read_node(
|
||||
self.node_ptr,
|
||||
offset,
|
||||
buf,
|
||||
atime.as_secs(),
|
||||
atime.subsec_nanos(),
|
||||
)
|
||||
}
|
||||
|
||||
fn write(&mut self, buf: &[u8], offset: u64, tx: &mut Transaction<D>) -> Result<usize> {
|
||||
if self.flags & O_ACCMODE != O_RDWR && self.flags & O_ACCMODE != O_WRONLY {
|
||||
return Err(Error::new(EBADF));
|
||||
}
|
||||
let effective_offset = if self.flags & O_APPEND == O_APPEND {
|
||||
let node = tx.read_tree(self.node_ptr)?;
|
||||
node.data().size()
|
||||
} else {
|
||||
offset
|
||||
};
|
||||
let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
tx.write_node(
|
||||
self.node_ptr,
|
||||
effective_offset,
|
||||
buf,
|
||||
mtime.as_secs(),
|
||||
mtime.subsec_nanos(),
|
||||
)
|
||||
}
|
||||
|
||||
fn fsize(&mut self, tx: &mut Transaction<D>) -> Result<u64> {
|
||||
let node = tx.read_tree(self.node_ptr)?;
|
||||
Ok(node.data().size())
|
||||
}
|
||||
|
||||
fn fmap(
|
||||
&mut self,
|
||||
fmaps: &mut Fmaps,
|
||||
flags: MapFlags,
|
||||
unaligned_size: usize,
|
||||
offset: u64,
|
||||
tx: &mut Transaction<D>,
|
||||
) -> Result<usize> {
|
||||
//dbg!(&self.fmaps);
|
||||
let accmode = self.flags & O_ACCMODE;
|
||||
if flags.contains(PROT_READ) && !(accmode == O_RDWR || accmode == O_RDONLY) {
|
||||
return Err(Error::new(EBADF));
|
||||
}
|
||||
if flags.contains(PROT_WRITE) && !(accmode == O_RDWR || accmode == O_WRONLY) {
|
||||
return Err(Error::new(EBADF));
|
||||
}
|
||||
|
||||
let aligned_size = unaligned_size.next_multiple_of(PAGE_SIZE);
|
||||
|
||||
// TODO: PROT_EXEC? It is however unenforcable without restricting anonymous mmap, since a
|
||||
// program can always map anonymous RW-, read from a file, then remap as R-E. But it might
|
||||
// be usable as a hint, prohibiting direct executable mmaps at least.
|
||||
|
||||
// TODO: Pass entry directory to Resource trait functions, since the node_ptr can be
|
||||
// obtained by the caller.
|
||||
let fmap_info = fmaps
|
||||
.get_mut(&self.node_ptr.id())
|
||||
.ok_or(Error::new(EBADFD))?;
|
||||
|
||||
if !fmap_info.in_use() {
|
||||
// Notify filesystem of open
|
||||
tx.on_open_node(self.node_ptr)?;
|
||||
}
|
||||
|
||||
let new_size = (offset as usize + aligned_size).next_multiple_of(PAGE_SIZE);
|
||||
if new_size > fmap_info.size {
|
||||
fmap_info.base = if fmap_info.base.is_null() {
|
||||
unsafe {
|
||||
libredox::call::mmap(MmapArgs {
|
||||
length: new_size,
|
||||
// PRIVATE/SHARED doesn't matter once the pages are passed in the fmap
|
||||
// handler.
|
||||
prot: libredox::flag::PROT_READ | libredox::flag::PROT_WRITE,
|
||||
flags: libredox::flag::MAP_PRIVATE,
|
||||
|
||||
offset: 0,
|
||||
fd: !0,
|
||||
addr: core::ptr::null_mut(),
|
||||
})? as *mut u8
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
syscall::syscall5(
|
||||
syscall::SYS_MREMAP,
|
||||
fmap_info.base as usize,
|
||||
fmap_info.size,
|
||||
0,
|
||||
new_size,
|
||||
syscall::MremapFlags::empty().bits() | (PROT_READ | PROT_WRITE).bits(),
|
||||
)? as *mut u8
|
||||
}
|
||||
};
|
||||
fmap_info.size = new_size;
|
||||
}
|
||||
|
||||
let affected_fmaps = fmap_info
|
||||
.ranges
|
||||
.remove_and_unused(offset..offset + aligned_size as u64);
|
||||
|
||||
for (range, v_opt) in affected_fmaps {
|
||||
//dbg!(&range);
|
||||
if let Some(mut fmap) = v_opt {
|
||||
fmap.rc += 1;
|
||||
fmap.flags |= flags;
|
||||
//FIXME: Use result?
|
||||
let _ = fmap_info
|
||||
.ranges
|
||||
.insert(range.start, range.end - range.start, fmap);
|
||||
} else {
|
||||
let map = unsafe {
|
||||
Fmap::new(
|
||||
self.node_ptr,
|
||||
flags,
|
||||
unaligned_size,
|
||||
offset,
|
||||
fmap_info.base,
|
||||
tx,
|
||||
)?
|
||||
};
|
||||
//FIXME: Use result?
|
||||
let _ = fmap_info.ranges.insert(offset, aligned_size as u64, map);
|
||||
}
|
||||
}
|
||||
//dbg!(&self.fmaps);
|
||||
|
||||
Ok(fmap_info.base as usize + offset as usize)
|
||||
}
|
||||
|
||||
fn funmap(
|
||||
&mut self,
|
||||
fmaps: &mut Fmaps,
|
||||
offset: u64,
|
||||
size: usize,
|
||||
tx: &mut Transaction<D>,
|
||||
) -> Result<()> {
|
||||
let fmap_info = fmaps
|
||||
.get_mut(&self.node_ptr.id())
|
||||
.ok_or(Error::new(EBADFD))?;
|
||||
|
||||
//dbg!(&self.fmaps);
|
||||
//dbg!(self.fmaps.conflicts(offset..offset + size as u64).collect::<Vec<_>>());
|
||||
#[allow(unused_mut)]
|
||||
let mut affected_fmaps = fmap_info.ranges.remove(offset..offset + size as u64);
|
||||
|
||||
for (range, mut fmap) in affected_fmaps {
|
||||
fmap.rc = fmap.rc.checked_sub(1).unwrap();
|
||||
|
||||
//log::info!("SYNCING {}..{}", range.start, range.end);
|
||||
unsafe {
|
||||
fmap.sync(
|
||||
self.node_ptr,
|
||||
fmap_info.base,
|
||||
range.start,
|
||||
(range.end - range.start) as usize,
|
||||
tx,
|
||||
)?;
|
||||
}
|
||||
|
||||
if fmap.rc > 0 {
|
||||
//FIXME: Use result?
|
||||
let _ = fmap_info
|
||||
.ranges
|
||||
.insert(range.start, range.end - range.start, fmap);
|
||||
}
|
||||
}
|
||||
//dbg!(&self.fmaps);
|
||||
|
||||
// Allow release of node if not in use anymore
|
||||
if !fmap_info.in_use() {
|
||||
// Notify filesystem of close
|
||||
tx.on_close_node(self.node_ptr)?;
|
||||
|
||||
/*TODO: leaks memory, but why?
|
||||
// Remove from fmaps list
|
||||
fmaps.remove(&self.node_ptr.id());
|
||||
*/
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fcntl(&mut self, cmd: usize, arg: usize) -> Result<usize> {
|
||||
match cmd {
|
||||
F_GETFL => Ok(self.flags),
|
||||
F_SETFL => {
|
||||
self.flags = (self.flags & O_ACCMODE) | (arg & !O_ACCMODE);
|
||||
Ok(0)
|
||||
}
|
||||
_ => Err(Error::new(EINVAL)),
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> &str {
|
||||
&self.path
|
||||
}
|
||||
|
||||
fn sync(&mut self, fmaps: &mut Fmaps, tx: &mut Transaction<D>) -> Result<()> {
|
||||
if let Some(fmap_info) = fmaps.get_mut(&self.node_ptr.id()) {
|
||||
for (range, fmap) in fmap_info.ranges.iter_mut() {
|
||||
unsafe {
|
||||
fmap.sync(
|
||||
self.node_ptr,
|
||||
fmap_info.base,
|
||||
range.start,
|
||||
(range.end - range.start) as usize,
|
||||
tx,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn truncate(&mut self, len: u64, tx: &mut Transaction<D>) -> Result<()> {
|
||||
if self.flags & O_ACCMODE == O_RDWR || self.flags & O_ACCMODE == O_WRONLY {
|
||||
let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
tx.truncate_node(self.node_ptr, len, mtime.as_secs(), mtime.subsec_nanos())?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
}
|
||||
|
||||
fn utimens(&mut self, times: &[TimeSpec], tx: &mut Transaction<D>) -> Result<()> {
|
||||
let mut node = tx.read_tree(self.node_ptr)?;
|
||||
|
||||
if node.data().uid() == self.uid || self.uid == 0 {
|
||||
if let &[atime, mtime] = times {
|
||||
let mut node_changed = false;
|
||||
|
||||
let old_mtime = node.data().mtime();
|
||||
let new_mtime = (mtime.tv_sec as u64, mtime.tv_nsec as u32);
|
||||
if old_mtime != new_mtime {
|
||||
node.data_mut().set_mtime(new_mtime.0, new_mtime.1);
|
||||
node_changed = true;
|
||||
}
|
||||
|
||||
let old_atime = node.data().atime();
|
||||
let new_atime = (atime.tv_sec as u64, atime.tv_nsec as u32);
|
||||
if old_atime != new_atime {
|
||||
node.data_mut().set_atime(new_atime.0, new_atime.1);
|
||||
node_changed = true;
|
||||
}
|
||||
|
||||
if node_changed {
|
||||
tx.sync_tree(node)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(EPERM))
|
||||
}
|
||||
}
|
||||
|
||||
fn getdents<'buf>(
|
||||
&mut self,
|
||||
_buf: DirentBuf<&'buf mut [u8]>,
|
||||
_opaque_offset: u64,
|
||||
_tx: &mut Transaction<D>,
|
||||
) -> Result<DirentBuf<&'buf mut [u8]>> {
|
||||
Err(Error::new(ENOTDIR))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FileResource {
|
||||
fn drop(&mut self) {
|
||||
/*
|
||||
if !self.fmaps.is_empty() {
|
||||
eprintln!(
|
||||
"redoxfs: file {} still has {} fmaps!",
|
||||
self.path,
|
||||
self.fmaps.len()
|
||||
);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
impl range_tree::Value for Fmap {
|
||||
type K = u64;
|
||||
|
||||
fn try_merge_forward(self, other: &Self) -> core::result::Result<Self, Self> {
|
||||
if self.rc == other.rc && self.flags == other.flags && self.last_page_tail == 0 {
|
||||
Ok(self)
|
||||
} else {
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
fn try_merge_backwards(self, other: &Self) -> core::result::Result<Self, Self> {
|
||||
if self.rc == other.rc && self.flags == other.flags && other.last_page_tail == 0 {
|
||||
Ok(self)
|
||||
} else {
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
#[allow(unused_variables)]
|
||||
fn split(
|
||||
self,
|
||||
prev_range: Option<core::ops::Range<Self::K>>,
|
||||
range: core::ops::Range<Self::K>,
|
||||
next_range: Option<core::ops::Range<Self::K>>,
|
||||
) -> (Option<Self>, Self, Option<Self>) {
|
||||
(
|
||||
prev_range.map(|_range| Fmap {
|
||||
rc: self.rc,
|
||||
flags: self.flags,
|
||||
last_page_tail: 0,
|
||||
}),
|
||||
Fmap {
|
||||
rc: self.rc,
|
||||
flags: self.flags,
|
||||
last_page_tail: if next_range.is_none() {
|
||||
self.last_page_tail
|
||||
} else {
|
||||
0
|
||||
},
|
||||
},
|
||||
next_range.map(|_range| Fmap {
|
||||
rc: self.rc,
|
||||
flags: self.flags,
|
||||
last_page_tail: self.last_page_tail,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +0,0 @@
|
||||
use std::{io, path::Path};
|
||||
|
||||
use crate::{filesystem, Disk};
|
||||
|
||||
pub fn mount<D, P, T, F>(
|
||||
mut _filesystem: filesystem::FileSystem<D>,
|
||||
_mountpoint: P,
|
||||
_callback: F,
|
||||
) -> io::Result<T>
|
||||
where
|
||||
D: Disk,
|
||||
P: AsRef<Path>,
|
||||
F: FnOnce(&Path) -> T,
|
||||
{
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
"FUSE mount feature is disabled",
|
||||
))
|
||||
}
|
||||
-584
@@ -1,584 +0,0 @@
|
||||
use core::{fmt, mem, ops, slice};
|
||||
use endian_num::Le;
|
||||
|
||||
use crate::{BlockLevel, BlockList, BlockPtr, BlockTrait, RecordRaw, BLOCK_SIZE, RECORD_LEVEL};
|
||||
|
||||
bitflags::bitflags! {
|
||||
pub struct NodeFlags: u32 {
|
||||
const INLINE_DATA = 0x1;
|
||||
}
|
||||
}
|
||||
|
||||
/// An index into a [`Node`]'s block table.
|
||||
pub enum NodeLevel {
|
||||
L0(usize),
|
||||
L1(usize, usize),
|
||||
L2(usize, usize, usize),
|
||||
L3(usize, usize, usize, usize),
|
||||
L4(usize, usize, usize, usize, usize),
|
||||
}
|
||||
|
||||
impl NodeLevel {
|
||||
// Warning: this uses constant record offsets, make sure to sync with Node
|
||||
|
||||
/// Return the [`NodeLevel`] of the record with the given index.
|
||||
/// - the first 128 are level 0,
|
||||
/// - the next 64*256 are level 1,
|
||||
/// - ...and so on.
|
||||
pub fn new(mut record_offset: u64) -> Option<Self> {
|
||||
// 1 << 8 = 256, this is the number of entries in a BlockList
|
||||
const SHIFT: u64 = 8;
|
||||
const NUM: u64 = 1 << SHIFT;
|
||||
const MASK: u64 = NUM - 1;
|
||||
|
||||
const L0: u64 = 128;
|
||||
if record_offset < L0 {
|
||||
return Some(Self::L0((record_offset & MASK) as usize));
|
||||
} else {
|
||||
record_offset -= L0;
|
||||
}
|
||||
|
||||
const L1: u64 = 64 * NUM;
|
||||
if record_offset < L1 {
|
||||
return Some(Self::L1(
|
||||
((record_offset >> SHIFT) & MASK) as usize,
|
||||
(record_offset & MASK) as usize,
|
||||
));
|
||||
} else {
|
||||
record_offset -= L1;
|
||||
}
|
||||
|
||||
const L2: u64 = 32 * NUM * NUM;
|
||||
if record_offset < L2 {
|
||||
return Some(Self::L2(
|
||||
((record_offset >> (2 * SHIFT)) & MASK) as usize,
|
||||
((record_offset >> SHIFT) & MASK) as usize,
|
||||
(record_offset & MASK) as usize,
|
||||
));
|
||||
} else {
|
||||
record_offset -= L2;
|
||||
}
|
||||
|
||||
const L3: u64 = 16 * NUM * NUM * NUM;
|
||||
if record_offset < L3 {
|
||||
return Some(Self::L3(
|
||||
((record_offset >> (3 * SHIFT)) & MASK) as usize,
|
||||
((record_offset >> (2 * SHIFT)) & MASK) as usize,
|
||||
((record_offset >> SHIFT) & MASK) as usize,
|
||||
(record_offset & MASK) as usize,
|
||||
));
|
||||
} else {
|
||||
record_offset -= L3;
|
||||
}
|
||||
|
||||
const L4: u64 = 12 * NUM * NUM * NUM * NUM;
|
||||
if record_offset < L4 {
|
||||
Some(Self::L4(
|
||||
((record_offset >> (4 * SHIFT)) & MASK) as usize,
|
||||
((record_offset >> (3 * SHIFT)) & MASK) as usize,
|
||||
((record_offset >> (2 * SHIFT)) & MASK) as usize,
|
||||
((record_offset >> SHIFT) & MASK) as usize,
|
||||
(record_offset & MASK) as usize,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type BlockListL1 = BlockList<RecordRaw>;
|
||||
type BlockListL2 = BlockList<BlockListL1>;
|
||||
type BlockListL3 = BlockList<BlockListL2>;
|
||||
type BlockListL4 = BlockList<BlockListL3>;
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct NodeLevelData {
|
||||
/// The first 128 blocks of this file.
|
||||
///
|
||||
/// Total size: 128 * RECORD_SIZE (16 MiB, 128 KiB each)
|
||||
pub level0: [BlockPtr<RecordRaw>; 128],
|
||||
|
||||
/// The next 64 * 256 blocks of this file,
|
||||
/// stored behind 64 level one tables.
|
||||
///
|
||||
/// Total size: 64 * 256 * RECORD_SIZE (2 GiB, 32 MiB each)
|
||||
pub level1: [BlockPtr<BlockListL1>; 64],
|
||||
|
||||
/// The next 32 * 256 * 256 blocks of this file,
|
||||
/// stored behind 32 level two tables.
|
||||
/// Each level two table points to 256 level one tables.
|
||||
///
|
||||
/// Total size: 32 * 256 * 256 * RECORD_SIZE (256 GiB, 8 GiB each)
|
||||
pub level2: [BlockPtr<BlockListL2>; 32],
|
||||
|
||||
/// The next 16 * 256 * 256 * 256 blocks of this file,
|
||||
/// stored behind 16 level three tables.
|
||||
///
|
||||
/// Total size: 16 * 256 * 256 * 256 * RECORD_SIZE (32 TiB, 2 TiB each)
|
||||
pub level3: [BlockPtr<BlockListL3>; 16],
|
||||
|
||||
/// The next 8 * 256 * 256 * 256 * 256 blocks of this file,
|
||||
/// stored behind 8 level four tables.
|
||||
///
|
||||
/// Total size: 8 * 256 * 256 * 256 * 256 * RECORD_SIZE (4 PiB, 512 TiB each)
|
||||
pub level4: [BlockPtr<BlockListL4>; 8],
|
||||
}
|
||||
|
||||
impl Default for NodeLevelData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
level0: [BlockPtr::default(); 128],
|
||||
level1: [BlockPtr::default(); 64],
|
||||
level2: [BlockPtr::default(); 32],
|
||||
level3: [BlockPtr::default(); 16],
|
||||
level4: [BlockPtr::default(); 8],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A file/folder node
|
||||
#[repr(C, packed)]
|
||||
pub struct Node {
|
||||
/// This node's type & permissions.
|
||||
/// - four most significant bits are the node's type
|
||||
/// - next four bits are permissions for the node's user
|
||||
/// - next four bits are permissions for the node's group
|
||||
/// - four least significant bits are permissions for everyone else
|
||||
pub mode: Le<u16>,
|
||||
|
||||
/// The uid that owns this file
|
||||
pub uid: Le<u32>,
|
||||
|
||||
/// The gid that owns this file
|
||||
pub gid: Le<u32>,
|
||||
|
||||
/// The number of hard links to this file
|
||||
pub links: Le<u32>,
|
||||
|
||||
/// The length of this file, in bytes
|
||||
pub size: Le<u64>,
|
||||
/// The disk usage of this file, in blocks
|
||||
pub blocks: Le<u64>,
|
||||
|
||||
/// Creation time
|
||||
pub ctime: Le<u64>,
|
||||
pub ctime_nsec: Le<u32>,
|
||||
|
||||
/// Modification time
|
||||
pub mtime: Le<u64>,
|
||||
pub mtime_nsec: Le<u32>,
|
||||
|
||||
/// Access time
|
||||
pub atime: Le<u64>,
|
||||
pub atime_nsec: Le<u32>,
|
||||
|
||||
/// Record level
|
||||
pub record_level: Le<u32>,
|
||||
|
||||
/// Flags
|
||||
pub flags: Le<u32>,
|
||||
|
||||
/// Padding
|
||||
pub padding: [u8; BLOCK_SIZE as usize - 4042],
|
||||
|
||||
/// Level data, should not be used directly so inline data can be supported
|
||||
pub(crate) level_data: NodeLevelData,
|
||||
}
|
||||
|
||||
unsafe impl BlockTrait for Node {
|
||||
fn empty(level: BlockLevel) -> Option<Self> {
|
||||
if level.0 == 0 {
|
||||
Some(Self::default())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Node {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mode: 0.into(),
|
||||
uid: 0.into(),
|
||||
gid: 0.into(),
|
||||
links: 0.into(),
|
||||
size: 0.into(),
|
||||
// This node counts as a block
|
||||
blocks: 1.into(),
|
||||
ctime: 0.into(),
|
||||
ctime_nsec: 0.into(),
|
||||
mtime: 0.into(),
|
||||
mtime_nsec: 0.into(),
|
||||
atime: 0.into(),
|
||||
atime_nsec: 0.into(),
|
||||
record_level: 0.into(),
|
||||
flags: 0.into(),
|
||||
padding: [0; BLOCK_SIZE as usize - 4042],
|
||||
level_data: NodeLevelData::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub const MODE_TYPE: u16 = 0xF000;
|
||||
pub const MODE_FILE: u16 = 0x8000;
|
||||
pub const MODE_DIR: u16 = 0x4000;
|
||||
pub const MODE_SYMLINK: u16 = 0xA000;
|
||||
pub const MODE_SOCK: u16 = 0xC000;
|
||||
|
||||
/// Mask for node permission bits
|
||||
pub const MODE_PERM: u16 = 0x0FFF;
|
||||
pub const MODE_EXEC: u16 = 0o1;
|
||||
pub const MODE_WRITE: u16 = 0o2;
|
||||
pub const MODE_READ: u16 = 0o4;
|
||||
|
||||
/// Create a new, empty node with the given metadata
|
||||
pub fn new(mode: u16, uid: u32, gid: u32, ctime: u64, ctime_nsec: u32) -> Self {
|
||||
Self {
|
||||
mode: mode.into(),
|
||||
uid: uid.into(),
|
||||
gid: gid.into(),
|
||||
links: 0.into(),
|
||||
ctime: ctime.into(),
|
||||
ctime_nsec: ctime_nsec.into(),
|
||||
mtime: ctime.into(),
|
||||
mtime_nsec: ctime_nsec.into(),
|
||||
atime: ctime.into(),
|
||||
atime_nsec: ctime_nsec.into(),
|
||||
record_level: if mode & Self::MODE_TYPE == Self::MODE_FILE {
|
||||
// Files take on record level
|
||||
RECORD_LEVEL as u32
|
||||
} else {
|
||||
// Folders do not
|
||||
0
|
||||
}
|
||||
.into(),
|
||||
flags: if mode & Self::MODE_TYPE == Self::MODE_DIR {
|
||||
// Directories must not use inline data (until h-tree supports it)
|
||||
NodeFlags::empty()
|
||||
} else {
|
||||
NodeFlags::INLINE_DATA
|
||||
}
|
||||
.bits()
|
||||
.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// This node's type & permissions.
|
||||
/// - four most significant bits are the node's type
|
||||
/// - next four bits are permissions for the node's user
|
||||
/// - next four bits are permissions for the node's group
|
||||
/// - four least significant bits are permissions for everyone else
|
||||
pub fn mode(&self) -> u16 {
|
||||
self.mode.to_ne()
|
||||
}
|
||||
|
||||
/// The uid that owns this file
|
||||
pub fn uid(&self) -> u32 {
|
||||
self.uid.to_ne()
|
||||
}
|
||||
|
||||
/// The gid that owns this file
|
||||
pub fn gid(&self) -> u32 {
|
||||
self.gid.to_ne()
|
||||
}
|
||||
|
||||
/// The number of links to this file
|
||||
/// (directory entries, symlinks, etc)
|
||||
pub fn links(&self) -> u32 {
|
||||
self.links.to_ne()
|
||||
}
|
||||
|
||||
/// The length of this file, in bytes.
|
||||
pub fn size(&self) -> u64 {
|
||||
self.size.to_ne()
|
||||
}
|
||||
|
||||
/// The disk usage of this file, in blocks.
|
||||
pub fn blocks(&self) -> u64 {
|
||||
self.blocks.to_ne()
|
||||
}
|
||||
|
||||
pub fn ctime(&self) -> (u64, u32) {
|
||||
(self.ctime.to_ne(), self.ctime_nsec.to_ne())
|
||||
}
|
||||
|
||||
pub fn mtime(&self) -> (u64, u32) {
|
||||
(self.mtime.to_ne(), self.mtime_nsec.to_ne())
|
||||
}
|
||||
|
||||
pub fn atime(&self) -> (u64, u32) {
|
||||
(self.atime.to_ne(), self.atime_nsec.to_ne())
|
||||
}
|
||||
|
||||
pub fn record_level(&self) -> BlockLevel {
|
||||
BlockLevel(self.record_level.to_ne() as usize)
|
||||
}
|
||||
|
||||
pub fn flags(&self) -> NodeFlags {
|
||||
NodeFlags::from_bits_retain(self.flags.to_ne())
|
||||
}
|
||||
|
||||
pub fn set_mode(&mut self, mode: u16) {
|
||||
self.mode = mode.into();
|
||||
}
|
||||
|
||||
pub fn set_uid(&mut self, uid: u32) {
|
||||
self.uid = uid.into();
|
||||
}
|
||||
|
||||
pub fn set_gid(&mut self, gid: u32) {
|
||||
self.gid = gid.into();
|
||||
}
|
||||
|
||||
pub fn set_links(&mut self, links: u32) {
|
||||
self.links = links.into();
|
||||
}
|
||||
|
||||
pub fn set_size(&mut self, size: u64) {
|
||||
self.size = size.into();
|
||||
}
|
||||
|
||||
pub fn set_blocks(&mut self, blocks: u64) {
|
||||
self.blocks = blocks.into();
|
||||
}
|
||||
|
||||
pub fn set_mtime(&mut self, mtime: u64, mtime_nsec: u32) {
|
||||
self.mtime = mtime.into();
|
||||
self.mtime_nsec = mtime_nsec.into();
|
||||
}
|
||||
|
||||
pub fn set_atime(&mut self, atime: u64, atime_nsec: u32) {
|
||||
self.atime = atime.into();
|
||||
self.atime_nsec = atime_nsec.into();
|
||||
}
|
||||
|
||||
pub fn set_flags(&mut self, flags: NodeFlags) {
|
||||
self.flags = flags.bits().into();
|
||||
}
|
||||
|
||||
pub fn has_inline_data(&self) -> bool {
|
||||
self.flags().contains(NodeFlags::INLINE_DATA)
|
||||
}
|
||||
|
||||
pub fn inline_data(&self) -> Option<&[u8]> {
|
||||
if self.has_inline_data() {
|
||||
Some(unsafe {
|
||||
slice::from_raw_parts(
|
||||
&self.level_data as *const NodeLevelData as *const u8,
|
||||
mem::size_of::<NodeLevelData>(),
|
||||
)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inline_data_mut(&mut self) -> Option<&mut [u8]> {
|
||||
if self.has_inline_data() {
|
||||
Some(unsafe {
|
||||
slice::from_raw_parts_mut(
|
||||
&mut self.level_data as *mut NodeLevelData as *mut u8,
|
||||
mem::size_of::<NodeLevelData>(),
|
||||
)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn level_data(&self) -> Option<&NodeLevelData> {
|
||||
if !self.has_inline_data() {
|
||||
Some(&self.level_data)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn level_data_mut(&mut self) -> Option<&mut NodeLevelData> {
|
||||
if !self.has_inline_data() {
|
||||
Some(&mut self.level_data)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_dir(&self) -> bool {
|
||||
self.mode() & Self::MODE_TYPE == Self::MODE_DIR
|
||||
}
|
||||
|
||||
pub fn is_file(&self) -> bool {
|
||||
self.mode() & Self::MODE_TYPE == Self::MODE_FILE
|
||||
}
|
||||
|
||||
pub fn is_symlink(&self) -> bool {
|
||||
self.mode() & Self::MODE_TYPE == Self::MODE_SYMLINK
|
||||
}
|
||||
|
||||
pub fn is_sock(&self) -> bool {
|
||||
self.mode() & Self::MODE_SOCK == Self::MODE_SOCK
|
||||
}
|
||||
|
||||
/// Tests if UID is the owner of that file, only true when uid=0 or when the UID stored in metadata is equal to the UID you supply
|
||||
pub fn owner(&self, uid: u32) -> bool {
|
||||
uid == 0 || self.uid() == uid
|
||||
}
|
||||
|
||||
/// Tests if the current user has enough permissions to view the file, op is the operation,
|
||||
/// like read and write, these modes are MODE_EXEC, MODE_READ, and MODE_WRITE
|
||||
pub fn permission(&self, uid: u32, gid: u32, op: u16) -> bool {
|
||||
let mut perm = self.mode() & 0o7;
|
||||
if self.uid() == uid {
|
||||
// If self.mode is 101100110, >> 6 would be 000000101
|
||||
// 0o7 is octal for 111, or, when expanded to 9 digits is 000000111
|
||||
perm |= (self.mode() >> 6) & 0o7;
|
||||
// Since we erased the GID and OTHER bits when >>6'ing, |= will keep those bits in place.
|
||||
}
|
||||
if self.gid() == gid || gid == 0 {
|
||||
perm |= (self.mode() >> 3) & 0o7;
|
||||
}
|
||||
if uid == 0 {
|
||||
//set the `other` bits to 111
|
||||
perm |= 0o7;
|
||||
}
|
||||
perm & op == op
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Node {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mode = self.mode;
|
||||
let uid = self.uid;
|
||||
let gid = self.gid;
|
||||
let links = self.links;
|
||||
let size = self.size;
|
||||
let blocks = self.blocks;
|
||||
let ctime = self.ctime;
|
||||
let ctime_nsec = self.ctime_nsec;
|
||||
let mtime = self.mtime;
|
||||
let mtime_nsec = self.mtime_nsec;
|
||||
let atime = self.atime;
|
||||
let atime_nsec = self.atime_nsec;
|
||||
f.debug_struct("Node")
|
||||
.field("mode", &mode)
|
||||
.field("uid", &uid)
|
||||
.field("gid", &gid)
|
||||
.field("links", &links)
|
||||
.field("size", &size)
|
||||
.field("blocks", &blocks)
|
||||
.field("ctime", &ctime)
|
||||
.field("ctime_nsec", &ctime_nsec)
|
||||
.field("mtime", &mtime)
|
||||
.field("mtime_nsec", &mtime_nsec)
|
||||
.field("atime", &atime)
|
||||
.field("atime_nsec", &atime_nsec)
|
||||
//TODO: level0/1/2/3
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for Node {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts(self as *const Node as *const u8, mem::size_of::<Node>())
|
||||
as &[u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::DerefMut for Node {
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts_mut(self as *mut Node as *mut u8, mem::size_of::<Node>())
|
||||
as &mut [u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn node_size_test() {
|
||||
assert_eq!(mem::size_of::<Node>(), crate::BLOCK_SIZE as usize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn node_inline_data_test() {
|
||||
let mut node = Node::default();
|
||||
assert!(!node.has_inline_data());
|
||||
assert!(node.inline_data().is_none());
|
||||
assert!(node.inline_data_mut().is_none());
|
||||
assert!(node.level_data().is_some());
|
||||
assert!(node.level_data_mut().is_some());
|
||||
|
||||
node.set_flags(NodeFlags::INLINE_DATA);
|
||||
assert!(node.has_inline_data());
|
||||
assert!(node.level_data().is_none());
|
||||
assert!(node.level_data_mut().is_none());
|
||||
|
||||
let node_addr = &node as *const Node as usize;
|
||||
let meta_size = 128;
|
||||
{
|
||||
let inline_data = node.inline_data().unwrap();
|
||||
let inline_data_addr = inline_data.as_ptr() as usize;
|
||||
assert_eq!(node_addr + meta_size, inline_data_addr);
|
||||
assert_eq!(inline_data.len(), (crate::BLOCK_SIZE as usize) - meta_size);
|
||||
}
|
||||
{
|
||||
let inline_data = node.inline_data_mut().unwrap();
|
||||
let inline_data_addr = inline_data.as_ptr() as usize;
|
||||
assert_eq!(node_addr + meta_size, inline_data_addr);
|
||||
assert_eq!(inline_data.len(), (crate::BLOCK_SIZE as usize) - meta_size);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(kani)]
|
||||
#[kani::proof]
|
||||
fn check_node_level() {
|
||||
let offset = kani::any();
|
||||
NodeLevel::new(offset);
|
||||
}
|
||||
|
||||
#[cfg(kani)]
|
||||
#[kani::proof]
|
||||
fn check_node_perms() {
|
||||
let mode = 0o750;
|
||||
|
||||
let uid = kani::any();
|
||||
let gid = kani::any();
|
||||
|
||||
let ctime = kani::any();
|
||||
let ctime_nsec = kani::any();
|
||||
|
||||
let node = Node::new(mode, uid, gid, ctime, ctime_nsec);
|
||||
|
||||
let root_uid = 0;
|
||||
let root_gid = 0;
|
||||
|
||||
let other_uid = kani::any();
|
||||
kani::assume(other_uid != uid);
|
||||
kani::assume(other_uid != root_uid);
|
||||
let other_gid = kani::any();
|
||||
kani::assume(other_gid != gid);
|
||||
kani::assume(other_gid != root_gid);
|
||||
|
||||
assert!(node.owner(uid));
|
||||
assert!(node.permission(uid, gid, 0o7));
|
||||
assert!(node.permission(uid, gid, 0o5));
|
||||
assert!(node.permission(uid, other_gid, 0o7));
|
||||
assert!(node.permission(uid, other_gid, 0o5));
|
||||
assert!(!node.permission(other_uid, gid, 0o7));
|
||||
assert!(node.permission(other_uid, gid, 0o5));
|
||||
|
||||
assert!(node.owner(root_uid));
|
||||
assert!(node.permission(root_uid, root_gid, 0o7));
|
||||
assert!(node.permission(root_uid, root_gid, 0o5));
|
||||
assert!(node.permission(root_uid, other_gid, 0o7));
|
||||
assert!(node.permission(root_uid, other_gid, 0o5));
|
||||
assert!(!node.permission(other_uid, root_gid, 0o7));
|
||||
assert!(node.permission(other_uid, root_gid, 0o5));
|
||||
|
||||
assert!(!node.owner(other_uid));
|
||||
assert!(!node.permission(other_uid, other_gid, 0o7));
|
||||
assert!(!node.permission(other_uid, other_gid, 0o5));
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
use core::{mem, ptr};
|
||||
use redoxfs::{BLOCK_SIZE, Disk};
|
||||
use syscall::error::{EIO, Error, Result};
|
||||
|
||||
use super::{DISK_ADDRESS_PACKET_ADDR, DISK_BIOS_ADDR, ThunkData};
|
||||
|
||||
const SECTOR_SIZE: u64 = 512;
|
||||
const BLOCKS_PER_SECTOR: u64 = BLOCK_SIZE / SECTOR_SIZE;
|
||||
// 128 sectors is the amount allocated for DISK_BIOS_ADDR
|
||||
// 127 sectors is the maximum for many BIOSes
|
||||
const MAX_SECTORS: u64 = 127;
|
||||
const MAX_BLOCKS: u64 = MAX_SECTORS * SECTOR_SIZE / BLOCK_SIZE;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C, packed)]
|
||||
pub struct DiskAddressPacket {
|
||||
size: u8,
|
||||
reserved: u8,
|
||||
sectors: u16,
|
||||
buffer: u16,
|
||||
segment: u16,
|
||||
address: u64,
|
||||
}
|
||||
|
||||
impl DiskAddressPacket {
|
||||
pub fn from_block(block: u64, count: u64) -> DiskAddressPacket {
|
||||
let address = block * BLOCKS_PER_SECTOR;
|
||||
let sectors = count * BLOCKS_PER_SECTOR;
|
||||
assert!(sectors <= MAX_SECTORS);
|
||||
DiskAddressPacket {
|
||||
size: mem::size_of::<DiskAddressPacket>() as u8,
|
||||
reserved: 0,
|
||||
sectors: sectors as u16,
|
||||
buffer: (DISK_BIOS_ADDR & 0xF) as u16,
|
||||
segment: (DISK_BIOS_ADDR >> 4) as u16,
|
||||
address,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DiskBios {
|
||||
boot_disk: u8,
|
||||
thunk13: extern "C" fn(),
|
||||
chs_opt: Option<(u32, u32, u32)>,
|
||||
}
|
||||
|
||||
impl DiskBios {
|
||||
pub fn new(boot_disk: u8, thunk13: extern "C" fn()) -> Self {
|
||||
let chs_opt = unsafe {
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x4100;
|
||||
data.ebx = 0x55AA;
|
||||
data.edx = boot_disk as u32;
|
||||
|
||||
data.with(thunk13);
|
||||
|
||||
if (data.ebx & 0xFFFF) == 0xAA55 {
|
||||
// Extensions are installed, do not use CHS
|
||||
None
|
||||
} else {
|
||||
// Extensions are not installed, get CHS geometry
|
||||
data = ThunkData::new();
|
||||
data.eax = 0x0800;
|
||||
data.edx = boot_disk as u32;
|
||||
data.edi = 0;
|
||||
|
||||
data.with(thunk13);
|
||||
|
||||
//TODO: return result on error
|
||||
let ah = ({ data.eax } >> 8) & 0xFF;
|
||||
assert_eq!(ah, 0);
|
||||
|
||||
let c = (data.ecx >> 8) & 0xFF | ((data.ecx >> 6) & 0x3) << 8;
|
||||
let h = ((data.edx >> 8) & 0xFF) + 1;
|
||||
let s = data.ecx & 0x3F;
|
||||
|
||||
Some((c, h, s))
|
||||
}
|
||||
};
|
||||
|
||||
Self {
|
||||
boot_disk,
|
||||
thunk13,
|
||||
chs_opt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Disk for DiskBios {
|
||||
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
|
||||
unsafe {
|
||||
// Optimization for live disks
|
||||
if let Some(live) = crate::LIVE_OPT {
|
||||
if block >= live.0 {
|
||||
let start = ((block - live.0) * BLOCK_SIZE) as usize;
|
||||
let end = start + buffer.len();
|
||||
if end <= live.1.len() {
|
||||
buffer.copy_from_slice(&live.1[start..end]);
|
||||
return Ok(buffer.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, chunk) in buffer
|
||||
.chunks_mut((MAX_BLOCKS * BLOCK_SIZE) as usize)
|
||||
.enumerate()
|
||||
{
|
||||
let dap = DiskAddressPacket::from_block(
|
||||
block + i as u64 * MAX_BLOCKS,
|
||||
chunk.len() as u64 / BLOCK_SIZE,
|
||||
);
|
||||
|
||||
if let Some((_, h_max, s_max)) = self.chs_opt {
|
||||
let s = (dap.address % s_max as u64) + 1;
|
||||
assert!(s <= 63, "invalid sector {s}");
|
||||
|
||||
let tmp = dap.address / s_max as u64;
|
||||
let h = tmp % h_max as u64;
|
||||
assert!(h <= 255, "invalid head {h}");
|
||||
|
||||
let c = tmp / h_max as u64;
|
||||
assert!(c <= 1023, "invalid cylinder {c}");
|
||||
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x0200 | (dap.sectors as u32);
|
||||
data.ebx = dap.buffer as u32;
|
||||
data.ecx =
|
||||
(s as u32) | (((c as u32) & 0xFF) << 8) | ((((c as u32) >> 8) & 0x3) << 6);
|
||||
data.edx = (self.boot_disk as u32) | ((h as u32) << 8);
|
||||
data.es = dap.segment;
|
||||
|
||||
data.with(self.thunk13);
|
||||
|
||||
//TODO: return result on error
|
||||
let ah = ({ data.eax } >> 8) & 0xFF;
|
||||
assert_eq!(ah, 0);
|
||||
} else {
|
||||
ptr::write(DISK_ADDRESS_PACKET_ADDR as *mut DiskAddressPacket, dap);
|
||||
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x4200;
|
||||
data.edx = self.boot_disk as u32;
|
||||
data.esi = DISK_ADDRESS_PACKET_ADDR as u32;
|
||||
|
||||
data.with(self.thunk13);
|
||||
|
||||
//TODO: return result on error
|
||||
let ah = ({ data.eax } >> 8) & 0xFF;
|
||||
assert_eq!(ah, 0);
|
||||
|
||||
//TODO: check blocks transferred
|
||||
// dap = ptr::read(DISK_ADDRESS_PACKET_ADDR as *mut DiskAddressPacket);
|
||||
}
|
||||
|
||||
ptr::copy(DISK_BIOS_ADDR as *const u8, chunk.as_mut_ptr(), chunk.len());
|
||||
}
|
||||
|
||||
Ok(buffer.len())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
|
||||
log::error!(
|
||||
"DiskBios::write_at(0x{:X}, 0x{:X}:0x{:X}) not allowed",
|
||||
block,
|
||||
buffer.as_ptr() as usize,
|
||||
buffer.len()
|
||||
);
|
||||
Err(Error::new(EIO))
|
||||
}
|
||||
|
||||
fn size(&mut self) -> Result<u64> {
|
||||
log::error!("DiskBios::size not implemented");
|
||||
Err(Error::new(EIO))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/// Print to console
|
||||
#[macro_export]
|
||||
macro_rules! print {
|
||||
($($arg:tt)*) => ({
|
||||
use core::fmt::Write;
|
||||
#[cfg(feature = "serial_debug")]
|
||||
{
|
||||
let _ = write!($crate::os::serial::COM1.lock(), $($arg)*);
|
||||
}
|
||||
let _ = write!($crate::os::VGA.lock(), $($arg)*);
|
||||
});
|
||||
}
|
||||
|
||||
/// Print with new line to console
|
||||
#[macro_export]
|
||||
macro_rules! println {
|
||||
() => (print!("\n"));
|
||||
($fmt:expr_2021) => (print!(concat!($fmt, "\n")));
|
||||
($fmt:expr_2021, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
use core::{cmp, mem, ptr};
|
||||
|
||||
use crate::area_add;
|
||||
use crate::os::{OsMemoryEntry, OsMemoryKind};
|
||||
|
||||
use super::{MEMORY_MAP_ADDR, thunk::ThunkData};
|
||||
|
||||
#[repr(C, packed)]
|
||||
struct MemoryMapEntry {
|
||||
pub base: u64,
|
||||
pub size: u64,
|
||||
pub kind: u32,
|
||||
}
|
||||
|
||||
pub struct MemoryMapIter {
|
||||
thunk15: extern "C" fn(),
|
||||
data: ThunkData,
|
||||
first: bool,
|
||||
}
|
||||
|
||||
impl MemoryMapIter {
|
||||
pub fn new(thunk15: extern "C" fn()) -> Self {
|
||||
Self {
|
||||
thunk15,
|
||||
data: ThunkData::new(),
|
||||
first: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for MemoryMapIter {
|
||||
type Item = OsMemoryEntry;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.first {
|
||||
self.first = false;
|
||||
} else if self.data.ebx == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.data.eax = 0xE820;
|
||||
self.data.ecx = mem::size_of::<MemoryMapEntry>() as u32;
|
||||
self.data.edx = 0x534D4150;
|
||||
self.data.edi = MEMORY_MAP_ADDR as u32;
|
||||
|
||||
unsafe {
|
||||
self.data.with(self.thunk15);
|
||||
}
|
||||
|
||||
//TODO: return error?
|
||||
assert_eq!({ self.data.eax }, 0x534D4150);
|
||||
assert_eq!({ self.data.ecx }, mem::size_of::<MemoryMapEntry>() as u32);
|
||||
|
||||
let entry = unsafe { ptr::read(MEMORY_MAP_ADDR as *const MemoryMapEntry) };
|
||||
Some(Self::Item {
|
||||
base: entry.base,
|
||||
size: entry.size,
|
||||
kind: match entry.kind {
|
||||
0 => OsMemoryKind::Null,
|
||||
1 => OsMemoryKind::Free,
|
||||
3 => OsMemoryKind::Reclaim,
|
||||
_ => OsMemoryKind::Reserved,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn memory_map(thunk15: extern "C" fn()) -> Option<(usize, usize)> {
|
||||
let mut heap_limits = None;
|
||||
for entry in MemoryMapIter::new(thunk15) {
|
||||
let heap_start = 1024 * 1024;
|
||||
if { entry.kind } == OsMemoryKind::Free
|
||||
&& entry.base <= heap_start as u64
|
||||
&& (entry.base + entry.size) >= heap_start as u64
|
||||
{
|
||||
let heap_end = cmp::min(entry.base + entry.size, usize::MAX as u64) as usize;
|
||||
if heap_end >= heap_start {
|
||||
heap_limits = Some((heap_start, heap_end - heap_start));
|
||||
}
|
||||
}
|
||||
|
||||
area_add(entry);
|
||||
}
|
||||
heap_limits
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
use alloc::alloc::{Layout, alloc_zeroed};
|
||||
use core::{convert::TryFrom, mem, ptr, slice};
|
||||
use linked_list_allocator::LockedHeap;
|
||||
use spin::Mutex;
|
||||
|
||||
use crate::KernelArgs;
|
||||
use crate::logger::LOGGER;
|
||||
use crate::os::{Os, OsHwDesc, OsKey, OsVideoMode};
|
||||
|
||||
use self::disk::DiskBios;
|
||||
use self::memory_map::memory_map;
|
||||
use self::thunk::ThunkData;
|
||||
use self::vbe::VideoModeIter;
|
||||
use self::vga::{Vga, VgaTextColor};
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
mod disk;
|
||||
mod memory_map;
|
||||
mod panic;
|
||||
pub(crate) mod serial;
|
||||
mod thunk;
|
||||
mod vbe;
|
||||
mod vga;
|
||||
|
||||
// Real mode memory allocation, for use with thunk
|
||||
// 0x500 to 0x7BFF is free
|
||||
const DISK_BIOS_ADDR: usize = 0x70000; // 64 KiB at 448 KiB, ends at 512 KiB
|
||||
const VBE_CARD_INFO_ADDR: usize = 0x1000; // 512 bytes, ends at 0x11FF
|
||||
const VBE_MODE_INFO_ADDR: usize = 0x1200; // 256 bytes, ends at 0x12FF
|
||||
const VBE_EDID_ADDR: usize = 0x1300; // 128 bytes, ends at 0x137F
|
||||
const MEMORY_MAP_ADDR: usize = 0x1380; // 24 bytes, ends at 0x1397
|
||||
const DISK_ADDRESS_PACKET_ADDR: usize = 0x1398; // 16 bytes, ends at 0x13A7
|
||||
const THUNK_STACK_ADDR: usize = 0x7C00; // Grows downwards
|
||||
const VGA_ADDR: usize = 0xB8000;
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: LockedHeap = LockedHeap::empty();
|
||||
|
||||
pub(crate) static VGA: Mutex<Vga> = Mutex::new(unsafe { Vga::new(VGA_ADDR, 80, 25) });
|
||||
|
||||
pub struct OsBios {
|
||||
boot_disk: usize,
|
||||
thunk10: extern "C" fn(),
|
||||
thunk13: extern "C" fn(),
|
||||
thunk15: extern "C" fn(),
|
||||
thunk16: extern "C" fn(),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct Rsdp {
|
||||
signature: [u8; 8],
|
||||
checksum: u8,
|
||||
oemid: [u8; 6],
|
||||
revision: u8,
|
||||
rsdt_address: u32,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct Xsdp {
|
||||
rsdp: Rsdp,
|
||||
|
||||
length: u32,
|
||||
xsdt_address: u64,
|
||||
extended_checksum: u8,
|
||||
reserved: [u8; 3],
|
||||
}
|
||||
|
||||
unsafe fn search_rsdp(start: usize, end: usize) -> Option<(u64, u64)> {
|
||||
unsafe {
|
||||
// Align start up to 16 bytes
|
||||
let mut addr = start.div_ceil(16) * 16;
|
||||
// Search until reading the end of the Rsdp would be past the end of the memory area
|
||||
while addr + mem::size_of::<Rsdp>() <= end {
|
||||
let rsdp = ptr::read(addr as *const Rsdp);
|
||||
if &rsdp.signature == b"RSD PTR " {
|
||||
//TODO: check checksum?
|
||||
if rsdp.revision == 0 {
|
||||
return Some((addr as u64, mem::size_of::<Rsdp>() as u64));
|
||||
} else if rsdp.revision == 2 {
|
||||
let xsdp = ptr::read(addr as *const Xsdp);
|
||||
//TODO: check extended checksum?
|
||||
return Some((addr as u64, xsdp.length as u64));
|
||||
}
|
||||
}
|
||||
|
||||
// Rsdp is always aligned to 16 bytes
|
||||
addr += 16;
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Os for OsBios {
|
||||
type D = DiskBios;
|
||||
type V = VideoModeIter;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"x86/BIOS"
|
||||
}
|
||||
|
||||
fn alloc_zeroed_page_aligned(&self, size: usize) -> *mut u8 {
|
||||
assert!(size != 0);
|
||||
|
||||
let page_size = self.page_size();
|
||||
let pages = size.div_ceil(page_size);
|
||||
|
||||
let ptr =
|
||||
unsafe { alloc_zeroed(Layout::from_size_align(pages * page_size, page_size).unwrap()) };
|
||||
|
||||
assert!(!ptr.is_null());
|
||||
ptr
|
||||
}
|
||||
|
||||
fn page_size(&self) -> usize {
|
||||
4096
|
||||
}
|
||||
|
||||
fn filesystem(
|
||||
&self,
|
||||
password_opt: Option<&[u8]>,
|
||||
) -> syscall::Result<redoxfs::FileSystem<DiskBios>> {
|
||||
let disk = DiskBios::new(u8::try_from(self.boot_disk).unwrap(), self.thunk13);
|
||||
|
||||
//TODO: get block from partition table
|
||||
let block = 2 * crate::MIBI as u64 / redoxfs::BLOCK_SIZE;
|
||||
redoxfs::FileSystem::open(disk, password_opt, Some(block), false)
|
||||
}
|
||||
|
||||
fn hwdesc(&self) -> OsHwDesc {
|
||||
// See ACPI specification - Finding the RSDP on IA-PC Systems
|
||||
unsafe {
|
||||
let ebda_segment = ptr::read(0x40E as *const u16);
|
||||
let ebda_addr = (ebda_segment as usize) << 4;
|
||||
if let Some((addr, size)) =
|
||||
search_rsdp(ebda_addr, ebda_addr + 1024).or(search_rsdp(0xE0000, 0xFFFFF))
|
||||
{
|
||||
// Copy to a page
|
||||
let page_aligned = self.alloc_zeroed_page_aligned(size as usize);
|
||||
ptr::copy(addr as *const u8, page_aligned, size as usize);
|
||||
return OsHwDesc::Acpi(page_aligned as u64, size);
|
||||
}
|
||||
}
|
||||
OsHwDesc::NotFound
|
||||
}
|
||||
|
||||
fn video_outputs(&self) -> usize {
|
||||
//TODO: return 1 only if vbe supported?
|
||||
1
|
||||
}
|
||||
|
||||
fn video_modes(&self, _output_i: usize) -> VideoModeIter {
|
||||
VideoModeIter::new(self.thunk10)
|
||||
}
|
||||
|
||||
fn set_video_mode(&self, _output_i: usize, mode: &mut OsVideoMode) {
|
||||
// Set video mode
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x4F02;
|
||||
data.ebx = mode.id;
|
||||
unsafe {
|
||||
data.with(self.thunk10);
|
||||
}
|
||||
//TODO: check result
|
||||
}
|
||||
|
||||
fn best_resolution(&self, _output_i: usize) -> Option<(u32, u32)> {
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x4F15;
|
||||
data.ebx = 0x01;
|
||||
data.ecx = 0;
|
||||
data.edx = 0;
|
||||
data.edi = VBE_EDID_ADDR as u32;
|
||||
unsafe {
|
||||
data.with(self.thunk10);
|
||||
}
|
||||
|
||||
if data.eax == 0x4F {
|
||||
let edid = unsafe { slice::from_raw_parts(VBE_EDID_ADDR as *const u8, 128) };
|
||||
|
||||
Some((
|
||||
(edid[0x38] as u32) | (((edid[0x3A] as u32) & 0xF0) << 4),
|
||||
(edid[0x3B] as u32) | (((edid[0x3D] as u32) & 0xF0) << 4),
|
||||
))
|
||||
} else {
|
||||
log::warn!("Failed to get VBE EDID: 0x{:X}", { data.eax });
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_key(&self) -> OsKey {
|
||||
// Read keypress
|
||||
let mut data = ThunkData::new();
|
||||
unsafe {
|
||||
data.with(self.thunk16);
|
||||
}
|
||||
match (data.eax >> 8) as u8 {
|
||||
0x4B => OsKey::Left,
|
||||
0x4D => OsKey::Right,
|
||||
0x48 => OsKey::Up,
|
||||
0x50 => OsKey::Down,
|
||||
0x0E => OsKey::Backspace,
|
||||
0x53 => OsKey::Delete,
|
||||
0x1C => OsKey::Enter,
|
||||
_ => match data.eax as u8 {
|
||||
0 => OsKey::Other,
|
||||
b => OsKey::Char(b as char),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_text(&self) {
|
||||
let mut vga = VGA.lock();
|
||||
vga.clear();
|
||||
}
|
||||
|
||||
fn get_text_position(&self) -> (usize, usize) {
|
||||
let vga = VGA.lock();
|
||||
(vga.x, vga.y)
|
||||
}
|
||||
|
||||
fn set_text_position(&self, x: usize, y: usize) {
|
||||
//TODO: ensure this is inside bounds!
|
||||
let mut vga = VGA.lock();
|
||||
vga.x = x;
|
||||
vga.y = y;
|
||||
}
|
||||
|
||||
fn set_text_highlight(&self, highlight: bool) {
|
||||
let mut vga = VGA.lock();
|
||||
if highlight {
|
||||
vga.bg = VgaTextColor::Gray;
|
||||
vga.fg = VgaTextColor::Black;
|
||||
} else {
|
||||
vga.bg = VgaTextColor::Black;
|
||||
vga.fg = VgaTextColor::Gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn start(
|
||||
kernel_entry: extern "C" fn(
|
||||
page_table: usize,
|
||||
stack: u64,
|
||||
func: u64,
|
||||
args: *const KernelArgs,
|
||||
long_mode: usize,
|
||||
) -> !,
|
||||
boot_disk: usize,
|
||||
thunk10: extern "C" fn(),
|
||||
thunk13: extern "C" fn(),
|
||||
thunk15: extern "C" fn(),
|
||||
thunk16: extern "C" fn(),
|
||||
) -> ! {
|
||||
unsafe {
|
||||
#[cfg(feature = "serial_debug")]
|
||||
{
|
||||
let mut com1 = serial::COM1.lock();
|
||||
com1.init();
|
||||
com1.write(b"SERIAL\n");
|
||||
}
|
||||
|
||||
{
|
||||
// Make sure we are in mode 3 (80x25 text mode)
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x03;
|
||||
data.with(thunk10);
|
||||
}
|
||||
|
||||
{
|
||||
// Disable cursor
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x0100;
|
||||
data.ecx = 0x3F00;
|
||||
data.with(thunk10);
|
||||
}
|
||||
|
||||
// Clear screen
|
||||
VGA.lock().clear();
|
||||
|
||||
// Set logger
|
||||
LOGGER.init();
|
||||
|
||||
let mut os = OsBios {
|
||||
boot_disk,
|
||||
thunk10,
|
||||
thunk13,
|
||||
thunk15,
|
||||
thunk16,
|
||||
};
|
||||
|
||||
let (heap_start, heap_size) = memory_map(os.thunk15).expect("No memory for heap");
|
||||
|
||||
ALLOCATOR.lock().init(heap_start as *mut u8, heap_size);
|
||||
|
||||
let (page_phys, func, args) = crate::main(&mut os);
|
||||
|
||||
kernel_entry(
|
||||
page_phys,
|
||||
args.stack_base
|
||||
+ args.stack_size
|
||||
+ if crate::KERNEL_64BIT {
|
||||
crate::arch::x64::PHYS_OFFSET
|
||||
} else {
|
||||
crate::arch::x32::PHYS_OFFSET as u64
|
||||
},
|
||||
func,
|
||||
&args,
|
||||
if crate::KERNEL_64BIT { 1 } else { 0 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//! Intrinsics for panic handling
|
||||
|
||||
use core::alloc::Layout;
|
||||
use core::arch::asm;
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
/// Required to handle panics
|
||||
#[panic_handler]
|
||||
pub fn rust_begin_unwind(info: &PanicInfo) -> ! {
|
||||
unsafe {
|
||||
println!("BOOTLOADER PANIC:\n{}", info);
|
||||
loop {
|
||||
asm!("hlt");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
use spin::Mutex;
|
||||
use syscall::Pio;
|
||||
|
||||
use crate::serial_16550::SerialPort;
|
||||
|
||||
pub static COM1: Mutex<SerialPort<Pio<u8>>> = Mutex::new(SerialPort::<Pio<u8>>::new(0x3F8));
|
||||
pub static COM2: Mutex<SerialPort<Pio<u8>>> = Mutex::new(SerialPort::<Pio<u8>>::new(0x2F8));
|
||||
pub static COM3: Mutex<SerialPort<Pio<u8>>> = Mutex::new(SerialPort::<Pio<u8>>::new(0x3E8));
|
||||
pub static COM4: Mutex<SerialPort<Pio<u8>>> = Mutex::new(SerialPort::<Pio<u8>>::new(0x2E8));
|
||||
@@ -0,0 +1,52 @@
|
||||
use core::ptr;
|
||||
|
||||
use super::THUNK_STACK_ADDR;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct ThunkData {
|
||||
pub es: u16,
|
||||
pub edi: u32,
|
||||
pub esi: u32,
|
||||
pub ebp: u32,
|
||||
pub ebx: u32,
|
||||
pub edx: u32,
|
||||
pub ecx: u32,
|
||||
pub eax: u32,
|
||||
}
|
||||
|
||||
impl ThunkData {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
es: 0,
|
||||
edi: 0,
|
||||
esi: 0,
|
||||
ebp: 0,
|
||||
ebx: 0,
|
||||
edx: 0,
|
||||
ecx: 0,
|
||||
eax: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn save(&self) {
|
||||
unsafe {
|
||||
ptr::write((THUNK_STACK_ADDR - 64) as *mut ThunkData, *self);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn load(&mut self) {
|
||||
unsafe {
|
||||
*self = ptr::read((THUNK_STACK_ADDR - 64) as *const ThunkData);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn with(&mut self, f: extern "C" fn()) {
|
||||
unsafe {
|
||||
self.save();
|
||||
f();
|
||||
self.load();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
use core::ptr;
|
||||
use log::error;
|
||||
|
||||
use crate::os::OsVideoMode;
|
||||
|
||||
use super::{ThunkData, VBE_CARD_INFO_ADDR, VBE_MODE_INFO_ADDR};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct VbeFarPtr {
|
||||
pub offset: u16,
|
||||
pub segment: u16,
|
||||
}
|
||||
|
||||
impl VbeFarPtr {
|
||||
pub unsafe fn as_ptr<T>(&self) -> *const T {
|
||||
(((self.segment as usize) << 4) + (self.offset as usize)) as *const T
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct VbeCardInfo {
|
||||
pub signature: [u8; 4],
|
||||
pub version: u16,
|
||||
pub oemstring: VbeFarPtr,
|
||||
pub capabilities: [u8; 4],
|
||||
pub videomodeptr: VbeFarPtr,
|
||||
pub totalmemory: u16,
|
||||
pub oemsoftwarerev: u16,
|
||||
pub oemvendornameptr: VbeFarPtr,
|
||||
pub oemproductnameptr: VbeFarPtr,
|
||||
pub oemproductrevptr: VbeFarPtr,
|
||||
pub reserved: [u8; 222],
|
||||
pub oemdata: [u8; 256],
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct VbeModeInfo {
|
||||
pub attributes: u16,
|
||||
pub win_a: u8,
|
||||
pub win_b: u8,
|
||||
pub granularity: u16,
|
||||
pub winsize: u16,
|
||||
pub segment_a: u16,
|
||||
pub segment_b: u16,
|
||||
pub winfuncptr: u32,
|
||||
pub bytesperscanline: u16,
|
||||
pub xresolution: u16,
|
||||
pub yresolution: u16,
|
||||
pub xcharsize: u8,
|
||||
pub ycharsize: u8,
|
||||
pub numberofplanes: u8,
|
||||
pub bitsperpixel: u8,
|
||||
pub numberofbanks: u8,
|
||||
pub memorymodel: u8,
|
||||
pub banksize: u8,
|
||||
pub numberofimagepages: u8,
|
||||
pub unused: u8,
|
||||
pub redmasksize: u8,
|
||||
pub redfieldposition: u8,
|
||||
pub greenmasksize: u8,
|
||||
pub greenfieldposition: u8,
|
||||
pub bluemasksize: u8,
|
||||
pub bluefieldposition: u8,
|
||||
pub rsvdmasksize: u8,
|
||||
pub rsvdfieldposition: u8,
|
||||
pub directcolormodeinfo: u8,
|
||||
pub physbaseptr: u32,
|
||||
pub offscreenmemoryoffset: u32,
|
||||
pub offscreenmemsize: u16,
|
||||
pub reserved: [u8; 206],
|
||||
}
|
||||
|
||||
pub struct VideoModeIter {
|
||||
thunk10: extern "C" fn(),
|
||||
mode_ptr: *const u16,
|
||||
}
|
||||
|
||||
impl VideoModeIter {
|
||||
pub fn new(thunk10: extern "C" fn()) -> Self {
|
||||
// Get card info
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x4F00;
|
||||
data.edi = VBE_CARD_INFO_ADDR as u32;
|
||||
unsafe {
|
||||
data.with(thunk10);
|
||||
}
|
||||
let mode_ptr = if data.eax == 0x004F {
|
||||
let card_info = unsafe { ptr::read(VBE_CARD_INFO_ADDR as *const VbeCardInfo) };
|
||||
unsafe { card_info.videomodeptr.as_ptr::<u16>() }
|
||||
} else {
|
||||
error!("Failed to read VBE card info: 0x{:04X}", { data.eax });
|
||||
ptr::null()
|
||||
};
|
||||
Self { thunk10, mode_ptr }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for VideoModeIter {
|
||||
type Item = OsVideoMode;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.mode_ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
loop {
|
||||
// Set bit 14 to get linear frame buffer
|
||||
let mode = unsafe { *self.mode_ptr } | (1 << 14);
|
||||
if mode == 0xFFFF {
|
||||
return None;
|
||||
}
|
||||
self.mode_ptr = unsafe { self.mode_ptr.add(1) };
|
||||
|
||||
// Get mode info
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x4F01;
|
||||
data.ecx = mode as u32;
|
||||
data.edi = VBE_MODE_INFO_ADDR as u32;
|
||||
unsafe {
|
||||
data.with(self.thunk10);
|
||||
}
|
||||
if data.eax == 0x004F {
|
||||
let mode_info = unsafe { ptr::read(VBE_MODE_INFO_ADDR as *const VbeModeInfo) };
|
||||
|
||||
// We only support 32-bits per pixel modes
|
||||
if mode_info.bitsperpixel != 32 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let width = mode_info.xresolution as u32;
|
||||
let height = mode_info.yresolution as u32;
|
||||
//TODO: support stride that is not a multiple of 4
|
||||
let stride = mode_info.bytesperscanline as u32 / 4;
|
||||
|
||||
return Some(OsVideoMode {
|
||||
id: mode as u32,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
base: mode_info.physbaseptr as u64,
|
||||
});
|
||||
} else {
|
||||
error!("Failed to read VBE mode 0x{:04X} info: 0x{:04X}", mode, {
|
||||
data.eax
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
use core::{fmt, slice};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C, packed)]
|
||||
pub struct VgaTextBlock {
|
||||
pub char: u8,
|
||||
pub color: u8,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum VgaTextColor {
|
||||
Black = 0,
|
||||
Blue = 1,
|
||||
Green = 2,
|
||||
Cyan = 3,
|
||||
Red = 4,
|
||||
Purple = 5,
|
||||
Brown = 6,
|
||||
Gray = 7,
|
||||
DarkGray = 8,
|
||||
LightBlue = 9,
|
||||
LightGreen = 10,
|
||||
LightCyan = 11,
|
||||
LightRed = 12,
|
||||
LightPurple = 13,
|
||||
Yellow = 14,
|
||||
White = 15,
|
||||
}
|
||||
|
||||
pub struct Vga {
|
||||
pub base: usize,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub x: usize,
|
||||
pub y: usize,
|
||||
pub bg: VgaTextColor,
|
||||
pub fg: VgaTextColor,
|
||||
}
|
||||
|
||||
impl Vga {
|
||||
pub const unsafe fn new(base: usize, width: usize, height: usize) -> Self {
|
||||
Self {
|
||||
base,
|
||||
width,
|
||||
height,
|
||||
x: 0,
|
||||
y: 0,
|
||||
bg: VgaTextColor::Black,
|
||||
fg: VgaTextColor::Gray,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn blocks(&mut self) -> &'static mut [VgaTextBlock] {
|
||||
unsafe {
|
||||
slice::from_raw_parts_mut(self.base as *mut VgaTextBlock, self.width * self.height)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.x = 0;
|
||||
self.y = 0;
|
||||
let blocks = unsafe { self.blocks() };
|
||||
for i in 0..blocks.len() {
|
||||
blocks[i] = VgaTextBlock {
|
||||
char: 0,
|
||||
color: ((self.bg as u8) << 4) | (self.fg as u8),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Write for Vga {
|
||||
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
|
||||
let blocks = unsafe { self.blocks() };
|
||||
for c in s.chars() {
|
||||
if self.x >= self.width {
|
||||
self.x = 0;
|
||||
self.y += 1;
|
||||
}
|
||||
while self.y >= self.height {
|
||||
for y in 1..self.height {
|
||||
for x in 0..self.width {
|
||||
let i = y * self.width + x;
|
||||
let j = i - self.width;
|
||||
blocks[j] = blocks[i];
|
||||
if y + 1 == self.height {
|
||||
blocks[i].char = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.y -= 1;
|
||||
}
|
||||
match c {
|
||||
'\x08' => {
|
||||
if self.x > 0 {
|
||||
self.x -= 1;
|
||||
}
|
||||
}
|
||||
'\r' => {
|
||||
self.x = 0;
|
||||
}
|
||||
'\n' => {
|
||||
self.x = 0;
|
||||
self.y += 1;
|
||||
}
|
||||
_ => {
|
||||
let i = self.y * self.width + self.x;
|
||||
if let Some(block) = blocks.get_mut(i) {
|
||||
block.char = c as u8;
|
||||
block.color = ((self.bg as u8) << 4) | (self.fg as u8);
|
||||
}
|
||||
self.x += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
use redoxfs::Disk;
|
||||
|
||||
#[cfg(all(target_arch = "x86", target_os = "none"))]
|
||||
pub use self::bios::*;
|
||||
|
||||
#[cfg(all(target_arch = "x86", target_os = "none"))]
|
||||
#[macro_use]
|
||||
mod bios;
|
||||
|
||||
#[cfg(any(target_arch = "riscv64", target_os = "uefi"))]
|
||||
#[allow(unused_imports)]
|
||||
pub use self::uefi::*;
|
||||
|
||||
#[cfg(any(target_arch = "riscv64", target_os = "uefi"))]
|
||||
#[macro_use]
|
||||
mod uefi;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum OsHwDesc {
|
||||
Acpi(u64, u64),
|
||||
DeviceTree(u64, u64),
|
||||
NotFound,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum OsKey {
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down,
|
||||
Backspace,
|
||||
Delete,
|
||||
Enter,
|
||||
Char(char),
|
||||
Other,
|
||||
}
|
||||
|
||||
// Keep synced with BootloaderMemoryKind in kernel
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[repr(u64)]
|
||||
pub enum OsMemoryKind {
|
||||
Null = 0,
|
||||
Free = 1,
|
||||
Reclaim = 2,
|
||||
Reserved = 3,
|
||||
}
|
||||
|
||||
// Keep synced with BootloaderMemoryEntry in kernel
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C, packed(8))]
|
||||
pub struct OsMemoryEntry {
|
||||
pub base: u64,
|
||||
pub size: u64,
|
||||
pub kind: OsMemoryKind,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct OsVideoMode {
|
||||
pub id: u32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub stride: u32,
|
||||
pub base: u64,
|
||||
}
|
||||
|
||||
pub trait Os {
|
||||
type D: Disk;
|
||||
type V: Iterator<Item = OsVideoMode>;
|
||||
|
||||
fn name(&self) -> &str;
|
||||
|
||||
fn alloc_zeroed_page_aligned(&self, size: usize) -> *mut u8;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn page_size(&self) -> usize;
|
||||
|
||||
fn filesystem(
|
||||
&self,
|
||||
password_opt: Option<&[u8]>,
|
||||
) -> syscall::Result<redoxfs::FileSystem<Self::D>>;
|
||||
|
||||
fn hwdesc(&self) -> OsHwDesc;
|
||||
|
||||
fn video_outputs(&self) -> usize;
|
||||
fn video_modes(&self, output_i: usize) -> Self::V;
|
||||
fn set_video_mode(&self, output_i: usize, mode: &mut OsVideoMode);
|
||||
fn best_resolution(&self, output_i: usize) -> Option<(u32, u32)>;
|
||||
|
||||
fn get_key(&self) -> OsKey;
|
||||
|
||||
fn clear_text(&self);
|
||||
fn get_text_position(&self) -> (usize, usize);
|
||||
fn set_text_position(&self, x: usize, y: usize);
|
||||
fn set_text_highlight(&self, highlight: bool);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
use core::slice;
|
||||
use uefi::guid::{ACPI_20_TABLE_GUID, ACPI_TABLE_GUID};
|
||||
|
||||
use crate::Os;
|
||||
|
||||
struct Invalid;
|
||||
|
||||
fn validate_rsdp(address: usize, _v2: bool) -> core::result::Result<usize, Invalid> {
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct Rsdp {
|
||||
signature: [u8; 8], // b"RSD PTR "
|
||||
chksum: u8,
|
||||
oem_id: [u8; 6],
|
||||
revision: u8,
|
||||
rsdt_addr: u32,
|
||||
// the following fields are only available for ACPI 2.0, and are reserved otherwise
|
||||
length: u32,
|
||||
xsdt_addr: u64,
|
||||
extended_chksum: u8,
|
||||
_rsvd: [u8; 3],
|
||||
}
|
||||
// paging is not enabled at this stage; we can just read the physical address here.
|
||||
let rsdp_bytes =
|
||||
unsafe { core::slice::from_raw_parts(address as *const u8, core::mem::size_of::<Rsdp>()) };
|
||||
let rsdp = unsafe {
|
||||
(rsdp_bytes.as_ptr() as *const Rsdp)
|
||||
.as_ref::<'static>()
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
log::debug!("RSDP: {:?}", rsdp);
|
||||
|
||||
if rsdp.signature != *b"RSD PTR " {
|
||||
return Err(Invalid);
|
||||
}
|
||||
let mut base_sum = 0u8;
|
||||
for base_byte in &rsdp_bytes[..20] {
|
||||
base_sum = base_sum.wrapping_add(*base_byte);
|
||||
}
|
||||
if base_sum != 0 {
|
||||
return Err(Invalid);
|
||||
}
|
||||
|
||||
if rsdp.revision == 2 {
|
||||
let mut extended_sum = 0u8;
|
||||
for byte in rsdp_bytes {
|
||||
extended_sum = extended_sum.wrapping_add(*byte);
|
||||
}
|
||||
|
||||
if extended_sum != 0 {
|
||||
return Err(Invalid);
|
||||
}
|
||||
}
|
||||
|
||||
let length = if rsdp.revision == 2 {
|
||||
rsdp.length as usize
|
||||
} else {
|
||||
core::mem::size_of::<Rsdp>()
|
||||
};
|
||||
|
||||
Ok(length)
|
||||
}
|
||||
|
||||
pub(crate) fn find_acpi_table_pointers(os: &impl Os) -> Option<(u64, u64)> {
|
||||
let cfg_tables = std::system_table().config_tables();
|
||||
let mut acpi = None;
|
||||
let mut acpi2 = None;
|
||||
for cfg_table in cfg_tables.iter() {
|
||||
if cfg_table.VendorGuid == ACPI_TABLE_GUID {
|
||||
match validate_rsdp(cfg_table.VendorTable, false) {
|
||||
Ok(length) => {
|
||||
acpi = Some(unsafe {
|
||||
core::slice::from_raw_parts(cfg_table.VendorTable as *const u8, length)
|
||||
});
|
||||
}
|
||||
Err(_) => log::warn!(
|
||||
"Found RSDP that was not valid at {:p}",
|
||||
cfg_table.VendorTable as *const u8
|
||||
),
|
||||
}
|
||||
} else if cfg_table.VendorGuid == ACPI_20_TABLE_GUID {
|
||||
match validate_rsdp(cfg_table.VendorTable, true) {
|
||||
Ok(length) => {
|
||||
acpi2 = Some(unsafe {
|
||||
core::slice::from_raw_parts(cfg_table.VendorTable as *const u8, length)
|
||||
});
|
||||
}
|
||||
Err(_) => log::warn!(
|
||||
"Found RSDP that was not valid at {:p}",
|
||||
cfg_table.VendorTable as *const u8
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let rsdp_area = acpi2.or(acpi).unwrap_or(&[]);
|
||||
|
||||
if !rsdp_area.is_empty() {
|
||||
unsafe {
|
||||
// Copy to page aligned area
|
||||
let size = rsdp_area.len();
|
||||
let base = os.alloc_zeroed_page_aligned(size);
|
||||
slice::from_raw_parts_mut(base, size).copy_from_slice(rsdp_area);
|
||||
Some((base as u64, size as u64))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
use core::{arch::asm, fmt::Write, mem, slice};
|
||||
use uefi::status::Result;
|
||||
|
||||
use crate::{
|
||||
KernelArgs,
|
||||
arch::{ENTRY_ADDRESS_MASK, PAGE_ENTRIES, PF_PRESENT, PF_TABLE, PHYS_OFFSET},
|
||||
logger::LOGGER,
|
||||
};
|
||||
|
||||
use super::super::{OsEfi, memory_map::memory_map};
|
||||
|
||||
unsafe fn dump_page_tables(table_phys: u64, table_virt: u64, table_level: u64) {
|
||||
unsafe {
|
||||
let entries = slice::from_raw_parts(table_phys as *const u64, PAGE_ENTRIES);
|
||||
for (i, entry) in entries.iter().enumerate() {
|
||||
let phys = entry & ENTRY_ADDRESS_MASK;
|
||||
let flags = entry & !ENTRY_ADDRESS_MASK;
|
||||
if flags & PF_PRESENT == 0 {
|
||||
continue;
|
||||
}
|
||||
let mut shift = 39u64;
|
||||
for _ in 0..table_level {
|
||||
shift -= 9;
|
||||
print!("\t");
|
||||
}
|
||||
let virt = table_virt + (i as u64) << shift;
|
||||
println!(
|
||||
"index {} virt {:#x}: phys {:#x} flags {:#x}",
|
||||
i, virt, phys, flags
|
||||
);
|
||||
if table_level < 3 && flags & PF_TABLE == PF_TABLE {
|
||||
dump_page_tables(phys, virt, table_level + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn kernel_entry(
|
||||
page_phys: usize,
|
||||
stack: u64,
|
||||
func: u64,
|
||||
args: *const KernelArgs,
|
||||
) -> ! {
|
||||
unsafe {
|
||||
// Read memory map and exit boot services
|
||||
memory_map().exit_boot_services();
|
||||
|
||||
let currentel: u64;
|
||||
asm!(
|
||||
"mrs {0}, currentel", // Read current exception level
|
||||
out(reg) currentel,
|
||||
);
|
||||
if currentel == (2 << 2) {
|
||||
// Need to drop from EL2 to EL1
|
||||
|
||||
// Allow access to timers
|
||||
asm!(
|
||||
"mrs {0}, cnthctl_el2",
|
||||
"orr {0}, {0}, #0x3",
|
||||
"msr cnthctl_el2, {0}",
|
||||
"msr cntvoff_el2, xzr",
|
||||
out(reg) _
|
||||
);
|
||||
|
||||
// Initialize ID registers
|
||||
asm!(
|
||||
"mrs {0}, midr_el1",
|
||||
"msr vpidr_el2, {0}",
|
||||
"mrs {0}, mpidr_el1",
|
||||
"msr vmpidr_el2, {0}",
|
||||
out(reg) _
|
||||
);
|
||||
|
||||
// Disable traps
|
||||
asm!(
|
||||
"msr cptr_el2, {0}",
|
||||
"msr hstr_el2, xzr",
|
||||
in(reg) 0x33FF as u64
|
||||
);
|
||||
|
||||
// Enable floating point
|
||||
asm!(
|
||||
"msr cpacr_el1, {0}",
|
||||
in(reg) (3 << 20) as u64
|
||||
);
|
||||
|
||||
// Set EL1 system control register
|
||||
asm!(
|
||||
"msr sctlr_el1, {0}",
|
||||
in(reg) 0x30d00800 as u64
|
||||
);
|
||||
|
||||
// Set EL1 stack and VBAR
|
||||
asm!(
|
||||
"mov {0}, sp",
|
||||
"msr sp_el1, {0}",
|
||||
"mrs {0}, vbar_el2",
|
||||
"msr vbar_el1, {0}",
|
||||
out(reg) _
|
||||
);
|
||||
|
||||
// Configure execution state of EL1 as aarch64 and disable hypervisor call.
|
||||
asm!(
|
||||
"msr hcr_el2, {0}",
|
||||
in(reg) ((1u64 << 31) | (1u64 << 29)),
|
||||
);
|
||||
|
||||
// Set saved program status register
|
||||
asm!(
|
||||
"msr spsr_el2, {0}",
|
||||
in(reg) 0x3C5 as u64
|
||||
);
|
||||
|
||||
// Switch to EL1
|
||||
asm!(
|
||||
"adr {0}, 1f",
|
||||
"msr elr_el2, {0}",
|
||||
"eret",
|
||||
"1:",
|
||||
out(reg) _
|
||||
);
|
||||
} else if currentel == (1 << 2) {
|
||||
// Already in EL1
|
||||
} else {
|
||||
//TODO: what to do if not EL2 or already EL1?
|
||||
loop {
|
||||
asm!("wfi");
|
||||
}
|
||||
}
|
||||
|
||||
// Disable MMU
|
||||
asm!(
|
||||
"mrs {0}, sctlr_el1", // Read system control register
|
||||
"bic {0}, {0}, 1", // Clear MMU enable bit
|
||||
"msr sctlr_el1, {0}", // Write system control register
|
||||
"isb", // Instruction sync barrier
|
||||
out(reg) _,
|
||||
);
|
||||
|
||||
// Set MAIR
|
||||
// You can think about MAIRs as of an array with 8 elements each of 8 bits long.
|
||||
// You can store inside MAIRs up to 8 attributes sets and reffer them by the index 0..7 stored in INDX (AttrIndx) field of the table descriptor.
|
||||
// https://lowenware.com/blog/aarch64-mmu-programming/
|
||||
// https://developer.arm.com/documentation/102376/0200/Describing-memory-in-AArch64
|
||||
// https://developer.arm.com/documentation/ddi0595/2021-06/AArch64-Registers/MAIR-EL1--Memory-Attribute-Indirection-Register--EL1-
|
||||
// Attribute 0 (0xFF) - normal memory, caches are enabled
|
||||
// Attribute 1 (0x44) - normal memory, caches are disabled. Atomics wouldn't work here if memory doesn't support exclusive access (most real hardware don't)
|
||||
// Attribute 2 (0x00) - nGnRnE device memory, caches are disabled, gathering, re-ordering, and early write acknowledgement aren't allowed.
|
||||
asm!(
|
||||
"msr mair_el1, {0}",
|
||||
in(reg) 0x00000000000044FF as u64, // MAIR: Arrange for Device, Normal Non-Cache, Normal Write-Back access types
|
||||
);
|
||||
|
||||
// Set TCR
|
||||
asm!(
|
||||
"mrs {1}, id_aa64mmfr0_el1", // Read memory model feature register
|
||||
"bfi {0}, {1}, #32, #3",
|
||||
"msr tcr_el1, {0}", // Write translation control register
|
||||
"isb", // Instruction sync barrier
|
||||
in(reg) 0x1085100510u64, // TCR: (TxSZ, ASID_16, TG1_4K, Cache Attrs, SMP Attrs)
|
||||
out(reg) _,
|
||||
);
|
||||
|
||||
// Set page tables
|
||||
asm!(
|
||||
"dsb sy", // Data sync barrier
|
||||
"msr ttbr1_el1, {0}", // Set higher half page table
|
||||
"msr ttbr0_el1, {0}", // Set lower half page table
|
||||
"isb", // Instruction sync barrier
|
||||
"dsb ishst", // Data sync barrier, only for stores, and only for inner shareable domain
|
||||
"tlbi vmalle1is", // Invalidate TLB
|
||||
"dsb ish", // Dta sync bariar, only for inner shareable domain
|
||||
"isb", // Instruction sync barrier
|
||||
in(reg) page_phys,
|
||||
);
|
||||
|
||||
// Enable MMU
|
||||
asm!(
|
||||
"mrs {2}, sctlr_el1", // Read system control register
|
||||
"bic {2}, {2}, {0}", // Clear bits
|
||||
"orr {2}, {2}, {1}", // Set bits
|
||||
"msr sctlr_el1, {2}", // Write system control register
|
||||
"isb", // Instruction sync barrier
|
||||
in(reg) 0x32802c2u64, // Clear SCTLR bits: (EE, EOE, IESB, WXN, UMA, ITD, THEE, A)
|
||||
in(reg) 0x3485d13du64, // Set SCTLR bits: (LSMAOE, nTLSMD, UCI, SPAN, nTWW, nTWI, UCT, DZE, I, SED, SA0, SA, C, M, CP15BEN)
|
||||
out(reg) _,
|
||||
);
|
||||
|
||||
// Set stack
|
||||
asm!("mov sp, {}", in(reg) stack);
|
||||
|
||||
// Call kernel entry
|
||||
let entry_fn: extern "C" fn(*const KernelArgs) -> ! = mem::transmute(func);
|
||||
entry_fn(args);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() -> Result<()> {
|
||||
LOGGER.init();
|
||||
|
||||
let mut os = OsEfi::new();
|
||||
|
||||
// Disable cursor
|
||||
let _ = (os.st.ConsoleOut.EnableCursor)(os.st.ConsoleOut, false);
|
||||
|
||||
let currentel: u64;
|
||||
unsafe {
|
||||
asm!(
|
||||
"mrs {0}, currentel", // Read current exception level
|
||||
out(reg) currentel,
|
||||
);
|
||||
}
|
||||
log::info!("Currently in EL{}", (currentel >> 2) & 3);
|
||||
|
||||
let (page_phys, func, args) = crate::main(&mut os);
|
||||
|
||||
unsafe {
|
||||
let stack = args.stack_base + args.stack_size + PHYS_OFFSET;
|
||||
|
||||
// dump_page_tables(page_phys as _, 0, 0);
|
||||
|
||||
println!(
|
||||
"kernel_entry({:#x}, {:#x}, {:#x}, {:p})",
|
||||
page_phys, stack, func, &args
|
||||
);
|
||||
println!("{:#x?}", args);
|
||||
|
||||
kernel_entry(page_phys, stack, func, &args);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disable_interrupts() {
|
||||
unsafe {
|
||||
asm!("msr daifset, #2");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
mod aarch64;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use self::aarch64::*;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod x86_64;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use self::x86_64::*;
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
mod riscv64;
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
pub use self::riscv64::*;
|
||||
@@ -0,0 +1,45 @@
|
||||
use std::proto::Protocol;
|
||||
use uefi::guid::Guid;
|
||||
use uefi::status::{Result, Status};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
struct RiscVEfiBootProtocol {
|
||||
pub revision: u64,
|
||||
pub efi_get_boot_hartid:
|
||||
unsafe extern "efiapi" fn(this: *mut Self, phartid: *mut usize) -> Status,
|
||||
}
|
||||
|
||||
impl RiscVEfiBootProtocol {
|
||||
pub const GUID: Guid = Guid::parse_str("ccd15fec-6f73-4eec-8395-3e69e4b940bf");
|
||||
// pub const REVISION: u64 = 0x00010000;
|
||||
}
|
||||
|
||||
struct RiscVEfiBoot(pub &'static mut RiscVEfiBootProtocol);
|
||||
|
||||
impl Protocol<RiscVEfiBootProtocol> for RiscVEfiBoot {
|
||||
fn guid() -> Guid {
|
||||
RiscVEfiBootProtocol::GUID
|
||||
}
|
||||
|
||||
fn new(inner: &'static mut RiscVEfiBootProtocol) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl RiscVEfiBoot {
|
||||
pub fn efi_get_boot_hartid(&mut self) -> Result<usize> {
|
||||
let mut boot_hartid: usize = 0;
|
||||
match unsafe { (self.0.efi_get_boot_hartid)(self.0, &mut boot_hartid) } {
|
||||
ok if ok.is_success() => Ok(boot_hartid),
|
||||
err => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn efi_get_boot_hartid() -> Result<usize> {
|
||||
let handles = RiscVEfiBoot::locate_handle()?;
|
||||
let handle = handles.first().ok_or(Status::NOT_FOUND)?;
|
||||
let mut proto = RiscVEfiBoot::handle_protocol(*handle)?;
|
||||
proto.efi_get_boot_hartid()
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
use core::arch::{global_asm, naked_asm};
|
||||
|
||||
/// Unfortunately this can't be written in Rust because it might use some not-yet
|
||||
/// relocated data such as jump tables
|
||||
#[unsafe(naked)]
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn coff_relocate(dynentry: *const u8, base: usize) -> usize {
|
||||
unsafe {
|
||||
naked_asm!(
|
||||
"
|
||||
mv t4, zero // RELA
|
||||
li t5, -1 // RELASZ
|
||||
li t6, -1 // RELAENT
|
||||
|
||||
5:
|
||||
ld t0, 0(a0)
|
||||
beqz t0, 6f
|
||||
addi a0, a0, 16
|
||||
addi t0, t0, -4
|
||||
bltz t0, 3f // fail on DT_NEEDED=1, DT_PLTRELSZ=2, DT_PLTGOT=3
|
||||
addi t0, t0, -3
|
||||
bltz t0, 5b // skip DT_HASH=4, DT_STRTAB=5, DT_SYMTAB=6
|
||||
bnez t0, 2f
|
||||
ld t4, -8(a0) // DT_RELA=7
|
||||
j 5b
|
||||
2: addi t0, t0, -1 // DT_RELASZ=8
|
||||
bnez t0, 2f
|
||||
ld t5, -8(a0)
|
||||
j 5b
|
||||
2: addi t0, t0, -1 // DT_RELAENT=9
|
||||
bnez t0, 2f
|
||||
ld t6, -8(a0)
|
||||
j 5b
|
||||
2: addi t0, t0, -3
|
||||
bltz t0, 5b // skip DT_STRSZ=10, DT_SYMENT=11
|
||||
addi t0, t0, -2
|
||||
bltz t0, 3f // fail on DT_INIT=12, DT_FINI=13
|
||||
beqz t0, 5b // skip DT_SONAME=14
|
||||
2: addi t0, t0, -2
|
||||
bltz t0, 3f // fail on DT_RPATH
|
||||
beqz t0, 5b // skip SYMBOLIC=16
|
||||
li t1, 0x6ffffef5-16
|
||||
sub t0, t0, t1
|
||||
beqz t0, 5b // skip DT_GNU_HASH=0x6ffffef5
|
||||
nop
|
||||
3: // error
|
||||
mv a0, zero
|
||||
ret
|
||||
|
||||
6:
|
||||
bnez t4, 2f
|
||||
4: // success
|
||||
li a0, 1
|
||||
ret
|
||||
2: bltz t5, 3b
|
||||
blez t6, 3b
|
||||
|
||||
add t4, t4, a1
|
||||
add t5, t5, t4
|
||||
7:
|
||||
bge t4, t5, 4b
|
||||
ld t0, 0(t4) // r_offset
|
||||
add t0, t0, a1
|
||||
lwu t1, 8(t4) // r_type
|
||||
ld t2, 16(t4) // r_addend
|
||||
add t4, t4, t6
|
||||
addi t1, t1, -3 // R_RISCV_RELATIVE=3
|
||||
bnez t1, 3b
|
||||
add t2, t2, a1 // RELATIVE: *value = base + addend
|
||||
sd t2, 0(t0)
|
||||
j 7b
|
||||
"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
global_asm!(
|
||||
r#"
|
||||
.global coff_start
|
||||
coff_start:
|
||||
.option norelax
|
||||
addi sp, sp, -24
|
||||
sd a0, 0(sp)
|
||||
sd a1, 8(sp)
|
||||
sd ra, 16(sp)
|
||||
lla a0, _DYNAMIC
|
||||
lla a1, ImageBase // actual loaded image base to relocate to
|
||||
jal coff_relocate
|
||||
.option relax
|
||||
mv t0, a0
|
||||
ld a0, 0(sp)
|
||||
ld a1, 8(sp)
|
||||
ld ra, 16(sp)
|
||||
addi sp, sp, 24
|
||||
beqz t0, 2f
|
||||
j efi_main
|
||||
2: ret
|
||||
"#
|
||||
);
|
||||
|
||||
// GNU-EFI .reloc trick to make objcopy say we are relocatable
|
||||
global_asm!(
|
||||
r#"
|
||||
.section .data
|
||||
DUMMY_RELOCATION: .4byte 0
|
||||
.section .reloc, "a"
|
||||
|
||||
2:
|
||||
.4byte DUMMY_RELOCATION - ImageBase
|
||||
.4byte 12
|
||||
.4byte 0
|
||||
"#
|
||||
);
|
||||
@@ -0,0 +1,70 @@
|
||||
use crate::KernelArgs;
|
||||
use crate::arch::PHYS_OFFSET;
|
||||
use crate::arch::SATP_BITS;
|
||||
use crate::logger::LOGGER;
|
||||
use crate::os::OsEfi;
|
||||
use crate::os::uefi::memory_map::memory_map;
|
||||
use core::arch::asm;
|
||||
use core::mem;
|
||||
use uefi::status::Result;
|
||||
|
||||
mod boot_protocol;
|
||||
mod coff_helper;
|
||||
|
||||
pub use boot_protocol::*;
|
||||
|
||||
unsafe extern "C" fn kernel_entry(
|
||||
page_phys: usize,
|
||||
stack: u64,
|
||||
func: u64,
|
||||
args: *const KernelArgs,
|
||||
) -> ! {
|
||||
unsafe {
|
||||
// Set page tables
|
||||
asm!(
|
||||
"csrw satp, {0}",
|
||||
"sfence.vma",
|
||||
in(reg) (page_phys >> 12 | SATP_BITS << 60)
|
||||
);
|
||||
|
||||
let entry_fn: extern "C" fn(*const KernelArgs) -> ! = mem::transmute(func);
|
||||
|
||||
// Set stack and go to kernel
|
||||
asm!("mv sp, {0}",
|
||||
"mv a0, {1}",
|
||||
"jalr {2}",
|
||||
in(reg) stack,
|
||||
in(reg) args,
|
||||
in(reg) entry_fn
|
||||
);
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() -> Result<()> {
|
||||
LOGGER.init();
|
||||
|
||||
let mut os = OsEfi::new();
|
||||
|
||||
// Disable cursor
|
||||
let _ = (os.st.ConsoleOut.EnableCursor)(os.st.ConsoleOut, false);
|
||||
|
||||
let (page_phys, func, args) = crate::main(&mut os);
|
||||
|
||||
unsafe {
|
||||
memory_map().exit_boot_services();
|
||||
|
||||
kernel_entry(
|
||||
page_phys,
|
||||
args.stack_base + args.stack_size + PHYS_OFFSET,
|
||||
func,
|
||||
&args,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disable_interrupts() {
|
||||
unsafe {
|
||||
asm!("csrci sstatus, 2");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
use core::{arch::asm, mem};
|
||||
use uefi::status::Result;
|
||||
use x86::{
|
||||
controlregs::{self, Cr0, Cr4},
|
||||
msr,
|
||||
};
|
||||
|
||||
use crate::{KernelArgs, logger::LOGGER};
|
||||
|
||||
use super::super::{OsEfi, memory_map::memory_map};
|
||||
|
||||
unsafe extern "C" fn kernel_entry(
|
||||
page_phys: usize,
|
||||
stack: u64,
|
||||
func: u64,
|
||||
args: *const KernelArgs,
|
||||
) -> ! {
|
||||
unsafe {
|
||||
// Read memory map and exit boot services
|
||||
memory_map().exit_boot_services();
|
||||
|
||||
// Enable FXSAVE/FXRSTOR, Page Global, Page Address Extension, and Page Size Extension
|
||||
let mut cr4 = controlregs::cr4();
|
||||
cr4 |= Cr4::CR4_ENABLE_SSE
|
||||
| Cr4::CR4_ENABLE_GLOBAL_PAGES
|
||||
| Cr4::CR4_ENABLE_PAE
|
||||
| Cr4::CR4_ENABLE_PSE;
|
||||
controlregs::cr4_write(cr4);
|
||||
|
||||
// Enable Long mode and NX bit
|
||||
let mut efer = msr::rdmsr(msr::IA32_EFER);
|
||||
efer |= 1 << 11 | 1 << 8;
|
||||
msr::wrmsr(msr::IA32_EFER, efer);
|
||||
|
||||
// Set new page map
|
||||
controlregs::cr3_write(page_phys as u64);
|
||||
|
||||
// Enable paging, write protect kernel, protected mode
|
||||
let mut cr0 = controlregs::cr0();
|
||||
cr0 |= Cr0::CR0_ENABLE_PAGING | Cr0::CR0_WRITE_PROTECT | Cr0::CR0_PROTECTED_MODE;
|
||||
controlregs::cr0_write(cr0);
|
||||
|
||||
// Set stack
|
||||
asm!("mov rsp, {}", in(reg) stack);
|
||||
|
||||
// Call kernel entry
|
||||
let entry_fn: extern "sysv64" fn(*const KernelArgs) -> ! = mem::transmute(func);
|
||||
entry_fn(args);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() -> Result<()> {
|
||||
LOGGER.init();
|
||||
|
||||
let mut os = OsEfi::new();
|
||||
|
||||
// Disable cursor
|
||||
let _ = (os.st.ConsoleOut.EnableCursor)(os.st.ConsoleOut, false);
|
||||
|
||||
let (page_phys, func, args) = crate::main(&mut os);
|
||||
|
||||
unsafe {
|
||||
kernel_entry(
|
||||
page_phys,
|
||||
args.stack_base
|
||||
+ args.stack_size
|
||||
+ if crate::KERNEL_64BIT {
|
||||
crate::arch::x64::PHYS_OFFSET
|
||||
} else {
|
||||
crate::arch::x32::PHYS_OFFSET as u64
|
||||
},
|
||||
func,
|
||||
&args,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disable_interrupts() {
|
||||
unsafe {
|
||||
asm!("cli");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,509 @@
|
||||
use alloc::{string::String, vec, vec::Vec};
|
||||
use core::{fmt::Write, mem, ptr, slice};
|
||||
use uefi::{
|
||||
Handle,
|
||||
device::{
|
||||
DevicePath, DevicePathAcpiType, DevicePathBbsType, DevicePathEndType,
|
||||
DevicePathHardwareType, DevicePathMediaType, DevicePathMessagingType, DevicePathType,
|
||||
},
|
||||
guid::Guid,
|
||||
status::Status,
|
||||
};
|
||||
use uefi_std::{fs::FileSystem, loaded_image::LoadedImage, proto::Protocol};
|
||||
|
||||
use super::disk::{DiskEfi, DiskOrFileEfi};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum DevicePathRelation {
|
||||
This,
|
||||
Parent(usize),
|
||||
Child(usize),
|
||||
None,
|
||||
}
|
||||
|
||||
fn device_path_relation(a_path: &DevicePath, b_path: &DevicePath) -> DevicePathRelation {
|
||||
let mut a_iter = DevicePathIter::new(a_path);
|
||||
let mut b_iter = DevicePathIter::new(b_path);
|
||||
loop {
|
||||
match (a_iter.next(), b_iter.next()) {
|
||||
(None, None) => return DevicePathRelation::This,
|
||||
(None, Some(_)) => return DevicePathRelation::Parent(b_iter.count()),
|
||||
(Some(_), None) => return DevicePathRelation::Child(a_iter.count()),
|
||||
(Some((a_node, a_data)), Some((b_node, b_data))) => {
|
||||
if a_node.Type != b_node.Type {
|
||||
return DevicePathRelation::None;
|
||||
}
|
||||
|
||||
if a_node.SubType != b_node.SubType {
|
||||
return DevicePathRelation::None;
|
||||
}
|
||||
|
||||
if a_data != b_data {
|
||||
return DevicePathRelation::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn esp_live_image(esp_handle: Handle, esp_device_path: &DevicePath) -> Option<Vec<u8>> {
|
||||
let mut esp_fs = match FileSystem::handle_protocol(esp_handle) {
|
||||
Ok(esp_fs) => esp_fs,
|
||||
Err(err) => {
|
||||
log::warn!("Failed to find SimpleFileSystem protocol: {:?}", err);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let mut root = match esp_fs.root() {
|
||||
Ok(root) => root,
|
||||
Err(err) => {
|
||||
log::warn!("Failed to open ESP filesystem: {:?}", err);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
const fn as_utf16_str<const N: usize>(s: [u8; N]) -> [u16; N] {
|
||||
let mut ret = [0; N];
|
||||
let mut i = 0;
|
||||
while i < N {
|
||||
ret[i] = s[i] as u16;
|
||||
i += 1;
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
let filename = const { &as_utf16_str(*b"redox-live.iso\0") };
|
||||
let mut live_image = match root.open(filename) {
|
||||
Ok(live_image) => live_image,
|
||||
Err(Status::NOT_FOUND) => return None,
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"Failed to open {}\\redox-live.iso: {:?}",
|
||||
device_path_to_string(esp_device_path),
|
||||
err
|
||||
);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
live_image.read_to_end(&mut buffer).unwrap();
|
||||
|
||||
Some(buffer)
|
||||
}
|
||||
|
||||
pub struct DiskDevice {
|
||||
pub handle: Handle,
|
||||
pub disk: DiskOrFileEfi,
|
||||
pub partition_offset: u64,
|
||||
pub device_path: DevicePathProtocol,
|
||||
pub file_path: Option<&'static str>,
|
||||
}
|
||||
|
||||
pub fn disk_device_priority() -> Vec<DiskDevice> {
|
||||
// Get the handle of the partition this program was loaded from, which should be the ESP
|
||||
let esp_handle = match LoadedImage::handle_protocol(std::handle()) {
|
||||
Ok(loaded_image) => loaded_image.0.DeviceHandle,
|
||||
Err(err) => {
|
||||
log::warn!("Failed to find LoadedImage protocol: {:?}", err);
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
// Get the device path of the ESP
|
||||
let esp_device_path = match DevicePathProtocol::handle_protocol(esp_handle) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"Failed to find device path protocol on {:?}: {:?}",
|
||||
esp_handle,
|
||||
err
|
||||
);
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
if cfg!(feature = "live") {
|
||||
// First try to get a live image from redox-live.iso. This is required to support netbooting.
|
||||
if let Some(buffer) = esp_live_image(esp_handle, esp_device_path.0) {
|
||||
return vec![DiskDevice {
|
||||
handle: esp_handle,
|
||||
// Support both a copy of livedisk.iso and a standalone redoxfs partition
|
||||
partition_offset: if &buffer[512..520] == b"EFI PART" {
|
||||
//TODO: get block from partition table
|
||||
2 * crate::MIBI as u64
|
||||
} else {
|
||||
0
|
||||
},
|
||||
disk: DiskOrFileEfi::File(buffer),
|
||||
device_path: esp_device_path,
|
||||
file_path: Some("redox-live.iso"),
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
// Get all block I/O handles along with their block I/O implementations and device paths
|
||||
let handles = match DiskEfi::locate_handle() {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
log::warn!("Failed to find block I/O handles: {:?}", err);
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
let mut devices = Vec::with_capacity(handles.len());
|
||||
for handle in handles {
|
||||
let disk = match DiskEfi::handle_protocol(handle) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"Failed to find block I/O protocol on {:?}: {:?}",
|
||||
handle,
|
||||
err
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if !disk.0.Media.MediaPresent {
|
||||
continue;
|
||||
}
|
||||
|
||||
let device_path = match DevicePathProtocol::handle_protocol(handle) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"Failed to find device path protocol on {:?}: {:?}",
|
||||
handle,
|
||||
err
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
devices.push(DiskDevice {
|
||||
handle,
|
||||
partition_offset: if disk.0.Media.LogicalPartition {
|
||||
0
|
||||
} else {
|
||||
//TODO: get block from partition table
|
||||
2 * crate::MIBI as u64
|
||||
},
|
||||
disk: DiskOrFileEfi::Disk(disk),
|
||||
device_path,
|
||||
file_path: None,
|
||||
});
|
||||
}
|
||||
|
||||
// Find possible boot disks
|
||||
let mut boot_disks = Vec::with_capacity(1);
|
||||
{
|
||||
let mut i = 0;
|
||||
while i < devices.len() {
|
||||
if let DevicePathRelation::Parent(0) =
|
||||
device_path_relation(devices[i].device_path.0, esp_device_path.0)
|
||||
{
|
||||
boot_disks.push(devices.remove(i));
|
||||
continue;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Find all children of possible boot devices
|
||||
let mut priority = Vec::with_capacity(devices.capacity());
|
||||
for boot_disk in boot_disks {
|
||||
let mut i = 0;
|
||||
while i < devices.len() {
|
||||
// Only prioritize non-ESP devices
|
||||
if devices[i].handle != esp_handle {
|
||||
if let DevicePathRelation::Child(0) =
|
||||
device_path_relation(devices[i].device_path.0, boot_disk.device_path.0)
|
||||
{
|
||||
priority.push(devices.remove(i));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
priority.push(boot_disk);
|
||||
}
|
||||
|
||||
// Add any remaining devices
|
||||
priority.extend(devices);
|
||||
|
||||
priority
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[allow(dead_code)]
|
||||
struct DevicePathHarddrive {
|
||||
partition_number: u32,
|
||||
partition_start: u64,
|
||||
partition_size: u64,
|
||||
partition_signature: [u8; 16],
|
||||
partition_format: u8,
|
||||
signature_type: u8,
|
||||
}
|
||||
|
||||
pub fn device_path_to_string(device_path: &DevicePath) -> String {
|
||||
let mut s = String::new();
|
||||
for (node, node_data) in DevicePathIter::new(device_path) {
|
||||
let read_u16 = |i: usize| -> u16 { (node_data[i] as u16) | (node_data[i + 1] as u16) << 8 };
|
||||
|
||||
let read_u32 = |i: usize| -> u32 {
|
||||
(node_data[i] as u32)
|
||||
| (node_data[i + 1] as u32) << 8
|
||||
| (node_data[i + 2] as u32) << 16
|
||||
| (node_data[i + 3] as u32) << 24
|
||||
};
|
||||
|
||||
if !s.is_empty() {
|
||||
s.push('/');
|
||||
}
|
||||
|
||||
let _ = match DevicePathType::try_from(node.Type) {
|
||||
Ok(path_type) => match path_type {
|
||||
DevicePathType::Hardware => match DevicePathHardwareType::try_from(node.SubType) {
|
||||
Ok(sub_type) => match sub_type {
|
||||
DevicePathHardwareType::Pci if node_data.len() == 2 => {
|
||||
let func = node_data[0];
|
||||
let dev = node_data[1];
|
||||
write!(s, "Pci(0x{dev:X},0x{func:X})")
|
||||
}
|
||||
_ => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"),
|
||||
},
|
||||
Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data),
|
||||
},
|
||||
DevicePathType::Acpi => match DevicePathAcpiType::try_from(node.SubType) {
|
||||
Ok(sub_type) => match sub_type {
|
||||
DevicePathAcpiType::Acpi if node_data.len() == 8 => {
|
||||
let hid = read_u32(0);
|
||||
let uid = read_u32(4);
|
||||
if hid & 0xFFFF == 0x41D0 {
|
||||
write!(s, "Acpi(PNP{:04X},0x{:X})", hid >> 16, uid)
|
||||
} else {
|
||||
write!(s, "Acpi(0x{hid:08X},0x{uid:X})")
|
||||
}
|
||||
}
|
||||
_ => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"),
|
||||
},
|
||||
Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data),
|
||||
},
|
||||
DevicePathType::Messaging => {
|
||||
match DevicePathMessagingType::try_from(node.SubType) {
|
||||
Ok(sub_type) => match sub_type {
|
||||
DevicePathMessagingType::Sata if node_data.len() == 6 => {
|
||||
let hba_port = read_u16(0);
|
||||
let multiplier_port = read_u16(2);
|
||||
let logical_unit = read_u16(4);
|
||||
if multiplier_port & (1 << 15) != 0 {
|
||||
write!(s, "Sata(0x{hba_port:X},0x{logical_unit:X})")
|
||||
} else {
|
||||
write!(
|
||||
s,
|
||||
"Sata(0x{hba_port:X},0x{multiplier_port:X},0x{logical_unit:X})"
|
||||
)
|
||||
}
|
||||
}
|
||||
DevicePathMessagingType::Usb if node_data.len() == 2 => {
|
||||
let port = node_data[0];
|
||||
let iface = node_data[1];
|
||||
write!(s, "Usb(0x{port:X},0x{iface:X})")
|
||||
}
|
||||
DevicePathMessagingType::Nvme if node_data.len() == 12 => {
|
||||
let nsid = read_u32(0);
|
||||
let eui = &node_data[4..];
|
||||
if eui == [0, 0, 0, 0, 0, 0, 0, 0] {
|
||||
write!(s, "NVMe(0x{nsid:X})")
|
||||
} else {
|
||||
write!(
|
||||
s,
|
||||
"NVMe(0x{:X},{:02X}-{:02X}-{:02X}-{:02X}-{:02X}-{:02X}-{:02X}-{:02X})",
|
||||
nsid,
|
||||
eui[0],
|
||||
eui[1],
|
||||
eui[2],
|
||||
eui[3],
|
||||
eui[4],
|
||||
eui[5],
|
||||
eui[6],
|
||||
eui[7],
|
||||
)
|
||||
}
|
||||
}
|
||||
DevicePathMessagingType::Mac
|
||||
if node_data.len() == 33 && node_data[32] == 0
|
||||
|| node_data[32] == 1 =>
|
||||
{
|
||||
write!(
|
||||
s,
|
||||
"Mac({:02x}{:02x}{:02x}{:02x}{:02x}{:02x},{:#02x})",
|
||||
node_data[0],
|
||||
node_data[1],
|
||||
node_data[2],
|
||||
node_data[3],
|
||||
node_data[4],
|
||||
node_data[5],
|
||||
node_data[32],
|
||||
)
|
||||
}
|
||||
_ => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"),
|
||||
},
|
||||
Err(()) => {
|
||||
write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data)
|
||||
}
|
||||
}
|
||||
}
|
||||
DevicePathType::Media => match DevicePathMediaType::try_from(node.SubType) {
|
||||
Ok(sub_type) => {
|
||||
match sub_type {
|
||||
DevicePathMediaType::Harddrive
|
||||
if node_data.len() == mem::size_of::<DevicePathHarddrive>() =>
|
||||
{
|
||||
let harddrive = unsafe {
|
||||
ptr::read(node_data.as_ptr() as *const DevicePathHarddrive)
|
||||
};
|
||||
let partition_number = unsafe {
|
||||
ptr::read_unaligned(ptr::addr_of!(harddrive.partition_number))
|
||||
};
|
||||
match harddrive.signature_type {
|
||||
1 => {
|
||||
let id = unsafe {
|
||||
ptr::read(harddrive.partition_signature.as_ptr()
|
||||
as *const u32)
|
||||
};
|
||||
write!(s, "HD(0x{partition_number:X},MBR,0x{id:X})")
|
||||
}
|
||||
2 => {
|
||||
let guid = unsafe {
|
||||
ptr::read(harddrive.partition_signature.as_ptr()
|
||||
as *const Guid)
|
||||
};
|
||||
write!(
|
||||
s,
|
||||
"HD(0x{:X},GPT,{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X})",
|
||||
partition_number,
|
||||
guid.0,
|
||||
guid.1,
|
||||
guid.2,
|
||||
guid.3[0],
|
||||
guid.3[1],
|
||||
guid.3[2],
|
||||
guid.3[3],
|
||||
guid.3[4],
|
||||
guid.3[5],
|
||||
guid.3[6],
|
||||
guid.3[7],
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
write!(
|
||||
s,
|
||||
"HD(0x{:X},0x{:X},{:X?})",
|
||||
partition_number,
|
||||
harddrive.signature_type,
|
||||
harddrive.partition_signature
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
DevicePathMediaType::Filepath => {
|
||||
for chunk in node_data.chunks_exact(2) {
|
||||
let data = (chunk[0] as u16) | (chunk[1] as u16) << 8;
|
||||
match unsafe { char::from_u32_unchecked(data as u32) } {
|
||||
'\\' => s.push('/'),
|
||||
c => s.push(c),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
_ => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"),
|
||||
}
|
||||
}
|
||||
Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data),
|
||||
},
|
||||
DevicePathType::Bbs => match DevicePathBbsType::try_from(node.SubType) {
|
||||
Ok(sub_type) => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"),
|
||||
Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data),
|
||||
},
|
||||
DevicePathType::End => match DevicePathEndType::try_from(node.SubType) {
|
||||
Ok(sub_type) => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"),
|
||||
Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data),
|
||||
},
|
||||
},
|
||||
Err(()) => {
|
||||
write!(
|
||||
s,
|
||||
"0x{:02X} 0x{:02X} {:X?}",
|
||||
node.Type, node.SubType, node_data
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
pub struct DevicePathProtocol(pub &'static mut DevicePath);
|
||||
|
||||
impl Protocol<DevicePath> for DevicePathProtocol {
|
||||
fn guid() -> Guid {
|
||||
uefi::guid::DEVICE_PATH_GUID
|
||||
}
|
||||
|
||||
fn new(inner: &'static mut DevicePath) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LoadedImageDevicePathProtocol(pub &'static mut DevicePath);
|
||||
|
||||
impl Protocol<DevicePath> for LoadedImageDevicePathProtocol {
|
||||
fn guid() -> Guid {
|
||||
uefi::guid::LOADED_IMAGE_DEVICE_PATH_GUID
|
||||
}
|
||||
|
||||
fn new(inner: &'static mut DevicePath) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DevicePathIter<'a> {
|
||||
device_path: &'a DevicePath,
|
||||
node_ptr: *const DevicePath,
|
||||
}
|
||||
|
||||
impl<'a> DevicePathIter<'a> {
|
||||
pub fn new(device_path: &'a DevicePath) -> Self {
|
||||
Self {
|
||||
device_path,
|
||||
node_ptr: device_path as *const DevicePath,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for DevicePathIter<'a> {
|
||||
type Item = (&'a DevicePath, &'a [u8]);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let node = unsafe { &*self.node_ptr };
|
||||
|
||||
if node.Type == DevicePathType::End as u8 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let node_data = unsafe {
|
||||
slice::from_raw_parts(
|
||||
self.node_ptr.add(1) as *mut u8,
|
||||
node.Length.saturating_sub(4) as usize,
|
||||
)
|
||||
};
|
||||
|
||||
self.node_ptr = (self.node_ptr as usize + node.Length as usize) as *const DevicePath;
|
||||
|
||||
Some((node, node_data))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
use alloc::vec::Vec;
|
||||
use core::slice;
|
||||
use redoxfs::{BLOCK_SIZE, Disk, RECORD_SIZE};
|
||||
use std::proto::Protocol;
|
||||
use syscall::{EINVAL, EIO, Error, Result};
|
||||
use uefi::block_io::BlockIo as UefiBlockIo;
|
||||
use uefi::guid::{BLOCK_IO_GUID, Guid};
|
||||
|
||||
pub enum DiskOrFileEfi {
|
||||
Disk(DiskEfi),
|
||||
File(Vec<u8>),
|
||||
}
|
||||
|
||||
impl redoxfs::Disk for DiskOrFileEfi {
|
||||
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> syscall::Result<usize> {
|
||||
unsafe {
|
||||
match self {
|
||||
DiskOrFileEfi::Disk(disk_efi) => disk_efi.read_at(block, buffer),
|
||||
DiskOrFileEfi::File(data) => {
|
||||
buffer.copy_from_slice(
|
||||
&data[(block * redoxfs::BLOCK_SIZE) as usize
|
||||
..(block * redoxfs::BLOCK_SIZE) as usize + buffer.len()],
|
||||
);
|
||||
Ok(buffer.len())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn write_at(&mut self, _block: u64, _buffer: &[u8]) -> syscall::Result<usize> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn size(&mut self) -> syscall::Result<u64> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DiskEfi(pub &'static mut UefiBlockIo, &'static mut [u8]);
|
||||
|
||||
impl Protocol<UefiBlockIo> for DiskEfi {
|
||||
fn guid() -> Guid {
|
||||
BLOCK_IO_GUID
|
||||
}
|
||||
|
||||
fn new(inner: &'static mut UefiBlockIo) -> Self {
|
||||
// Hack to get aligned buffer
|
||||
let block = unsafe {
|
||||
let ptr = super::alloc_zeroed_page_aligned(RECORD_SIZE as usize);
|
||||
slice::from_raw_parts_mut(ptr, RECORD_SIZE as usize)
|
||||
};
|
||||
|
||||
Self(inner, block)
|
||||
}
|
||||
}
|
||||
|
||||
impl Disk for DiskEfi {
|
||||
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
|
||||
unsafe {
|
||||
// Optimization for live disks
|
||||
if let Some(live) = crate::LIVE_OPT {
|
||||
if block >= live.0 {
|
||||
let start = ((block - live.0) * BLOCK_SIZE) as usize;
|
||||
let end = start + buffer.len();
|
||||
if end <= live.1.len() {
|
||||
buffer.copy_from_slice(&live.1[start..end]);
|
||||
return Ok(buffer.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use aligned buffer if necessary
|
||||
let mut ptr = buffer.as_mut_ptr();
|
||||
if self.0.Media.IoAlign != 0 {
|
||||
if (ptr as usize) % (self.0.Media.IoAlign as usize) != 0 {
|
||||
if buffer.len() <= self.1.len() {
|
||||
ptr = self.1.as_mut_ptr();
|
||||
} else {
|
||||
println!(
|
||||
"DiskEfi::read_at 0x{:X} requires alignment, ptr = 0x{:p}, len = 0x{:x}",
|
||||
block,
|
||||
ptr,
|
||||
buffer.len()
|
||||
);
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let block_size = self.0.Media.BlockSize as u64;
|
||||
let lba = block * BLOCK_SIZE / block_size;
|
||||
|
||||
match (self.0.ReadBlocks)(self.0, self.0.Media.MediaId, lba, buffer.len(), ptr) {
|
||||
status if status.is_success() => {
|
||||
// Copy to original buffer if using aligned buffer
|
||||
if ptr != buffer.as_mut_ptr() {
|
||||
let (left, _) = self.1.split_at(buffer.len());
|
||||
buffer.copy_from_slice(left);
|
||||
}
|
||||
Ok(buffer.len())
|
||||
}
|
||||
err => {
|
||||
println!("DiskEfi::read_at 0x{:X} failed: {:?}", block, err);
|
||||
Err(Error::new(EIO))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn write_at(&mut self, block: u64, _buffer: &[u8]) -> Result<usize> {
|
||||
println!("DiskEfi::write_at 0x{:X} not implemented", block);
|
||||
Err(Error::new(EIO))
|
||||
}
|
||||
|
||||
fn size(&mut self) -> Result<u64> {
|
||||
println!("DiskEfi::size not implemented");
|
||||
Err(Error::new(EIO))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
use std::proto::Protocol;
|
||||
use uefi::graphics::GraphicsOutput;
|
||||
use uefi::guid::{GRAPHICS_OUTPUT_PROTOCOL_GUID, Guid};
|
||||
|
||||
pub struct Output(pub &'static mut GraphicsOutput);
|
||||
|
||||
impl Protocol<GraphicsOutput> for Output {
|
||||
fn guid() -> Guid {
|
||||
GRAPHICS_OUTPUT_PROTOCOL_GUID
|
||||
}
|
||||
|
||||
fn new(inner: &'static mut GraphicsOutput) -> Self {
|
||||
Output(inner)
|
||||
}
|
||||
}
|
||||
|
||||
const EDID_ACTIVE_PROTOCOL_GUID: Guid = Guid(
|
||||
0xbd8c1056,
|
||||
0x9f36,
|
||||
0x44ec,
|
||||
[0x92, 0xa8, 0xa6, 0x33, 0x7f, 0x81, 0x79, 0x86],
|
||||
);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C)]
|
||||
pub struct EdidActiveProtocol {
|
||||
pub SizeOfEdid: u32,
|
||||
pub Edid: *const u8,
|
||||
}
|
||||
|
||||
pub struct EdidActive(pub &'static mut EdidActiveProtocol);
|
||||
|
||||
impl Protocol<EdidActiveProtocol> for EdidActive {
|
||||
fn guid() -> Guid {
|
||||
EDID_ACTIVE_PROTOCOL_GUID
|
||||
}
|
||||
|
||||
fn new(inner: &'static mut EdidActiveProtocol) -> Self {
|
||||
EdidActive(inner)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
use crate::Os;
|
||||
use alloc::vec::Vec;
|
||||
use byteorder::BE;
|
||||
use byteorder::ByteOrder;
|
||||
use core::slice;
|
||||
use fdt::Fdt;
|
||||
use uefi::guid::DEVICE_TREE_GUID;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use uefi::{
|
||||
guid::SMBIOS3_TABLE_GUID,
|
||||
status::{Result, Status},
|
||||
};
|
||||
|
||||
pub static mut DEV_MEM_AREA: Vec<(usize, usize)> = Vec::new();
|
||||
|
||||
pub unsafe fn is_in_dev_mem_region(addr: usize) -> bool {
|
||||
#[allow(static_mut_refs)]
|
||||
unsafe {
|
||||
if DEV_MEM_AREA.is_empty() {
|
||||
return false;
|
||||
}
|
||||
for item in DEV_MEM_AREA.iter() {
|
||||
if (addr >= item.0) && (addr < item.0 + item.1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn get_dev_mem_region(fdt: &Fdt) {
|
||||
unsafe {
|
||||
let Some(soc) = fdt.find_node("/soc") else {
|
||||
return;
|
||||
};
|
||||
let Some(ranges) = soc.ranges() else {
|
||||
return;
|
||||
};
|
||||
let cell_sizes = soc.cell_sizes();
|
||||
for chunk in ranges {
|
||||
let child_bus_addr = chunk.child_bus_address;
|
||||
let parent_bus_addr = chunk.parent_bus_address;
|
||||
let addr_size = chunk.size;
|
||||
println!(
|
||||
"dev mem 0x{:08x} 0x{:08x} 0x{:08x}",
|
||||
child_bus_addr, parent_bus_addr, addr_size
|
||||
);
|
||||
#[allow(static_mut_refs)]
|
||||
DEV_MEM_AREA.push((parent_bus_addr as usize, addr_size as usize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_dtb(os: &impl Os, address: *const u8) -> Option<(u64, u64)> {
|
||||
unsafe {
|
||||
if let Ok(fdt) = fdt::Fdt::from_ptr(address) {
|
||||
let mut rsdps_area = Vec::new();
|
||||
//println!("DTB model = {}", fdt.root().model());
|
||||
get_dev_mem_region(&fdt);
|
||||
let length = fdt.total_size();
|
||||
let align = 8;
|
||||
rsdps_area.extend(core::slice::from_raw_parts(address, length));
|
||||
rsdps_area.resize(((rsdps_area.len() + (align - 1)) / align) * align, 0u8);
|
||||
let size = rsdps_area.len();
|
||||
let base = os.alloc_zeroed_page_aligned(size);
|
||||
slice::from_raw_parts_mut(base, size).copy_from_slice(&rsdps_area);
|
||||
Some((base as u64, size as u64))
|
||||
} else {
|
||||
println!("Failed to parse DTB");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn find_smbios3_system(address: *const u8) -> Result<dmidecode::System<'static>> {
|
||||
unsafe {
|
||||
let smb = core::slice::from_raw_parts(address, 24);
|
||||
if let Ok(smbios) = dmidecode::EntryPoint::search(smb) {
|
||||
let smb_structure_data = core::slice::from_raw_parts(
|
||||
smbios.smbios_address() as *const u8,
|
||||
smbios.smbios_len() as usize,
|
||||
);
|
||||
for structure in smbios.structures(smb_structure_data) {
|
||||
if let Ok(sval) = structure {
|
||||
//println!("SMBIOS: {:#?}", sval);
|
||||
if let dmidecode::Structure::System(buf) = sval {
|
||||
return Ok(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(Status::NOT_FOUND)
|
||||
}
|
||||
|
||||
pub(crate) fn find_dtb(os: &impl Os) -> Option<(u64, u64)> {
|
||||
let cfg_tables = std::system_table().config_tables();
|
||||
for cfg_table in cfg_tables.iter() {
|
||||
if cfg_table.VendorGuid == DEVICE_TREE_GUID {
|
||||
let addr = cfg_table.VendorTable;
|
||||
return parse_dtb(os, addr as *const u8);
|
||||
}
|
||||
}
|
||||
|
||||
/* This hack is no longer needed, but can be re-enabled for testing
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
for cfg_table in cfg_tables.iter() {
|
||||
if cfg_table.VendorGuid == SMBIOS3_TABLE_GUID {
|
||||
let addr = cfg_table.VendorTable;
|
||||
if let Ok(sys) = find_smbios3_system(addr as *const u8) {
|
||||
let get_dtb_addr = match (sys.manufacturer, sys.version) {
|
||||
("QEMU", version) if version.starts_with("virt") => Some(0x4000_0000 as usize),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(dtb_addr) = get_dtb_addr {
|
||||
return parse_dtb(os, dtb_addr as *const u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
None
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use core::{mem, ptr};
|
||||
use uefi::memory::{MemoryDescriptor, MemoryType};
|
||||
|
||||
use crate::area_add;
|
||||
use crate::os::{OsMemoryEntry, OsMemoryKind};
|
||||
|
||||
use super::status_to_result;
|
||||
|
||||
pub struct MemoryMapIter {
|
||||
map: Vec<u8>,
|
||||
map_key: usize,
|
||||
descriptor_size: usize,
|
||||
descriptor_version: u32,
|
||||
i: usize,
|
||||
}
|
||||
|
||||
impl MemoryMapIter {
|
||||
pub fn new() -> Self {
|
||||
let uefi = std::system_table();
|
||||
|
||||
let mut map = vec![0; 65536];
|
||||
let mut map_size = map.len();
|
||||
let mut map_key = 0;
|
||||
let mut descriptor_size = 0;
|
||||
let mut descriptor_version = 0;
|
||||
status_to_result((uefi.BootServices.GetMemoryMap)(
|
||||
&mut map_size,
|
||||
map.as_mut_ptr() as *mut MemoryDescriptor,
|
||||
&mut map_key,
|
||||
&mut descriptor_size,
|
||||
&mut descriptor_version,
|
||||
))
|
||||
.expect("Failed to get UEFI memory map");
|
||||
|
||||
// Ensure descriptor size is usable
|
||||
assert!(descriptor_size >= mem::size_of::<MemoryDescriptor>());
|
||||
|
||||
// Ensure descriptor version is supported
|
||||
assert_eq!(descriptor_version, 1);
|
||||
|
||||
// Reduce map size to returned value
|
||||
map.truncate(map_size);
|
||||
|
||||
Self {
|
||||
map,
|
||||
map_key,
|
||||
descriptor_size,
|
||||
descriptor_version,
|
||||
i: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exit_boot_services(mut self) {
|
||||
let handle = std::handle();
|
||||
let uefi = std::system_table();
|
||||
|
||||
// We are writing to the memory map that will be passed to
|
||||
// SetVirtualAddressMap before ExitBootServices as on some firmware
|
||||
// EfiLoaderData memory regions like this one are marked as read-only
|
||||
// after ExitBootServices
|
||||
for i in 0..self.map.len() / self.descriptor_size {
|
||||
let descriptor_ptr = unsafe { self.map.as_mut_ptr().add(i * self.descriptor_size) };
|
||||
let descriptor = unsafe { &mut *(descriptor_ptr as *mut MemoryDescriptor) };
|
||||
|
||||
// Map all memory regions even when not marked as EFI_MEMORY_RUNTIME
|
||||
// as some firmware uses memory regions not marked as
|
||||
// EFI_MEMORY_RUNTIME in runtime services. Linux has a list of
|
||||
// exactly which memory regions need to be mapped, but for simplicity
|
||||
// we are mapping all regions here.
|
||||
|
||||
// Identity map all memory regions as some firmware fails to update
|
||||
// all pointers in SetVirtualAddressMap.
|
||||
|
||||
descriptor.VirtualStart.0 = descriptor.PhysicalStart.0;
|
||||
}
|
||||
|
||||
status_to_result((uefi.BootServices.ExitBootServices)(handle, self.map_key))
|
||||
.expect("Failed to exit UEFI boot services");
|
||||
|
||||
// Runtime services must be called with interrupts disabled
|
||||
super::arch::disable_interrupts();
|
||||
|
||||
status_to_result((uefi.RuntimeServices.SetVirtualAddressMap)(
|
||||
self.map.len(),
|
||||
self.descriptor_size,
|
||||
self.descriptor_version,
|
||||
self.map.as_ptr() as *const MemoryDescriptor,
|
||||
))
|
||||
.expect("Failed to set UEFI runtime services virtual address map");
|
||||
|
||||
// After ExitBootServices, GlobalAlloc::dealloc() is not allowed anymore
|
||||
// as it uses boot services.
|
||||
mem::forget(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for MemoryMapIter {
|
||||
type Item = OsMemoryEntry;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.i < self.map.len() / self.descriptor_size {
|
||||
let descriptor_ptr = unsafe { self.map.as_ptr().add(self.i * self.descriptor_size) };
|
||||
self.i += 1;
|
||||
|
||||
let descriptor = unsafe { ptr::read(descriptor_ptr as *const MemoryDescriptor) };
|
||||
let descriptor_type: MemoryType = unsafe { mem::transmute(descriptor.Type) };
|
||||
|
||||
Some(OsMemoryEntry {
|
||||
base: descriptor.PhysicalStart.0,
|
||||
//TODO: do not hard code page size
|
||||
size: descriptor.NumberOfPages * 4096,
|
||||
kind: match descriptor_type {
|
||||
MemoryType::EfiLoaderCode
|
||||
| MemoryType::EfiLoaderData
|
||||
| MemoryType::EfiBootServicesCode
|
||||
| MemoryType::EfiBootServicesData
|
||||
| MemoryType::EfiConventionalMemory => OsMemoryKind::Free,
|
||||
//TODO: mark ACPI memory as reclaim
|
||||
_ => OsMemoryKind::Reserved,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn memory_map() -> MemoryMapIter {
|
||||
let mut iter = MemoryMapIter::new();
|
||||
|
||||
// Using next to avoid consuming iterator
|
||||
while let Some(entry) = iter.next() {
|
||||
area_add(entry);
|
||||
}
|
||||
|
||||
// Rewind iterator
|
||||
iter.i = 0;
|
||||
|
||||
iter
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
use alloc::vec::Vec;
|
||||
use core::{cell::RefCell, mem, ptr, slice};
|
||||
use std::proto::Protocol;
|
||||
use uefi::{
|
||||
Handle,
|
||||
boot::LocateSearchType,
|
||||
memory::MemoryType,
|
||||
reset::ResetType,
|
||||
status::{Result, Status},
|
||||
system::SystemTable,
|
||||
text::TextInputKey,
|
||||
};
|
||||
|
||||
use crate::os::{Os, OsHwDesc, OsKey, OsVideoMode};
|
||||
|
||||
use self::{
|
||||
device::{device_path_to_string, disk_device_priority},
|
||||
disk::DiskOrFileEfi,
|
||||
display::{EdidActive, Output},
|
||||
video_mode::VideoModeIter,
|
||||
};
|
||||
|
||||
mod acpi;
|
||||
mod arch;
|
||||
mod device;
|
||||
mod disk;
|
||||
mod display;
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
pub mod dtb;
|
||||
mod memory_map;
|
||||
mod video_mode;
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
pub use arch::efi_get_boot_hartid;
|
||||
|
||||
pub(crate) fn page_size() -> usize {
|
||||
// EDK2 always uses 4096 as the page size
|
||||
4096
|
||||
}
|
||||
|
||||
pub(crate) fn alloc_zeroed_page_aligned(size: usize) -> *mut u8 {
|
||||
assert!(size != 0);
|
||||
|
||||
let page_size = page_size();
|
||||
let pages = size.div_ceil(page_size);
|
||||
|
||||
let ptr = {
|
||||
// Max address mapped by src/arch paging code (8 GiB)
|
||||
let mut ptr = 0x2_0000_0000;
|
||||
status_to_result((std::system_table().BootServices.AllocatePages)(
|
||||
1, // AllocateMaxAddress
|
||||
MemoryType::EfiRuntimeServicesData, // Keeps this memory out of free space list
|
||||
pages,
|
||||
&mut ptr,
|
||||
))
|
||||
.unwrap();
|
||||
ptr as *mut u8
|
||||
};
|
||||
|
||||
assert!(!ptr.is_null());
|
||||
unsafe { ptr::write_bytes(ptr, 0, pages * page_size) };
|
||||
ptr
|
||||
}
|
||||
|
||||
pub struct OsEfi {
|
||||
st: &'static SystemTable,
|
||||
outputs: RefCell<Vec<(Output, Option<EdidActive>)>>,
|
||||
}
|
||||
|
||||
impl OsEfi {
|
||||
pub fn new() -> Self {
|
||||
let st = std::system_table();
|
||||
let mut outputs = Vec::<(Output, Option<EdidActive>)>::new();
|
||||
{
|
||||
let guid = Output::guid();
|
||||
let mut handles = Vec::with_capacity(256);
|
||||
let mut len = handles.capacity() * mem::size_of::<Handle>();
|
||||
match status_to_result((st.BootServices.LocateHandle)(
|
||||
LocateSearchType::ByProtocol,
|
||||
&guid,
|
||||
ptr::null(),
|
||||
&mut len,
|
||||
handles.as_mut_ptr(),
|
||||
)) {
|
||||
Ok(_) => {
|
||||
unsafe {
|
||||
handles.set_len(len / mem::size_of::<Handle>());
|
||||
}
|
||||
'handles: for handle in handles {
|
||||
//TODO: do we have to query all modes to get good edid?
|
||||
match Output::handle_protocol(handle) {
|
||||
Ok(output) => {
|
||||
log::debug!(
|
||||
"Output {:?} at {:x}",
|
||||
handle,
|
||||
output.0.Mode.FrameBufferBase
|
||||
);
|
||||
|
||||
if output.0.Mode.FrameBufferBase == 0 {
|
||||
log::debug!("Skipping output with frame buffer base of 0");
|
||||
continue 'handles;
|
||||
}
|
||||
|
||||
for other_output in outputs.iter() {
|
||||
if output.0.Mode.FrameBufferBase
|
||||
== other_output.0.0.Mode.FrameBufferBase
|
||||
{
|
||||
log::debug!(
|
||||
"Skipping output with frame buffer base matching another output"
|
||||
);
|
||||
continue 'handles;
|
||||
}
|
||||
}
|
||||
|
||||
outputs.push((
|
||||
output,
|
||||
match EdidActive::handle_protocol(handle) {
|
||||
Ok(efi_edid) => Some(efi_edid),
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"Failed to get EFI EDID from handle {:?}: {:?}",
|
||||
handle,
|
||||
err
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"Failed to get Output from handle {:?}: {:?}",
|
||||
handle,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("Failed to locate Outputs: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Self {
|
||||
st,
|
||||
outputs: RefCell::new(outputs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Os for OsEfi {
|
||||
type D = DiskOrFileEfi;
|
||||
type V = VideoModeIter;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn name(&self) -> &str {
|
||||
"aarch64/UEFI"
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn name(&self) -> &str {
|
||||
"x86_64/UEFI"
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
fn name(&self) -> &str {
|
||||
"riscv64/UEFI"
|
||||
}
|
||||
|
||||
fn alloc_zeroed_page_aligned(&self, size: usize) -> *mut u8 {
|
||||
alloc_zeroed_page_aligned(size)
|
||||
}
|
||||
|
||||
fn page_size(&self) -> usize {
|
||||
page_size()
|
||||
}
|
||||
|
||||
fn filesystem(
|
||||
&self,
|
||||
password_opt: Option<&[u8]>,
|
||||
) -> syscall::Result<redoxfs::FileSystem<DiskOrFileEfi>> {
|
||||
// Search for RedoxFS on disks in prioritized order
|
||||
println!("Looking for RedoxFS:");
|
||||
for device in disk_device_priority() {
|
||||
if let Some(file_path) = device.file_path {
|
||||
log::debug!(
|
||||
" - {}\\{}",
|
||||
device_path_to_string(device.device_path.0),
|
||||
file_path
|
||||
);
|
||||
} else {
|
||||
log::debug!(" - {}", device_path_to_string(device.device_path.0));
|
||||
}
|
||||
|
||||
let block = device.partition_offset / redoxfs::BLOCK_SIZE;
|
||||
|
||||
match redoxfs::FileSystem::open(device.disk, password_opt, Some(block), false) {
|
||||
Ok(ok) => return Ok(ok),
|
||||
Err(err) => match err.errno {
|
||||
// Ignore header not found error
|
||||
syscall::ENOENT => (),
|
||||
// Print any other errors
|
||||
_ => {
|
||||
log::warn!("BlockIo error: {:?}", err);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
log::warn!("No RedoxFS partitions found");
|
||||
Err(syscall::Error::new(syscall::ENOENT))
|
||||
}
|
||||
|
||||
fn hwdesc(&self) -> OsHwDesc {
|
||||
//TODO: if both DTB and ACPI are found, we should probably let the OS choose what to use?
|
||||
|
||||
// For now we will prefer DTB on platforms that have it
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
if let Some((addr, size)) = dtb::find_dtb(self) {
|
||||
return OsHwDesc::DeviceTree(addr, size);
|
||||
}
|
||||
|
||||
if let Some((addr, size)) = acpi::find_acpi_table_pointers(self) {
|
||||
return OsHwDesc::Acpi(addr, size);
|
||||
}
|
||||
|
||||
OsHwDesc::NotFound
|
||||
}
|
||||
|
||||
fn video_outputs(&self) -> usize {
|
||||
self.outputs.borrow().len()
|
||||
}
|
||||
|
||||
fn video_modes(&self, output_i: usize) -> VideoModeIter {
|
||||
let output_opt = match self.outputs.borrow_mut().get_mut(output_i) {
|
||||
Some(output) => unsafe {
|
||||
// Hack to enable clone
|
||||
let ptr = output.0.0 as *mut _;
|
||||
Some(Output::new(&mut *ptr))
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
VideoModeIter::new(output_opt)
|
||||
}
|
||||
|
||||
fn set_video_mode(&self, output_i: usize, mode: &mut OsVideoMode) {
|
||||
//TODO: return error?
|
||||
let mut outputs = self.outputs.borrow_mut();
|
||||
let (output, _efi_edid_opt) = &mut outputs[output_i];
|
||||
status_to_result((output.0.SetMode)(output.0, mode.id)).unwrap();
|
||||
|
||||
// Update with actual mode information
|
||||
mode.width = output.0.Mode.Info.HorizontalResolution;
|
||||
mode.height = output.0.Mode.Info.VerticalResolution;
|
||||
mode.base = output.0.Mode.FrameBufferBase as u64;
|
||||
}
|
||||
|
||||
fn best_resolution(&self, output_i: usize) -> Option<(u32, u32)> {
|
||||
let mut outputs = self.outputs.borrow_mut();
|
||||
let (output, efi_edid_opt) = outputs.get_mut(output_i)?;
|
||||
|
||||
if let Some(efi_edid) = efi_edid_opt {
|
||||
let edid =
|
||||
unsafe { slice::from_raw_parts(efi_edid.0.Edid, efi_edid.0.SizeOfEdid as usize) };
|
||||
|
||||
if edid.len() > 0x3D {
|
||||
return Some((
|
||||
(edid[0x38] as u32) | (((edid[0x3A] as u32) & 0xF0) << 4),
|
||||
(edid[0x3B] as u32) | (((edid[0x3D] as u32) & 0xF0) << 4),
|
||||
));
|
||||
} else {
|
||||
log::warn!("EFI EDID too small: {}", edid.len());
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to the current output resolution
|
||||
Some((
|
||||
output.0.Mode.Info.HorizontalResolution,
|
||||
output.0.Mode.Info.VerticalResolution,
|
||||
))
|
||||
}
|
||||
|
||||
fn get_key(&self) -> OsKey {
|
||||
//TODO: do not unwrap
|
||||
|
||||
let mut index = 0;
|
||||
status_to_result((self.st.BootServices.WaitForEvent)(
|
||||
1,
|
||||
&self.st.ConsoleIn.WaitForKey,
|
||||
&mut index,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let mut key = TextInputKey {
|
||||
ScanCode: 0,
|
||||
UnicodeChar: 0,
|
||||
};
|
||||
status_to_result((self.st.ConsoleIn.ReadKeyStroke)(
|
||||
self.st.ConsoleIn,
|
||||
&mut key,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
match key.ScanCode {
|
||||
0 => match key.UnicodeChar {
|
||||
8 => OsKey::Backspace,
|
||||
13 => OsKey::Enter,
|
||||
w => match char::from_u32(w as u32) {
|
||||
Some(c) => OsKey::Char(c),
|
||||
None => OsKey::Other,
|
||||
},
|
||||
},
|
||||
1 => OsKey::Up,
|
||||
2 => OsKey::Down,
|
||||
3 => OsKey::Right,
|
||||
4 => OsKey::Left,
|
||||
8 => OsKey::Delete,
|
||||
_ => OsKey::Other,
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_text(&self) {
|
||||
//TODO: why does this sometimes return InvalidParameter, but otherwise appear to work?
|
||||
let _ = status_to_result((self.st.ConsoleOut.ClearScreen)(self.st.ConsoleOut));
|
||||
}
|
||||
|
||||
fn get_text_position(&self) -> (usize, usize) {
|
||||
(
|
||||
self.st.ConsoleOut.Mode.CursorColumn as usize,
|
||||
self.st.ConsoleOut.Mode.CursorRow as usize,
|
||||
)
|
||||
}
|
||||
|
||||
fn set_text_position(&self, x: usize, y: usize) {
|
||||
// Ignore error because Tow-Boot appears to not implement this
|
||||
let _ = status_to_result((self.st.ConsoleOut.SetCursorPosition)(
|
||||
self.st.ConsoleOut,
|
||||
x,
|
||||
y,
|
||||
));
|
||||
}
|
||||
|
||||
fn set_text_highlight(&self, highlight: bool) {
|
||||
let attr = if highlight { 0x70 } else { 0x07 };
|
||||
status_to_result((self.st.ConsoleOut.SetAttribute)(self.st.ConsoleOut, attr)).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn status_to_result(status: Status) -> Result<usize> {
|
||||
match status {
|
||||
Status(ok) if status.is_success() => Ok(ok),
|
||||
err => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_max_mode(output: &uefi::text::TextOutput) -> Result<()> {
|
||||
let mut max_i = None;
|
||||
let mut max_w = 0;
|
||||
let mut max_h = 0;
|
||||
|
||||
for i in 0..output.Mode.MaxMode as usize {
|
||||
let mut w = 0;
|
||||
let mut h = 0;
|
||||
if (output.QueryMode)(output, i, &mut w, &mut h).is_success() {
|
||||
if w >= max_w && h >= max_h {
|
||||
max_i = Some(i);
|
||||
max_w = w;
|
||||
max_h = h;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(i) = max_i {
|
||||
status_to_result((output.SetMode)(output, i))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn main() -> Status {
|
||||
let uefi = std::system_table();
|
||||
|
||||
let _ = (uefi.BootServices.SetWatchdogTimer)(0, 0, 0, ptr::null());
|
||||
|
||||
if let Err(err) = set_max_mode(uefi.ConsoleOut) {
|
||||
println!("Failed to set max mode: {:?}", err);
|
||||
}
|
||||
|
||||
if let Err(err) = arch::main() {
|
||||
panic!("App error: {:?}", err);
|
||||
}
|
||||
|
||||
(uefi.RuntimeServices.ResetSystem)(ResetType::Cold, Status(0), 0, ptr::null());
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
use core::ptr;
|
||||
use log::error;
|
||||
use uefi::status::Status;
|
||||
|
||||
use crate::os::OsVideoMode;
|
||||
use crate::os::uefi::display::Output;
|
||||
|
||||
pub struct VideoModeIter {
|
||||
output_opt: Option<Output>,
|
||||
i: u32,
|
||||
}
|
||||
|
||||
impl VideoModeIter {
|
||||
pub fn new(output_opt: Option<Output>) -> Self {
|
||||
Self { output_opt, i: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for VideoModeIter {
|
||||
type Item = OsVideoMode;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(ref mut output) = self.output_opt {
|
||||
while self.i < output.0.Mode.MaxMode {
|
||||
let id = self.i;
|
||||
self.i += 1;
|
||||
|
||||
let mut mode_ptr = ::core::ptr::null_mut();
|
||||
let mut mode_size = 0;
|
||||
match (output.0.QueryMode)(output.0, id, &mut mode_size, &mut mode_ptr) {
|
||||
Status::SUCCESS => (),
|
||||
err => {
|
||||
error!("Failed to read mode {}: {:?}", id, err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: ensure mode_size is set correctly
|
||||
let mode = unsafe { ptr::read(mode_ptr) };
|
||||
|
||||
let width = mode.HorizontalResolution;
|
||||
let height = mode.VerticalResolution;
|
||||
let stride = mode.PixelsPerScanLine;
|
||||
|
||||
return Some(OsVideoMode {
|
||||
id,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
// Base is retrieved later by setting the mode
|
||||
base: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
use alloc::{boxed::Box, vec};
|
||||
use core::ops;
|
||||
|
||||
use crate::{BlockLevel, BlockTrait, RECORD_LEVEL};
|
||||
|
||||
//TODO: this is a box to prevent stack overflows
|
||||
#[derive(Clone)]
|
||||
pub struct RecordRaw(pub(crate) Box<[u8]>);
|
||||
|
||||
unsafe impl BlockTrait for RecordRaw {
|
||||
fn empty(level: BlockLevel) -> Option<Self> {
|
||||
if level.0 <= RECORD_LEVEL {
|
||||
Some(Self(vec![0; level.bytes() as usize].into_boxed_slice()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for RecordRaw {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::DerefMut for RecordRaw {
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_raw_size_test() {
|
||||
for level_i in 0..RECORD_LEVEL {
|
||||
let level = BlockLevel(level_i);
|
||||
assert_eq!(
|
||||
RecordRaw::empty(level).unwrap().len(),
|
||||
level.bytes() as usize
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
use bitflags::bitflags;
|
||||
use core::convert::TryInto;
|
||||
use core::fmt;
|
||||
use core::ptr::{addr_of, addr_of_mut};
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
use syscall::io::Pio;
|
||||
use syscall::io::{Io, Mmio, ReadOnly};
|
||||
|
||||
bitflags! {
|
||||
/// Interrupt enable flags
|
||||
struct IntEnFlags: u8 {
|
||||
const RECEIVED = 1;
|
||||
const SENT = 1 << 1;
|
||||
const ERRORED = 1 << 2;
|
||||
const STATUS_CHANGE = 1 << 3;
|
||||
// 4 to 7 are unused
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Line status flags
|
||||
struct LineStsFlags: u8 {
|
||||
const INPUT_FULL = 1;
|
||||
// 1 to 4 unknown
|
||||
const OUTPUT_EMPTY = 1 << 5;
|
||||
// 6 and 7 unknown
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(C, packed)]
|
||||
pub struct SerialPort<T: Io> {
|
||||
/// Data register, read to receive, write to send
|
||||
data: T,
|
||||
/// Interrupt enable
|
||||
int_en: T,
|
||||
/// FIFO control
|
||||
fifo_ctrl: T,
|
||||
/// Line control
|
||||
line_ctrl: T,
|
||||
/// Modem control
|
||||
modem_ctrl: T,
|
||||
/// Line status
|
||||
line_sts: ReadOnly<T>,
|
||||
/// Modem status
|
||||
modem_sts: ReadOnly<T>,
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
impl SerialPort<Pio<u8>> {
|
||||
pub const fn new(base: u16) -> SerialPort<Pio<u8>> {
|
||||
SerialPort {
|
||||
data: Pio::new(base),
|
||||
int_en: Pio::new(base + 1),
|
||||
fifo_ctrl: Pio::new(base + 2),
|
||||
line_ctrl: Pio::new(base + 3),
|
||||
modem_ctrl: Pio::new(base + 4),
|
||||
line_sts: ReadOnly::new(Pio::new(base + 5)),
|
||||
modem_sts: ReadOnly::new(Pio::new(base + 6)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SerialPort<Mmio<u32>> {
|
||||
pub unsafe fn new(base: usize) -> &'static mut SerialPort<Mmio<u32>> {
|
||||
unsafe { &mut *(base as *mut Self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Io> SerialPort<T>
|
||||
where
|
||||
T::Value: From<u8> + TryInto<u8>,
|
||||
{
|
||||
pub fn init(&mut self) {
|
||||
unsafe {
|
||||
//TODO: Cleanup
|
||||
// FIXME: Fix UB if unaligned
|
||||
(*addr_of_mut!(self.int_en)).write(0x00.into());
|
||||
(*addr_of_mut!(self.line_ctrl)).write(0x80.into());
|
||||
(*addr_of_mut!(self.data)).write(0x01.into());
|
||||
(*addr_of_mut!(self.int_en)).write(0x00.into());
|
||||
(*addr_of_mut!(self.line_ctrl)).write(0x03.into());
|
||||
(*addr_of_mut!(self.fifo_ctrl)).write(0xC7.into());
|
||||
(*addr_of_mut!(self.modem_ctrl)).write(0x0B.into());
|
||||
(*addr_of_mut!(self.int_en)).write(0x01.into());
|
||||
}
|
||||
}
|
||||
|
||||
fn line_sts(&self) -> LineStsFlags {
|
||||
LineStsFlags::from_bits_truncate(
|
||||
(unsafe { &*addr_of!(self.line_sts) }.read() & 0xFF.into())
|
||||
.try_into()
|
||||
.unwrap_or(0),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn receive(&mut self) -> Option<u8> {
|
||||
if self.line_sts().contains(LineStsFlags::INPUT_FULL) {
|
||||
Some(
|
||||
(unsafe { &*addr_of!(self.data) }.read() & 0xFF.into())
|
||||
.try_into()
|
||||
.unwrap_or(0),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send(&mut self, data: u8) {
|
||||
while !self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY) {}
|
||||
unsafe { &mut *addr_of_mut!(self.data) }.write(data.into())
|
||||
}
|
||||
|
||||
pub fn write(&mut self, buf: &[u8]) {
|
||||
for &b in buf {
|
||||
match b {
|
||||
8 | 0x7F => {
|
||||
self.send(8);
|
||||
self.send(b' ');
|
||||
self.send(8);
|
||||
}
|
||||
b'\n' => {
|
||||
self.send(b'\r');
|
||||
self.send(b'\n');
|
||||
}
|
||||
_ => {
|
||||
self.send(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Io> fmt::Write for SerialPort<T>
|
||||
where
|
||||
T::Value: From<u8> + TryInto<u8>,
|
||||
{
|
||||
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
|
||||
self.write(s.as_bytes());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
-1104
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user